From b8afbd03668b6f86a498cdcb0f1046b427396f08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:32:31 +0000 Subject: [PATCH 001/549] Bump google.golang.org/grpc from 1.69.4 to 1.70.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.69.4 to 1.70.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.69.4...v1.70.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 691479f..57f899d 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.5.17 go.etcd.io/etcd/server/v3 v3.5.17 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.69.4 + google.golang.org/grpc v1.70.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.3 ) @@ -74,12 +74,12 @@ require ( go.etcd.io/etcd/pkg/v3 v3.5.17 // indirect go.etcd.io/etcd/raft/v3 v3.5.17 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.31.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.31.0 // indirect @@ -88,8 +88,8 @@ require ( golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // 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 diff --git a/go.sum b/go.sum index 24b51e2..66ca92c 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= -github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM= +github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -194,20 +194,20 @@ go.etcd.io/etcd/server/v3 v3.5.17 h1:xykBwLZk9IdDsB8z8rMdCCPRvhrG+fwvARaGA0TRiyc go.etcd.io/etcd/server/v3 v3.5.17/go.mod h1:40sqgtGt6ZJNKm8nk8x6LexZakPu+NDl/DCgZTZ69Cc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -245,8 +245,8 @@ golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -291,18 +291,18 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= From d4b2e765758becbd338bc3786e483645c90c701d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:59:25 +0000 Subject: [PATCH 002/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.5.17 to 3.5.18 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.17...v3.5.18) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.5.17 to 3.5.18 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.17...v3.5.18) Updates `go.etcd.io/etcd/client/v3` from 3.5.17 to 3.5.18 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.17...v3.5.18) Updates `go.etcd.io/etcd/server/v3` from 3.5.17 to 3.5.18 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.17...v3.5.18) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 691479f..09dc5ca 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/pion/sdp/v3 v3.0.10 github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.5.17 - go.etcd.io/etcd/client/pkg/v3 v3.5.17 - go.etcd.io/etcd/client/v3 v3.5.17 - go.etcd.io/etcd/server/v3 v3.5.17 + go.etcd.io/etcd/api/v3 v3.5.18 + go.etcd.io/etcd/client/pkg/v3 v3.5.18 + go.etcd.io/etcd/client/v3 v3.5.18 + go.etcd.io/etcd/server/v3 v3.5.18 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.69.4 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -70,9 +70,9 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.etcd.io/etcd/client/v2 v2.305.17 // indirect - go.etcd.io/etcd/pkg/v3 v3.5.17 // indirect - go.etcd.io/etcd/raft/v3 v3.5.17 // indirect + go.etcd.io/etcd/client/v2 v2.305.18 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.18 // indirect + go.etcd.io/etcd/raft/v3 v3.5.18 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect @@ -82,9 +82,9 @@ require ( go.opentelemetry.io/otel/trace v1.31.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index 24b51e2..c0950fa 100644 --- a/go.sum +++ b/go.sum @@ -178,20 +178,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.5.17 h1:cQB8eb8bxwuxOilBpMJAEo8fAONyrdXTHUNcMd8yT1w= -go.etcd.io/etcd/api/v3 v3.5.17/go.mod h1:d1hvkRuXkts6PmaYk2Vrgqbv7H4ADfAKhyJqHNLJCB4= -go.etcd.io/etcd/client/pkg/v3 v3.5.17 h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw= -go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w= -go.etcd.io/etcd/client/v2 v2.305.17 h1:ajFukQfI//xY5VuSeuUw4TJ4WnNR2kAFfV/P0pDdPMs= -go.etcd.io/etcd/client/v2 v2.305.17/go.mod h1:EttKgEgvwikmXN+b7pkEWxDZr6sEaYsqCiS3k4fa/Vg= -go.etcd.io/etcd/client/v3 v3.5.17 h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY= -go.etcd.io/etcd/client/v3 v3.5.17/go.mod h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo= -go.etcd.io/etcd/pkg/v3 v3.5.17 h1:1k2wZ+oDp41jrk3F9o15o8o7K3/qliBo0mXqxo1PKaE= -go.etcd.io/etcd/pkg/v3 v3.5.17/go.mod h1:FrztuSuaJG0c7RXCOzT08w+PCugh2kCQXmruNYCpCGA= -go.etcd.io/etcd/raft/v3 v3.5.17 h1:wHPW/b1oFBw/+HjDAQ9vfr17OIInejTIsmwMZpK1dNo= -go.etcd.io/etcd/raft/v3 v3.5.17/go.mod h1:uapEfOMPaJ45CqBYIraLO5+fqyIY2d57nFfxzFwy4D4= -go.etcd.io/etcd/server/v3 v3.5.17 h1:xykBwLZk9IdDsB8z8rMdCCPRvhrG+fwvARaGA0TRiyc= -go.etcd.io/etcd/server/v3 v3.5.17/go.mod h1:40sqgtGt6ZJNKm8nk8x6LexZakPu+NDl/DCgZTZ69Cc= +go.etcd.io/etcd/api/v3 v3.5.18 h1:Q4oDAKnmwqTo5lafvB+afbgCDF7E35E4EYV2g+FNGhs= +go.etcd.io/etcd/api/v3 v3.5.18/go.mod h1:uY03Ob2H50077J7Qq0DeehjM/A9S8PhVfbQ1mSaMopU= +go.etcd.io/etcd/client/pkg/v3 v3.5.18 h1:mZPOYw4h8rTk7TeJ5+3udUkfVGBqc+GCjOJYd68QgNM= +go.etcd.io/etcd/client/pkg/v3 v3.5.18/go.mod h1:BxVf2o5wXG9ZJV+/Cu7QNUiJYk4A29sAhoI5tIRsCu4= +go.etcd.io/etcd/client/v2 v2.305.18 h1:jT7ANzlD47yu7t6ZGBr1trUDEN6P0RG9Wnyio6XP2Qo= +go.etcd.io/etcd/client/v2 v2.305.18/go.mod h1:JikXfwJymsNv633PzkAb5xnVZmROgNWr4E68YCEz4jo= +go.etcd.io/etcd/client/v3 v3.5.18 h1:nvvYmNHGumkDjZhTHgVU36A9pykGa2K4lAJ0yY7hcXA= +go.etcd.io/etcd/client/v3 v3.5.18/go.mod h1:kmemwOsPU9broExyhYsBxX4spCTDX3yLgPMWtpBXG6E= +go.etcd.io/etcd/pkg/v3 v3.5.18 h1:ny8rLA18/4AMdrILacOKwt7//TJjc7oS8JIJoLuNvbY= +go.etcd.io/etcd/pkg/v3 v3.5.18/go.mod h1:gb4CDXuN/OgzUgj+VmUFumLYQ2FUMDC6r/plLIjHPI8= +go.etcd.io/etcd/raft/v3 v3.5.18 h1:gueCda+9U76Lvk6rINjNc/mXalUp0u8OK5CVESDZh4I= +go.etcd.io/etcd/raft/v3 v3.5.18/go.mod h1:XBaZHTJt3nLnpS8hMDR55Sxrq76cEC4xWYMBYSY3jcs= +go.etcd.io/etcd/server/v3 v3.5.18 h1:u67DmyYyGOu08OiO9O3wgCSQEjGBNzjhH+FM3BcabcI= +go.etcd.io/etcd/server/v3 v3.5.18/go.mod h1:waeL2uw6TdXniXaus105tiK1aSbblIBi21uk8y7D6Ng= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= @@ -222,8 +222,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -241,8 +241,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= @@ -263,8 +263,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= From 82a73659b7a3b86c6f0a7aceb65638149f733350 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:59:29 +0000 Subject: [PATCH 003/549] Bump google.golang.org/protobuf from 1.36.3 to 1.36.4 Bumps google.golang.org/protobuf from 1.36.3 to 1.36.4. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 691479f..4dd0d70 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.69.4 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.3 + google.golang.org/protobuf v1.36.4 ) require ( diff --git a/go.sum b/go.sum index 24b51e2..5f8908e 100644 --- a/go.sum +++ b/go.sum @@ -305,8 +305,8 @@ google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 9606c212b06160413b757b814a9aba8a67cfcaeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:02:32 +0000 Subject: [PATCH 004/549] Update generated files from 82a73659b7a3b86c6f0a7aceb65638149f733350 --- grpc_backend.pb.go | 12 ++++++------ grpc_internal.pb.go | 12 ++++++------ grpc_mcu.pb.go | 12 ++++++------ grpc_sessions.pb.go | 12 ++++++------ session.pb.go | 12 ++++++------ 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/grpc_backend.pb.go b/grpc_backend.pb.go index 03c7762..099aa84 100644 --- a/grpc_backend.pb.go +++ b/grpc_backend.pb.go @@ -29,6 +29,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -128,7 +129,7 @@ func (x *GetSessionCountReply) GetCount() uint32 { var File_grpc_backend_proto protoreflect.FileDescriptor -var file_grpc_backend_proto_rawDesc = []byte{ +var file_grpc_backend_proto_rawDesc = string([]byte{ 0x0a, 0x12, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x2a, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, @@ -148,16 +149,16 @@ var file_grpc_backend_proto_rawDesc = []byte{ 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_grpc_backend_proto_rawDescOnce sync.Once - file_grpc_backend_proto_rawDescData = file_grpc_backend_proto_rawDesc + file_grpc_backend_proto_rawDescData []byte ) func file_grpc_backend_proto_rawDescGZIP() []byte { file_grpc_backend_proto_rawDescOnce.Do(func() { - file_grpc_backend_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_backend_proto_rawDescData) + file_grpc_backend_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_backend_proto_rawDesc), len(file_grpc_backend_proto_rawDesc))) }) return file_grpc_backend_proto_rawDescData } @@ -186,7 +187,7 @@ func file_grpc_backend_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_grpc_backend_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_backend_proto_rawDesc), len(file_grpc_backend_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -197,7 +198,6 @@ func file_grpc_backend_proto_init() { MessageInfos: file_grpc_backend_proto_msgTypes, }.Build() File_grpc_backend_proto = out.File - file_grpc_backend_proto_rawDesc = nil file_grpc_backend_proto_goTypes = nil file_grpc_backend_proto_depIdxs = nil } diff --git a/grpc_internal.pb.go b/grpc_internal.pb.go index eee099c..75636e4 100644 --- a/grpc_internal.pb.go +++ b/grpc_internal.pb.go @@ -29,6 +29,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -128,7 +129,7 @@ func (x *GetServerIdReply) GetVersion() string { var File_grpc_internal_proto protoreflect.FileDescriptor -var file_grpc_internal_proto_rawDesc = []byte{ +var file_grpc_internal_proto_rawDesc = string([]byte{ 0x0a, 0x13, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x52, @@ -148,16 +149,16 @@ var file_grpc_internal_proto_rawDesc = []byte{ 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_grpc_internal_proto_rawDescOnce sync.Once - file_grpc_internal_proto_rawDescData = file_grpc_internal_proto_rawDesc + file_grpc_internal_proto_rawDescData []byte ) func file_grpc_internal_proto_rawDescGZIP() []byte { file_grpc_internal_proto_rawDescOnce.Do(func() { - file_grpc_internal_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_internal_proto_rawDescData) + file_grpc_internal_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_internal_proto_rawDesc), len(file_grpc_internal_proto_rawDesc))) }) return file_grpc_internal_proto_rawDescData } @@ -186,7 +187,7 @@ func file_grpc_internal_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_grpc_internal_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_internal_proto_rawDesc), len(file_grpc_internal_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -197,7 +198,6 @@ func file_grpc_internal_proto_init() { MessageInfos: file_grpc_internal_proto_msgTypes, }.Build() File_grpc_internal_proto = out.File - file_grpc_internal_proto_rawDesc = nil file_grpc_internal_proto_goTypes = nil file_grpc_internal_proto_depIdxs = nil } diff --git a/grpc_mcu.pb.go b/grpc_mcu.pb.go index 59ef5fc..4ec0f8d 100644 --- a/grpc_mcu.pb.go +++ b/grpc_mcu.pb.go @@ -29,6 +29,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -152,7 +153,7 @@ func (x *GetPublisherIdReply) GetIp() string { var File_grpc_mcu_proto protoreflect.FileDescriptor -var file_grpc_mcu_proto_rawDesc = []byte{ +var file_grpc_mcu_proto_rawDesc = string([]byte{ 0x0a, 0x0e, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x6d, 0x63, 0x75, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x55, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x49, 0x64, 0x52, 0x65, 0x71, @@ -177,16 +178,16 @@ var file_grpc_mcu_proto_rawDesc = []byte{ 0x2f, 0x6e, 0x65, 0x78, 0x74, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_grpc_mcu_proto_rawDescOnce sync.Once - file_grpc_mcu_proto_rawDescData = file_grpc_mcu_proto_rawDesc + file_grpc_mcu_proto_rawDescData []byte ) func file_grpc_mcu_proto_rawDescGZIP() []byte { file_grpc_mcu_proto_rawDescOnce.Do(func() { - file_grpc_mcu_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_mcu_proto_rawDescData) + file_grpc_mcu_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_mcu_proto_rawDesc), len(file_grpc_mcu_proto_rawDesc))) }) return file_grpc_mcu_proto_rawDescData } @@ -215,7 +216,7 @@ func file_grpc_mcu_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_grpc_mcu_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_mcu_proto_rawDesc), len(file_grpc_mcu_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -226,7 +227,6 @@ func file_grpc_mcu_proto_init() { MessageInfos: file_grpc_mcu_proto_msgTypes, }.Build() File_grpc_mcu_proto = out.File - file_grpc_mcu_proto_rawDesc = nil file_grpc_mcu_proto_goTypes = nil file_grpc_mcu_proto_depIdxs = nil } diff --git a/grpc_sessions.pb.go b/grpc_sessions.pb.go index f47e8fb..e560dba 100644 --- a/grpc_sessions.pb.go +++ b/grpc_sessions.pb.go @@ -29,6 +29,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -633,7 +634,7 @@ func (x *ServerSessionMessage) GetMessage() []byte { var File_grpc_sessions_proto protoreflect.FileDescriptor -var file_grpc_sessions_proto_rawDesc = []byte{ +var file_grpc_sessions_proto_rawDesc = string([]byte{ 0x0a, 0x13, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x33, 0x0a, 0x15, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, @@ -731,16 +732,16 @@ var file_grpc_sessions_proto_rawDesc = []byte{ 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_grpc_sessions_proto_rawDescOnce sync.Once - file_grpc_sessions_proto_rawDescData = file_grpc_sessions_proto_rawDesc + file_grpc_sessions_proto_rawDescData []byte ) func file_grpc_sessions_proto_rawDescGZIP() []byte { file_grpc_sessions_proto_rawDescOnce.Do(func() { - file_grpc_sessions_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_sessions_proto_rawDescData) + file_grpc_sessions_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_sessions_proto_rawDesc), len(file_grpc_sessions_proto_rawDesc))) }) return file_grpc_sessions_proto_rawDescData } @@ -789,7 +790,7 @@ func file_grpc_sessions_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_grpc_sessions_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_sessions_proto_rawDesc), len(file_grpc_sessions_proto_rawDesc)), NumEnums: 0, NumMessages: 12, NumExtensions: 0, @@ -800,7 +801,6 @@ func file_grpc_sessions_proto_init() { MessageInfos: file_grpc_sessions_proto_msgTypes, }.Build() File_grpc_sessions_proto = out.File - file_grpc_sessions_proto_rawDesc = nil file_grpc_sessions_proto_goTypes = nil file_grpc_sessions_proto_depIdxs = nil } diff --git a/session.pb.go b/session.pb.go index f875cbf..5406311 100644 --- a/session.pb.go +++ b/session.pb.go @@ -30,6 +30,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -101,7 +102,7 @@ func (x *SessionIdData) GetBackendId() string { var File_session_proto protoreflect.FileDescriptor -var file_session_proto_rawDesc = []byte{ +var file_session_proto_rawDesc = string([]byte{ 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, @@ -118,16 +119,16 @@ var file_session_proto_rawDesc = []byte{ 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_session_proto_rawDescOnce sync.Once - file_session_proto_rawDescData = file_session_proto_rawDesc + file_session_proto_rawDescData []byte ) func file_session_proto_rawDescGZIP() []byte { file_session_proto_rawDescOnce.Do(func() { - file_session_proto_rawDescData = protoimpl.X.CompressGZIP(file_session_proto_rawDescData) + file_session_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_session_proto_rawDesc), len(file_session_proto_rawDesc))) }) return file_session_proto_rawDescData } @@ -155,7 +156,7 @@ func file_session_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_session_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_session_proto_rawDesc), len(file_session_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -166,7 +167,6 @@ func file_session_proto_init() { MessageInfos: file_session_proto_msgTypes, }.Build() File_session_proto = out.File - file_session_proto_rawDesc = nil file_session_proto_goTypes = nil file_session_proto_depIdxs = nil } From a730417b3bcafc1d2002f9ff3e926b1c1c3347dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:33:53 +0000 Subject: [PATCH 005/549] Bump coverallsapp/github-action from 2.3.4 to 2.3.6 Bumps [coverallsapp/github-action](https://github.com/coverallsapp/github-action) from 2.3.4 to 2.3.6. - [Release notes](https://github.com/coverallsapp/github-action/releases) - [Commits](https://github.com/coverallsapp/github-action/compare/v2.3.4...v2.3.6) --- updated-dependencies: - dependency-name: coverallsapp/github-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c74638..55cd1e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: outfile: cover.lcov - name: Coveralls Parallel - uses: coverallsapp/github-action@v2.3.4 + uses: coverallsapp/github-action@v2.3.6 env: COVERALLS_FLAG_NAME: run-${{ matrix.go-version }} with: @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Coveralls Finished - uses: coverallsapp/github-action@v2.3.4 + uses: coverallsapp/github-action@v2.3.6 with: github-token: ${{ secrets.github_token }} parallel-finished: true From bcac66cd7ce8b7206cf841063108d050ace34cb8 Mon Sep 17 00:00:00 2001 From: Adphi Date: Tue, 28 Jan 2025 13:30:42 +0100 Subject: [PATCH 006/549] sessions secrets: read with environment override Signed-off-by: Adphi --- client/main.go | 6 +++--- hub.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/main.go b/client/main.go index d66503f..c320334 100644 --- a/client/main.go +++ b/client/main.go @@ -493,10 +493,10 @@ func main() { log.Fatal("Could not read configuration: ", err) } - secret, _ := config.GetString("backend", "secret") + secret, _ := signaling.GetStringOptionWithEnv(config, "backend", "secret") backendSecret = []byte(secret) - hashKey, _ := config.GetString("sessions", "hashkey") + hashKey, _ := signaling.GetStringOptionWithEnv(config, "sessions", "hashkey") switch len(hashKey) { case 32: case 64: @@ -504,7 +504,7 @@ func main() { log.Printf("WARNING: The sessions hash key should be 32 or 64 bytes but is %d bytes", len(hashKey)) } - blockKey, _ := config.GetString("sessions", "blockkey") + blockKey, _ := signaling.GetStringOptionWithEnv(config, "sessions", "blockkey") blockBytes := []byte(blockKey) switch len(blockKey) { case 0: diff --git a/hub.go b/hub.go index 747b435..653c155 100644 --- a/hub.go +++ b/hub.go @@ -187,7 +187,7 @@ type Hub struct { } func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { - hashKey, _ := config.GetString("sessions", "hashkey") + hashKey, _ := GetStringOptionWithEnv(config, "sessions", "hashkey") switch len(hashKey) { case 32: case 64: @@ -195,7 +195,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer log.Printf("WARNING: The sessions hash key should be 32 or 64 bytes but is %d bytes", len(hashKey)) } - blockKey, _ := config.GetString("sessions", "blockkey") + blockKey, _ := GetStringOptionWithEnv(config, "sessions", "blockkey") blockBytes := []byte(blockKey) switch len(blockKey) { case 0: @@ -207,7 +207,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer return nil, fmt.Errorf("the sessions block key must be 16, 24 or 32 bytes but is %d bytes", len(blockKey)) } - internalClientsSecret, _ := config.GetString("clients", "internalsecret") + internalClientsSecret, _ := GetStringOptionWithEnv(config, "clients", "internalsecret") if internalClientsSecret == "" { log.Println("WARNING: No shared secret has been set for internal clients.") } From 9747e4d8e042c84c1f4f5cd4b06b44d01842e03c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:59:16 +0000 Subject: [PATCH 007/549] Bump github.com/nats-io/nats-server/v2 from 2.10.24 to 2.10.25 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.24 to 2.10.25. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.10.24...v2.10.25) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index dadaee8..c5c7ca1 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/marcw/cachecontrol v0.0.0-20140722115028-30341fe9a7d5 - github.com/nats-io/nats-server/v2 v2.10.24 + github.com/nats-io/nats-server/v2 v2.10.25 github.com/nats-io/nats.go v1.38.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -86,7 +86,7 @@ require ( golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect + golang.org/x/time v0.9.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect diff --git a/go.sum b/go.sum index a83f36f..a2476ce 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= -github.com/nats-io/nats-server/v2 v2.10.24 h1:KcqqQAD0ZZcG4yLxtvSFJY7CYKVYlnlWoAiVZ6i/IY4= -github.com/nats-io/nats-server/v2 v2.10.24/go.mod h1:olvKt8E5ZlnjyqBGbAXtxvSQKsPodISK5Eo/euIta4s= +github.com/nats-io/nats-server/v2 v2.10.25 h1:J0GWLDDXo5HId7ti/lTmBfs+lzhmu8RPkoKl0eSCqwc= +github.com/nats-io/nats-server/v2 v2.10.25/go.mod h1:/YYYQO7cuoOBt+A7/8cVjuhWTaTUEAlZbJT+3sMAfFU= github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= @@ -269,8 +269,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 471469f1c84a0f7b0dc63d9b8f0dcce1f8567d13 Mon Sep 17 00:00:00 2001 From: Adphi Date: Tue, 28 Jan 2025 13:31:55 +0100 Subject: [PATCH 008/549] nats: do not log url credentials Signed-off-by: Adphi --- natsclient.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/natsclient.go b/natsclient.go index 3c2f6d1..1893a8d 100644 --- a/natsclient.go +++ b/natsclient.go @@ -27,6 +27,7 @@ import ( "encoding/json" "fmt" "log" + "net/url" "os" "os/signal" "strings" @@ -101,7 +102,7 @@ func NewNatsClient(url string) (NatsClient, error) { client.conn, err = nats.Connect(url) } - log.Printf("Connection established to %s (%s)", client.conn.ConnectedUrl(), client.conn.ConnectedServerId()) + log.Printf("Connection established to %s (%s)", removeURLCredentials(client.conn.ConnectedUrl()), client.conn.ConnectedServerId()) return client, nil } @@ -153,3 +154,11 @@ func (c *natsClient) Decode(msg *nats.Msg, vPtr interface{}) (err error) { } return } + +func removeURLCredentials(u string) string { + if u, err := url.Parse(u); err == nil && u.User != nil { + u.User = url.User("***") + return u.String() + } + return u +} From dcda0984fa9a79982c373362a2b903230543a26e Mon Sep 17 00:00:00 2001 From: Adphi Date: Tue, 28 Jan 2025 19:21:53 +0100 Subject: [PATCH 009/549] backend secrets: read with environment override Signed-off-by: Adphi --- backend_server.go | 4 ++-- backend_storage_static.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend_server.go b/backend_server.go index 016f0eb..e7aaebd 100644 --- a/backend_server.go +++ b/backend_server.go @@ -73,8 +73,8 @@ type BackendServer struct { } func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { - turnapikey, _ := config.GetString("turn", "apikey") - turnsecret, _ := config.GetString("turn", "secret") + turnapikey, _ := GetStringOptionWithEnv(config, "turn", "apikey") + turnsecret, _ := GetStringOptionWithEnv(config, "turn", "secret") turnservers, _ := config.GetString("turn", "servers") // TODO(jojo): Make the validity for TURN credentials configurable. turnvalid := 24 * time.Hour diff --git a/backend_storage_static.go b/backend_storage_static.go index 4a60c3f..9d9ab53 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -42,7 +42,7 @@ type backendStorageStatic struct { func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) { allowAll, _ := config.GetBool("backend", "allowall") allowHttp, _ := config.GetBool("backend", "allowhttp") - commonSecret, _ := config.GetString("backend", "secret") + commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") sessionLimit, err := config.GetInt("backend", "sessionlimit") if err != nil || sessionLimit < 0 { sessionLimit = 0 @@ -206,7 +206,7 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { hosts = make(map[string][]*Backend) for _, id := range getConfiguredBackendIDs(backendIds) { - u, _ := config.GetString(id, "url") + u, _ := GetStringOptionWithEnv(config, id, "url") if u == "" { log.Printf("Backend %s is missing or incomplete, skipping", id) continue @@ -226,7 +226,7 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr u = parsed.String() } - secret, _ := config.GetString(id, "secret") + secret, _ := GetStringOptionWithEnv(config, id, "secret") if secret == "" && commonSecret != "" { log.Printf("Backend %s has no own shared secret set, using common shared secret", id) secret = commonSecret @@ -280,7 +280,7 @@ func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { return } - commonSecret, _ := config.GetString("backend", "secret") + commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { configuredHosts := getConfiguredHosts(backendIds, config, commonSecret) From 5d44577394fa313b18b1367cd55e56cade821c90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:50:21 +0000 Subject: [PATCH 010/549] Bump golangci/golangci-lint-action from 6.2.0 to 6.3.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.2.0 to 6.3.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.2.0...v6.3.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1856787..421274e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.2.0 + uses: golangci/golangci-lint-action@v6.3.0 with: version: latest args: --timeout=2m0s From eadded9aa2b661987348941c0c84103ff4961b09 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Feb 2025 10:59:53 +0100 Subject: [PATCH 011/549] Migrate cache-control parsing to https://github.com/pquerna/cachecontrol The previously used code was unmaintained for a very long time and caused problems for packagers. --- capabilities.go | 17 ++++++++--------- go.mod | 2 +- go.sum | 5 +++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/capabilities.go b/capabilities.go index e606bf0..d77731c 100644 --- a/capabilities.go +++ b/capabilities.go @@ -33,7 +33,7 @@ import ( "sync" "time" - "github.com/marcw/cachecontrol" + "github.com/pquerna/cachecontrol/cacheobject" ) const ( @@ -156,15 +156,14 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim var maxAge time.Duration if cacheControl := response.Header.Get("Cache-Control"); cacheControl != "" { - cc := cachecontrol.Parse(cacheControl) - if nc, _ := cc.NoCache(); !nc { - maxAge = cc.MaxAge() + if cc, err := cacheobject.ParseResponseCacheControl(cacheControl); err == nil { + if !cc.NoCachePresent && cc.MaxAge > 0 { + maxAge = time.Duration(cc.MaxAge) * time.Second + } + e.mustRevalidate = cc.MustRevalidate } - if maxAge < minCapabilitiesCacheDuration { - maxAge = minCapabilitiesCacheDuration - } - e.mustRevalidate = cc.MustRevalidate() - } else { + } + if maxAge < minCapabilitiesCacheDuration { maxAge = minCapabilitiesCacheDuration } e.nextUpdate = now.Add(maxAge) diff --git a/go.mod b/go.mod index c5c7ca1..9eaad0e 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,12 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/marcw/cachecontrol v0.0.0-20140722115028-30341fe9a7d5 github.com/nats-io/nats-server/v2 v2.10.25 github.com/nats-io/nats.go v1.38.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.10 + github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.5.18 diff --git a/go.sum b/go.sum index a2476ce..bcfc701 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/marcw/cachecontrol v0.0.0-20140722115028-30341fe9a7d5 h1:Wnc+HxXmAhN6xRzhmPJTiip9/sVZzwa6XlWksxjObCA= -github.com/marcw/cachecontrol v0.0.0-20140722115028-30341fe9a7d5/go.mod h1:e4ZZwiqLDqvzKu9TVxuGnh2kXCWeU6PxLG2hw/+no7g= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -143,6 +141,8 @@ github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwM github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= +github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -167,6 +167,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= From de315082b830245839cc6c2351b5a597be2bf53c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 10:11:28 +0000 Subject: [PATCH 012/549] Bump github.com/nats-io/nats.go from 1.38.0 to 1.39.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.38.0 to 1.39.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.38.0...v1.39.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9eaad0e..e79ac97 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.10.25 - github.com/nats-io/nats.go v1.38.0 + github.com/nats-io/nats.go v1.39.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.10 diff --git a/go.sum b/go.sum index bcfc701..36c2c9c 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= github.com/nats-io/nats-server/v2 v2.10.25 h1:J0GWLDDXo5HId7ti/lTmBfs+lzhmu8RPkoKl0eSCqwc= github.com/nats-io/nats-server/v2 v2.10.25/go.mod h1:/YYYQO7cuoOBt+A7/8cVjuhWTaTUEAlZbJT+3sMAfFU= -github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= -github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= +github.com/nats-io/nats.go v1.39.0 h1:2/yg2JQjiYYKLwDuBzV0FbB2sIV+eFNkEevlRi4n9lI= +github.com/nats-io/nats.go v1.39.0/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From f7efc3f1554d53136bba570d5c7c0e6408f4c735 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:37:40 +0000 Subject: [PATCH 013/549] Bump google.golang.org/protobuf from 1.36.4 to 1.36.5 Bumps google.golang.org/protobuf from 1.36.4 to 1.36.5. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e79ac97..67534fc 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.70.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.4 + google.golang.org/protobuf v1.36.5 ) require ( diff --git a/go.sum b/go.sum index 36c2c9c..81d023d 100644 --- a/go.sum +++ b/go.sum @@ -306,8 +306,8 @@ google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 8ddeedce8bdda0bad5295b8d5caaf13b2d7268ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:08:00 +0000 Subject: [PATCH 014/549] Bump golangci/golangci-lint-action from 6.3.0 to 6.3.2 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.3.0 to 6.3.2. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.3.0...v6.3.2) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 421274e..cff79f0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.3.0 + uses: golangci/golangci-lint-action@v6.3.2 with: version: latest args: --timeout=2m0s From a747190d551ebd3210f03451700dbe38a90dfa21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 20:41:24 +0000 Subject: [PATCH 015/549] build(deps): bump golang in /docker/server Bumps golang from 1.23-alpine to 1.24-alpine. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker/server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 9c59e7c..454794b 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.24-alpine AS builder ARG TARGETARCH ARG TARGETOS From 06119ce07b7b8a57a47009ad1930b51e754a905a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:11:29 +0000 Subject: [PATCH 016/549] build(deps): bump golangci/golangci-lint-action from 6.3.2 to 6.3.3 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.3.2 to 6.3.3. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.3.2...v6.3.3) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cff79f0..09fe6ce 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.3.2 + uses: golangci/golangci-lint-action@v6.3.3 with: version: latest args: --timeout=2m0s From 972f0db360fdaa9c3b5a81f2c090e7ce45492ed3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:21:19 +0000 Subject: [PATCH 017/549] build(deps): bump golang in /docker/proxy Bumps golang from 1.23-alpine to 1.24-alpine. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker/proxy/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile index fb78e03..048dbd7 100644 --- a/docker/proxy/Dockerfile +++ b/docker/proxy/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.24-alpine AS builder ARG TARGETARCH ARG TARGETOS From ca07f41e78be8074c1e0ecd30fde2f2376c940b3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 14 Feb 2025 08:40:11 +0100 Subject: [PATCH 018/549] CI: Test with Golang 1.24 --- .github/workflows/govuln.yml | 1 + .github/workflows/tarball.yml | 2 ++ .github/workflows/test.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index a50bc62..6a0648b 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -26,6 +26,7 @@ jobs: go-version: - "1.22" - "1.23" + - "1.24" steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 747254a..25fdff4 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -26,6 +26,7 @@ jobs: go-version: - "1.22" - "1.23" + - "1.24" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -50,6 +51,7 @@ jobs: go-version: - "1.22" - "1.23" + - "1.24" runs-on: ubuntu-latest needs: [create] steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55cd1e0..2d86525 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,7 @@ jobs: go-version: - "1.22" - "1.23" + - "1.24" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 29cb0d7a4bec32e4c12688a7c8feab1bfa376e68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:58:16 +0000 Subject: [PATCH 019/549] build(deps): bump golangci/golangci-lint-action from 6.3.3 to 6.5.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.3.3 to 6.5.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.3.3...v6.5.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 09fe6ce..a386cb9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.3.3 + uses: golangci/golangci-lint-action@v6.5.0 with: version: latest args: --timeout=2m0s From 752c3cffd5076dad92fdcd9d1f1a9a449fdd4928 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:50:15 +0000 Subject: [PATCH 020/549] build(deps): bump github.com/nats-io/nats.go from 1.39.0 to 1.39.1 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.39.0 to 1.39.1. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.39.0...v1.39.1) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 67534fc..b2e5c7f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.10.25 - github.com/nats-io/nats.go v1.39.0 + github.com/nats-io/nats.go v1.39.1 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.10 diff --git a/go.sum b/go.sum index 81d023d..883eb2b 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= github.com/nats-io/nats-server/v2 v2.10.25 h1:J0GWLDDXo5HId7ti/lTmBfs+lzhmu8RPkoKl0eSCqwc= github.com/nats-io/nats-server/v2 v2.10.25/go.mod h1:/YYYQO7cuoOBt+A7/8cVjuhWTaTUEAlZbJT+3sMAfFU= -github.com/nats-io/nats.go v1.39.0 h1:2/yg2JQjiYYKLwDuBzV0FbB2sIV+eFNkEevlRi4n9lI= -github.com/nats-io/nats.go v1.39.0/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM= +github.com/nats-io/nats.go v1.39.1 h1:oTkfKBmz7W047vRxV762M67ZdXeOtUgvbBaNoQ+3PPk= +github.com/nats-io/nats.go v1.39.1/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From c0d640dbd912b68a3cbb9c115a2fc57c93c5326b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:50:18 +0000 Subject: [PATCH 021/549] build(deps): bump github.com/prometheus/client_golang Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.5 to 1.21.0. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.5...v1.21.0) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 67534fc..fd28475 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.10 github.com/pquerna/cachecontrol v0.2.0 - github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_golang v1.21.0 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.5.18 go.etcd.io/etcd/client/pkg/v3 v3.5.18 @@ -62,7 +62,7 @@ require ( github.com/pion/randutil v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect diff --git a/go.sum b/go.sum index 81d023d..e65c9ed 100644 --- a/go.sum +++ b/go.sum @@ -143,13 +143,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= From 397f645b08555bd4b7fd546b4ca3cca23c7bb04a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:53:42 +0000 Subject: [PATCH 022/549] build(deps): bump sphinx from 8.1.3 to 8.2.0 in /docs Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.1.3 to 8.2.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.1.3...v8.2.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 97531c7..4a32289 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ jinja2==3.1.5 markdown==3.7 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 -sphinx==8.1.3 +sphinx==8.2.0 sphinx_rtd_theme==3.0.2 From 03a4d27d828221aa4b980590dc39a5b7e59f1a0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:24:23 +0000 Subject: [PATCH 023/549] build(deps): bump sphinx from 8.2.0 to 8.2.1 in /docs Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.0 to 8.2.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/v8.2.1/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.2.0...v8.2.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 4a32289..a5e0e4d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ jinja2==3.1.5 markdown==3.7 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 -sphinx==8.2.0 +sphinx==8.2.1 sphinx_rtd_theme==3.0.2 From 0ff645560134a9f38ed5513155e8e6264796384f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 20:56:40 +0000 Subject: [PATCH 024/549] build(deps): bump sphinx from 8.2.1 to 8.2.3 in /docs Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.1 to 8.2.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.2.1...v8.2.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index a5e0e4d..5f8d5e5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ jinja2==3.1.5 markdown==3.7 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 -sphinx==8.2.1 +sphinx==8.2.3 sphinx_rtd_theme==3.0.2 From cffcf084f8af49b945db3cf154643fa258374946 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 20:54:50 +0000 Subject: [PATCH 025/549] build(deps): bump google.golang.org/grpc from 1.70.0 to 1.71.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.70.0 to 1.71.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.70.0...v1.71.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 15 ++++++++------- go.sum | 58 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index cf64f06..f424322 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.5.18 go.etcd.io/etcd/server/v3 v3.5.18 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.70.0 + google.golang.org/grpc v1.71.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.5 ) @@ -73,13 +73,14 @@ require ( go.etcd.io/etcd/client/v2 v2.305.18 // indirect go.etcd.io/etcd/pkg/v3 v3.5.18 // indirect go.etcd.io/etcd/raft/v3 v3.5.18 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect - go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect - go.opentelemetry.io/otel/metric v1.32.0 // indirect - go.opentelemetry.io/otel/sdk v1.32.0 // indirect - go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.32.0 // indirect @@ -88,8 +89,8 @@ require ( golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // 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 diff --git a/go.sum b/go.sum index 675ab88..44bb43b 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= -cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -16,8 +16,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= @@ -35,8 +35,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -56,8 +56,8 @@ github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM= -github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -153,8 +153,8 @@ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkq github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -193,22 +193,24 @@ go.etcd.io/etcd/raft/v3 v3.5.18 h1:gueCda+9U76Lvk6rINjNc/mXalUp0u8OK5CVESDZh4I= go.etcd.io/etcd/raft/v3 v3.5.18/go.mod h1:XBaZHTJt3nLnpS8hMDR55Sxrq76cEC4xWYMBYSY3jcs= go.etcd.io/etcd/server/v3 v3.5.18 h1:u67DmyYyGOu08OiO9O3wgCSQEjGBNzjhH+FM3BcabcI= go.etcd.io/etcd/server/v3 v3.5.18/go.mod h1:waeL2uw6TdXniXaus105tiK1aSbblIBi21uk8y7D6Ng= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -246,8 +248,8 @@ golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -292,18 +294,18 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= From 53280369692dc4d0199c711abdbd0f8a1e710575 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 20:54:55 +0000 Subject: [PATCH 026/549] build(deps): bump github.com/prometheus/client_golang Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.21.0 to 1.21.1. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.21.0...v1.21.1) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cf64f06..530f306 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.10 github.com/pquerna/cachecontrol v0.2.0 - github.com/prometheus/client_golang v1.21.0 + github.com/prometheus/client_golang v1.21.1 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.5.18 go.etcd.io/etcd/client/pkg/v3 v3.5.18 diff --git a/go.sum b/go.sum index 675ab88..392d8c6 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= -github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= From 864ac6d38ab06a7d5277329e745cb1b1995f77c8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 15:46:14 +0100 Subject: [PATCH 027/549] CI: Fix option to ignore generated headers in linter. --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index c62551d..63d71bc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ linters: linters-settings: revive: - ignoreGeneratedHeader: true + ignore-generated-header: true severity: warning rules: - name: blank-imports From 1232bfb3b3030b9410084a890c963b47b994c40c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 16:35:50 +0100 Subject: [PATCH 028/549] nats: Reconnect client indefinitely. --- async_events_test.go | 4 +-- backend_server_test.go | 6 ++--- hub_test.go | 11 ++++---- natsclient.go | 9 ++++--- natsclient_test.go | 60 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/async_events_test.go b/async_events_test.go index b72a30a..3ded5a0 100644 --- a/async_events_test.go +++ b/async_events_test.go @@ -50,8 +50,8 @@ func getAsyncEventsForTest(t *testing.T) AsyncEvents { } func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { - url := startLocalNatsServer(t) - events, err := NewAsyncEvents(url) + server, _ := startLocalNatsServer(t) + events, err := NewAsyncEvents(server.ClientURL()) if err != nil { require.NoError(t, err) } diff --git a/backend_server_test.go b/backend_server_test.go index 858b7f3..8a84805 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -137,7 +137,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g server2.Close() }) - nats := startLocalNatsServer(t) + nats, _ := startLocalNatsServer(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -156,7 +156,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config1.AddOption("clients", "internalsecret", string(testInternalSecret)) config1.AddOption("geoip", "url", "none") - events1, err := NewAsyncEvents(nats) + events1, err := NewAsyncEvents(nats.ClientURL()) require.NoError(err) t.Cleanup(func() { events1.Close() @@ -179,7 +179,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config2.AddOption("sessions", "blockkey", "09876543210987654321098765432109") config2.AddOption("clients", "internalsecret", string(testInternalSecret)) config2.AddOption("geoip", "url", "none") - events2, err := NewAsyncEvents(nats) + events2, err := NewAsyncEvents(nats.ClientURL()) require.NoError(err) t.Cleanup(func() { events2.Close() diff --git a/hub_test.go b/hub_test.go index 0d4bf48..cf191e4 100644 --- a/hub_test.go +++ b/hub_test.go @@ -47,6 +47,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/gorilla/mux" "github.com/gorilla/websocket" + "github.com/nats-io/nats-server/v2/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -190,10 +191,10 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http server2.Close() }) - nats1 := startLocalNatsServer(t) - var nats2 string + nats1, _ := startLocalNatsServer(t) + var nats2 *server.Server if strings.Contains(t.Name(), "Federation") { - nats2 = startLocalNatsServer(t) + nats2, _ = startLocalNatsServer(t) } else { nats2 = nats1 } @@ -205,7 +206,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http addr1, addr2 = addr2, addr1 } - events1, err := NewAsyncEvents(nats1) + events1, err := NewAsyncEvents(nats1.ClientURL()) require.NoError(err) t.Cleanup(func() { events1.Close() @@ -217,7 +218,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http require.NoError(err) b1, err := NewBackendServer(config1, h1, "no-version") require.NoError(err) - events2, err := NewAsyncEvents(nats2) + events2, err := NewAsyncEvents(nats2.ClientURL()) require.NoError(err) t.Cleanup(func() { events2.Close() diff --git a/natsclient.go b/natsclient.go index 1893a8d..66e8a84 100644 --- a/natsclient.go +++ b/natsclient.go @@ -67,7 +67,7 @@ type natsClient struct { conn *nats.Conn } -func NewNatsClient(url string) (NatsClient, error) { +func NewNatsClient(url string, options ...nats.Option) (NatsClient, error) { if url == ":loopback:" { log.Printf("WARNING: events url %s is deprecated, please use %s instead", url, NatsLoopbackUrl) url = NatsLoopbackUrl @@ -84,10 +84,13 @@ func NewNatsClient(url string) (NatsClient, error) { client := &natsClient{} - client.conn, err = nats.Connect(url, + options = append([]nats.Option{ nats.ClosedHandler(client.onClosed), nats.DisconnectHandler(client.onDisconnected), - nats.ReconnectHandler(client.onReconnected)) + nats.ReconnectHandler(client.onReconnected), + nats.MaxReconnects(-1), + }, options...) + client.conn, err = nats.Connect(url, options...) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() diff --git a/natsclient_test.go b/natsclient_test.go index 430ef6d..362895b 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -30,29 +30,37 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/nats-io/nats-server/v2/server" natsserver "github.com/nats-io/nats-server/v2/test" ) -func startLocalNatsServer(t *testing.T) string { +func startLocalNatsServer(t *testing.T) (*server.Server, int) { + t.Helper() + return startLocalNatsServerPort(t, server.RANDOM_PORT) +} + +func startLocalNatsServerPort(t *testing.T, port int) (*server.Server, int) { + t.Helper() opts := natsserver.DefaultTestOptions - opts.Port = -1 + opts.Port = port opts.Cluster.Name = "testing" srv := natsserver.RunServer(&opts) t.Cleanup(func() { srv.Shutdown() srv.WaitForShutdown() }) - return srv.ClientURL() + return srv, opts.Port } -func CreateLocalNatsClientForTest(t *testing.T) NatsClient { - url := startLocalNatsServer(t) - result, err := NewNatsClient(url) +func CreateLocalNatsClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, NatsClient) { + t.Helper() + server, port := startLocalNatsServer(t) + result, err := NewNatsClient(server.ClientURL(), options...) require.NoError(t, err) t.Cleanup(func() { result.Close() }) - return result + return server, port, result } func testNatsClient_Subscribe(t *testing.T, client NatsClient) { @@ -100,7 +108,7 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { func TestNatsClient_Subscribe(t *testing.T) { CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_Subscribe(t, client) }) @@ -115,7 +123,7 @@ func testNatsClient_PublishAfterClose(t *testing.T, client NatsClient) { func TestNatsClient_PublishAfterClose(t *testing.T) { CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_PublishAfterClose(t, client) }) @@ -132,7 +140,7 @@ func testNatsClient_SubscribeAfterClose(t *testing.T, client NatsClient) { func TestNatsClient_SubscribeAfterClose(t *testing.T) { CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_SubscribeAfterClose(t, client) }) @@ -155,8 +163,38 @@ func testNatsClient_BadSubjects(t *testing.T, client NatsClient) { func TestNatsClient_BadSubjects(t *testing.T) { CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_BadSubjects(t, client) }) } + +func TestNatsClient_MaxReconnects(t *testing.T) { + CatchLogForTest(t) + ensureNoGoroutinesLeak(t, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + reconnectWait := 5 * time.Millisecond + server, port, client := CreateLocalNatsClientForTest(t, + nats.ReconnectWait(reconnectWait), + nats.ReconnectJitter(0, 0), + ) + c, ok := client.(*natsClient) + require.True(ok, "wrong class: %T", client) + require.True(c.conn.IsConnected(), "not connected initially") + assert.Equal(server.ID(), c.conn.ConnectedServerId()) + + server.Shutdown() + server.WaitForShutdown() + + // The NATS client tries to reconnect a maximum of 100 times by default. + time.Sleep(time.Second + (100 * reconnectWait)) + require.False(c.conn.IsConnected(), "should be disconnected after server shutdown") + + server, _ = startLocalNatsServerPort(t, port) + + time.Sleep(time.Second) + require.True(c.conn.IsConnected(), "not connected after restart") + assert.Equal(server.ID(), c.conn.ConnectedServerId()) + }) +} From 396da6ea28770df55acbf63caa2a85e5d910152e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 20:59:02 +0000 Subject: [PATCH 029/549] build(deps): bump jinja2 from 3.1.5 to 3.1.6 in /docs Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5f8d5e5..1e7f67a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -jinja2==3.1.5 +jinja2==3.1.6 markdown==3.7 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 From 85bf1fa398694b3480be89cf2bdaa54a339eb887 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 20:31:40 +0000 Subject: [PATCH 030/549] build(deps): bump github.com/pion/sdp/v3 from 3.0.10 to 3.0.11 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.10 to 3.0.11. - [Release notes](https://github.com/pion/sdp/releases) - [Changelog](https://github.com/pion/sdp/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/sdp/compare/v3.0.10...v3.0.11) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 19c51cc..61a9395 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/nats-io/nats.go v1.39.1 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/sdp/v3 v3.0.10 + github.com/pion/sdp/v3 v3.0.11 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.21.1 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 719d97a..73babbd 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA= -github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI= +github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From b68c6842f43b3308e5ebc7b2a53c02e73b672fe3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:47:49 +0000 Subject: [PATCH 031/549] build(deps): bump golangci/golangci-lint-action from 6.5.0 to 6.5.1 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.5.0 to 6.5.1. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.5.0...v6.5.1) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a386cb9..f61c912 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.5.0 + uses: golangci/golangci-lint-action@v6.5.1 with: version: latest args: --timeout=2m0s From 08669dc6ea3aff9485e76aa166480e0395bf44ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:55:33 +0000 Subject: [PATCH 032/549] build(deps): bump golangci/golangci-lint-action from 6.5.1 to 6.5.2 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.5.1 to 6.5.2. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.5.1...v6.5.2) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f61c912..01ef565 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.5.1 + uses: golangci/golangci-lint-action@v6.5.2 with: version: latest args: --timeout=2m0s From d25c8dd8abf9dec82e75a31f4ce5bd7fdbe4b923 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:57:12 +0000 Subject: [PATCH 033/549] build(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 61a9395..6046a53 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 github.com/fsnotify/fsnotify v1.8.0 - github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/securecookie v1.1.2 diff --git a/go.sum b/go.sum index 73babbd..516d80d 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= From b4ace199f25fa61f03ec860d71716a794dfc271b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 15:05:49 +0000 Subject: [PATCH 034/549] build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6046a53..de0d291 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect diff --git a/go.sum b/go.sum index 516d80d..e350a99 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= From e1470068ffdc0fa83b3657ddf44394b4df0c8183 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:54:08 +0000 Subject: [PATCH 035/549] build(deps): bump golangci/golangci-lint-action from 6.5.2 to 7.0.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.5.2 to 7.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.5.2...v7.0.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 01ef565..7c3f60a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.22" - name: lint - uses: golangci/golangci-lint-action@v6.5.2 + uses: golangci/golangci-lint-action@v7.0.0 with: version: latest args: --timeout=2m0s From d4cdc059bfc3ea403a158e8d67a65aa67ca6f22f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:49:55 +0000 Subject: [PATCH 036/549] build(deps): bump google.golang.org/protobuf from 1.36.5 to 1.36.6 Bumps google.golang.org/protobuf from 1.36.5 to 1.36.6. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index de0d291..2dc1528 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.71.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.5 + google.golang.org/protobuf v1.36.6 ) require ( diff --git a/go.sum b/go.sum index e350a99..f790684 100644 --- a/go.sum +++ b/go.sum @@ -308,8 +308,8 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 1f0a61a032469915d2cd3aa2361e2a2365d03787 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:51:34 +0000 Subject: [PATCH 037/549] Update generated files from d4cdc059bfc3ea403a158e8d67a65aa67ca6f22f --- grpc_backend.pb.go | 31 +++------- grpc_internal.pb.go | 30 +++------ grpc_mcu.pb.go | 40 +++++------- grpc_sessions.pb.go | 144 ++++++++++++++------------------------------ session.pb.go | 25 +++----- 5 files changed, 85 insertions(+), 185 deletions(-) diff --git a/grpc_backend.pb.go b/grpc_backend.pb.go index 099aa84..35d30d2 100644 --- a/grpc_backend.pb.go +++ b/grpc_backend.pb.go @@ -129,27 +129,16 @@ func (x *GetSessionCountReply) GetCount() uint32 { var File_grpc_backend_proto protoreflect.FileDescriptor -var file_grpc_backend_proto_rawDesc = string([]byte{ - 0x0a, 0x12, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x22, - 0x2a, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2c, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x65, 0x0a, 0x0a, 0x52, 0x70, 0x63, - 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x57, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, - 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x74, 0x72, 0x75, 0x6b, 0x74, 0x75, 0x72, 0x61, 0x67, 0x2f, 0x6e, 0x65, 0x78, 0x74, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_grpc_backend_proto_rawDesc = "" + + "\n" + + "\x12grpc_backend.proto\x12\tsignaling\"*\n" + + "\x16GetSessionCountRequest\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\",\n" + + "\x14GetSessionCountReply\x12\x14\n" + + "\x05count\x18\x01 \x01(\rR\x05count2e\n" + + "\n" + + "RpcBackend\x12W\n" + + "\x0fGetSessionCount\x12!.signaling.GetSessionCountRequest\x1a\x1f.signaling.GetSessionCountReply\"\x00B Date: Wed, 26 Mar 2025 13:40:06 +0100 Subject: [PATCH 038/549] Migrate config file of golangci-lint. --- .golangci.yml | 80 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 63d71bc..6d822a2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,34 +1,52 @@ +version: "2" linters: enable: - - gofmt - revive - -linters-settings: - revive: - ignore-generated-header: true - severity: warning - rules: - - name: blank-imports - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: error-return - #- name: error-strings - - name: error-naming - - name: exported - - name: if-return - - name: increment-decrement - #- name: var-naming - - name: var-declaration - - name: package-comments - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - #- name: indent-error-flow - - name: errorf - - name: empty-block - - name: superfluous-else - #- name: unused-parameter - - name: unreachable-code - - name: redefines-builtin-id + settings: + revive: + severity: warning + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + #- name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + #- name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + #- name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + #- name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ From 3ea226c89011cab7655d6901d070382fc807ef73 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 26 Mar 2025 13:41:12 +0100 Subject: [PATCH 039/549] Better use "string.ReplaceAll". --- backend_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend_client.go b/backend_client.go index 8b60869..f39fb15 100644 --- a/backend_client.go +++ b/backend_client.go @@ -126,8 +126,8 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ var requestUrl *url.URL if b.capabilities.HasCapabilityFeature(ctx, u, FeatureSignalingV3Api) { newUrl := *u - newUrl.Path = strings.Replace(newUrl.Path, "/spreed/api/v1/signaling/", "/spreed/api/v3/signaling/", -1) - newUrl.Path = strings.Replace(newUrl.Path, "/spreed/api/v2/signaling/", "/spreed/api/v3/signaling/", -1) + 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/") requestUrl = &newUrl } else { requestUrl = u From 32a86fdd762af669c44c4f7d00676e834581f244 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 26 Mar 2025 14:03:29 +0100 Subject: [PATCH 040/549] Don't capitalize error messages. --- etcd_client.go | 6 +++--- grpc_client.go | 4 ++-- hub.go | 6 +++--- mcu_janus.go | 24 ++++++++++++------------ mcu_janus_publisher.go | 6 +++--- mcu_janus_stream_selection.go | 8 ++++---- mcu_janus_subscriber.go | 10 +++++----- mcu_proxy.go | 26 +++++++++++++------------- proxy/proxy_server.go | 26 +++++++++++++------------- proxy/proxy_tokens_etcd.go | 2 +- proxy/proxy_tokens_static.go | 6 +++--- proxy_config_etcd.go | 2 +- proxy_config_static.go | 2 +- 13 files changed, 64 insertions(+), 64 deletions(-) diff --git a/etcd_client.go b/etcd_client.go index ea1b64d..23bce3a 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -94,7 +94,7 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { clients, err := srv.GetClient("etcd-client", discoverySrv, discoveryService) if err != nil { if !ignoreErrors { - return fmt.Errorf("Could not discover etcd endpoints for %s: %w", discoverySrv, err) + return fmt.Errorf("could not discover etcd endpoints for %s: %w", discoverySrv, err) } } else { endpoints = clients.Endpoints @@ -118,7 +118,7 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { if logLevel, _ := config.GetString("etcd", "loglevel"); logLevel != "" { var l zapcore.Level if err := l.Set(logLevel); err != nil { - return fmt.Errorf("Unsupported etcd log level %s: %w", logLevel, err) + return fmt.Errorf("unsupported etcd log level %s: %w", logLevel, err) } logConfig := zap.NewProductionConfig() @@ -138,7 +138,7 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { tlsConfig, err := tlsInfo.ClientConfig() if err != nil { if !ignoreErrors { - return fmt.Errorf("Could not setup etcd TLS configuration: %w", err) + return fmt.Errorf("could not setup etcd TLS configuration: %w", err) } log.Printf("Could not setup TLS configuration, will be disabled (%s)", err) diff --git a/grpc_client.go b/grpc_client.go index 1d33e62..4b10631 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -727,12 +727,12 @@ func (c *GrpcClients) onLookup(entry *DnsMonitorEntry, all []net.IP, added []net func (c *GrpcClients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool, opts ...grpc.DialOption) error { if !c.etcdClient.IsConfigured() { - return fmt.Errorf("No etcd endpoints configured") + return fmt.Errorf("no etcd endpoints configured") } targetPrefix, _ := config.GetString("grpc", "targetprefix") if targetPrefix == "" { - return fmt.Errorf("No GRPC target prefix configured") + return fmt.Errorf("no GRPC target prefix configured") } c.targetPrefix = targetPrefix if c.targetInformation == nil { diff --git a/hub.go b/hub.go index 653c155..e7143c6 100644 --- a/hub.go +++ b/hub.go @@ -1315,7 +1315,7 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message } default: log.Printf("Unexpected signing method: %v", token.Header["alg"]) - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } // Run in timeout context to prevent blocking too long. @@ -1331,13 +1331,13 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message keyData, _, found = h.backend.capabilities.GetStringConfig(backendCtx, url, ConfigGroupSignaling, ConfigKeyHelloV2TokenKey) } if !found { - return nil, fmt.Errorf("No key found for issuer") + return nil, fmt.Errorf("no key found for issuer") } } key, err := loadKeyFunc([]byte(keyData)) if err != nil { - return nil, fmt.Errorf("Could not parse token key: %w", err) + return nil, fmt.Errorf("could not parse token key: %w", err) } return key, nil diff --git a/mcu_janus.go b/mcu_janus.go index 1a68003..da5cd71 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -49,7 +49,7 @@ const ( ) var ( - ErrRemoteStreamsNotSupported = errors.New("Need Janus 1.1.0 for remote streams") + ErrRemoteStreamsNotSupported = errors.New("need Janus 1.1.0 for remote streams") streamTypeUserIds = map[StreamType]uint64{ StreamTypeVideo: videoPublisherUserId, @@ -73,19 +73,19 @@ func convertIntValue(value interface{}) (uint64, error) { switch t := value.(type) { case float64: if t < 0 { - return 0, fmt.Errorf("Unsupported float64 number: %+v", t) + return 0, fmt.Errorf("unsupported float64 number: %+v", t) } return uint64(t), nil case uint64: return t, nil case int: if t < 0 { - return 0, fmt.Errorf("Unsupported int number: %+v", t) + return 0, fmt.Errorf("unsupported int number: %+v", t) } return uint64(t), nil case int64: if t < 0 { - return 0, fmt.Errorf("Unsupported int64 number: %+v", t) + return 0, fmt.Errorf("unsupported int64 number: %+v", t) } return uint64(t), nil case json.Number: @@ -93,11 +93,11 @@ func convertIntValue(value interface{}) (uint64, error) { if err != nil { return 0, err } else if r < 0 { - return 0, fmt.Errorf("Unsupported JSON number: %+v", t) + return 0, fmt.Errorf("unsupported JSON number: %+v", t) } return uint64(r), nil default: - return 0, fmt.Errorf("Unknown number type: %+v (%T)", t, t) + return 0, fmt.Errorf("unknown number type: %+v (%T)", t, t) } } @@ -342,14 +342,14 @@ func (m *mcuJanus) Start(ctx context.Context) error { log.Printf("Connected to %s %s by %s", info.Name, info.VersionString, info.Author) plugin, found := info.Plugins[pluginVideoRoom] if !found { - return fmt.Errorf("Plugin %s is not supported", pluginVideoRoom) + return fmt.Errorf("plugin %s is not supported", pluginVideoRoom) } m.version = info.Version log.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) if !info.DataChannels { - return fmt.Errorf("Data channels are not supported") + return fmt.Errorf("data channels are not supported") } log.Println("Data channels are supported") @@ -543,7 +543,7 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, if _, err := handle.Detach(ctx); err != nil { log.Printf("Error detaching handle %d: %s", handle.Id, err) } - return 0, 0, fmt.Errorf("No room id received: %+v", create_response) + return 0, 0, fmt.Errorf("no room id received: %+v", create_response) } log.Println("Created room", roomId, create_response.PluginData) @@ -590,7 +590,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id string, st func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { if _, found := streamTypeUserIds[streamType]; !found { - return nil, fmt.Errorf("Unsupported stream type %s", streamType) + return nil, fmt.Errorf("unsupported stream type %s", streamType) } handle, session, roomId, maxBitrate, err := m.getOrCreatePublisherHandle(ctx, id, streamType, settings) @@ -688,7 +688,7 @@ func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher st func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publisher string, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { if _, found := streamTypeUserIds[streamType]; !found { - return nil, fmt.Errorf("Unsupported stream type %s", streamType) + return nil, fmt.Errorf("unsupported stream type %s", streamType) } handle, pub, err := m.getOrCreateSubscriberHandle(ctx, publisher, streamType) @@ -826,7 +826,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re func (m *mcuJanus) NewRemotePublisher(ctx context.Context, listener McuListener, controller RemotePublisherController, streamType StreamType) (McuRemotePublisher, error) { if _, found := streamTypeUserIds[streamType]; !found { - return nil, fmt.Errorf("Unsupported stream type %s", streamType) + return nil, fmt.Errorf("unsupported stream type %s", streamType) } if !m.hasRemotePublisher() { diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 9e82d80..fa0b509 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -171,7 +171,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli p.deferred <- func() { if data.offerSdp == nil { // Should have been checked before. - go callback(errors.New("No sdp found in offer"), nil) + go callback(errors.New("no sdp found in offer"), nil) return } @@ -226,13 +226,13 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli if data.Sid == "" || data.Sid == p.Sid() { p.sendCandidate(msgctx, jsep_msg["candidate"], callback) } else { - go callback(fmt.Errorf("Candidate message sid (%s) does not match publisher sid (%s)", data.Sid, p.Sid()), nil) + go callback(fmt.Errorf("candidate message sid (%s) does not match publisher sid (%s)", data.Sid, p.Sid()), nil) } } case "endOfCandidates": // Ignore default: - go callback(fmt.Errorf("Unsupported message type: %s", data.Type), nil) + go callback(fmt.Errorf("unsupported message type: %s", data.Type), nil) } } diff --git a/mcu_janus_stream_selection.go b/mcu_janus_stream_selection.go index 9381ef3..db12a88 100644 --- a/mcu_janus_stream_selection.go +++ b/mcu_janus_stream_selection.go @@ -66,7 +66,7 @@ func parseStreamSelection(payload map[string]interface{}) (*streamSelection, err stream.substream.Valid = true stream.substream.Int16 = int16(value) default: - return nil, fmt.Errorf("Unsupported substream value: %v", value) + return nil, fmt.Errorf("unsupported substream value: %v", value) } } @@ -82,7 +82,7 @@ func parseStreamSelection(payload map[string]interface{}) (*streamSelection, err stream.temporal.Valid = true stream.temporal.Int16 = int16(value) default: - return nil, fmt.Errorf("Unsupported temporal value: %v", value) + return nil, fmt.Errorf("unsupported temporal value: %v", value) } } @@ -92,7 +92,7 @@ func parseStreamSelection(payload map[string]interface{}) (*streamSelection, err stream.audio.Valid = true stream.audio.Bool = value default: - return nil, fmt.Errorf("Unsupported audio value: %v", value) + return nil, fmt.Errorf("unsupported audio value: %v", value) } } @@ -102,7 +102,7 @@ func parseStreamSelection(payload map[string]interface{}) (*streamSelection, err stream.video.Valid = true stream.video.Bool = value default: - return nil, fmt.Errorf("Unsupported video value: %v", value) + return nil, fmt.Errorf("unsupported video value: %v", value) } } diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index b79575f..123520e 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -179,7 +179,7 @@ retry: // so a new object will be created if the request is retried. p.mcu.unregisterClient(p) p.listener.SubscriberClosed(p) - callback(fmt.Errorf("Already connected as subscriber for %s, error during re-joining: %s", p.streamType, err), nil) + callback(fmt.Errorf("already connected as subscriber for %s, error during re-joining: %s", p.streamType, err), nil) return } @@ -215,7 +215,7 @@ retry: goto retry default: // TODO(jojo): Should we handle other errors, too? - callback(fmt.Errorf("Error joining room as subscriber: %+v", join_response), nil) + callback(fmt.Errorf("error joining room as subscriber: %+v", join_response), nil) return } } @@ -279,7 +279,7 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl if data.Sid == "" || data.Sid == p.Sid() { p.sendAnswer(msgctx, jsep_msg, callback) } else { - go callback(fmt.Errorf("Answer message sid (%s) does not match subscriber sid (%s)", data.Sid, p.Sid()), nil) + go callback(fmt.Errorf("answer message sid (%s) does not match subscriber sid (%s)", data.Sid, p.Sid()), nil) } } case "candidate": @@ -290,7 +290,7 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl if data.Sid == "" || data.Sid == p.Sid() { p.sendCandidate(msgctx, jsep_msg["candidate"], callback) } else { - go callback(fmt.Errorf("Candidate message sid (%s) does not match subscriber sid (%s)", data.Sid, p.Sid()), nil) + go callback(fmt.Errorf("candidate message sid (%s) does not match subscriber sid (%s)", data.Sid, p.Sid()), nil) } } case "endOfCandidates": @@ -316,6 +316,6 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl } default: // Return error asynchronously - go callback(fmt.Errorf("Unsupported message type: %s", data.Type), nil) + go callback(fmt.Errorf("unsupported message type: %s", data.Type), nil) } } diff --git a/mcu_proxy.go b/mcu_proxy.go index 0d8c537..8dff7b4 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1165,7 +1165,7 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe // TODO: Cancel request return nil, err } else if response.Type == "error" { - return nil, fmt.Errorf("Error creating %s publisher for %s on %s: %+v", streamType, id, c, response.Error) + return nil, fmt.Errorf("error creating %s publisher for %s on %s: %+v", streamType, id, c, response.Error) } proxyId := response.Command.Id @@ -1195,7 +1195,7 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList // TODO: Cancel request return nil, err } else if response.Type == "error" { - return nil, fmt.Errorf("Error creating %s subscriber for %s on %s: %+v", streamType, publisherSessionId, c, response.Error) + return nil, fmt.Errorf("error creating %s subscriber for %s on %s: %+v", streamType, publisherSessionId, c, response.Error) } proxyId := response.Command.Id @@ -1236,7 +1236,7 @@ func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener M // TODO: Cancel request return nil, err } else if response.Type == "error" { - return nil, fmt.Errorf("Error creating remote %s subscriber for %s on %s (forwarded to %s): %+v", streamType, publisherSessionId, c, publisherConn, response.Error) + return nil, fmt.Errorf("error creating remote %s subscriber for %s on %s (forwarded to %s): %+v", streamType, publisherSessionId, c, publisherConn, response.Error) } proxyId := response.Command.Id @@ -1317,19 +1317,19 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * tokenId, _ := config.GetString("mcu", "token_id") if tokenId == "" { - return nil, fmt.Errorf("No token id configured") + return nil, fmt.Errorf("no token id configured") } tokenKeyFilename, _ := config.GetString("mcu", "token_key") if tokenKeyFilename == "" { - return nil, fmt.Errorf("No token key configured") + return nil, fmt.Errorf("no token key configured") } tokenKeyData, err := os.ReadFile(tokenKeyFilename) if err != nil { - return nil, fmt.Errorf("Could not read private key from %s: %s", tokenKeyFilename, err) + return nil, fmt.Errorf("could not read private key from %s: %s", tokenKeyFilename, err) } tokenKey, err := jwt.ParseRSAPrivateKeyFromPEM(tokenKeyData) if err != nil { - return nil, fmt.Errorf("Could not parse private key from %s: %s", tokenKeyFilename, err) + return nil, fmt.Errorf("could not parse private key from %s: %s", tokenKeyFilename, err) } settings, err := newMcuProxySettings((config)) @@ -1372,7 +1372,7 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * case proxyUrlTypeEtcd: mcu.config, err = NewProxyConfigEtcd(config, etcdClient, mcu) default: - err = fmt.Errorf("Unsupported proxy URL type %s", urlType) + err = fmt.Errorf("unsupported proxy URL type %s", urlType) } if err != nil { return nil, err @@ -1862,7 +1862,7 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id st if publisher == nil { statsProxyNobackendAvailableTotal.WithLabelValues(string(streamType)).Inc() - return nil, fmt.Errorf("No MCU connection available") + return nil, fmt.Errorf("no MCU connection available") } return publisher, nil @@ -1944,7 +1944,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ id, found := conn.publisherIds[getStreamId(publisher, streamType)] conn.publishersLock.Unlock() if !found { - return nil, fmt.Errorf("Unknown publisher %s", publisher) + return nil, fmt.Errorf("unknown publisher %s", publisher) } publisherInfo = &proxyPublisherInfo{ @@ -1971,7 +1971,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ conn.publishersLock.Unlock() if !found { ch <- &proxyPublisherInfo{ - err: fmt.Errorf("Unknown id for local %s publisher %s", streamType, publisher), + err: fmt.Errorf("unknown id for local %s publisher %s", streamType, publisher), } return } @@ -2055,7 +2055,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ wg.Wait() select { case ch <- &proxyPublisherInfo{ - err: fmt.Errorf("No %s publisher %s found", streamType, publisher), + err: fmt.Errorf("no %s publisher %s found", streamType, publisher), }: default: } @@ -2064,7 +2064,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ case info := <-ch: publisherInfo = info case <-ctx.Done(): - return nil, fmt.Errorf("No %s publisher %s found", streamType, publisher) + return nil, fmt.Errorf("no %s publisher %s found", streamType, publisher) } } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index b256d09..e01dd64 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -208,12 +208,12 @@ func getTargetBandwidths(config *goconf.ConfigFile) (int, int) { func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*ProxyServer, error) { hashKey := make([]byte, 64) if _, err := rand.Read(hashKey); err != nil { - return nil, fmt.Errorf("Could not generate random hash key: %s", err) + return nil, fmt.Errorf("could not generate random hash key: %s", err) } blockKey := make([]byte, 32) if _, err := rand.Read(blockKey); err != nil { - return nil, fmt.Errorf("Could not generate random block key: %s", err) + return nil, fmt.Errorf("could not generate random block key: %s", err) } var tokens ProxyTokens @@ -229,7 +229,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* case TokenTypeStatic: tokens, err = NewProxyTokensStatic(config) default: - return nil, fmt.Errorf("Unsupported token type configured: %s", tokenType) + return nil, fmt.Errorf("unsupported token type configured: %s", tokenType) } if err != nil { return nil, err @@ -266,7 +266,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* if signaling.IsValidCountry(country) { log.Printf("Sending %s as country information", country) } else if country != "" { - return nil, fmt.Errorf("Invalid country: %s", country) + return nil, fmt.Errorf("invalid country: %s", country) } else { log.Printf("Not sending country information") } @@ -288,15 +288,15 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* if tokenId != "" { tokenKeyFilename, _ := config.GetString("app", "token_key") if tokenKeyFilename == "" { - return nil, fmt.Errorf("No token key configured") + return nil, fmt.Errorf("no token key configured") } tokenKeyData, err := os.ReadFile(tokenKeyFilename) if err != nil { - return nil, fmt.Errorf("Could not read private key from %s: %s", tokenKeyFilename, err) + return nil, fmt.Errorf("could not read private key from %s: %s", tokenKeyFilename, err) } tokenKey, err = jwt.ParseRSAPrivateKeyFromPEM(tokenKeyData) if err != nil { - return nil, fmt.Errorf("Could not parse private key from %s: %s", tokenKeyFilename, err) + return nil, fmt.Errorf("could not parse private key from %s: %s", tokenKeyFilename, err) } log.Printf("Using \"%s\" as token id for remote streams", tokenId) @@ -400,7 +400,7 @@ func (s *ProxyServer) checkOrigin(r *http.Request) bool { func (s *ProxyServer) Start(config *goconf.ConfigFile) error { s.url, _ = signaling.GetStringOptionWithEnv(config, "mcu", "url") if s.url == "" { - return fmt.Errorf("No MCU server url configured") + return fmt.Errorf("no MCU server url configured") } mcuType, _ := config.GetString("mcu", "type") @@ -425,7 +425,7 @@ func (s *ProxyServer) Start(config *goconf.ConfigFile) error { signaling.RegisterJanusMcuStats() } default: - return fmt.Errorf("Unsupported MCU type: %s", mcuType) + return fmt.Errorf("unsupported MCU type: %s", mcuType) } if err == nil { mcu.SetOnConnected(s.onMcuConnected) @@ -442,7 +442,7 @@ func (s *ProxyServer) Start(config *goconf.ConfigFile) error { log.Printf("Could not initialize %s MCU at %s (%s) will retry in %s", mcuType, s.url, err, backoff.NextWait()) backoff.Wait(ctx) if ctx.Err() != nil { - return fmt.Errorf("Cancelled") + return fmt.Errorf("cancelled") } } @@ -1330,14 +1330,14 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { log.Printf("Unexpected signing method: %v", token.Header["alg"]) reason = "unsupported-signing-method" - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } claims, ok := token.Claims.(*signaling.TokenClaims) if !ok { log.Printf("Unsupported claims type: %+v", token.Claims) reason = "unsupported-claims" - return nil, fmt.Errorf("Unsupported claims type") + return nil, fmt.Errorf("unsupported claims type") } tokenKey, err := s.tokens.Get(claims.Issuer) @@ -1350,7 +1350,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str if tokenKey == nil || tokenKey.key == nil { log.Printf("Issuer %s is not supported", claims.Issuer) reason = "unsupported-issuer" - return nil, fmt.Errorf("No key found for issuer") + return nil, fmt.Errorf("no key found for issuer") } return tokenKey.key, nil diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index 09dfc51..3e6e602 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -59,7 +59,7 @@ func NewProxyTokensEtcd(config *goconf.ConfigFile) (ProxyTokens, error) { } if !client.IsConfigured() { - return nil, fmt.Errorf("No etcd endpoints configured") + return nil, fmt.Errorf("no etcd endpoints configured") } result := &tokensEtcd{ diff --git a/proxy/proxy_tokens_static.go b/proxy/proxy_tokens_static.go index 8de255a..9ad7917 100644 --- a/proxy/proxy_tokens_static.go +++ b/proxy/proxy_tokens_static.go @@ -71,7 +71,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error for id, filename := range options { if filename == "" { if !ignoreErrors { - return fmt.Errorf("No filename given for token %s", id) + return fmt.Errorf("no filename given for token %s", id) } log.Printf("No filename given for token %s, ignoring", id) @@ -81,7 +81,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error keyData, err := os.ReadFile(filename) if err != nil { if !ignoreErrors { - return fmt.Errorf("Could not read public key from %s: %s", filename, err) + return fmt.Errorf("could not read public key from %s: %s", filename, err) } log.Printf("Could not read public key from %s, ignoring: %s", filename, err) @@ -90,7 +90,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error key, err := jwt.ParseRSAPublicKeyFromPEM(keyData) if err != nil { if !ignoreErrors { - return fmt.Errorf("Could not parse public key from %s: %s", filename, err) + return fmt.Errorf("could not parse public key from %s: %s", filename, err) } log.Printf("Could not parse public key from %s, ignoring: %s", filename, err) diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index 35ccade..68cc996 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -48,7 +48,7 @@ type proxyConfigEtcd struct { func NewProxyConfigEtcd(config *goconf.ConfigFile, etcdClient *EtcdClient, proxy McuProxy) (ProxyConfig, error) { if !etcdClient.IsConfigured() { - return nil, errors.New("No etcd endpoints configured") + return nil, errors.New("no etcd endpoints configured") } closeCtx, closeFunc := context.WithCancel(context.Background()) diff --git a/proxy_config_static.go b/proxy_config_static.go index eda67d7..bea279f 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -59,7 +59,7 @@ func NewProxyConfigStatic(config *goconf.ConfigFile, proxy McuProxy, dnsMonitor return nil, err } if len(result.connectionsMap) == 0 { - return nil, errors.New("No MCU proxy connections configured") + return nil, errors.New("no MCU proxy connections configured") } return result, nil } From 24dd3f08ef246f06e0c0d2da2f75f45afe64cc82 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 26 Mar 2025 14:08:35 +0100 Subject: [PATCH 041/549] Use tagged switch instead of comparison where sensible. --- client.go | 5 +++-- hub_test.go | 5 +++-- mcu_proxy.go | 5 +++-- mcu_test.go | 5 +++-- proxy/proxy_remote.go | 5 +++-- room.go | 5 +++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 3980218..6c534d4 100644 --- a/client.go +++ b/client.go @@ -271,7 +271,8 @@ func (c *Client) Close() { func (c *Client) doClose() { closed := c.closed.Add(1) - if closed == 1 { + switch closed { + case 1: c.mu.Lock() defer c.mu.Unlock() if c.conn != nil { @@ -279,7 +280,7 @@ func (c *Client) doClose() { c.conn.Close() c.conn = nil } - } else if closed == 2 { + case 2: // Both the read pump and message processing must be finished before closing. c.closer.Close() <-c.messagesDone diff --git a/hub_test.go b/hub_test.go index cf191e4..16b2341 100644 --- a/hub_test.go +++ b/hub_test.go @@ -349,9 +349,10 @@ func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re if len(request.Auth.Params) > 0 { require.NoError(json.Unmarshal(request.Auth.Params, ¶ms)) } - if params.UserId == "" { + switch params.UserId { + case "": params.UserId = testDefaultUserId - } else if params.UserId == authAnonymousUserId { + case authAnonymousUserId: params.UserId = "" } diff --git a/mcu_proxy.go b/mcu_proxy.go index 8dff7b4..a50a835 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -749,9 +749,10 @@ func (c *mcuProxyConnection) reconnect() { c.scheduleReconnect() return } - if u.Scheme == "http" { + switch u.Scheme { + case "http": u.Scheme = "ws" - } else if u.Scheme == "https" { + case "https": u.Scheme = "wss" } diff --git a/mcu_test.go b/mcu_test.go index 1fb6841..6db0db6 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -200,13 +200,14 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClie sdp := data.Payload["sdp"] if sdp, ok := sdp.(string); ok { p.sdp = sdp - if sdp == MockSdpOfferAudioOnly { + switch sdp { + case MockSdpOfferAudioOnly: callback(nil, map[string]interface{}{ "type": "answer", "sdp": MockSdpAnswerAudioOnly, }) return - } else if sdp == MockSdpOfferAudioAndVideo { + case MockSdpOfferAudioAndVideo: callback(nil, map[string]interface{}{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 81a7bbf..fadc4d0 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -118,9 +118,10 @@ func (c *RemoteConnection) reconnect() { c.scheduleReconnect() return } - if u.Scheme == "http" { + switch u.Scheme { + case "http": u.Scheme = "ws" - } else if u.Scheme == "https" { + case "https": u.Scheme = "wss" } diff --git a/room.go b/room.go index e6e04fb..46f6cff 100644 --- a/room.go +++ b/room.go @@ -979,14 +979,15 @@ func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) { } if joinLeave != 0 { - if joinLeave == 1 { + switch joinLeave { + case 1: r.mu.Lock() if !r.inCallSessions[session] { r.inCallSessions[session] = true log.Printf("Session %s joined call %s", session.PublicId(), r.id) } r.mu.Unlock() - } else if joinLeave == 2 { + case 2: r.mu.Lock() delete(r.inCallSessions, session) r.mu.Unlock() From 0f9cee7773a55e4a7d023651a2c475060ee23641 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 26 Mar 2025 14:51:14 +0100 Subject: [PATCH 042/549] Generate GRPC server id directly in hash. --- grpc_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc_server.go b/grpc_server.go index 77c6aec..c0ca8b0 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -51,7 +51,7 @@ func init() { hostname = newRandomString(8) } md := sha256.New() - md.Write([]byte(fmt.Sprintf("%s-%s-%d", newRandomString(32), hostname, os.Getpid()))) + fmt.Fprintf(md, "%s-%s-%d", newRandomString(32), hostname, os.Getpid()) GrpcServerId = hex.EncodeToString(md.Sum(nil)) } From 1cee8ebbfd4bdd288b56dac54b898b6847b7e2c6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 26 Mar 2025 14:51:33 +0100 Subject: [PATCH 043/549] Merge conditional assignment into variable declaration --- hub_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hub_test.go b/hub_test.go index 16b2341..6be65d7 100644 --- a/hub_test.go +++ b/hub_test.go @@ -681,10 +681,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "MultiRoom") { signaling[ConfigKeySessionPingLimit] = 2 } - useV2 := true - if os.Getenv("SKIP_V2_CAPABILITIES") != "" { - useV2 = false - } + useV2 := os.Getenv("SKIP_V2_CAPABILITIES") == "" if (strings.Contains(t.Name(), "V2") && useV2) || strings.Contains(t.Name(), "Federation") { key := getPublicAuthToken(t) public, err := x509.MarshalPKIXPublicKey(key) From 934422d05e32d52bf396d99c4077a8afdea32a68 Mon Sep 17 00:00:00 2001 From: Jean Kahrs Date: Tue, 1 Apr 2025 15:33:22 +0200 Subject: [PATCH 044/549] Explicitly set TMPDIR to ensure that it is a path where go-installed utils (pe. easyjson-bootstrap) can be executed. Fixes #955 --- .gitignore | 1 + Makefile | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 680cc06..3ba4e09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ +tmp/ vendor/ *.pem diff --git a/Makefile b/Makefile index 3db88cf..b228d7a 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ GOFMT := "$(GODIR)/gofmt" GOOS ?= linux GOARCH ?= amd64 GOVERSION := $(shell "$(GO)" env GOVERSION | sed "s|go||" ) +TMPDIR := $(CURDIR)/tmp BINDIR := $(CURDIR)/bin VENDORDIR := "$(CURDIR)/vendor" VERSION := $(shell "$(CURDIR)/scripts/get-version.sh") @@ -76,7 +77,7 @@ endif hook: [ ! -d "$(CURDIR)/.git/hooks" ] || ln -sf "$(CURDIR)/scripts/pre-commit.hook" "$(CURDIR)/.git/hooks/pre-commit" -$(GOPATHBIN)/easyjson: go.mod go.sum +$(GOPATHBIN)/easyjson: go.mod go.sum | $(TMPDIR) $(GO) install github.com/mailru/easyjson/... $(GOPATHBIN)/protoc-gen-go: go.mod go.sum @@ -124,7 +125,7 @@ coverhtml: vet %_easyjson.go: %.go $(GOPATHBIN)/easyjson | $(PROTO_GO_FILES) rm -f easyjson-bootstrap*.go - PATH="$(GODIR)":$(PATH) "$(GOPATHBIN)/easyjson" -all $*.go + TMPDIR=$(TMPDIR) PATH="$(GODIR)":$(PATH) "$(GOPATHBIN)/easyjson" -all $*.go %.pb.go: %.proto $(GOPATHBIN)/protoc-gen-go $(GOPATHBIN)/protoc-gen-go-grpc PATH="$(GODIR)":"$(GOPATHBIN)":$(PATH) protoc \ @@ -142,12 +143,15 @@ common: $(EASYJSON_GO_FILES) $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES) # Optimize easyjson files that could call generated functions instead of duplicating code. for file in $(EASYJSON_FILES); do \ rm -f easyjson-bootstrap*.go; \ - PATH="$(GODIR)":$(PATH) "$(GOPATHBIN)/easyjson" -all $$file; \ + TMPDIR=$(TMPDIR) PATH="$(GODIR)":$(PATH) "$(GOPATHBIN)/easyjson" -all $$file; \ done $(BINDIR): mkdir -p "$(BINDIR)" +$(TMPDIR): + mkdir -p "$(TMPDIR)" + client: $(BINDIR)/client $(BINDIR)/client: go.mod go.sum $(CLIENT_GO_FILES) $(COMMON_GO_FILES) | $(BINDIR) @@ -168,6 +172,7 @@ clean: rm -f "$(BINDIR)/client" rm -f "$(BINDIR)/signaling" rm -f "$(BINDIR)/proxy" + rm -rf "$(TMPDIR)" clean-generated: clean rm -f $(EASYJSON_GO_FILES) $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES) From 088c809c6f51790f4b4b8ce4bfb12099b2b434fa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 2 Apr 2025 16:21:46 +0200 Subject: [PATCH 045/549] Close subscribers on errors during initial connection. This makes sure the subscriber is not cached but created newly which will fetch the publisher room id again in case it was changed since the initial subscription. --- mcu_janus_subscriber.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 123520e..a60afb1 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -197,7 +197,10 @@ retry: case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: switch error_code { case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: - log.Printf("Publisher %s not created yet for %s, wait and retry to join room %d as subscriber", p.publisher, p.streamType, p.roomId) + log.Printf("Publisher %s not created yet for %s, not joining room %d as subscriber", p.publisher, p.streamType, p.roomId) + p.listener.SubscriberClosed(p) + callback(fmt.Errorf("Publisher %s not created yet for %s", p.publisher, p.streamType), nil) + return case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: log.Printf("Publisher %s not sending yet for %s, wait and retry to join room %d as subscriber", p.publisher, p.streamType, p.roomId) } @@ -208,6 +211,7 @@ retry: } if err := waiter.Wait(ctx); err != nil { + p.listener.SubscriberClosed(p) callback(err, nil) return } From c4d4aacc4e4d9f89cbf5566d8036a0f021834e68 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 9 Apr 2025 16:57:37 +0200 Subject: [PATCH 046/549] Add "/usr/lib64" to systemd ExecPath Fixes "Permission denied" errors on Fedora / RedHat systems. --- dist/init/systemd/signaling.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/init/systemd/signaling.service b/dist/init/systemd/signaling.service index cdf67fa..21d75c3 100644 --- a/dist/init/systemd/signaling.service +++ b/dist/init/systemd/signaling.service @@ -12,7 +12,7 @@ ConfigurationDirectory=signaling # Hardening - see systemd.exec(5) CapabilityBoundingSet= -ExecPaths=/usr/bin/signaling /usr/lib +ExecPaths=/usr/bin/signaling /usr/lib /usr/lib64 LockPersonality=yes MemoryDenyWriteExecute=yes NoExecPaths=/ From 2827506ac5678d803c2b69441bbb5af7f4971b2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 20:48:08 +0000 Subject: [PATCH 047/549] Bump markdown from 3.7 to 3.8 in /docs Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.7 to 3.8. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.7...3.8) --- updated-dependencies: - dependency-name: markdown dependency-version: '3.8' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 1e7f67a..c42a3ea 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ jinja2==3.1.6 -markdown==3.7 +markdown==3.8 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==8.2.3 From 4bcd4f9d3a1f460b37e09943a44ff66056d1f562 Mon Sep 17 00:00:00 2001 From: Thomas Anderson <127358482+zc-devs@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:39:15 +0300 Subject: [PATCH 048/549] Allow to set GOMAXPROCS (#965) Allow to set GOMAXPROCS https://stackoverflow.com/questions/17853831/what-is-the-gomaxprocs-default-value https://pkg.go.dev/runtime#GOMAXPROCS --- client/main.go | 4 +--- proxy/main.go | 4 +--- server/main.go | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/client/main.go b/client/main.go index c320334..ef803fe 100644 --- a/client/main.go +++ b/client/main.go @@ -517,9 +517,7 @@ func main() { } cookie := signaling.NewSessionIdCodec([]byte(hashKey), blockBytes) - cpus := runtime.NumCPU() - runtime.GOMAXPROCS(cpus) - log.Printf("Using a maximum of %d CPUs", cpus) + log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) diff --git a/proxy/main.go b/proxy/main.go index 18f7bf5..706f220 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -76,9 +76,7 @@ func main() { log.Fatal("Could not read configuration: ", err) } - cpus := runtime.NumCPU() - runtime.GOMAXPROCS(cpus) - log.Printf("Using a maximum of %d CPUs", cpus) + log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) r := mux.NewRouter() diff --git a/server/main.go b/server/main.go index e372bd9..97becd6 100644 --- a/server/main.go +++ b/server/main.go @@ -166,9 +166,7 @@ func main() { log.Fatal("Could not read configuration: ", err) } - cpus := runtime.NumCPU() - runtime.GOMAXPROCS(cpus) - log.Printf("Using a maximum of %d CPUs", cpus) + log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) signaling.RegisterStats() From f99977718b4442cf7b93199a5ad5306b9e99a39e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 08:44:54 +0200 Subject: [PATCH 049/549] Drop support for Go 1.22 --- .github/workflows/govuln.yml | 1 - .github/workflows/lint.yml | 4 ++-- .github/workflows/tarball.yml | 2 -- .github/workflows/test.yml | 1 - README.md | 2 +- go.mod | 2 +- 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index 6a0648b..6b3e4aa 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -24,7 +24,6 @@ jobs: strategy: matrix: go-version: - - "1.22" - "1.23" - "1.24" steps: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7c3f60a..43c1f03 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: "1.23" - name: lint uses: golangci/golangci-lint-action@v7.0.0 @@ -49,7 +49,7 @@ jobs: - name: Check minimum supported version of Go run: | - go mod tidy -go=1.22.0 -compat=1.22.0 + go mod tidy -go=1.23.0 -compat=1.23.0 - name: Check go.mod / go.sum run: | diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 25fdff4..d7aa549 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -24,7 +24,6 @@ jobs: strategy: matrix: go-version: - - "1.22" - "1.23" - "1.24" runs-on: ubuntu-latest @@ -49,7 +48,6 @@ jobs: strategy: matrix: go-version: - - "1.22" - "1.23" - "1.24" runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d86525..a59b47f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,6 @@ jobs: strategy: matrix: go-version: - - "1.22" - "1.23" - "1.24" runs-on: ubuntu-latest diff --git a/README.md b/README.md index 270aedf..f9eef3c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ information on the API of the signaling server. The following tools are required for building the signaling server. - git -- go >= 1.22 +- go >= 1.23 - make Usually the last two versions of Go are supported. This follows the release diff --git a/go.mod b/go.mod index 2dc1528..c58ee5f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/strukturag/nextcloud-spreed-signaling -go 1.22.0 +go 1.23.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 From 730a0c870901279f39db8e4260e3182eace244b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 06:59:30 +0000 Subject: [PATCH 050/549] Bump golang.org/x/crypto from 0.32.0 to 0.35.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.35.0. - [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.35.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.35.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index c58ee5f..908df9e 100644 --- a/go.mod +++ b/go.mod @@ -83,10 +83,10 @@ require ( go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect diff --git a/go.sum b/go.sum index f790684..f42232c 100644 --- a/go.sum +++ b/go.sum @@ -225,8 +225,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -256,8 +256,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -266,12 +266,12 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 00d26fdee1d6e18e3678635a7560138b277c6b4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 06:59:34 +0000 Subject: [PATCH 051/549] Bump github.com/nats-io/nats.go from 1.39.1 to 1.41.1 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.39.1 to 1.41.1. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.39.1...v1.41.1) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.41.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c58ee5f..3abb298 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.10.25 - github.com/nats-io/nats.go v1.39.1 + github.com/nats-io/nats.go v1.41.1 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.11 @@ -50,7 +50,7 @@ require ( github.com/jonboulle/clockwork v0.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index f790684..4fd069a 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -123,8 +123,8 @@ github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= github.com/nats-io/nats-server/v2 v2.10.25 h1:J0GWLDDXo5HId7ti/lTmBfs+lzhmu8RPkoKl0eSCqwc= github.com/nats-io/nats-server/v2 v2.10.25/go.mod h1:/YYYQO7cuoOBt+A7/8cVjuhWTaTUEAlZbJT+3sMAfFU= -github.com/nats-io/nats.go v1.39.1 h1:oTkfKBmz7W047vRxV762M67ZdXeOtUgvbBaNoQ+3PPk= -github.com/nats-io/nats.go v1.39.1/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM= +github.com/nats-io/nats.go v1.41.1 h1:lCc/i5x7nqXbspxtmXaV4hRguMPHqE/kYltG9knrCdU= +github.com/nats-io/nats.go v1.41.1/go.mod h1:mzHiutcAdZrg6WLfYVKXGseqqow2fWmwlTEUOHsI4jY= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From ea3f2af2fbcbc88dce60dc5c32f76ff6fb0a562f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 07:01:46 +0000 Subject: [PATCH 052/549] Bump google.golang.org/grpc from 1.71.0 to 1.71.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.71.0 to 1.71.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.71.0...v1.71.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.71.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c58ee5f..0799f1a 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.5.18 go.etcd.io/etcd/server/v3 v3.5.18 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.71.0 + google.golang.org/grpc v1.71.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index f790684..8edbdf1 100644 --- a/go.sum +++ b/go.sum @@ -304,8 +304,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From a15cfde3d7a05e9f39f4ec392f7944b0d1597942 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 07:16:21 +0000 Subject: [PATCH 053/549] build(deps): bump golang.org/x/net from 0.34.0 to 0.36.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.34.0 to 0.36.0. - [Commits](https://github.com/golang/net/compare/v0.34.0...v0.36.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 443db86..8f48fc3 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.36.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect diff --git a/go.sum b/go.sum index 108efd0..aeda2bf 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= From 594e7645601ead68424e97850f2f16f5ff7ba784 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 07:25:37 +0000 Subject: [PATCH 054/549] build(deps): bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.5.18 to 3.5.19 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.18...v3.5.19) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.5.18 to 3.5.19 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.18...v3.5.19) Updates `go.etcd.io/etcd/client/v3` from 3.5.18 to 3.5.19 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.18...v3.5.19) Updates `go.etcd.io/etcd/server/v3` from 3.5.18 to 3.5.19 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.18...v3.5.19) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 22 +++++++++++----------- go.sum | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index 8f48fc3..7aafc44 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.21.1 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.5.18 - go.etcd.io/etcd/client/pkg/v3 v3.5.18 - go.etcd.io/etcd/client/v3 v3.5.18 - go.etcd.io/etcd/server/v3 v3.5.18 + go.etcd.io/etcd/api/v3 v3.5.21 + go.etcd.io/etcd/client/pkg/v3 v3.5.21 + go.etcd.io/etcd/client/v3 v3.5.21 + go.etcd.io/etcd/server/v3 v3.5.21 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.71.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -70,9 +70,9 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.etcd.io/etcd/client/v2 v2.305.18 // indirect - go.etcd.io/etcd/pkg/v3 v3.5.18 // indirect - go.etcd.io/etcd/raft/v3 v3.5.18 // indirect + go.etcd.io/etcd/client/v2 v2.305.21 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.21 // indirect + go.etcd.io/etcd/raft/v3 v3.5.21 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect @@ -83,10 +83,10 @@ require ( go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.36.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect diff --git a/go.sum b/go.sum index aeda2bf..c81205c 100644 --- a/go.sum +++ b/go.sum @@ -179,20 +179,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.5.18 h1:Q4oDAKnmwqTo5lafvB+afbgCDF7E35E4EYV2g+FNGhs= -go.etcd.io/etcd/api/v3 v3.5.18/go.mod h1:uY03Ob2H50077J7Qq0DeehjM/A9S8PhVfbQ1mSaMopU= -go.etcd.io/etcd/client/pkg/v3 v3.5.18 h1:mZPOYw4h8rTk7TeJ5+3udUkfVGBqc+GCjOJYd68QgNM= -go.etcd.io/etcd/client/pkg/v3 v3.5.18/go.mod h1:BxVf2o5wXG9ZJV+/Cu7QNUiJYk4A29sAhoI5tIRsCu4= -go.etcd.io/etcd/client/v2 v2.305.18 h1:jT7ANzlD47yu7t6ZGBr1trUDEN6P0RG9Wnyio6XP2Qo= -go.etcd.io/etcd/client/v2 v2.305.18/go.mod h1:JikXfwJymsNv633PzkAb5xnVZmROgNWr4E68YCEz4jo= -go.etcd.io/etcd/client/v3 v3.5.18 h1:nvvYmNHGumkDjZhTHgVU36A9pykGa2K4lAJ0yY7hcXA= -go.etcd.io/etcd/client/v3 v3.5.18/go.mod h1:kmemwOsPU9broExyhYsBxX4spCTDX3yLgPMWtpBXG6E= -go.etcd.io/etcd/pkg/v3 v3.5.18 h1:ny8rLA18/4AMdrILacOKwt7//TJjc7oS8JIJoLuNvbY= -go.etcd.io/etcd/pkg/v3 v3.5.18/go.mod h1:gb4CDXuN/OgzUgj+VmUFumLYQ2FUMDC6r/plLIjHPI8= -go.etcd.io/etcd/raft/v3 v3.5.18 h1:gueCda+9U76Lvk6rINjNc/mXalUp0u8OK5CVESDZh4I= -go.etcd.io/etcd/raft/v3 v3.5.18/go.mod h1:XBaZHTJt3nLnpS8hMDR55Sxrq76cEC4xWYMBYSY3jcs= -go.etcd.io/etcd/server/v3 v3.5.18 h1:u67DmyYyGOu08OiO9O3wgCSQEjGBNzjhH+FM3BcabcI= -go.etcd.io/etcd/server/v3 v3.5.18/go.mod h1:waeL2uw6TdXniXaus105tiK1aSbblIBi21uk8y7D6Ng= +go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= +go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= +go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= +go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= +go.etcd.io/etcd/client/v2 v2.305.21 h1:eLiFfexc2mE+pTLz9WwnoEsX5JTTpLCYVivKkmVXIRA= +go.etcd.io/etcd/client/v2 v2.305.21/go.mod h1:OKkn4hlYNf43hpjEM3Ke3aRdUkhSl8xjKjSf8eCq2J8= +go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= +go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= +go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk= +go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU= +go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk= +go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs= +go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU= +go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= @@ -225,8 +225,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -244,8 +244,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= @@ -256,8 +256,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -266,12 +266,12 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From a7a5e889c9bf1fa84252f23671fd8fa33b5b1d6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 07:39:36 +0000 Subject: [PATCH 055/549] Bump github.com/nats-io/nats-server/v2 from 2.10.25 to 2.11.1 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.25 to 2.11.1. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.10.25...v2.11.1) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 7 ++++--- go.sum | 16 ++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 7aafc44..148b7dd 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.10.25 + github.com/nats-io/nats-server/v2 v2.11.1 github.com/nats-io/nats.go v1.41.1 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -43,6 +43,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/go-tpm v0.9.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect @@ -57,7 +58,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/jwt/v2 v2.7.3 // indirect - github.com/nats-io/nkeys v0.4.9 // indirect + github.com/nats-io/nkeys v0.4.10 // 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 @@ -87,7 +88,7 @@ require ( golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.9.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect diff --git a/go.sum b/go.sum index c81205c..a7e331b 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1F github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= +github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -70,6 +72,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= +github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -121,12 +125,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= -github.com/nats-io/nats-server/v2 v2.10.25 h1:J0GWLDDXo5HId7ti/lTmBfs+lzhmu8RPkoKl0eSCqwc= -github.com/nats-io/nats-server/v2 v2.10.25/go.mod h1:/YYYQO7cuoOBt+A7/8cVjuhWTaTUEAlZbJT+3sMAfFU= +github.com/nats-io/nats-server/v2 v2.11.1 h1:LwdauqMqMNhTxTN3+WFTX6wGDOKntHljgZ+7gL5HCnk= +github.com/nats-io/nats-server/v2 v2.11.1/go.mod h1:leXySghbdtXSUmWem8K9McnJ6xbJOb0t9+NQ5HTRZjI= github.com/nats-io/nats.go v1.41.1 h1:lCc/i5x7nqXbspxtmXaV4hRguMPHqE/kYltG9knrCdU= github.com/nats-io/nats.go v1.41.1/go.mod h1:mzHiutcAdZrg6WLfYVKXGseqqow2fWmwlTEUOHsI4jY= -github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= -github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= +github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc= +github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8iFMHvQleYlF5M3PY6zpAbxsngImjE= @@ -272,8 +276,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 75d311c67c5dbeff74be03e93d5d7b28c2f855f2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 10:09:22 +0200 Subject: [PATCH 056/549] Fix formatting of errors in "assert.Fail" calls. --- backend_configuration_test.go | 33 +++++++-------- backend_server_test.go | 14 +++---- dns_monitor_test.go | 8 ++-- federation_test.go | 50 +++++++++++----------- hub_test.go | 74 ++++++++++++++++----------------- mcu_janus_publisher_test.go | 12 +++--- mcu_proxy_test.go | 10 ++--- proxy/proxy_server_test.go | 9 ++-- proxy/proxy_tokens_etcd_test.go | 2 +- proxy_config_test.go | 4 +- roomsessions_test.go | 6 +-- sessionid_codec_test.go | 8 ++-- stats_prometheus_test.go | 7 ++-- transient_data_test.go | 6 +-- 14 files changed, 118 insertions(+), 125 deletions(-) diff --git a/backend_configuration_test.go b/backend_configuration_test.go index e467cbd..a657733 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -493,8 +493,8 @@ func TestBackendConfiguration_Etcd(t *testing.T) { if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal(url1, backends[0].url) && assert.Equal(initialSecret1, string(backends[0].secret)) { - if backend := cfg.GetBackend(mustParse(url1)); backend != backends[0] { - assert.Fail("Expected backend %+v, got %+v", backends[0], backend) + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) } } @@ -504,8 +504,8 @@ func TestBackendConfiguration_Etcd(t *testing.T) { if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal(url1, backends[0].url) && assert.Equal(secret1, string(backends[0].secret)) { - if backend := cfg.GetBackend(mustParse(url1)); backend != backends[0] { - assert.Fail("Expected backend %+v, got %+v", backends[0], backend) + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) } } @@ -520,10 +520,10 @@ func TestBackendConfiguration_Etcd(t *testing.T) { assert.Equal(secret1, string(backends[0].secret)) && assert.Equal(url2, backends[1].url) && assert.Equal(secret2, string(backends[1].secret)) { - if backend := cfg.GetBackend(mustParse(url1)); backend != backends[0] { - assert.Fail("Expected backend %+v, got %+v", backends[0], backend) - } else if backend := cfg.GetBackend(mustParse(url2)); backend != backends[1] { - assert.Fail("Expected backend %+v, got %+v", backends[1], backend) + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } else if backend := cfg.GetBackend(mustParse(url2)); assert.NotNil(backend) { + assert.Equal(backends[1], backend) } } @@ -540,12 +540,12 @@ func TestBackendConfiguration_Etcd(t *testing.T) { assert.Equal(secret2, string(backends[1].secret)) && assert.Equal(url3, backends[2].url) && assert.Equal(secret3, string(backends[2].secret)) { - if backend := cfg.GetBackend(mustParse(url1)); backend != backends[0] { - assert.Fail("Expected backend %+v, got %+v", backends[0], backend) - } else if backend := cfg.GetBackend(mustParse(url2)); backend != backends[1] { - assert.Fail("Expected backend %+v, got %+v", backends[1], backend) - } else if backend := cfg.GetBackend(mustParse(url3)); backend != backends[2] { - assert.Fail("Expected backend %+v, got %+v", backends[2], backend) + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } else if backend := cfg.GetBackend(mustParse(url2)); assert.NotNil(backend) { + assert.Equal(backends[1], backend) + } else if backend := cfg.GetBackend(mustParse(url3)); assert.NotNil(backend) { + assert.Equal(backends[2], backend) } } @@ -567,9 +567,8 @@ func TestBackendConfiguration_Etcd(t *testing.T) { assert.Equal(secret3, string(backends[0].secret)) } - if _, found := storage.backends["domain1.invalid"]; found { - assert.Fail("Should have removed host information for %s", "domain1.invalid") - } + _, found := storage.backends["domain1.invalid"] + assert.False(found, "Should have removed host information") } func TestBackendCommonSecret(t *testing.T) { diff --git a/backend_server_test.go b/backend_server_test.go index 8a84805..b5e3b42 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -530,9 +530,9 @@ func RunTestBackendServer_RoomDisinvite(t *testing.T) { } if message, err := client.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { - assert.Fail("Received unexpected error %s", err) + assert.NoError(err, "Received unexpected error") } else if err == nil { - assert.Fail("Server should have closed the connection, received %+v", *message) + assert.Fail("Server should have closed the connection", "received %+v", *message) } } @@ -597,7 +597,7 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { if message, err := client1.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { assert.NoError(err) } else if err == nil { - assert.Fail("Server should have closed the connection, received %+v", *message) + assert.Fail("Server should have closed the connection", "received %+v", *message) } if message, err := client2.RunUntilRoomlistDisinvite(ctx); assert.NoError(err) { @@ -1234,7 +1234,7 @@ func TestBackendServer_InCallAll(t *testing.T) { defer cancel2() if message, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -1243,7 +1243,7 @@ func TestBackendServer_InCallAll(t *testing.T) { defer cancel3() if message, err := client2.RunUntilMessage(ctx3); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -1299,7 +1299,7 @@ func TestBackendServer_InCallAll(t *testing.T) { defer cancel4() if message, err := client1.RunUntilMessage(ctx4); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -1308,7 +1308,7 @@ func TestBackendServer_InCallAll(t *testing.T) { defer cancel5() if message, err := client2.RunUntilMessage(ctx5); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } diff --git a/dns_monitor_test.go b/dns_monitor_test.go index ee10772..809e833 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -147,13 +147,13 @@ func (r *dnsMonitorReceiver) OnLookup(entry *DnsMonitorEntry, all, add, keep, re expected := r.expected r.expected = nil if expected == expectNone { - assert.Fail(r.t, "expected no event, got %v", received) + assert.Fail(r.t, "expected no event", "received %v", received) return } if expected == nil { if r.received != nil && !r.received.Equal(received) { - assert.Fail(r.t, "already received %v, got %v", r.received, received) + assert.Fail(r.t, "unexpected message", "already received %v, got %v", r.received, received) } return } @@ -188,7 +188,7 @@ func (r *dnsMonitorReceiver) Expect(all, add, keep, remove []net.IP) { defer r.Unlock() if r.expected != nil && r.expected != expectNone { - assert.Fail(r.t, "didn't get previously expected %v", r.expected) + assert.Fail(r.t, "didn't get previous message", "expected %v", r.expected) } expected := &dnsMonitorReceiverRecord{ @@ -211,7 +211,7 @@ func (r *dnsMonitorReceiver) ExpectNone() { defer r.Unlock() if r.expected != nil && r.expected != expectNone { - assert.Fail(r.t, "didn't get previously expected %v", r.expected) + assert.Fail(r.t, "didn't get previous message", "expected %v", r.expected) } r.expected = expectNone diff --git a/federation_test.go b/federation_test.go index c185b5c..255cc28 100644 --- a/federation_test.go +++ b/federation_test.go @@ -172,23 +172,24 @@ func Test_Federation(t *testing.T) { request1 := getPingRequests(t) clearPingRequests(t) - assert.Len(request1, 1) - if ping := request1[0].Ping; assert.NotNil(ping) { - assert.Equal(roomId, ping.RoomId) - assert.Equal("1.0", ping.Version) - assert.Len(ping.Entries, 2) - // The order of entries is not defined - if ping.Entries[0].SessionId == federatedRoomId+"-"+hello2.Hello.SessionId { - assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) + if assert.Len(request1, 1) { + if ping := request1[0].Ping; assert.NotNil(ping) { + assert.Equal(roomId, ping.RoomId) + assert.Equal("1.0", ping.Version) + assert.Len(ping.Entries, 2) + // The order of entries is not defined + if ping.Entries[0].SessionId == federatedRoomId+"-"+hello2.Hello.SessionId { + assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) - assert.Equal(roomId+"-"+hello1.Hello.SessionId, ping.Entries[1].SessionId) - assert.Equal(hello1.Hello.UserId, ping.Entries[1].UserId) - } else { - assert.Equal(roomId+"-"+hello1.Hello.SessionId, ping.Entries[0].SessionId) - assert.Equal(hello1.Hello.UserId, ping.Entries[0].UserId) + assert.Equal(roomId+"-"+hello1.Hello.SessionId, ping.Entries[1].SessionId) + assert.Equal(hello1.Hello.UserId, ping.Entries[1].UserId) + } else { + assert.Equal(roomId+"-"+hello1.Hello.SessionId, ping.Entries[0].SessionId) + assert.Equal(hello1.Hello.UserId, ping.Entries[0].UserId) - assert.Equal(federatedRoomId+"-"+hello2.Hello.SessionId, ping.Entries[1].SessionId) - assert.Equal(hello2.Hello.UserId, ping.Entries[1].UserId) + assert.Equal(federatedRoomId+"-"+hello2.Hello.SessionId, ping.Entries[1].SessionId) + assert.Equal(hello2.Hello.UserId, ping.Entries[1].UserId) + } } } @@ -199,13 +200,14 @@ func Test_Federation(t *testing.T) { request2 := getPingRequests(t) clearPingRequests(t) - assert.Len(request2, 1) - if ping := request2[0].Ping; assert.NotNil(ping) { - assert.Equal(federatedRoomId, ping.RoomId) - assert.Equal("1.0", ping.Version) - assert.Len(ping.Entries, 1) - assert.Equal(federatedRoomId+"-"+hello2.Hello.SessionId, ping.Entries[0].SessionId) - assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) + if assert.Len(request2, 1) { + if ping := request2[0].Ping; assert.NotNil(ping) { + assert.Equal(federatedRoomId, ping.RoomId) + assert.Equal("1.0", ping.Version) + assert.Len(ping.Entries, 1) + assert.Equal(federatedRoomId+"-"+hello2.Hello.SessionId, ping.Entries[0].SessionId) + assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) + } } // Leaving and re-joining a room as "direct" session will trigger correct events. @@ -335,7 +337,7 @@ func Test_Federation(t *testing.T) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("expected no message, got %+v", message) + assert.Fail("expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -350,7 +352,7 @@ func Test_Federation(t *testing.T) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("expected no message, got %+v", message) + assert.Fail("expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } diff --git a/hub_test.go b/hub_test.go index 6be65d7..4213655 100644 --- a/hub_test.go +++ b/hub_test.go @@ -281,7 +281,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { h.mu.Lock() h.ru.Lock() dumpGoroutines("", os.Stderr) - assert.Fail(t, "Error waiting for clients %+v / rooms %+v / sessions %+v / remoteSessions %v / %d read / %d write to terminate: %s", h.clients, h.rooms, h.sessions, h.remoteSessions, readActive, writeActive, ctx.Err()) + assert.Fail(t, "Error waiting for hub to terminate", "clients %+v / rooms %+v / sessions %+v / remoteSessions %v / %d read / %d write: %s", h.clients, h.rooms, h.sessions, h.remoteSessions, readActive, writeActive, ctx.Err()) h.ru.Unlock() h.mu.Unlock() return @@ -300,11 +300,11 @@ func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Req rnd := r.Header.Get(HeaderBackendSignalingRandom) checksum := r.Header.Get(HeaderBackendSignalingChecksum) if rnd == "" || checksum == "" { - require.Fail("No checksum headers found in request to %s", r.URL) + require.Fail("No checksum headers found", "request to %s", r.URL) } if verify := CalculateBackendChecksum(rnd, body, testBackendSecret); verify != checksum { - require.Fail("Backend checksum verification failed for request to %s", r.URL) + require.Fail("Backend checksum verification failed", "request to %s", r.URL) } var request BackendClientRequest @@ -342,7 +342,7 @@ func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Req func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { require := require.New(t) if request.Type != "auth" || request.Auth == nil { - require.Fail("Expected an auth backend request, got %+v", request) + require.Fail("Expected an auth backend request", "received %+v", request) } var params TestBackendClientAuthParams @@ -376,7 +376,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re require := require.New(t) assert := assert.New(t) if request.Type != "room" || request.Room == nil { - require.Fail("Expected an room backend request, got %+v", request) + require.Fail("Expected an room backend request", "received %+v", request) } switch request.Room.RoomId { @@ -385,7 +385,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re case "test-room-takeover-room-session": // Additional checks for testcase "TestClientTakeoverRoomSession" if request.Room.Action == "leave" && request.Room.UserId == "test-userid1" { - assert.Fail("Should not receive \"leave\" event for first user, received %+v", request.Room) + assert.Fail("Should not receive \"leave\" event for first user", "received %+v", request.Room) } case "test-invalid-room": response := &BackendClientResponse{ @@ -466,7 +466,7 @@ func clearSessionRequestHandler(t *testing.T) { // nolint func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { if request.Type != "session" || request.Session == nil { - require.Fail(t, "Expected an session backend request, got %+v", request) + require.Fail(t, "Expected an session backend request", "received %+v", request) } sessionRequestHander.Lock() @@ -513,7 +513,7 @@ func storePingRequest(t *testing.T, request *BackendClientRequest) { func processPingRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { if request.Type != "ping" || request.Ping == nil { - require.Fail(t, "Expected an ping backend request, got %+v", request) + require.Fail(t, "Expected an ping backend request", "received %+v", request) } if request.Ping.RoomId == "test-room-with-sessiondata" { @@ -650,7 +650,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { case "ping": return processPingRequest(t, w, r, request) default: - require.Fail(t, "Unsupported request received: %+v", request) + require.Fail(t, "Unsupported request", "received: %+v", request) return nil } }) @@ -784,10 +784,10 @@ func TestWebsocketFeatures(t *testing.T) { } } if len(featuresList) <= 1 { - assert.Fail("expected valid features header, got \"%s\"", features) + assert.Fail("expected valid features header", "received \"%s\"", features) } _, found := featuresList["hello-v2"] - assert.True(found, "expected feature \"hello-v2\", got \"%s\"", features) + assert.True(found, "expected feature \"hello-v2\"", "received \"%s\"", features) assert.NoError(conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{})) } @@ -838,7 +838,7 @@ func TestExpectClientHello(t *testing.T) { message2, err := client.RunUntilMessage(ctx) if message2 != nil { - require.Fail("Received multiple messages, already have %+v, also got %+v", message, message2) + require.Fail("Received multiple messages", "already have %+v, also got %+v", message, message2) } require.NoError(checkUnexpectedClose(err)) @@ -1547,9 +1547,9 @@ func TestClientHelloResumeTakeover(t *testing.T) { } if msg, err := client1.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error but received %+v", msg) + assert.Fail("Expected error", "received %+v", msg) } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error but received %+v", err) + assert.Fail("Expected close error", "received %+v", err) } } @@ -1897,9 +1897,9 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { } if msg, err := client1.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error but received %+v", msg) + assert.Fail("Expected error", "received %+v", msg) } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error but received %+v", err) + assert.Fail("Expected close error", "received %+v", err) } client3 := NewTestClient(t, server1, hub1) @@ -1921,9 +1921,9 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { } if msg, err := client2.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error but received %+v", msg) + assert.Fail("Expected error", "received %+v", msg) } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error but received %+v", err) + assert.Fail("Expected close error", "received %+v", err) } }) }) @@ -2245,7 +2245,7 @@ func TestClientControlMissingPermissions(t *testing.T) { defer cancel2() if err := checkReceiveClientMessage(ctx2, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -2645,7 +2645,7 @@ func TestClientMessageToCall(t *testing.T) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -2766,7 +2766,7 @@ func TestClientControlToCall(t *testing.T) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -3008,7 +3008,7 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { defer cancel2() if message, err := client.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -3570,7 +3570,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { defer cancel2() if err := checkReceiveClientMessage(ctx2, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -3667,7 +3667,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { defer cancel2() if err := checkReceiveClientMessage(ctx2, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -3759,9 +3759,9 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { } if msg, err := client1.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error but received %+v", msg) + assert.Fail("Expected error", "received %+v", msg) } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error but received %+v", err) + assert.Fail("Expected close error", "received %+v", err) } // The first session has been closed @@ -3777,7 +3777,7 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -3890,7 +3890,7 @@ func TestClientSendOfferPermissions(t *testing.T) { defer cancel2() if message, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -4359,7 +4359,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -4416,7 +4416,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() if err := checkReceiveClientMessage(ctx2, client1, "session", hello2.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -4424,7 +4424,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() if err := checkReceiveClientMessage(ctx3, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -4485,9 +4485,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { hub.ru.RUnlock() if assert.Len(rooms, 2) { - if rooms[0].IsEqual(rooms[1]) { - assert.Fail("Rooms should be different: %+v", rooms) - } + assert.False(rooms[0].IsEqual(rooms[1]), "Rooms should be different: %+v", rooms) } recipient := MessageClientMessageRecipient{ @@ -4503,7 +4501,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() if err := checkReceiveClientMessage(ctx2, client1, "session", hello2.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -4511,7 +4509,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() if err := checkReceiveClientMessage(ctx3, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload, got %+v", payload) + assert.Fail("Expected no payload", "received %+v", payload) } else { assert.ErrorIs(err, ErrNoMessageReceived) } @@ -4606,7 +4604,7 @@ func TestClientSendOffer(t *testing.T) { defer cancel2() if message, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } @@ -5322,7 +5320,7 @@ func DoTestSwitchToOne(t *testing.T, details map[string]interface{}) { defer cancel2() if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message, got %+v", message) + assert.Fail("Expected no message", "received %+v", message) } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { assert.NoError(err) } diff --git a/mcu_janus_publisher_test.go b/mcu_janus_publisher_test.go index ab7d96a..37aa890 100644 --- a/mcu_janus_publisher_test.go +++ b/mcu_janus_publisher_test.go @@ -54,11 +54,11 @@ func TestGetFmtpValueH264(t *testing.T) { for _, tc := range testcases { value, found := getFmtpValue(tc.fmtp, "profile-level-id") if !found && tc.profile != "" { - assert.Fail("did not find profile \"%s\" in \"%s\"", tc.profile, tc.fmtp) + assert.Fail("did not find profile", "profile \"%s\" in \"%s\"", tc.profile, tc.fmtp) } else if found && tc.profile == "" { - assert.Fail("did not expect profile in \"%s\" but got \"%s\"", tc.fmtp, value) + assert.Fail("did not expect profile", "in \"%s\" but got \"%s\"", tc.fmtp, value) } else if found && tc.profile != value { - assert.Fail("expected profile \"%s\" in \"%s\" but got \"%s\"", tc.profile, tc.fmtp, value) + assert.Fail("expected profile", "profile \"%s\" in \"%s\" but got \"%s\"", tc.profile, tc.fmtp, value) } } } @@ -86,11 +86,11 @@ func TestGetFmtpValueVP9(t *testing.T) { for _, tc := range testcases { value, found := getFmtpValue(tc.fmtp, "profile-id") if !found && tc.profile != "" { - assert.Fail("did not find profile \"%s\" in \"%s\"", tc.profile, tc.fmtp) + assert.Fail("did not find profile", "profile \"%s\" in \"%s\"", tc.profile, tc.fmtp) } else if found && tc.profile == "" { - assert.Fail("did not expect profile in \"%s\" but got \"%s\"", tc.fmtp, value) + assert.Fail("did not expect profile", "in \"%s\" but got \"%s\"", tc.fmtp, value) } else if found && tc.profile != value { - assert.Fail("expected profile \"%s\" in \"%s\" but got \"%s\"", tc.profile, tc.fmtp, value) + assert.Fail("expected profile", "profile \"%s\" in \"%s\" but got \"%s\"", tc.profile, tc.fmtp, value) } } } diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 73c3260..d68e217 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -106,9 +106,7 @@ func Test_sortConnectionsForCountry(t *testing.T) { t.Run(country, func(t *testing.T) { sorted := sortConnectionsForCountry(test[0], country, nil) for idx, conn := range sorted { - if test[1][idx] != conn { - assert.Fail(t, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) - } + assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) } }) } @@ -180,9 +178,7 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { t.Run(country, func(t *testing.T) { sorted := sortConnectionsForCountry(test[0], country, continentMap) for idx, conn := range sorted { - if test[1][idx] != conn { - assert.Fail(t, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) - } + assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) } }) } @@ -358,7 +354,7 @@ func (c *testProxyServerClient) handleSendMessageError(fmt string, msg *ProxySer c.t.Helper() if !errors.Is(err, websocket.ErrCloseSent) || msg.Type != "event" || msg.Event.Type != "update-load" { - assert.Fail(c.t, fmt, msg, err) + assert.Fail(c.t, "error while sending message", fmt, msg, err) } } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 973b6dc..3dfd136 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -28,7 +28,6 @@ import ( "crypto/x509" "encoding/pem" "errors" - "fmt" "net" "net/http/httptest" "os" @@ -88,7 +87,7 @@ func WaitForProxyServer(ctx context.Context, t *testing.T, proxy *ProxyServer) { case <-ctx.Done(): proxy.clientsLock.Lock() proxy.remoteConnectionsLock.Lock() - assert.Fail(t, fmt.Sprintf("Error waiting for clients %+v / sessions %+v / remoteConnections %+v to terminate: %+v", proxy.clients, proxy.sessions, proxy.remoteConnections, ctx.Err())) + assert.Fail(t, "Error waiting for proxy to terminate", "clients %+v / sessions %+v / remoteConnections %+v: %+v", proxy.clients, proxy.sessions, proxy.remoteConnections, ctx.Err()) proxy.remoteConnectionsLock.Unlock() proxy.clientsLock.Unlock() return @@ -313,7 +312,7 @@ func TestWebsocketFeatures(t *testing.T) { defer conn.Close() // nolint if server := response.Header.Get("Server"); !strings.HasPrefix(server, "nextcloud-spreed-signaling-proxy/") { - assert.Fail("expected valid server header, got \"%s\"", server) + assert.Fail("expected valid server header", "received \"%s\"", server) } features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) @@ -321,14 +320,14 @@ func TestWebsocketFeatures(t *testing.T) { f = strings.TrimSpace(f) if f != "" { if _, found := featuresList[f]; found { - assert.Fail("duplicate feature id \"%s\" in \"%s\"", f, features) + assert.Fail("duplicate feature", "id \"%s\" in \"%s\"", f, features) } featuresList[f] = true } } assert.NotEmpty(featuresList, "expected valid features header, got \"%s\"", features) if _, found := featuresList["remote-streams"]; !found { - assert.Fail("expected feature \"remote-streams\", got \"%s\"", features) + assert.Fail("expected feature \"remote-streams\"", "received \"%s\"", features) } assert.NoError(conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{})) diff --git a/proxy/proxy_tokens_etcd_test.go b/proxy/proxy_tokens_etcd_test.go index 957f7d5..8b23ff3 100644 --- a/proxy/proxy_tokens_etcd_test.go +++ b/proxy/proxy_tokens_etcd_test.go @@ -132,7 +132,7 @@ func storeKey(t *testing.T, etcd *embed.Etcd, key string, pubkey crypto.PublicKe data, err = x509.MarshalPKIXPublicKey(&pubkey) require.NoError(t, err) default: - require.Fail(t, "unknown key type %T in %+v", pubkey, pubkey) + require.Fail(t, "unknown key type", "type %T in %+v", pubkey, pubkey) } data = pem.EncodeToMemory(&pem.Block{ diff --git a/proxy_config_test.go b/proxy_config_test.go index 47f51ad..2a65b7c 100644 --- a/proxy_config_test.go +++ b/proxy_config_test.go @@ -125,7 +125,7 @@ func (p *mcuProxyForConfig) checkEvent(event *proxyConfigEvent) { defer p.mu.Unlock() if len(p.expected) == 0 { - assert.Fail(p.t, "no event expected, got %+v from %s:%d", event, caller.File, caller.Line) + assert.Fail(p.t, "no event expected", "received %+v from %s:%d", event, caller.File, caller.Line) return } @@ -145,7 +145,7 @@ func (p *mcuProxyForConfig) checkEvent(event *proxyConfigEvent) { expected := p.expected[0] p.expected = p.expected[1:] if !reflect.DeepEqual(expected, *event) { - assert.Fail(p.t, "expected %+v, got %+v from %s:%d", expected, event, caller.File, caller.Line) + assert.Fail(p.t, "wrong event", "expected %+v, received %+v from %s:%d", expected, event, caller.File, caller.Line) } } diff --git a/roomsessions_test.go b/roomsessions_test.go index 6501819..a2947b3 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -119,7 +119,7 @@ func checkSession(t *testing.T, sessions RoomSessions, sessionId string, roomSes func testRoomSessions(t *testing.T, sessions RoomSessions) { assert := assert.New(t) if sid, err := sessions.GetSessionId("unknown"); err == nil { - assert.Fail("Expected error about invalid room session, got session id %s", sid) + assert.Fail("Expected error about invalid room session", "got session id %s", sid) } else { assert.ErrorIs(err, ErrNoSuchRoomSession) } @@ -133,7 +133,7 @@ func testRoomSessions(t *testing.T, sessions RoomSessions) { sessions.DeleteRoomSession(s1) if sid, err := sessions.GetSessionId("room1"); err == nil { - assert.Fail("Expected error about invalid room session, got session id %s", sid) + assert.Fail("Expected error about invalid room session", "got session id %s", sid) } else { assert.ErrorIs(err, ErrNoSuchRoomSession) } @@ -150,7 +150,7 @@ func testRoomSessions(t *testing.T, sessions RoomSessions) { assert.NoError(sessions.SetRoomSession(s2, "room-session2")) if sid, err := sessions.GetSessionId("room-session"); err == nil { - assert.Fail("Expected error about invalid room session, got session id %s", sid) + assert.Fail("Expected error about invalid room session", "got session id %s", sid) } else { assert.ErrorIs(err, ErrNoSuchRoomSession) } diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 6961458..58a7d0d 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -46,11 +46,11 @@ func TestReverseSessionId(t *testing.T) { // Invalid base64. if s, err := reverseSessionId("hello world!"); !assert.Error(err) { - assert.Fail("should have failed but got %s", s) + assert.Fail("should have failed", "received %s", s) } // Invalid base64 length. if s, err := reverseSessionId("123"); !assert.Error(err) { - assert.Fail("should have failed but got %s", s) + assert.Fail("should have failed", "received %s", s) } } @@ -71,9 +71,9 @@ func TestPublicPrivate(t *testing.T) { assert.NotEqual(private, public) if data, err := codec.DecodePublic(private); !assert.Error(err) { - assert.Fail("should have failed but got %+v", data) + assert.Fail("should have failed", "received %+v", data) } if data, err := codec.DecodePrivate(public); !assert.Error(err) { - assert.Fail("should have failed but got %+v", data) + assert.Fail("should have failed", "received %+v", data) } } diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index 626777a..859e2f3 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -41,8 +41,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 assert := assert.New(t) pc := make([]uintptr, 10) n := runtime.Callers(2, pc) - if n == 0 { - assert.Fail("Expected value %f for %s, got %f", value, desc, v) + if assert.NotEqualValues(0, n, "Expected value %f for %s, got %f", value, desc, v) { return } @@ -59,7 +58,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 break } } - assert.Fail("Expected value %f for %s, got %f at\n%s", value, desc, v, stack) + assert.EqualValues(value, v, "Unexpected value for %s at\n%s", desc, stack) } } @@ -72,7 +71,7 @@ func collectAndLint(t *testing.T, collectors ...prometheus.Collector) { } for _, problem := range problems { - assert.Fail("Problem with %s: %s", problem.Metric, problem.Text) + assert.Fail("Problem with metric", "%s: %s", problem.Metric, problem.Text) } } } diff --git a/transient_data_test.go b/transient_data_test.go index 66ac3d6..4523323 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -161,7 +161,7 @@ func Test_TransientMessages(t *testing.T) { defer cancel2() if msg, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no payload, got %+v", msg) + assert.Nil(msg, "Expected no payload") } else { require.ErrorIs(err, context.DeadlineExceeded) } @@ -193,7 +193,7 @@ func Test_TransientMessages(t *testing.T) { defer cancel3() if msg, err := client1.RunUntilMessage(ctx3); err == nil { - assert.Fail("Expected no payload, got %+v", msg) + assert.Nil(msg, "Expected no payload") } else { require.ErrorIs(err, context.DeadlineExceeded) } @@ -220,7 +220,7 @@ func Test_TransientMessages(t *testing.T) { } else if len(ignored) == 1 { msg = ignored[0] } else { - require.Fail("Received too many messages: %+v", ignored) + require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } require.NoError(checkMessageTransientInitial(msg, map[string]interface{}{ From 6c5eb78cc2e26ccfd17b99f554f179fc3e86500a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 21 Nov 2024 14:58:18 +0100 Subject: [PATCH 057/549] Use a single random string per concurrency level. --- concurrentmap_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/concurrentmap_test.go b/concurrentmap_test.go index cca1d29..276b038 100644 --- a/concurrentmap_test.go +++ b/concurrentmap_test.go @@ -82,8 +82,9 @@ func TestConcurrentStringStringMap(t *testing.T) { defer wg.Done() key := "key-" + strconv.Itoa(x) + rnd := newRandomString(32) for y := 0; y < count; y = y + 1 { - value := newRandomString(32) + value := rnd + "-" + strconv.Itoa(y) m.Set(key, value) if v, found := m.Get(key); !assert.True(found, "Expected entry for key %s", key) || !assert.Equal(value, v, "Unexpected value for key %s", key) { From 53ff3d39e794c07785ab2b4d4bafd86a54ccdf2a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 21 Nov 2024 19:49:28 +0100 Subject: [PATCH 058/549] Add buffer pool helper class. --- buffer_pool.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 buffer_pool.go diff --git a/buffer_pool.go b/buffer_pool.go new file mode 100644 index 0000000..5e13c0b --- /dev/null +++ b/buffer_pool.go @@ -0,0 +1,79 @@ +/** + * 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 ( + "bytes" + "encoding/json" + "io" + "sync" +) + +type BufferPool struct { + buffers sync.Pool + copyBuffers sync.Pool +} + +func (p *BufferPool) Get() *bytes.Buffer { + b := p.buffers.Get() + if b == nil { + return bytes.NewBuffer(nil) + } + + return b.(*bytes.Buffer) +} + +func (p *BufferPool) Put(b *bytes.Buffer) { + if b == nil { + return + } + + b.Reset() + p.buffers.Put(b) +} + +func (p *BufferPool) ReadAll(r io.Reader) (*bytes.Buffer, error) { + buf := p.copyBuffers.Get() + if buf == nil { + buf = make([]byte, 1024) + } + defer p.copyBuffers.Put(buf) + + b := p.Get() + if _, err := io.CopyBuffer(b, r, buf.([]byte)); err != nil { + p.Put(b) + return nil, err + } + + return b, nil +} + +func (p *BufferPool) MarshalAsJSON(v any) (*bytes.Buffer, error) { + b := p.Get() + encoder := json.NewEncoder(b) + if err := encoder.Encode(v); err != nil { + p.Put(b) + return nil, err + } + + return b, nil +} From bfc4d7facf6282f0838196001d2c1f5f3321b29f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 21 Nov 2024 19:51:14 +0100 Subject: [PATCH 059/549] Set WriteBufferPool for websocket connections. --- federation.go | 8 ++++++-- hub.go | 3 +++ hub_test.go | 2 +- janus_client.go | 5 +++-- testclient_test.go | 6 +++++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/federation.go b/federation.go index 9b609c8..d1ac58b 100644 --- a/federation.go +++ b/federation.go @@ -46,6 +46,8 @@ const ( var ( ErrFederationNotSupported = NewError("federation_unsupported", "The target server does not support federation.") + + federationWriteBufferPool = &sync.Pool{} ) func isClosedError(err error) bool { @@ -102,7 +104,9 @@ func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, return nil, fmt.Errorf("expected federation room message, got %+v", message) } - var dialer websocket.Dialer + dialer := &websocket.Dialer{ + WriteBufferPool: federationWriteBufferPool, + } if hub.skipFederationVerify { dialer.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, @@ -130,7 +134,7 @@ func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, reconnectDelay: initialFederationReconnectInterval, - dialer: &dialer, + dialer: dialer, url: url, closer: NewCloser(), } diff --git a/hub.go b/hub.go index e7143c6..4b00a38 100644 --- a/hub.go +++ b/hub.go @@ -105,6 +105,8 @@ var ( websocketReadBufferSize = 4096 websocketWriteBufferSize = 4096 + websocketWriteBufferPool = &sync.Pool{} + // Delay after which a screen publisher should be cleaned up. cleanupScreenPublisherDelay = time.Second @@ -322,6 +324,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer upgrader: websocket.Upgrader{ ReadBufferSize: websocketReadBufferSize, WriteBufferSize: websocketWriteBufferSize, + WriteBufferPool: websocketWriteBufferPool, }, cookie: NewSessionIdCodec([]byte(hashKey), blockBytes), info: NewWelcomeServerMessage(version, DefaultFeatures...), diff --git a/hub_test.go b/hub_test.go index 4213655..b364623 100644 --- a/hub_test.go +++ b/hub_test.go @@ -767,7 +767,7 @@ func TestWebsocketFeatures(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - conn, response, err := websocket.DefaultDialer.DialContext(ctx, getWebsocketUrl(server.URL), nil) + conn, response, err := testClientDialer.DialContext(ctx, getWebsocketUrl(server.URL), nil) require.NoError(err) defer conn.Close() // nolint diff --git a/janus_client.go b/janus_client.go index 5716bef..33a7ec2 100644 --- a/janus_client.go +++ b/janus_client.go @@ -119,8 +119,9 @@ const ( var ( janusDialer = websocket.Dialer{ - Subprotocols: []string{"janus-protocol"}, - Proxy: http.ProxyFromEnvironment, + Subprotocols: []string{"janus-protocol"}, + Proxy: http.ProxyFromEnvironment, + WriteBufferPool: &sync.Pool{}, } ) diff --git a/testclient_test.go b/testclient_test.go index 9078bcc..836fb6a 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -49,6 +49,10 @@ var ( testInternalSecret = []byte("internal-secret") ErrNoMessageReceived = fmt.Errorf("no message was received by the server") + + testClientDialer = websocket.Dialer{ + WriteBufferPool: &sync.Pool{}, + } ) type TestBackendClientAuthParams struct { @@ -226,7 +230,7 @@ type TestClient struct { func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub) *TestClient { // Reference "hub" to prevent compiler error. - conn, _, err := websocket.DefaultDialer.DialContext(ctx, getWebsocketUrl(server.URL), nil) + conn, _, err := testClientDialer.DialContext(ctx, getWebsocketUrl(server.URL), nil) require.NoError(t, err) messageChan := make(chan []byte) From 411cf34437482954108f13ed800a9afdde588748 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 21 Nov 2024 19:54:50 +0100 Subject: [PATCH 060/549] Use buffer pool for reading data. --- backend_client.go | 18 ++++++++++-------- backend_server.go | 7 +++++-- capabilities.go | 13 ++++++++----- client.go | 12 +++--------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/backend_client.go b/backend_client.go index f39fb15..e3eecef 100644 --- a/backend_client.go +++ b/backend_client.go @@ -27,7 +27,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "log" "net/http" "net/url" @@ -51,6 +50,7 @@ type BackendClient struct { pool *HttpClientPool capabilities *Capabilities + buffers BufferPool } func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) { @@ -175,12 +175,14 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ return ErrUnsupportedContentType } - body, err := io.ReadAll(resp.Body) + body, err := b.buffers.ReadAll(resp.Body) if err != nil { log.Printf("Could not read response body from %s: %s", req.URL, err) return err } + defer b.buffers.Put(body) + if 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: @@ -191,17 +193,17 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ // } // } var ocs OcsResponse - if err := json.Unmarshal(body, &ocs); err != nil { - log.Printf("Could not decode OCS response %s from %s: %s", string(body), req.URL, err) + if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { + log.Printf("Could not decode OCS response %s from %s: %s", body.String(), req.URL, err) return err } else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 { - log.Printf("Incomplete OCS response %s from %s", string(body), req.URL) + log.Printf("Incomplete OCS response %s from %s", body.String(), req.URL) return ErrIncompleteResponse } switch ocs.Ocs.Meta.StatusCode { case http.StatusTooManyRequests: - log.Printf("Throttled OCS response %s from %s", string(body), req.URL) + log.Printf("Throttled OCS response %s from %s", body.String(), req.URL) return ErrThrottledResponse } @@ -209,8 +211,8 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ log.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), req.URL, err) return err } - } else if err := json.Unmarshal(body, response); err != nil { - log.Printf("Could not decode response body %s from %s: %s", string(body), req.URL, err) + } else if err := json.Unmarshal(body.Bytes(), response); err != nil { + log.Printf("Could not decode response body %s from %s: %s", body.String(), req.URL, err) return err } return nil diff --git a/backend_server.go b/backend_server.go index e7aaebd..fa66074 100644 --- a/backend_server.go +++ b/backend_server.go @@ -70,6 +70,8 @@ type BackendServer struct { statsAllowedIps atomic.Pointer[AllowedIps] invalidSecret []byte + + buffers BufferPool } func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { @@ -284,14 +286,15 @@ func (b *BackendServer) parseRequestBody(f func(http.ResponseWriter, *http.Reque return } - body, err := io.ReadAll(r.Body) + body, err := b.buffers.ReadAll(r.Body) if err != nil { log.Println("Error reading body: ", err) http.Error(w, "Could not read body", http.StatusBadRequest) return } + defer b.buffers.Put(body) - f(w, r, body) + f(w, r, body.Bytes()) } } diff --git a/capabilities.go b/capabilities.go index d77731c..993e60c 100644 --- a/capabilities.go +++ b/capabilities.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "errors" - "io" "log" "net/http" "net/url" @@ -182,18 +181,20 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return e.errorIfMustRevalidate(ErrUnsupportedContentType) } - body, err := io.ReadAll(response.Body) + body, err := e.c.buffers.ReadAll(response.Body) if err != nil { log.Printf("Could not read response body from %s: %s", url, err) return e.errorIfMustRevalidate(err) } + defer e.c.buffers.Put(body) + var ocs OcsResponse - if err := json.Unmarshal(body, &ocs); err != nil { - log.Printf("Could not decode OCS response %s from %s: %s", string(body), url, err) + if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { + log.Printf("Could not decode OCS response %s from %s: %s", body.String(), url, err) return e.errorIfMustRevalidate(err) } else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 { - log.Printf("Incomplete OCS response %s from %s", string(body), url) + log.Printf("Incomplete OCS response %s from %s", body.String(), url) return e.errorIfMustRevalidate(ErrIncompleteResponse) } @@ -239,6 +240,8 @@ type Capabilities struct { pool *HttpClientPool entries map[string]*capabilitiesEntry nextInvalidate map[string]time.Time + + buffers BufferPool } func NewCapabilities(version string, pool *HttpClientPool) (*Capabilities, error) { diff --git a/client.go b/client.go index 6c534d4..3fd6ce3 100644 --- a/client.go +++ b/client.go @@ -82,11 +82,7 @@ func IsValidCountry(country string) bool { var ( InvalidFormat = NewError("invalid_format", "Invalid data format.") - bufferPool = sync.Pool{ - New: func() interface{} { - return new(bytes.Buffer) - }, - } + bufferPool BufferPool ) type WritableClientMessage interface { @@ -391,10 +387,8 @@ func (c *Client) ReadPump() { continue } - decodeBuffer := bufferPool.Get().(*bytes.Buffer) - decodeBuffer.Reset() - if _, err := decodeBuffer.ReadFrom(reader); err != nil { - bufferPool.Put(decodeBuffer) + decodeBuffer, err := bufferPool.ReadAll(reader) + if err != nil { if sessionId := c.GetSessionId(); sessionId != "" { log.Printf("Error reading message from client %s: %v", sessionId, err) } else { From 36f2f5026ffa7a922b89536888227fdbea93506e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 21 Nov 2024 20:04:49 +0100 Subject: [PATCH 061/549] Use buffer pool to marshal request body. --- backend_client.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend_client.go b/backend_client.go index e3eecef..b027673 100644 --- a/backend_client.go +++ b/backend_client.go @@ -22,7 +22,6 @@ package signaling import ( - "bytes" "context" "encoding/json" "errors" @@ -140,13 +139,14 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } defer pool.Put(c) - data, err := json.Marshal(request) + data, err := b.buffers.MarshalAsJSON(request) if err != nil { log.Printf("Could not marshal request %+v: %s", request, err) return err } - req, err := http.NewRequestWithContext(ctx, "POST", requestUrl.String(), bytes.NewReader(data)) + defer b.buffers.Put(data) + req, err := http.NewRequestWithContext(ctx, "POST", requestUrl.String(), data) if err != nil { log.Printf("Could not create request to %s: %s", requestUrl, err) return err @@ -160,11 +160,11 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } // Add checksum so the backend can validate the request. - AddBackendChecksum(req, data, secret) + AddBackendChecksum(req, data.Bytes(), secret) resp, err := c.Do(req) if err != nil { - log.Printf("Could not send request %s to %s: %s", string(data), req.URL, err) + log.Printf("Could not send request %s to %s: %s", data.String(), req.URL, err) return err } defer resp.Body.Close() From 951532d3b34ddf44ab52b27b55f2aea3a65f7fdc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 13:57:13 +0200 Subject: [PATCH 062/549] Fix race condition in flaky certificate/CA reload tests. --- certificate_reloader_test.go | 6 ++---- grpc_common_test.go | 8 ++++---- grpc_server_test.go | 12 ++++++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/certificate_reloader_test.go b/certificate_reloader_test.go index a180a9d..9e50fc6 100644 --- a/certificate_reloader_test.go +++ b/certificate_reloader_test.go @@ -39,8 +39,7 @@ func UpdateCertificateCheckIntervalForTest(t *testing.T, interval time.Duration) deduplicateWatchEvents.Store(int64(interval)) } -func (r *CertificateReloader) WaitForReload(ctx context.Context) error { - counter := r.GetReloadCounter() +func (r *CertificateReloader) WaitForReload(ctx context.Context, counter uint64) error { for counter == r.GetReloadCounter() { if err := ctx.Err(); err != nil { return err @@ -50,8 +49,7 @@ func (r *CertificateReloader) WaitForReload(ctx context.Context) error { return nil } -func (r *CertPoolReloader) WaitForReload(ctx context.Context) error { - counter := r.GetReloadCounter() +func (r *CertPoolReloader) WaitForReload(ctx context.Context, counter uint64) error { for counter == r.GetReloadCounter() { if err := ctx.Err(); err != nil { return err diff --git a/grpc_common_test.go b/grpc_common_test.go index 878efd0..a18deca 100644 --- a/grpc_common_test.go +++ b/grpc_common_test.go @@ -39,20 +39,20 @@ import ( "github.com/stretchr/testify/require" ) -func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context) error { +func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context, counter uint64) error { if c.loader == nil { return errors.New("no certificate loaded") } - return c.loader.WaitForReload(ctx) + return c.loader.WaitForReload(ctx, counter) } -func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context) error { +func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context, counter uint64) error { if c.pool == nil { return errors.New("no certificate pool loaded") } - return c.pool.WaitForReload(ctx) + return c.pool.WaitForReload(ctx, counter) } func GenerateSelfSignedCertificateForTesting(t *testing.T, bits int, organization string, key *rsa.PrivateKey) []byte { diff --git a/grpc_server_test.go b/grpc_server_test.go index e985fd2..8ebdc7c 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -43,22 +43,22 @@ import ( "google.golang.org/grpc/credentials" ) -func (s *GrpcServer) WaitForCertificateReload(ctx context.Context) error { +func (s *GrpcServer) WaitForCertificateReload(ctx context.Context, counter uint64) error { c, ok := s.creds.(*reloadableCredentials) if !ok { return errors.New("no reloadable credentials found") } - return c.WaitForCertificateReload(ctx) + return c.WaitForCertificateReload(ctx, counter) } -func (s *GrpcServer) WaitForCertPoolReload(ctx context.Context) error { +func (s *GrpcServer) WaitForCertPoolReload(ctx context.Context, counter uint64) error { c, ok := s.creds.(*reloadableCredentials) if !ok { return errors.New("no reloadable credentials found") } - return c.WaitForCertPoolReload(ctx) + return c.WaitForCertPoolReload(ctx, counter) } func NewGrpcServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *GrpcServer, addr string) { @@ -145,7 +145,7 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - require.NoError(server.WaitForCertificateReload(ctx)) + require.NoError(server.WaitForCertificateReload(ctx, 0)) cp2 := x509.NewCertPool() if !cp2.AppendCertsFromPEM(cert2) { @@ -225,7 +225,7 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { clientCert2 := GenerateSelfSignedCertificateForTesting(t, 1024, org2, clientKey) replaceFile(t, caFile, clientCert2, 0755) - require.NoError(server.WaitForCertPoolReload(ctx1)) + require.NoError(server.WaitForCertPoolReload(ctx1, 0)) pair2, err := tls.X509KeyPair(clientCert2, pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", From 3e9d02be00dd937c891e48a3ab110c5b45a5e72a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 20:53:42 +0100 Subject: [PATCH 063/549] proxy: Include "X-Spreed-Signaling-Features" header in all responses. --- proxy/proxy_server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index e01dd64..c51ad97 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -627,6 +627,7 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { func (s *ProxyServer) setCommonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Server", "nextcloud-spreed-signaling-proxy/"+s.version) + w.Header().Set("X-Spreed-Signaling-Features", strings.Join(s.welcomeMsg.Features, ", ")) f(w, r) } } From 00a9a6ee44e7675de2218be7c6c26b0dd506abf3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 20:54:22 +0100 Subject: [PATCH 064/549] Provide access to proxy version and features. --- mcu_proxy.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/mcu_proxy.go b/mcu_proxy.go index a50a835..ea9e1ef 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -362,6 +362,8 @@ type mcuProxyConnection struct { helloMsgId string sessionId atomic.Value country atomic.Value + version atomic.Value + features atomic.Value callbacks map[string]func(*ProxyServerMessage) @@ -395,6 +397,8 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP) (*mcuProx conn.load.Store(loadNotConnected) conn.bandwidth.Store(nil) conn.country.Store("") + conn.version.Store("") + conn.features.Store([]string{}) return conn, nil } @@ -505,6 +509,14 @@ func (c *mcuProxyConnection) Country() string { return c.country.Load().(string) } +func (c *mcuProxyConnection) Version() string { + return c.version.Load().(string) +} + +func (c *mcuProxyConnection) Features() []string { + return c.features.Load().([]string) +} + func (c *mcuProxyConnection) SessionId() string { sid := c.sessionId.Load() if sid == nil { @@ -932,11 +944,19 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { resumed := c.SessionId() == msg.Hello.SessionId c.sessionId.Store(msg.Hello.SessionId) country := "" - if msg.Hello.Server != nil { - if country = msg.Hello.Server.Country; country != "" && !IsValidCountry(country) { + if server := msg.Hello.Server; server != nil { + if country = server.Country; country != "" && !IsValidCountry(country) { log.Printf("Proxy %s sent invalid country %s in hello response", c, country) country = "" } + c.version.Store(server.Version) + if server.Features == nil { + server.Features = []string{} + } + c.features.Store(server.Features) + } else { + c.version.Store("") + c.features.Store([]string{}) } c.country.Store(country) if resumed { From 52ed2f0243c42ffb7f624b6a4150ab22a3c30a68 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 21:13:43 +0100 Subject: [PATCH 065/549] Provide access to Janus Info message. --- mcu_janus.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mcu_janus.go b/mcu_janus.go index da5cd71..f2d97bc 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -182,6 +182,7 @@ type mcuJanus struct { handle *JanusHandle version int + info atomic.Pointer[InfoMsg] closeChan chan struct{} @@ -372,6 +373,8 @@ func (m *mcuJanus) Start(ctx context.Context) error { } log.Println("Created Janus handle", m.handle.Id) + m.info.Store(info) + go m.run() m.notifyOnConnected() @@ -410,6 +413,14 @@ func (m *mcuJanus) Stop() { m.reconnectTimer.Stop() } +func (m *mcuJanus) IsConnected() bool { + return m.handle != nil +} + +func (m *mcuJanus) Info() *InfoMsg { + return m.info.Load() +} + func (m *mcuJanus) Reload(config *goconf.ConfigFile) { m.settings.Reload(config) } From d10469e1a68ec8a75af690f0bf61f302bb0c35b3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 21:14:01 +0100 Subject: [PATCH 066/549] Add serverinfo API. --- api_backend.go | 62 ++++++++++++++++++++++++++++++++++++++ backend_server.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/api_backend.go b/api_backend.go index 2fd0bc9..83fe5b0 100644 --- a/api_backend.go +++ b/api_backend.go @@ -464,3 +464,65 @@ func (p *BackendInformationEtcd) CheckValid() error { p.parsedUrl = parsedUrl return nil } + +type BackendServerInfoVideoRoom struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Author string `json:"author,omitempty"` +} + +type BackendServerInfoSfuJanus struct { + Url string `json:"url"` + + Connected bool `json:"connected"` + + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Author string `json:"author,omitempty"` + + DataChannels *bool `json:"datachannels,omitempty"` + FullTrickle *bool `json:"fulltrickle,omitempty"` + LocalIP string `json:"localip,omitempty"` + IPv6 *bool `json:"ipv6,omitempty"` + + VideoRoom *BackendServerInfoVideoRoom `json:"videoroom,omitempty"` +} + +type BackendServerInfoSfuProxy struct { + Url string `json:"url"` + IP string `json:"ip,omitempty"` + + Connected bool `json:"connected"` + Temporary bool `json:"temporary"` + Shutdown *bool `json:"shutdown,omitempty"` + Uptime *time.Time `json:"uptime,omitempty"` + + Version string `json:"version,omitempty"` + Features []string `json:"features,omitempty"` + + Country string `json:"country,omitempty"` + Load *int64 `json:"load,omitempty"` + Bandwidth *EventProxyServerBandwidth `json:"bandwidth,omitempty"` +} + +type SfuMode string + +const ( + SfuModeUnknown SfuMode = "unknown" + SfuModeJanus SfuMode = "janus" + SfuModeProxy SfuMode = "proxy" +) + +type BackendServerInfoSfu struct { + Mode SfuMode `json:"mode"` + + Janus *BackendServerInfoSfuJanus `json:"janus,omitempty"` + Proxies []BackendServerInfoSfuProxy `json:"proxies,omitempty"` +} + +type BackendServerInfo struct { + Version string `json:"version"` + Features []string `json:"features"` + + Sfu BackendServerInfoSfu `json:"sfu"` +} diff --git a/backend_server.go b/backend_server.go index fa66074..346e165 100644 --- a/backend_server.go +++ b/backend_server.go @@ -173,6 +173,7 @@ func (b *BackendServer) Start(r *mux.Router) error { s.HandleFunc("/welcome", b.setComonHeaders(b.welcomeFunc)).Methods("GET") s.HandleFunc("/room/{roomid}", b.setComonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST") s.HandleFunc("/stats", b.setComonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET") + s.HandleFunc("/serverinfo", b.setComonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET") // Expose prometheus metrics at "/metrics". r.HandleFunc("/metrics", b.setComonHeaders(b.validateStatsRequest(b.metricsHandler))).Methods("GET") @@ -950,6 +951,82 @@ func (b *BackendServer) statsHandler(w http.ResponseWriter, r *http.Request) { w.Write(statsData) // nolint } +func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request) { + var sfu BackendServerInfoSfu + switch m := b.hub.mcu.(type) { + case *mcuJanus: + sfu.Mode = SfuModeJanus + janus := &BackendServerInfoSfuJanus{ + Url: m.url, + } + if m.IsConnected() { + janus.Connected = true + if info := m.Info(); info != nil { + janus.Name = info.Name + janus.Version = info.VersionString + janus.Author = info.Author + janus.DataChannels = makePtr(info.DataChannels) + janus.FullTrickle = makePtr(info.FullTrickle) + janus.LocalIP = info.LocalIP + janus.IPv6 = makePtr(info.IPv6) + + if plugin, found := info.Plugins[pluginVideoRoom]; found { + janus.VideoRoom = &BackendServerInfoVideoRoom{ + Name: plugin.Name, + Version: plugin.VersionString, + Author: plugin.Author, + } + } + } + } + sfu.Janus = janus + case *mcuProxy: + sfu.Mode = SfuModeProxy + for _, c := range m.connections { + proxy := BackendServerInfoSfuProxy{ + Url: c.rawUrl, + + Temporary: c.IsTemporary(), + } + if len(c.ip) > 0 { + proxy.IP = c.ip.String() + } + if c.IsConnected() { + proxy.Connected = true + proxy.Shutdown = makePtr(c.IsShutdownScheduled()) + proxy.Uptime = &c.connectedSince + proxy.Version = c.Version() + proxy.Features = c.Features() + proxy.Country = c.Country() + proxy.Load = makePtr(c.Load()) + proxy.Bandwidth = c.Bandwidth() + } + sfu.Proxies = append(sfu.Proxies, proxy) + } + default: + sfu.Mode = SfuModeUnknown + } + + info := BackendServerInfo{ + Version: b.version, + Features: b.hub.info.Features, + + Sfu: sfu, + } + + infoData, err := json.MarshalIndent(info, "", " ") + if err != nil { + log.Printf("Could not serialize server info %+v: %s", info, err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(http.StatusOK) + w.Write(infoData) // nolint +} + func (b *BackendServer) metricsHandler(w http.ResponseWriter, r *http.Request) { promhttp.Handler().ServeHTTP(w, r) } From 864fc6b46b2158021cf6e4d8407fe7425f2bc947 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 21:15:00 +0100 Subject: [PATCH 067/549] Add serverinfo feature id. --- api_signaling.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api_signaling.go b/api_signaling.go index 9f978aa..d3e55f7 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -533,6 +533,7 @@ const ( ServerFeatureRecipientCall = "recipient-call" ServerFeatureJoinFeatures = "join-features" ServerFeatureOfferCodecs = "offer-codecs" + ServerFeatureServerInfo = "serverinfo" // Features to send to internal clients only. ServerFeatureInternalVirtualSessions = "virtual-sessions" @@ -555,6 +556,7 @@ var ( ServerFeatureRecipientCall, ServerFeatureJoinFeatures, ServerFeatureOfferCodecs, + ServerFeatureServerInfo, } DefaultFeaturesInternal = []string{ ServerFeatureInternalVirtualSessions, @@ -568,6 +570,7 @@ var ( ServerFeatureRecipientCall, ServerFeatureJoinFeatures, ServerFeatureOfferCodecs, + ServerFeatureServerInfo, } DefaultWelcomeFeatures = []string{ ServerFeatureAudioVideoPermissions, @@ -582,6 +585,7 @@ var ( ServerFeatureRecipientCall, ServerFeatureJoinFeatures, ServerFeatureOfferCodecs, + ServerFeatureServerInfo, } ) From 7cd55c741da0b0b302e355d3850f1abe53a72a27 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 21:38:44 +0100 Subject: [PATCH 068/549] Document welcome and serverinfo APIs. --- docs/standalone-signaling-api-v1.md | 122 ++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 7378914..9dab6fe 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1416,6 +1416,128 @@ The signaling server provides an internal API that can be called from Nextcloud to trigger events from the server side. +## Welcome message + +The welcome message at `/api/v1/welcome` can be retrieved using HTTP `GET` to +check if the signaling server is reachable and to get the version number and +supported features. + +A comma separated list of feature ids is in the `X-Spreed-Signaling-Features` +HTTP header, the version is available in the response body: + + { + "nextcloud-spreed-signaling": "Welcome", + "version": "1.2.3" + } + + +## Server info + +If the feature id `serverinfo` is supported, the server info API at +`/api/v1/serverinfo` can be called with HTTP `GET` to query information about +the server. + +Please note that the client calling this API must be allowed through the +`allowed_ips` option in the `[stats]` section. + + +### Example response with Janus backend + +Below is an example response of the serverinfo endpoint with a connected Janus +server: + + { + "version": "1.2.3", + "features": [ + "feature-1", + "feature-2", + ... + "serverinfo", + ... + ], + "sfu": { + "mode": "janus", + "janus": { + "url": "ws://localhost:8188/", + "connected": true, + "name": "Janus WebRTC Server", + "version": "1.3.1", + "author": "Meetecho s.r.l.", + "datachannels": true, + "fulltrickle": true, + "localip": "192.168.0.1", + "ipv6": false, + "videoroom": { + "name": "JANUS VideoRoom plugin", + "version": "0.0.10", + "author": "Meetecho s.r.l." + } + } + } + } + +If the backend is not connected, the value of `connected` in `janus` will be +`false` and most other entries will be missing. + + +### Example response with signaling proxy backends + +Below is an example response of the serverinfo endpoint with multiple signaling +proxies: + + { + "version": "1.2.3", + "features": [ + "feature-1", + "feature-2", + ... + "serverinfo", + ... + ], + "sfu": { + "mode": "proxy", + "proxies": [ + { + "url": "https://proxy.domain.tld/", + "ip": "192.168.0.1", + "connected": true, + "temporary": false, + "shutdown": false, + "uptime": "2025-03-05T18:09:35.435902408+01:00", + "version": "2.3.4", + "features": [ + "proxy-feature-1", + "proxy-feature-2", + ... + ], + "country": "DE", + "load": 0, + "bandwidth": { + "incoming": 0, + "outgoing": 0 + } + }, + { + "url": "https://proxy.domain.tld/", + "ip": "192.168.0.2", + "connected": false, + "temporary": false + } + ] + } + } + +The `ip` field will only be present if DNS discovery is enabled for resolving +proxy urls. +`uptime` is the ISO8601 time since the connection was established to the proxy. +`country` will only be returned if configured on the proxy. +`load` is an arbitrary value used to order signaling proxies when selecting the +proxy to use for publishing new streams. +`bandwidth` contains the percentage of incoming / outgoing bandwith utilization +for streams on the proxy. Only present if a bandwidth limit is configured on +the proxy. + + ## Rooms API The base URL for the rooms API is `/api/vi/room/`, all requests must be From a5e41e4822430243daa116464df004f3b9e1978d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 21:50:17 +0100 Subject: [PATCH 069/549] Include information on dialout sessions. --- api_backend.go | 11 ++++++++++- backend_server.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/api_backend.go b/api_backend.go index 83fe5b0..4fbc06b 100644 --- a/api_backend.go +++ b/api_backend.go @@ -520,9 +520,18 @@ type BackendServerInfoSfu struct { Proxies []BackendServerInfoSfuProxy `json:"proxies,omitempty"` } +type BackendServerInfoDialout struct { + SessionId string `json:"sessionid"` + Connected bool `json:"connected"` + Address string `json:"address,omitempty"` + UserAgent string `json:"useragent,omitempty"` + Features []string `json:"features,omitempty"` +} + type BackendServerInfo struct { Version string `json:"version"` Features []string `json:"features"` - Sfu BackendServerInfoSfu `json:"sfu"` + Sfu BackendServerInfoSfu `json:"sfu"` + Dialout []BackendServerInfoDialout `json:"dialout,omitempty"` } diff --git a/backend_server.go b/backend_server.go index 346e165..1a2ea7f 100644 --- a/backend_server.go +++ b/backend_server.go @@ -1014,6 +1014,21 @@ func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request Sfu: sfu, } + b.hub.mu.RLock() + defer b.hub.mu.RUnlock() + for session := range b.hub.dialoutSessions { + dialout := BackendServerInfoDialout{ + SessionId: session.PublicId(), + } + if client := session.GetClient(); client != nil && client.IsConnected() { + dialout.Connected = true + dialout.Address = client.RemoteAddr() + dialout.UserAgent = client.UserAgent() + dialout.Features = session.GetFeatures() + } + info.Dialout = append(info.Dialout, dialout) + } + infoData, err := json.MarshalIndent(info, "", " ") if err != nil { log.Printf("Could not serialize server info %+v: %s", info, err) From 6bb2582b61b0470a6de8f567aba202b069d4030a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Mar 2025 21:55:00 +0100 Subject: [PATCH 070/549] Document dialout serverinfo. --- docs/standalone-signaling-api-v1.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 9dab6fe..111a6dc 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1538,6 +1538,29 @@ for streams on the proxy. Only present if a bandwidth limit is configured on the proxy. +### Dialout session + +If a SIP bridge with support for dial-out is connected, the serverinfo response +will contain an additional property `dialout` with the following contents: + + [ + { + "sessionid": "the-session-id", + "connected": true, + "address": "192.168.1.0", + "useragent": "spreed-webrtc-sip-bridge/1.2.3", + "features": [ + "start-dialout", + "datachannels", + "encryption" + ] + } + ] + +If the connection between SIP bridge and signaling server is interrupted, the +`connected` property will be `false` and details on the session (`address`, `useragent` and `features`) omitted. + + ## Rooms API The base URL for the rooms API is `/api/vi/room/`, all requests must be From 957d93061338d29da03af4753bccf86e2a97270c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Mar 2025 09:15:49 +0100 Subject: [PATCH 071/549] Add "version" to dialin sessions. --- api_backend.go | 1 + backend_server.go | 12 +++++++++++- docs/standalone-signaling-api-v1.md | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/api_backend.go b/api_backend.go index 4fbc06b..7ac40ef 100644 --- a/api_backend.go +++ b/api_backend.go @@ -525,6 +525,7 @@ type BackendServerInfoDialout struct { Connected bool `json:"connected"` Address string `json:"address,omitempty"` UserAgent string `json:"useragent,omitempty"` + Version string `json:"version,omitempty"` Features []string `json:"features,omitempty"` } diff --git a/backend_server.go b/backend_server.go index 1a2ea7f..cd4ab40 100644 --- a/backend_server.go +++ b/backend_server.go @@ -1023,7 +1023,17 @@ func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request if client := session.GetClient(); client != nil && client.IsConnected() { dialout.Connected = true dialout.Address = client.RemoteAddr() - dialout.UserAgent = client.UserAgent() + if ua := client.UserAgent(); ua != "" { + dialout.UserAgent = ua + // Extract version from user-agent, expects "software/version". + if pos := strings.IndexByte(ua, '/'); pos != -1 { + version := ua[pos+1:] + if pos = strings.IndexByte(version, ' '); pos != -1 { + version = version[:pos] + } + dialout.Version = version + } + } dialout.Features = session.GetFeatures() } info.Dialout = append(info.Dialout, dialout) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 111a6dc..6bf571f 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1549,6 +1549,7 @@ will contain an additional property `dialout` with the following contents: "connected": true, "address": "192.168.1.0", "useragent": "spreed-webrtc-sip-bridge/1.2.3", + "version": "1.2.3", "features": [ "start-dialout", "datachannels", From 3e9bace0ad14378e932d381fa45943bd5f86ce4e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Mar 2025 13:08:03 +0100 Subject: [PATCH 072/549] Move serverinfo result generation to source, add nats, gprc and etcd. --- api_backend.go | 36 +++++++++-- async_events_nats.go | 25 ++++++++ backend_server.go | 92 ++++------------------------- docs/standalone-signaling-api-v1.md | 50 ++++++++++++++++ etcd_client.go | 20 +++++++ grpc_client.go | 54 ++++++++++++++--- hub.go | 35 +++++++++++ mcu_common.go | 1 + mcu_janus.go | 32 ++++++++++ mcu_proxy.go | 38 ++++++++++++ mcu_test.go | 4 ++ proxy/proxy_server_test.go | 4 ++ 12 files changed, 298 insertions(+), 93 deletions(-) diff --git a/api_backend.go b/api_backend.go index 7ac40ef..5ba210a 100644 --- a/api_backend.go +++ b/api_backend.go @@ -508,9 +508,8 @@ type BackendServerInfoSfuProxy struct { type SfuMode string const ( - SfuModeUnknown SfuMode = "unknown" - SfuModeJanus SfuMode = "janus" - SfuModeProxy SfuMode = "proxy" + SfuModeJanus SfuMode = "janus" + SfuModeProxy SfuMode = "proxy" ) type BackendServerInfoSfu struct { @@ -529,10 +528,39 @@ type BackendServerInfoDialout struct { Features []string `json:"features,omitempty"` } +type BackendServerInfoNats struct { + Urls []string `json:"urls"` + Connected bool `json:"connected"` + + ServerUrl string `json:"serverurl,omitempty"` + ServerID string `json:"serverid,omitempty"` + ServerVersion string `json:"version,omitempty"` + ClusterName string `json:"clustername,omitempty"` +} + +type BackendServerInfoGrpc struct { + Target string `json:"target"` + IP string `json:"ip,omitempty"` + Connected bool `json:"connected"` + + Version string `json:"version,omitempty"` +} + +type BackendServerInfoEtcd struct { + Endpoints []string `json:"endpoints"` + + Active string `json:"active,omitempty"` + Connected *bool `json:"connected,omitempty"` +} + type BackendServerInfo struct { Version string `json:"version"` Features []string `json:"features"` - Sfu BackendServerInfoSfu `json:"sfu"` + Sfu *BackendServerInfoSfu `json:"sfu,omitempty"` Dialout []BackendServerInfoDialout `json:"dialout,omitempty"` + + Nats *BackendServerInfoNats `json:"nats,omitempty"` + Grpc []BackendServerInfoGrpc `json:"grpc,omitempty"` + Etcd *BackendServerInfoEtcd `json:"etcd,omitempty"` } diff --git a/async_events_nats.go b/async_events_nats.go index 0db3502..46408d0 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -248,6 +248,31 @@ func NewAsyncEventsNats(client NatsClient) (AsyncEvents, error) { return events, nil } +func (e *asyncEventsNats) GetServerInfoNats() *BackendServerInfoNats { + var nats *BackendServerInfoNats + switch n := e.client.(type) { + case *natsClient: + nats = &BackendServerInfoNats{ + Urls: n.conn.Servers(), + } + if c := n.conn; c.IsConnected() { + nats.Connected = true + nats.ServerUrl = c.ConnectedUrl() + nats.ServerID = c.ConnectedServerId() + nats.ServerVersion = c.ConnectedServerVersion() + nats.ClusterName = c.ConnectedClusterName() + } + case *LoopbackNatsClient: + nats = &BackendServerInfoNats{ + Urls: []string{NatsLoopbackUrl}, + Connected: true, + ServerUrl: NatsLoopbackUrl, + } + } + + return nats +} + func (e *asyncEventsNats) Close() { e.mu.Lock() defer e.mu.Unlock() diff --git a/backend_server.go b/backend_server.go index cd4ab40..477f04b 100644 --- a/backend_server.go +++ b/backend_server.go @@ -952,91 +952,23 @@ func (b *BackendServer) statsHandler(w http.ResponseWriter, r *http.Request) { } func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request) { - var sfu BackendServerInfoSfu - switch m := b.hub.mcu.(type) { - case *mcuJanus: - sfu.Mode = SfuModeJanus - janus := &BackendServerInfoSfuJanus{ - Url: m.url, - } - if m.IsConnected() { - janus.Connected = true - if info := m.Info(); info != nil { - janus.Name = info.Name - janus.Version = info.VersionString - janus.Author = info.Author - janus.DataChannels = makePtr(info.DataChannels) - janus.FullTrickle = makePtr(info.FullTrickle) - janus.LocalIP = info.LocalIP - janus.IPv6 = makePtr(info.IPv6) - - if plugin, found := info.Plugins[pluginVideoRoom]; found { - janus.VideoRoom = &BackendServerInfoVideoRoom{ - Name: plugin.Name, - Version: plugin.VersionString, - Author: plugin.Author, - } - } - } - } - sfu.Janus = janus - case *mcuProxy: - sfu.Mode = SfuModeProxy - for _, c := range m.connections { - proxy := BackendServerInfoSfuProxy{ - Url: c.rawUrl, - - Temporary: c.IsTemporary(), - } - if len(c.ip) > 0 { - proxy.IP = c.ip.String() - } - if c.IsConnected() { - proxy.Connected = true - proxy.Shutdown = makePtr(c.IsShutdownScheduled()) - proxy.Uptime = &c.connectedSince - proxy.Version = c.Version() - proxy.Features = c.Features() - proxy.Country = c.Country() - proxy.Load = makePtr(c.Load()) - proxy.Bandwidth = c.Bandwidth() - } - sfu.Proxies = append(sfu.Proxies, proxy) - } - default: - sfu.Mode = SfuModeUnknown - } - info := BackendServerInfo{ Version: b.version, Features: b.hub.info.Features, - Sfu: sfu, + Dialout: b.hub.GetServerInfoDialout(), } - - b.hub.mu.RLock() - defer b.hub.mu.RUnlock() - for session := range b.hub.dialoutSessions { - dialout := BackendServerInfoDialout{ - SessionId: session.PublicId(), - } - if client := session.GetClient(); client != nil && client.IsConnected() { - dialout.Connected = true - dialout.Address = client.RemoteAddr() - if ua := client.UserAgent(); ua != "" { - dialout.UserAgent = ua - // Extract version from user-agent, expects "software/version". - if pos := strings.IndexByte(ua, '/'); pos != -1 { - version := ua[pos+1:] - if pos = strings.IndexByte(version, ' '); pos != -1 { - version = version[:pos] - } - dialout.Version = version - } - } - dialout.Features = session.GetFeatures() - } - info.Dialout = append(info.Dialout, dialout) + if mcu := b.hub.mcu; mcu != nil { + info.Sfu = mcu.GetServerInfoSfu() + } + if e, ok := b.events.(*asyncEventsNats); ok { + info.Nats = e.GetServerInfoNats() + } + if rpcClients := b.hub.rpcClients; rpcClients != nil { + info.Grpc = rpcClients.GetServerInfoGrpc() + } + if etcdClient := b.hub.etcdClient; etcdClient != nil { + info.Etcd = etcdClient.GetServerInfoEtcd() } infoData, err := json.MarshalIndent(info, "", " ") diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 6bf571f..1487d35 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1538,6 +1538,56 @@ for streams on the proxy. Only present if a bandwidth limit is configured on the proxy. +### NATS connection + +Information about the NATS connection are also returned by the serverinfo +endpoint: + + { + "urls": [ + "nats://localhost:4222" + ], + "connected": true, + "serverurl": "nats://localhost:4222", + "serverid": "556c9de63ac214e53a9b976b2e5305d8", + "version": "0.6.8" + } + + +### GRPC connections + +In clustered mode, the signaling server has GRPC connections to other instances +which are included in the serverinfo response: + + [ + { + "target": "192.168.1.1:8080", + "connected": true, + "version": "1.2.3" + }, + { + "target": "192.168.1.2:8080", + "connected": true, + "version": "1.2.3" + } + ] + + +### ETCD cluster + +Some configuration can be loaded from an etcd cluster, the serverinfo response +also contains information on that connection: + + { + "endpoints": [ + "192.168.4.1:2379", + "192.168.4.2:2379" + ], + "active": "etcd-endpoints://0xc0001fba40/192.168.4.2:2379", + "connected": true + } + + ### Dialout session If a SIP bridge with support for dial-out is connected, the serverinfo response diff --git a/etcd_client.go b/etcd_client.go index 23bce3a..e232fce 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -37,6 +37,7 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "google.golang.org/grpc/connectivity" ) type EtcdClientListener interface { @@ -68,6 +69,25 @@ func NewEtcdClient(config *goconf.ConfigFile, compatSection string) (*EtcdClient return result, nil } +func (c *EtcdClient) GetServerInfoEtcd() *BackendServerInfoEtcd { + client := c.getEtcdClient() + if client == nil { + return nil + } + + result := &BackendServerInfoEtcd{ + Endpoints: client.Endpoints(), + } + + conn := client.ActiveConnection() + if conn != nil { + result.Active = conn.Target() + result.Connected = makePtr(conn.GetState() == connectivity.Ready) + } + + return result +} + func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, option string) string { value, _ := config.GetString("etcd", option) if value == "" && c.compatSection != "" { diff --git a/grpc_client.go b/grpc_client.go index 4b10631..7aad2a2 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -39,6 +39,7 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" "google.golang.org/grpc" codes "google.golang.org/grpc/codes" + "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/resolver" @@ -79,12 +80,14 @@ func newGrpcClientImpl(conn grpc.ClientConnInterface) *grpcClientImpl { } type GrpcClient struct { - ip net.IP - target string - conn *grpc.ClientConn - impl *grpcClientImpl + ip net.IP + rawTarget string + target string + conn *grpc.ClientConn + impl *grpcClientImpl - isSelf atomic.Bool + isSelf atomic.Bool + version atomic.Value } type customIpResolver struct { @@ -151,15 +154,17 @@ func NewGrpcClient(target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClie } result := &GrpcClient{ - ip: ip, - target: target, - conn: conn, - impl: newGrpcClientImpl(conn), + ip: ip, + rawTarget: target, + target: target, + conn: conn, + impl: newGrpcClientImpl(conn), } if ip != nil { result.target += " (" + ip.String() + ")" } + result.version.Store("") return result, nil } @@ -167,6 +172,10 @@ func (c *GrpcClient) Target() string { return c.target } +func (c *GrpcClient) Version() string { + return c.version.Load().(string) +} + func (c *GrpcClient) Close() error { return c.conn.Close() } @@ -440,6 +449,32 @@ func NewGrpcClients(config *goconf.ConfigFile, etcdClient *EtcdClient, dnsMonito return result, nil } +func (c *GrpcClients) GetServerInfoGrpc() (result []BackendServerInfoGrpc) { + c.mu.RLock() + defer c.mu.RUnlock() + + for _, client := range c.clients { + if client.IsSelf() { + continue + } + + grpc := BackendServerInfoGrpc{ + Target: client.rawTarget, + } + if len(client.ip) > 0 { + grpc.IP = client.ip.String() + } + if client.conn.GetState() == connectivity.Ready { + grpc.Connected = true + grpc.Version = client.Version() + } + + result = append(result, grpc) + } + + return +} + func (c *GrpcClients) load(config *goconf.ConfigFile, fromReload bool) error { creds, err := NewReloadableCredentials(config, false) if err != nil { @@ -537,6 +572,7 @@ loop: continue } + client.version.Store(version) if id == GrpcServerId { log.Printf("GRPC target %s is this server, removing", client.Target()) c.closeClient(client) diff --git a/hub.go b/hub.go index 4b00a38..27d2bfd 100644 --- a/hub.go +++ b/hub.go @@ -179,6 +179,7 @@ type Hub struct { geoipOverrides atomic.Pointer[map[*net.IPNet]string] geoipUpdating atomic.Bool + etcdClient *EtcdClient rpcServer *GrpcServer rpcClients *GrpcClients @@ -365,6 +366,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer geoip: geoip, + etcdClient: etcdClient, rpcServer: rpcServer, rpcClients: rpcClients, @@ -2816,6 +2818,39 @@ func (h *Hub) GetStats() map[string]interface{} { return result } +func (h *Hub) GetServerInfoDialout() (result []BackendServerInfoDialout) { + h.mu.RLock() + defer h.mu.RUnlock() + + for session := range h.dialoutSessions { + dialout := BackendServerInfoDialout{ + SessionId: session.PublicId(), + } + if client := session.GetClient(); client != nil && client.IsConnected() { + dialout.Connected = true + dialout.Address = client.RemoteAddr() + if ua := client.UserAgent(); ua != "" { + dialout.UserAgent = ua + // Extract version from user-agent, expects "software/version". + if pos := strings.IndexByte(ua, '/'); pos != -1 { + version := ua[pos+1:] + if pos = strings.IndexByte(version, ' '); pos != -1 { + version = version[:pos] + } + dialout.Version = version + } + } + dialout.Features = session.GetFeatures() + } + result = append(result, dialout) + } + + slices.SortFunc(result, func(a, b BackendServerInfoDialout) int { + return strings.Compare(a.SessionId, b.SessionId) + }) + return +} + func GetRealUserIP(r *http.Request, trusted *AllowedIps) string { addr := r.RemoteAddr if host, _, err := net.SplitHostPort(addr); err == nil { diff --git a/mcu_common.go b/mcu_common.go index af22d4a..7d78690 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -128,6 +128,7 @@ type Mcu interface { SetOnDisconnected(func()) GetStats() interface{} + GetServerInfoSfu() *BackendServerInfoSfu NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) NewSubscriber(ctx context.Context, listener McuListener, publisher string, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) diff --git a/mcu_janus.go b/mcu_janus.go index f2d97bc..ea08371 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -421,6 +421,38 @@ func (m *mcuJanus) Info() *InfoMsg { return m.info.Load() } +func (m *mcuJanus) GetServerInfoSfu() *BackendServerInfoSfu { + janus := &BackendServerInfoSfuJanus{ + Url: m.url, + } + if m.IsConnected() { + janus.Connected = true + if info := m.Info(); info != nil { + janus.Name = info.Name + janus.Version = info.VersionString + janus.Author = info.Author + janus.DataChannels = makePtr(info.DataChannels) + janus.FullTrickle = makePtr(info.FullTrickle) + janus.LocalIP = info.LocalIP + janus.IPv6 = makePtr(info.IPv6) + + if plugin, found := info.Plugins[pluginVideoRoom]; found { + janus.VideoRoom = &BackendServerInfoVideoRoom{ + Name: plugin.Name, + Version: plugin.VersionString, + Author: plugin.Author, + } + } + } + } + + sfu := &BackendServerInfoSfu{ + Mode: SfuModeJanus, + Janus: janus, + } + return sfu +} + func (m *mcuJanus) Reload(config *goconf.ConfigFile) { m.settings.Reload(config) } diff --git a/mcu_proxy.go b/mcu_proxy.go index ea9e1ef..72fab9a 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1670,6 +1670,44 @@ func (m *mcuProxy) GetStats() interface{} { return result } +func (m *mcuProxy) GetServerInfoSfu() *BackendServerInfoSfu { + m.connectionsMu.RLock() + defer m.connectionsMu.RUnlock() + + sfu := &BackendServerInfoSfu{ + Mode: SfuModeProxy, + } + for _, c := range m.connections { + proxy := BackendServerInfoSfuProxy{ + Url: c.rawUrl, + + Temporary: c.IsTemporary(), + } + if len(c.ip) > 0 { + proxy.IP = c.ip.String() + } + if c.IsConnected() { + proxy.Connected = true + proxy.Shutdown = makePtr(c.IsShutdownScheduled()) + proxy.Uptime = &c.connectedSince + proxy.Version = c.Version() + proxy.Features = c.Features() + proxy.Country = c.Country() + proxy.Load = makePtr(c.Load()) + proxy.Bandwidth = c.Bandwidth() + } + sfu.Proxies = append(sfu.Proxies, proxy) + } + slices.SortFunc(sfu.Proxies, func(a, b BackendServerInfoSfuProxy) int { + c := strings.Compare(a.Url, b.Url) + if c == 0 { + c = strings.Compare(a.IP, b.IP) + } + return c + }) + return sfu +} + func (m *mcuProxy) getContinentsMap() map[string][]string { continentsMap := m.continentsMap.Load() if continentsMap == nil { diff --git a/mcu_test.go b/mcu_test.go index 6db0db6..9097267 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -70,6 +70,10 @@ func (m *TestMCU) GetStats() interface{} { return nil } +func (m *TestMCU) GetServerInfoSfu() *BackendServerInfoSfu { + return nil +} + func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { var maxBitrate int if streamType == StreamTypeScreen { diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 3dfd136..25eff8d 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -379,6 +379,10 @@ func (m *TestMCU) GetStats() interface{} { return nil } +func (m *TestMCU) GetServerInfoSfu() *signaling.BackendServerInfoSfu { + return nil +} + func (m *TestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id string, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { return nil, errors.New("not implemented") } From c830403cd7fc5b790699cc364e85950bff7b2849 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Mar 2025 13:14:09 +0100 Subject: [PATCH 073/549] Update generated files. --- api_backend_easyjson.go | 1851 ++++++++++++++++++++++++++++++++------- 1 file changed, 1558 insertions(+), 293 deletions(-) diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 95338f0..5284f0a 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -741,7 +741,1272 @@ func (v *BackendServerRoomRequest) UnmarshalJSON(data []byte) error { func (v *BackendServerRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { + 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 "name": + out.Name = string(in.String()) + case "version": + out.Version = string(in.String()) + case "author": + out.Author = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in BackendServerInfoVideoRoom) { + out.RawByte('{') + first := true + _ = first + if in.Name != "" { + const prefix string = ",\"name\":" + first = false + out.RawString(prefix[1:]) + out.String(string(in.Name)) + } + if in.Version != "" { + const prefix string = ",\"version\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Version)) + } + if in.Author != "" { + const prefix string = ",\"author\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Author)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoVideoRoom) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoVideoRoom) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoVideoRoom) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoVideoRoom) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { + 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 "url": + out.Url = string(in.String()) + case "ip": + out.IP = string(in.String()) + case "connected": + out.Connected = bool(in.Bool()) + case "temporary": + out.Temporary = bool(in.Bool()) + case "shutdown": + if in.IsNull() { + in.Skip() + out.Shutdown = nil + } else { + if out.Shutdown == nil { + out.Shutdown = new(bool) + } + *out.Shutdown = bool(in.Bool()) + } + case "uptime": + if in.IsNull() { + in.Skip() + out.Uptime = nil + } else { + if out.Uptime == nil { + out.Uptime = new(time.Time) + } + if data := in.Raw(); in.Ok() { + in.AddError((*out.Uptime).UnmarshalJSON(data)) + } + } + case "version": + out.Version = string(in.String()) + case "features": + if in.IsNull() { + in.Skip() + out.Features = nil + } else { + in.Delim('[') + if out.Features == nil { + if !in.IsDelim(']') { + out.Features = make([]string, 0, 4) + } else { + out.Features = []string{} + } + } else { + out.Features = (out.Features)[:0] + } + for !in.IsDelim(']') { + var v4 string + v4 = string(in.String()) + out.Features = append(out.Features, v4) + in.WantComma() + } + in.Delim(']') + } + case "country": + out.Country = string(in.String()) + case "load": + if in.IsNull() { + in.Skip() + out.Load = nil + } else { + if out.Load == nil { + out.Load = new(int64) + } + *out.Load = int64(in.Int64()) + } + case "bandwidth": + if in.IsNull() { + in.Skip() + out.Bandwidth = nil + } else { + if out.Bandwidth == nil { + out.Bandwidth = new(EventProxyServerBandwidth) + } + (*out.Bandwidth).UnmarshalEasyJSON(in) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in BackendServerInfoSfuProxy) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"url\":" + out.RawString(prefix[1:]) + out.String(string(in.Url)) + } + if in.IP != "" { + const prefix string = ",\"ip\":" + out.RawString(prefix) + out.String(string(in.IP)) + } + { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(in.Connected)) + } + { + const prefix string = ",\"temporary\":" + out.RawString(prefix) + out.Bool(bool(in.Temporary)) + } + if in.Shutdown != nil { + const prefix string = ",\"shutdown\":" + out.RawString(prefix) + out.Bool(bool(*in.Shutdown)) + } + if in.Uptime != nil { + const prefix string = ",\"uptime\":" + out.RawString(prefix) + out.Raw((*in.Uptime).MarshalJSON()) + } + if in.Version != "" { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.String(string(in.Version)) + } + if len(in.Features) != 0 { + const prefix string = ",\"features\":" + out.RawString(prefix) + { + out.RawByte('[') + for v5, v6 := range in.Features { + if v5 > 0 { + out.RawByte(',') + } + out.String(string(v6)) + } + out.RawByte(']') + } + } + if in.Country != "" { + const prefix string = ",\"country\":" + out.RawString(prefix) + out.String(string(in.Country)) + } + if in.Load != nil { + const prefix string = ",\"load\":" + out.RawString(prefix) + out.Int64(int64(*in.Load)) + } + if in.Bandwidth != nil { + const prefix string = ",\"bandwidth\":" + out.RawString(prefix) + (*in.Bandwidth).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoSfuProxy) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoSfuProxy) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoSfuProxy) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoSfuProxy) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { + 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 "url": + out.Url = string(in.String()) + case "connected": + out.Connected = bool(in.Bool()) + case "name": + out.Name = string(in.String()) + case "version": + out.Version = string(in.String()) + case "author": + out.Author = string(in.String()) + case "datachannels": + if in.IsNull() { + in.Skip() + out.DataChannels = nil + } else { + if out.DataChannels == nil { + out.DataChannels = new(bool) + } + *out.DataChannels = bool(in.Bool()) + } + case "fulltrickle": + if in.IsNull() { + in.Skip() + out.FullTrickle = nil + } else { + if out.FullTrickle == nil { + out.FullTrickle = new(bool) + } + *out.FullTrickle = bool(in.Bool()) + } + case "localip": + out.LocalIP = string(in.String()) + case "ipv6": + if in.IsNull() { + in.Skip() + out.IPv6 = nil + } else { + if out.IPv6 == nil { + out.IPv6 = new(bool) + } + *out.IPv6 = bool(in.Bool()) + } + case "videoroom": + if in.IsNull() { + in.Skip() + out.VideoRoom = nil + } else { + if out.VideoRoom == nil { + out.VideoRoom = new(BackendServerInfoVideoRoom) + } + (*out.VideoRoom).UnmarshalEasyJSON(in) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in BackendServerInfoSfuJanus) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"url\":" + out.RawString(prefix[1:]) + out.String(string(in.Url)) + } + { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(in.Connected)) + } + if in.Name != "" { + const prefix string = ",\"name\":" + out.RawString(prefix) + out.String(string(in.Name)) + } + if in.Version != "" { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.String(string(in.Version)) + } + if in.Author != "" { + const prefix string = ",\"author\":" + out.RawString(prefix) + out.String(string(in.Author)) + } + if in.DataChannels != nil { + const prefix string = ",\"datachannels\":" + out.RawString(prefix) + out.Bool(bool(*in.DataChannels)) + } + if in.FullTrickle != nil { + const prefix string = ",\"fulltrickle\":" + out.RawString(prefix) + out.Bool(bool(*in.FullTrickle)) + } + if in.LocalIP != "" { + const prefix string = ",\"localip\":" + out.RawString(prefix) + out.String(string(in.LocalIP)) + } + if in.IPv6 != nil { + const prefix string = ",\"ipv6\":" + out.RawString(prefix) + out.Bool(bool(*in.IPv6)) + } + if in.VideoRoom != nil { + const prefix string = ",\"videoroom\":" + out.RawString(prefix) + (*in.VideoRoom).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoSfuJanus) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoSfuJanus) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoSfuJanus) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoSfuJanus) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendServerInfoSfu) { + 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 "mode": + out.Mode = SfuMode(in.String()) + case "janus": + if in.IsNull() { + in.Skip() + out.Janus = nil + } else { + if out.Janus == nil { + out.Janus = new(BackendServerInfoSfuJanus) + } + (*out.Janus).UnmarshalEasyJSON(in) + } + case "proxies": + if in.IsNull() { + in.Skip() + out.Proxies = nil + } else { + in.Delim('[') + if out.Proxies == nil { + if !in.IsDelim(']') { + out.Proxies = make([]BackendServerInfoSfuProxy, 0, 0) + } else { + out.Proxies = []BackendServerInfoSfuProxy{} + } + } else { + out.Proxies = (out.Proxies)[:0] + } + for !in.IsDelim(']') { + var v7 BackendServerInfoSfuProxy + (v7).UnmarshalEasyJSON(in) + out.Proxies = append(out.Proxies, v7) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendServerInfoSfu) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"mode\":" + out.RawString(prefix[1:]) + out.String(string(in.Mode)) + } + if in.Janus != nil { + const prefix string = ",\"janus\":" + out.RawString(prefix) + (*in.Janus).MarshalEasyJSON(out) + } + if len(in.Proxies) != 0 { + const prefix string = ",\"proxies\":" + out.RawString(prefix) + { + out.RawByte('[') + for v8, v9 := range in.Proxies { + if v8 > 0 { + out.RawByte(',') + } + (v9).MarshalEasyJSON(out) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoSfu) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoSfu) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoSfu) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoSfu) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendServerInfoNats) { + 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 "urls": + if in.IsNull() { + in.Skip() + out.Urls = nil + } else { + in.Delim('[') + if out.Urls == nil { + if !in.IsDelim(']') { + out.Urls = make([]string, 0, 4) + } else { + out.Urls = []string{} + } + } else { + out.Urls = (out.Urls)[:0] + } + for !in.IsDelim(']') { + var v10 string + v10 = string(in.String()) + out.Urls = append(out.Urls, v10) + in.WantComma() + } + in.Delim(']') + } + case "connected": + out.Connected = bool(in.Bool()) + case "serverurl": + out.ServerUrl = string(in.String()) + case "serverid": + out.ServerID = string(in.String()) + case "version": + out.ServerVersion = string(in.String()) + case "clustername": + out.ClusterName = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendServerInfoNats) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"urls\":" + out.RawString(prefix[1:]) + if in.Urls == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v11, v12 := range in.Urls { + if v11 > 0 { + out.RawByte(',') + } + out.String(string(v12)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(in.Connected)) + } + if in.ServerUrl != "" { + const prefix string = ",\"serverurl\":" + out.RawString(prefix) + out.String(string(in.ServerUrl)) + } + if in.ServerID != "" { + const prefix string = ",\"serverid\":" + out.RawString(prefix) + out.String(string(in.ServerID)) + } + if in.ServerVersion != "" { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.String(string(in.ServerVersion)) + } + if in.ClusterName != "" { + const prefix string = ",\"clustername\":" + out.RawString(prefix) + out.String(string(in.ClusterName)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoNats) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoNats) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoNats) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoNats) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendServerInfoGrpc) { + 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 "target": + out.Target = string(in.String()) + case "ip": + out.IP = string(in.String()) + case "connected": + out.Connected = bool(in.Bool()) + case "version": + out.Version = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendServerInfoGrpc) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"target\":" + out.RawString(prefix[1:]) + out.String(string(in.Target)) + } + if in.IP != "" { + const prefix string = ",\"ip\":" + out.RawString(prefix) + out.String(string(in.IP)) + } + { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(in.Connected)) + } + if in.Version != "" { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.String(string(in.Version)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoGrpc) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoGrpc) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoGrpc) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoGrpc) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendServerInfoEtcd) { + 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 "endpoints": + if in.IsNull() { + in.Skip() + out.Endpoints = nil + } else { + in.Delim('[') + if out.Endpoints == nil { + if !in.IsDelim(']') { + out.Endpoints = make([]string, 0, 4) + } else { + out.Endpoints = []string{} + } + } else { + out.Endpoints = (out.Endpoints)[:0] + } + for !in.IsDelim(']') { + var v13 string + v13 = string(in.String()) + out.Endpoints = append(out.Endpoints, v13) + in.WantComma() + } + in.Delim(']') + } + case "active": + out.Active = string(in.String()) + case "connected": + if in.IsNull() { + in.Skip() + out.Connected = nil + } else { + if out.Connected == nil { + out.Connected = new(bool) + } + *out.Connected = bool(in.Bool()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendServerInfoEtcd) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"endpoints\":" + out.RawString(prefix[1:]) + if in.Endpoints == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v14, v15 := range in.Endpoints { + if v14 > 0 { + out.RawByte(',') + } + out.String(string(v15)) + } + out.RawByte(']') + } + } + if in.Active != "" { + const prefix string = ",\"active\":" + out.RawString(prefix) + out.String(string(in.Active)) + } + if in.Connected != nil { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(*in.Connected)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoEtcd) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoEtcd) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoEtcd) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendServerInfoDialout) { + 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 "sessionid": + out.SessionId = string(in.String()) + case "connected": + out.Connected = bool(in.Bool()) + case "address": + out.Address = string(in.String()) + case "useragent": + out.UserAgent = string(in.String()) + case "version": + out.Version = string(in.String()) + case "features": + if in.IsNull() { + in.Skip() + out.Features = nil + } else { + in.Delim('[') + if out.Features == nil { + if !in.IsDelim(']') { + out.Features = make([]string, 0, 4) + } else { + out.Features = []string{} + } + } else { + out.Features = (out.Features)[:0] + } + for !in.IsDelim(']') { + var v16 string + v16 = string(in.String()) + out.Features = append(out.Features, v16) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendServerInfoDialout) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"sessionid\":" + out.RawString(prefix[1:]) + out.String(string(in.SessionId)) + } + { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(in.Connected)) + } + if in.Address != "" { + const prefix string = ",\"address\":" + out.RawString(prefix) + out.String(string(in.Address)) + } + if in.UserAgent != "" { + const prefix string = ",\"useragent\":" + out.RawString(prefix) + out.String(string(in.UserAgent)) + } + if in.Version != "" { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.String(string(in.Version)) + } + if len(in.Features) != 0 { + const prefix string = ",\"features\":" + out.RawString(prefix) + { + out.RawByte('[') + for v17, v18 := range in.Features { + if v17 > 0 { + out.RawByte(',') + } + out.String(string(v18)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoDialout) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoDialout) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoDialout) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoDialout) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendServerInfo) { + 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 "version": + out.Version = string(in.String()) + case "features": + if in.IsNull() { + in.Skip() + out.Features = nil + } else { + in.Delim('[') + if out.Features == nil { + if !in.IsDelim(']') { + out.Features = make([]string, 0, 4) + } else { + out.Features = []string{} + } + } else { + out.Features = (out.Features)[:0] + } + for !in.IsDelim(']') { + var v19 string + v19 = string(in.String()) + out.Features = append(out.Features, v19) + in.WantComma() + } + in.Delim(']') + } + case "sfu": + if in.IsNull() { + in.Skip() + out.Sfu = nil + } else { + if out.Sfu == nil { + out.Sfu = new(BackendServerInfoSfu) + } + (*out.Sfu).UnmarshalEasyJSON(in) + } + case "dialout": + if in.IsNull() { + in.Skip() + out.Dialout = nil + } else { + in.Delim('[') + if out.Dialout == nil { + if !in.IsDelim(']') { + out.Dialout = make([]BackendServerInfoDialout, 0, 0) + } else { + out.Dialout = []BackendServerInfoDialout{} + } + } else { + out.Dialout = (out.Dialout)[:0] + } + for !in.IsDelim(']') { + var v20 BackendServerInfoDialout + (v20).UnmarshalEasyJSON(in) + out.Dialout = append(out.Dialout, v20) + in.WantComma() + } + in.Delim(']') + } + case "nats": + if in.IsNull() { + in.Skip() + out.Nats = nil + } else { + if out.Nats == nil { + out.Nats = new(BackendServerInfoNats) + } + (*out.Nats).UnmarshalEasyJSON(in) + } + case "grpc": + if in.IsNull() { + in.Skip() + out.Grpc = nil + } else { + in.Delim('[') + if out.Grpc == nil { + if !in.IsDelim(']') { + out.Grpc = make([]BackendServerInfoGrpc, 0, 1) + } else { + out.Grpc = []BackendServerInfoGrpc{} + } + } else { + out.Grpc = (out.Grpc)[:0] + } + for !in.IsDelim(']') { + var v21 BackendServerInfoGrpc + (v21).UnmarshalEasyJSON(in) + out.Grpc = append(out.Grpc, v21) + in.WantComma() + } + in.Delim(']') + } + case "etcd": + if in.IsNull() { + in.Skip() + out.Etcd = nil + } else { + if out.Etcd == nil { + out.Etcd = new(BackendServerInfoEtcd) + } + (*out.Etcd).UnmarshalEasyJSON(in) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendServerInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"version\":" + out.RawString(prefix[1:]) + out.String(string(in.Version)) + } + { + const prefix string = ",\"features\":" + out.RawString(prefix) + if in.Features == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v22, v23 := range in.Features { + if v22 > 0 { + out.RawByte(',') + } + out.String(string(v23)) + } + out.RawByte(']') + } + } + if in.Sfu != nil { + const prefix string = ",\"sfu\":" + out.RawString(prefix) + (*in.Sfu).MarshalEasyJSON(out) + } + if len(in.Dialout) != 0 { + const prefix string = ",\"dialout\":" + out.RawString(prefix) + { + out.RawByte('[') + for v24, v25 := range in.Dialout { + if v24 > 0 { + out.RawByte(',') + } + (v25).MarshalEasyJSON(out) + } + out.RawByte(']') + } + } + if in.Nats != nil { + const prefix string = ",\"nats\":" + out.RawString(prefix) + (*in.Nats).MarshalEasyJSON(out) + } + if len(in.Grpc) != 0 { + const prefix string = ",\"grpc\":" + out.RawString(prefix) + { + out.RawByte('[') + for v26, v27 := range in.Grpc { + if v26 > 0 { + out.RawByte(',') + } + (v27).MarshalEasyJSON(out) + } + out.RawByte(']') + } + } + if in.Etcd != nil { + const prefix string = ",\"etcd\":" + out.RawString(prefix) + (*in.Etcd).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) +} +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -776,9 +2041,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v4 string - v4 = string(in.String()) - out.UserIds = append(out.UserIds, v4) + var v28 string + v28 = string(in.String()) + out.UserIds = append(out.UserIds, v28) in.WantComma() } in.Delim(']') @@ -797,7 +2062,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in BackendRoomUpdateRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomUpdateRequest) { out.RawByte('{') first := true _ = first @@ -807,11 +2072,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr out.RawString(prefix[1:]) { out.RawByte('[') - for v5, v6 := range in.UserIds { - if v5 > 0 { + for v29, v30 := range in.UserIds { + if v29 > 0 { out.RawByte(',') } - out.String(string(v6)) + out.String(string(v30)) } out.RawByte(']') } @@ -832,27 +2097,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendRoomUpdateRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomUpdateRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *BackendRoomTransientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomTransientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -895,7 +2160,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in BackendRoomTransientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomTransientRequest) { out.RawByte('{') first := true _ = first @@ -931,27 +2196,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendRoomTransientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomTransientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -992,9 +2257,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex out.SessionsList = (out.SessionsList)[:0] } for !in.IsDelim(']') { - var v7 string - v7 = string(in.String()) - out.SessionsList = append(out.SessionsList, v7) + var v31 string + v31 = string(in.String()) + out.SessionsList = append(out.SessionsList, v31) in.WantComma() } in.Delim(']') @@ -1012,11 +2277,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v8 json.RawMessage + var v32 json.RawMessage if data := in.Raw(); in.Ok() { - in.AddError((v8).UnmarshalJSON(data)) + in.AddError((v32).UnmarshalJSON(data)) } - (out.SessionsMap)[key] = v8 + (out.SessionsMap)[key] = v32 in.WantComma() } in.Delim('}') @@ -1031,7 +2296,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { out.RawByte('{') first := true _ = first @@ -1050,11 +2315,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr out.RawString(prefix) { out.RawByte('[') - for v9, v10 := range in.SessionsList { - if v9 > 0 { + for v33, v34 := range in.SessionsList { + if v33 > 0 { out.RawByte(',') } - out.String(string(v10)) + out.String(string(v34)) } out.RawByte(']') } @@ -1064,16 +2329,16 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr out.RawString(prefix) { out.RawByte('{') - v11First := true - for v11Name, v11Value := range in.SessionsMap { - if v11First { - v11First = false + v35First := true + for v35Name, v35Value := range in.SessionsMap { + if v35First { + v35First = false } else { out.RawByte(',') } - out.String(string(v11Name)) + out.String(string(v35Name)) out.RawByte(':') - out.Raw((v11Value).MarshalJSON()) + out.Raw((v35Value).MarshalJSON()) } out.RawByte('}') } @@ -1084,27 +2349,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1139,33 +2404,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v12 map[string]interface{} + var v36 map[string]interface{} if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v12 = make(map[string]interface{}) + v36 = make(map[string]interface{}) } else { - v12 = nil + v36 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v13 interface{} - if m, ok := v13.(easyjson.Unmarshaler); ok { + var v37 interface{} + if m, ok := v37.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v13.(json.Unmarshaler); ok { + } else if m, ok := v37.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v13 = in.Interface() + v37 = in.Interface() } - (v12)[key] = v13 + (v36)[key] = v37 in.WantComma() } in.Delim('}') } - out.Changed = append(out.Changed, v12) + out.Changed = append(out.Changed, v36) in.WantComma() } in.Delim(']') @@ -1186,33 +2451,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v14 map[string]interface{} + var v38 map[string]interface{} if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v14 = make(map[string]interface{}) + v38 = make(map[string]interface{}) } else { - v14 = nil + v38 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v15 interface{} - if m, ok := v15.(easyjson.Unmarshaler); ok { + var v39 interface{} + if m, ok := v39.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v15.(json.Unmarshaler); ok { + } else if m, ok := v39.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v15 = in.Interface() + v39 = in.Interface() } - (v14)[key] = v15 + (v38)[key] = v39 in.WantComma() } in.Delim('}') } - out.Users = append(out.Users, v14) + out.Users = append(out.Users, v38) in.WantComma() } in.Delim(']') @@ -1227,7 +2492,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendRoomParticipantsRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendRoomParticipantsRequest) { out.RawByte('{') first := true _ = first @@ -1237,29 +2502,29 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v16, v17 := range in.Changed { - if v16 > 0 { + for v40, v41 := range in.Changed { + if v40 > 0 { out.RawByte(',') } - if v17 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + if v41 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { out.RawString(`null`) } else { out.RawByte('{') - v18First := true - for v18Name, v18Value := range v17 { - if v18First { - v18First = false + v42First := true + for v42Name, v42Value := range v41 { + if v42First { + v42First = false } else { out.RawByte(',') } - out.String(string(v18Name)) + out.String(string(v42Name)) out.RawByte(':') - if m, ok := v18Value.(easyjson.Marshaler); ok { + if m, ok := v42Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v18Value.(json.Marshaler); ok { + } else if m, ok := v42Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v18Value)) + out.Raw(json.Marshal(v42Value)) } } out.RawByte('}') @@ -1278,29 +2543,29 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw } { out.RawByte('[') - for v19, v20 := range in.Users { - if v19 > 0 { + for v43, v44 := range in.Users { + if v43 > 0 { out.RawByte(',') } - if v20 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + if v44 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { out.RawString(`null`) } else { out.RawByte('{') - v21First := true - for v21Name, v21Value := range v20 { - if v21First { - v21First = false + v45First := true + for v45Name, v45Value := range v44 { + if v45First { + v45First = false } else { out.RawByte(',') } - out.String(string(v21Name)) + out.String(string(v45Name)) out.RawByte(':') - if m, ok := v21Value.(easyjson.Marshaler); ok { + if m, ok := v45Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v21Value.(json.Marshaler); ok { + } else if m, ok := v45Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v21Value)) + out.Raw(json.Marshal(v45Value)) } } out.RawByte('}') @@ -1315,27 +2580,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendRoomMessageRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendRoomMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1368,7 +2633,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendRoomMessageRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendRoomMessageRequest) { out.RawByte('{') first := true _ = first @@ -1384,27 +2649,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendRoomInviteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendRoomInviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1439,9 +2704,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v22 string - v22 = string(in.String()) - out.UserIds = append(out.UserIds, v22) + var v46 string + v46 = string(in.String()) + out.UserIds = append(out.UserIds, v46) in.WantComma() } in.Delim(']') @@ -1462,9 +2727,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle out.AllUserIds = (out.AllUserIds)[:0] } for !in.IsDelim(']') { - var v23 string - v23 = string(in.String()) - out.AllUserIds = append(out.AllUserIds, v23) + var v47 string + v47 = string(in.String()) + out.AllUserIds = append(out.AllUserIds, v47) in.WantComma() } in.Delim(']') @@ -1483,7 +2748,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendRoomInviteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendRoomInviteRequest) { out.RawByte('{') first := true _ = first @@ -1493,11 +2758,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v24, v25 := range in.UserIds { - if v24 > 0 { + for v48, v49 := range in.UserIds { + if v48 > 0 { out.RawByte(',') } - out.String(string(v25)) + out.String(string(v49)) } out.RawByte(']') } @@ -1512,11 +2777,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw } { out.RawByte('[') - for v26, v27 := range in.AllUserIds { - if v26 > 0 { + for v50, v51 := range in.AllUserIds { + if v50 > 0 { out.RawByte(',') } - out.String(string(v27)) + out.String(string(v51)) } out.RawByte(']') } @@ -1537,27 +2802,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendRoomInCallRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendRoomInCallRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1598,33 +2863,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v28 map[string]interface{} + var v52 map[string]interface{} if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v28 = make(map[string]interface{}) + v52 = make(map[string]interface{}) } else { - v28 = nil + v52 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v29 interface{} - if m, ok := v29.(easyjson.Unmarshaler); ok { + var v53 interface{} + if m, ok := v53.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v29.(json.Unmarshaler); ok { + } else if m, ok := v53.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v29 = in.Interface() + v53 = in.Interface() } - (v28)[key] = v29 + (v52)[key] = v53 in.WantComma() } in.Delim('}') } - out.Changed = append(out.Changed, v28) + out.Changed = append(out.Changed, v52) in.WantComma() } in.Delim(']') @@ -1645,33 +2910,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v30 map[string]interface{} + var v54 map[string]interface{} if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v30 = make(map[string]interface{}) + v54 = make(map[string]interface{}) } else { - v30 = nil + v54 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v31 interface{} - if m, ok := v31.(easyjson.Unmarshaler); ok { + var v55 interface{} + if m, ok := v55.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v31.(json.Unmarshaler); ok { + } else if m, ok := v55.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v31 = in.Interface() + v55 = in.Interface() } - (v30)[key] = v31 + (v54)[key] = v55 in.WantComma() } in.Delim('}') } - out.Users = append(out.Users, v30) + out.Users = append(out.Users, v54) in.WantComma() } in.Delim(']') @@ -1686,7 +2951,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendRoomInCallRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendRoomInCallRequest) { out.RawByte('{') first := true _ = first @@ -1716,29 +2981,29 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw } { out.RawByte('[') - for v32, v33 := range in.Changed { - if v32 > 0 { + for v56, v57 := range in.Changed { + if v56 > 0 { out.RawByte(',') } - if v33 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + if v57 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { out.RawString(`null`) } else { out.RawByte('{') - v34First := true - for v34Name, v34Value := range v33 { - if v34First { - v34First = false + v58First := true + for v58Name, v58Value := range v57 { + if v58First { + v58First = false } else { out.RawByte(',') } - out.String(string(v34Name)) + out.String(string(v58Name)) out.RawByte(':') - if m, ok := v34Value.(easyjson.Marshaler); ok { + if m, ok := v58Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v34Value.(json.Marshaler); ok { + } else if m, ok := v58Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v34Value)) + out.Raw(json.Marshal(v58Value)) } } out.RawByte('}') @@ -1757,29 +3022,29 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw } { out.RawByte('[') - for v35, v36 := range in.Users { - if v35 > 0 { + for v59, v60 := range in.Users { + if v59 > 0 { out.RawByte(',') } - if v36 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + if v60 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { out.RawString(`null`) } else { out.RawByte('{') - v37First := true - for v37Name, v37Value := range v36 { - if v37First { - v37First = false + v61First := true + for v61Name, v61Value := range v60 { + if v61First { + v61First = false } else { out.RawByte(',') } - out.String(string(v37Name)) + out.String(string(v61Name)) out.RawByte(':') - if m, ok := v37Value.(easyjson.Marshaler); ok { + if m, ok := v61Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v37Value.(json.Marshaler); ok { + } else if m, ok := v61Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v37Value)) + out.Raw(json.Marshal(v61Value)) } } out.RawByte('}') @@ -1794,27 +3059,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInCallRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInCallRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1849,9 +3114,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v38 string - v38 = string(in.String()) - out.UserIds = append(out.UserIds, v38) + var v62 string + v62 = string(in.String()) + out.UserIds = append(out.UserIds, v62) in.WantComma() } in.Delim(']') @@ -1872,9 +3137,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle out.SessionIds = (out.SessionIds)[:0] } for !in.IsDelim(']') { - var v39 string - v39 = string(in.String()) - out.SessionIds = append(out.SessionIds, v39) + var v63 string + v63 = string(in.String()) + out.SessionIds = append(out.SessionIds, v63) in.WantComma() } in.Delim(']') @@ -1895,9 +3160,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle out.AllUserIds = (out.AllUserIds)[:0] } for !in.IsDelim(']') { - var v40 string - v40 = string(in.String()) - out.AllUserIds = append(out.AllUserIds, v40) + var v64 string + v64 = string(in.String()) + out.AllUserIds = append(out.AllUserIds, v64) in.WantComma() } in.Delim(']') @@ -1916,7 +3181,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendRoomDisinviteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendRoomDisinviteRequest) { out.RawByte('{') first := true _ = first @@ -1926,11 +3191,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v41, v42 := range in.UserIds { - if v41 > 0 { + for v65, v66 := range in.UserIds { + if v65 > 0 { out.RawByte(',') } - out.String(string(v42)) + out.String(string(v66)) } out.RawByte(']') } @@ -1945,11 +3210,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw } { out.RawByte('[') - for v43, v44 := range in.SessionIds { - if v43 > 0 { + for v67, v68 := range in.SessionIds { + if v67 > 0 { out.RawByte(',') } - out.String(string(v44)) + out.String(string(v68)) } out.RawByte(']') } @@ -1964,11 +3229,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw } { out.RawByte('[') - for v45, v46 := range in.AllUserIds { - if v45 > 0 { + for v69, v70 := range in.AllUserIds { + if v69 > 0 { out.RawByte(',') } - out.String(string(v46)) + out.String(string(v70)) } out.RawByte(']') } @@ -1989,27 +3254,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2050,7 +3315,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendRoomDialoutResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendRoomDialoutResponse) { out.RawByte('{') first := true _ = first @@ -2076,27 +3341,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2131,7 +3396,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomDialoutRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendRoomDialoutRequest) { out.RawByte('{') first := true _ = first @@ -2151,27 +3416,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomDialoutError) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendRoomDialoutError) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2204,7 +3469,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomDialoutError) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendRoomDialoutError) { out.RawByte('{') first := true _ = first @@ -2224,27 +3489,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutError) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutError) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2279,9 +3544,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v47 string - v47 = string(in.String()) - out.UserIds = append(out.UserIds, v47) + var v71 string + v71 = string(in.String()) + out.UserIds = append(out.UserIds, v71) in.WantComma() } in.Delim(']') @@ -2296,7 +3561,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomDeleteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendRoomDeleteRequest) { out.RawByte('{') first := true _ = first @@ -2306,11 +3571,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v48, v49 := range in.UserIds { - if v48 > 0 { + for v72, v73 := range in.UserIds { + if v72 > 0 { out.RawByte(',') } - out.String(string(v49)) + out.String(string(v73)) } out.RawByte(']') } @@ -2321,27 +3586,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDeleteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDeleteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendPingEntry) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendPingEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2374,7 +3639,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendPingEntry) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendPingEntry) { out.RawByte('{') first := true _ = first @@ -2400,27 +3665,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendPingEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendPingEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendPingEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendPingEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendInformationEtcd) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendInformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2459,7 +3724,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendInformationEtcd) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendInformationEtcd) { out.RawByte('{') first := true _ = first @@ -2494,27 +3759,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendInformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendInformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendClientSessionResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientSessionResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2547,7 +3812,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendClientSessionResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientSessionResponse) { out.RawByte('{') first := true _ = first @@ -2567,27 +3832,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendClientSessionRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientSessionRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2630,7 +3895,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendClientSessionRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientSessionRequest) { out.RawByte('{') first := true _ = first @@ -2670,27 +3935,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendClientRoomResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2744,9 +4009,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle *out.Permissions = (*out.Permissions)[:0] } for !in.IsDelim(']') { - var v50 Permission - v50 = Permission(in.String()) - *out.Permissions = append(*out.Permissions, v50) + var v74 Permission + v74 = Permission(in.String()) + *out.Permissions = append(*out.Permissions, v74) in.WantComma() } in.Delim(']') @@ -2762,7 +4027,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendClientRoomResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientRoomResponse) { out.RawByte('{') first := true _ = first @@ -2793,11 +4058,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw out.RawString("null") } else { out.RawByte('[') - for v51, v52 := range *in.Permissions { - if v51 > 0 { + for v75, v76 := range *in.Permissions { + if v75 > 0 { out.RawByte(',') } - out.String(string(v52)) + out.String(string(v76)) } out.RawByte(']') } @@ -2808,27 +4073,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendClientRoomRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2873,7 +4138,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendClientRoomRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientRoomRequest) { out.RawByte('{') first := true _ = first @@ -2923,27 +4188,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendClientRingResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientRingResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2976,7 +4241,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendClientRingResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientRingResponse) { out.RawByte('{') first := true _ = first @@ -2996,27 +4261,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRingResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRingResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendClientResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *BackendClientResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3097,7 +4362,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendClientResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in BackendClientResponse) { out.RawByte('{') first := true _ = first @@ -3137,27 +4402,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *BackendClientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3228,7 +4493,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in BackendClientRequest) { out.RawByte('{') first := true _ = first @@ -3263,27 +4528,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientPingRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *BackendClientPingRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3322,9 +4587,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle out.Entries = (out.Entries)[:0] } for !in.IsDelim(']') { - var v53 BackendPingEntry - (v53).UnmarshalEasyJSON(in) - out.Entries = append(out.Entries, v53) + var v77 BackendPingEntry + (v77).UnmarshalEasyJSON(in) + out.Entries = append(out.Entries, v77) in.WantComma() } in.Delim(']') @@ -3339,7 +4604,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientPingRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in BackendClientPingRequest) { out.RawByte('{') first := true _ = first @@ -3360,11 +4625,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw out.RawString("null") } else { out.RawByte('[') - for v54, v55 := range in.Entries { - if v54 > 0 { + for v78, v79 := range in.Entries { + if v78 > 0 { out.RawByte(',') } - (v55).MarshalEasyJSON(out) + (v79).MarshalEasyJSON(out) } out.RawByte(']') } @@ -3375,27 +4640,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientPingRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientPingRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientAuthResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *BackendClientAuthResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3432,7 +4697,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientAuthResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in BackendClientAuthResponse) { out.RawByte('{') first := true _ = first @@ -3457,27 +4722,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientAuthRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *BackendClientAuthRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3512,7 +4777,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientAuthRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in BackendClientAuthRequest) { out.RawByte('{') first := true _ = first @@ -3532,23 +4797,23 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) } From 953dc28e943c019d0103ab92cfef4a9ed14b0d70 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 14:49:37 +0200 Subject: [PATCH 074/549] Use RWMutex for ConcurrentStringStringMap and improve test performance. --- concurrentmap.go | 24 ++++++++++++------------ concurrentmap_test.go | 7 +++++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/concurrentmap.go b/concurrentmap.go index 1a4da0d..920fac4 100644 --- a/concurrentmap.go +++ b/concurrentmap.go @@ -26,13 +26,13 @@ import ( ) type ConcurrentStringStringMap struct { - sync.Mutex - d map[string]string + mu sync.RWMutex + d map[string]string } func (m *ConcurrentStringStringMap) Set(key, value string) { - m.Lock() - defer m.Unlock() + m.mu.Lock() + defer m.mu.Unlock() if m.d == nil { m.d = make(map[string]string) } @@ -40,26 +40,26 @@ func (m *ConcurrentStringStringMap) Set(key, value string) { } func (m *ConcurrentStringStringMap) Get(key string) (string, bool) { - m.Lock() - defer m.Unlock() + m.mu.RLock() + defer m.mu.RUnlock() s, found := m.d[key] return s, found } func (m *ConcurrentStringStringMap) Del(key string) { - m.Lock() - defer m.Unlock() + m.mu.Lock() + defer m.mu.Unlock() delete(m.d, key) } func (m *ConcurrentStringStringMap) Len() int { - m.Lock() - defer m.Unlock() + m.mu.RLock() + defer m.mu.RUnlock() return len(m.d) } func (m *ConcurrentStringStringMap) Clear() { - m.Lock() - defer m.Unlock() + m.mu.Lock() + defer m.mu.Unlock() m.d = nil } diff --git a/concurrentmap_test.go b/concurrentmap_test.go index 276b038..990a520 100644 --- a/concurrentmap_test.go +++ b/concurrentmap_test.go @@ -86,8 +86,11 @@ func TestConcurrentStringStringMap(t *testing.T) { for y := 0; y < count; y = y + 1 { value := rnd + "-" + strconv.Itoa(y) m.Set(key, value) - if v, found := m.Get(key); !assert.True(found, "Expected entry for key %s", key) || - !assert.Equal(value, v, "Unexpected value for key %s", key) { + if v, found := m.Get(key); !found { + assert.True(found, "Expected entry for key %s", key) + return + } else if v != value { + assert.Equal(value, v, "Unexpected value for key %s", key) return } } From 83e50d030ee5d9f41f03c17eb6d866d7b966c8d4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 15:09:03 +0200 Subject: [PATCH 075/549] Don't use hardcoded sleeps when waiting for NATS disconnect / reconnect. --- natsclient_test.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/natsclient_test.go b/natsclient_test.go index 362895b..bc92bfb 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -174,7 +174,7 @@ func TestNatsClient_MaxReconnects(t *testing.T) { ensureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) - reconnectWait := 5 * time.Millisecond + reconnectWait := time.Millisecond server, port, client := CreateLocalNatsClientForTest(t, nats.ReconnectWait(reconnectWait), nats.ReconnectJitter(0, 0), @@ -188,12 +188,18 @@ func TestNatsClient_MaxReconnects(t *testing.T) { server.WaitForShutdown() // The NATS client tries to reconnect a maximum of 100 times by default. - time.Sleep(time.Second + (100 * reconnectWait)) + time.Sleep(100 * reconnectWait) + for i := 0; i < 1000 && c.conn.IsConnected(); i++ { + time.Sleep(time.Millisecond) + } require.False(c.conn.IsConnected(), "should be disconnected after server shutdown") server, _ = startLocalNatsServerPort(t, port) - time.Sleep(time.Second) + // Wait for automatic reconnection + for i := 0; i < 1000 && !c.conn.IsConnected(); i++ { + time.Sleep(time.Millisecond) + } require.True(c.conn.IsConnected(), "not connected after restart") assert.Equal(server.ID(), c.conn.ConnectedServerId()) }) From 6206678e74f9d42a35a95fdeaf26f6eb711dd75f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 15:20:05 +0200 Subject: [PATCH 076/549] Run clients in "TestSessionIdsUnordered" concurrently. --- hub_test.go | 54 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/hub_test.go b/hub_test.go index b364623..b8cda73 100644 --- a/hub_test.go +++ b/hub_test.go @@ -1302,36 +1302,48 @@ func TestSessionIdsUnordered(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) + var mu sync.Mutex publicSessionIds := make([]string, 0) + var wg sync.WaitGroup for i := 0; i < 20; i++ { - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() + wg.Add(1) + go func() { + defer wg.Done() + client := NewTestClient(t, server, hub) + defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) + require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - data := hub.decodePublicSessionId(hello.Hello.SessionId) - if !assert.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) { - break + data := hub.decodePublicSessionId(hello.Hello.SessionId) + if !assert.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) { + return + } + + hub.mu.RLock() + session := hub.sessions[data.Sid] + hub.mu.RUnlock() + if !assert.NotNil(session, "Could not get session for id %+v", data) { + return + } + + mu.Lock() + publicSessionIds = append(publicSessionIds, session.PublicId()) + mu.Unlock() } - - hub.mu.RLock() - session := hub.sessions[data.Sid] - hub.mu.RUnlock() - if !assert.NotNil(session, "Could not get session for id %+v", data) { - break - } - - publicSessionIds = append(publicSessionIds, session.PublicId()) - } + }() } + wg.Wait() + + mu.Lock() + defer mu.Unlock() require.NotEmpty(publicSessionIds, "no session ids decoded") larger := 0 From 6c9bfd7b841971314180833d91030aeef1b49f65 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 15:29:10 +0200 Subject: [PATCH 077/549] Reduce timeout and ensure it is actually triggered. --- backend_server_test.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/backend_server_test.go b/backend_server_test.go index b5e3b42..b68b923 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -1100,21 +1100,17 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { } } - ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second+100*time.Millisecond) + ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if msg1_c, _ := client1.RunUntilMessage(ctx2); msg1_c != nil { - if in_call_2, err := checkMessageParticipantsInCall(msg1_c); assert.NoError(err) { - assert.Len(in_call_2.Users, 2) - } + if msg1_c, err := client1.RunUntilMessage(ctx2); !assert.ErrorIs(err, context.DeadlineExceeded) { + assert.Fail("should have timeout out", "received %+v", msg1_c) } - ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second+100*time.Millisecond) + ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() - if msg2_c, _ := client2.RunUntilMessage(ctx3); msg2_c != nil { - if in_call_2, err := checkMessageParticipantsInCall(msg2_c); assert.NoError(err) { - assert.Len(in_call_2.Users, 2) - } + if msg2_c, err := client2.RunUntilMessage(ctx3); !assert.ErrorIs(err, context.DeadlineExceeded) { + assert.Fail("should have timeout out", "received %+v", msg2_c) } } From 271b45c963b23bf87d7a4c7a27028647aa4c173f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 15:54:21 +0200 Subject: [PATCH 078/549] Don't hardcode sleep before running leak checked test. --- testutils_test.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/testutils_test.go b/testutils_test.go index f2d507a..8aaa076 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -57,8 +57,15 @@ func ensureNoGoroutinesLeak(t *testing.T, f func(t *testing.T)) { profile := pprof.Lookup("goroutine") // Give time for things to settle before capturing the number of // go routines - time.Sleep(500 * time.Millisecond) - before := profile.Count() + var before int + timeout := time.Now().Add(time.Second) + for time.Now().Before(timeout) { + before = profile.Count() + time.Sleep(10 * time.Millisecond) + if profile.Count() == before { + break + } + } var prev bytes.Buffer dumpGoroutines("Before:", &prev) @@ -67,7 +74,7 @@ func ensureNoGoroutinesLeak(t *testing.T, f func(t *testing.T)) { var after int // Give time for things to settle before capturing the number of // go routines - timeout := time.Now().Add(time.Second) + timeout = time.Now().Add(time.Second) for time.Now().Before(timeout) { after = profile.Count() if after == before { From 7bd4bf96f5c04aabd25114f22d50f398cef708a0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 15:59:49 +0200 Subject: [PATCH 079/549] Only simulate sleeping in "TestBackoff_Exponential". Add another test that (shortly) sleeps. --- backoff.go | 6 +++++- backoff_test.go | 22 +++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/backoff.go b/backoff.go index 5b49521..27af0f9 100644 --- a/backoff.go +++ b/backoff.go @@ -37,6 +37,8 @@ type exponentialBackoff struct { initial time.Duration maxWait time.Duration nextWait time.Duration + + getContextWithTimeout func(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) } func NewExponentialBackoff(initial time.Duration, maxWait time.Duration) (Backoff, error) { @@ -52,6 +54,8 @@ func NewExponentialBackoff(initial time.Duration, maxWait time.Duration) (Backof maxWait: maxWait, nextWait: initial, + + getContextWithTimeout: context.WithTimeout, }, nil } @@ -64,7 +68,7 @@ func (b *exponentialBackoff) NextWait() time.Duration { } func (b *exponentialBackoff) Wait(ctx context.Context) { - waiter, cancel := context.WithTimeout(ctx, b.nextWait) + waiter, cancel := b.getContextWithTimeout(ctx, b.nextWait) defer cancel() b.nextWait = b.nextWait * 2 diff --git a/backoff_test.go b/backoff_test.go index 87de048..7270c90 100644 --- a/backoff_test.go +++ b/backoff_test.go @@ -31,7 +31,6 @@ import ( ) func TestBackoff_Exponential(t *testing.T) { - t.Parallel() assert := assert.New(t) minWait := 100 * time.Millisecond backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) @@ -47,14 +46,27 @@ func TestBackoff_Exponential(t *testing.T) { for _, wait := range waitTimes { assert.Equal(wait, backoff.NextWait()) - - a := time.Now() + backoff.(*exponentialBackoff).getContextWithTimeout = func(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + assert.Equal(wait, timeout) + return context.WithTimeout(parent, time.Millisecond) + } backoff.Wait(context.Background()) - b := time.Now() - assert.GreaterOrEqual(b.Sub(a), wait) } backoff.Reset() + backoff.(*exponentialBackoff).getContextWithTimeout = func(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + assert.Equal(minWait, timeout) + return context.WithTimeout(parent, time.Millisecond) + } + backoff.Wait(context.Background()) +} + +func TestBackoff_ExponentialRealSleep(t *testing.T) { + assert := assert.New(t) + minWait := 100 * time.Millisecond + backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) + require.NoError(t, err) + a := time.Now() backoff.Wait(context.Background()) b := time.Now() From 1212c6021fce1db879e16f62c24b9e030686649b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 17:14:25 +0200 Subject: [PATCH 080/549] Describe new backend client metrics. --- docs/prometheus-metrics.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 70d6ef9..b8c5257 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -52,3 +52,6 @@ The following metrics are available: | `signaling_http_client_pool_connections` | Gauge | 1.2.4 | The current number of HTTP client connections per host | `host` | | `signaling_throttle_delayed_total` | Counter | 1.2.5 | The total number of delayed requests | `action`, `delay` | | `signaling_throttle_bruteforce_total` | Counter | 1.2.5 | The total number of rejected bruteforce requests | `action` | +| `signaling_backend_client_requests_total` | Counter | 2.0.3 | The total number of backend client requests | `backend` | +| `signaling_backend_client_requests_duration` | Histogram | 2.0.3 | The duration of backend client requests in seconds | `backend` | +| `signaling_backend_client_requests_errors_total` | Counter | 2.0.3 | The total number of backend client requests that had an error | `backend`, `error` | From 63ab3ccffcf71f835d1fdcdf9f5718dc41966f6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 20:28:51 +0000 Subject: [PATCH 081/549] Bump github.com/prometheus/client_golang from 1.21.1 to 1.22.0 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.21.1 to 1.22.0. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.21.1...v1.22.0) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-version: 1.22.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 148b7dd..ff8f7c3 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.11 github.com/pquerna/cachecontrol v0.2.0 - github.com/prometheus/client_golang v1.21.1 + github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.5.21 go.etcd.io/etcd/client/pkg/v3 v3.5.21 diff --git a/go.sum b/go.sum index a7e331b..dde72a9 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -147,8 +147,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= From 0a351256f00043480f0c24b1d805f978a67e550a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 20:28:53 +0000 Subject: [PATCH 082/549] Bump github.com/fsnotify/fsnotify from 1.8.0 to 1.9.0 Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.8.0 to 1.9.0. - [Release notes](https://github.com/fsnotify/fsnotify/releases) - [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md) - [Commits](https://github.com/fsnotify/fsnotify/compare/v1.8.0...v1.9.0) --- updated-dependencies: - dependency-name: github.com/fsnotify/fsnotify dependency-version: 1.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 148b7dd..e06887b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 - github.com/fsnotify/fsnotify v1.8.0 + github.com/fsnotify/fsnotify v1.9.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum index a7e331b..b40a7d3 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= From ca2d6eaf3d61fe636bbc7f00f043180fdcc0f64f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Apr 2025 17:14:56 +0200 Subject: [PATCH 083/549] Add metrics for backend client requests. --- backend_client.go | 32 ++++++++++++++--- backend_client_stats_prometheus.go | 58 ++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 backend_client_stats_prometheus.go diff --git a/backend_client.go b/backend_client.go index b027673..7d6f4d0 100644 --- a/backend_client.go +++ b/backend_client.go @@ -30,6 +30,7 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/dlintw/goconf" ) @@ -42,6 +43,10 @@ var ( ErrThrottledResponse = errors.New("throttled OCS response") ) +func init() { + RegisterBackendClientStats() +} + type BackendClient struct { hub *Hub version string @@ -117,9 +122,9 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ return fmt.Errorf("no url passed to perform JSON request %+v", request) } - secret := b.backends.GetSecret(u) - if secret == nil { - return fmt.Errorf("no backend secret configured for for %s", u) + backend := b.backends.GetBackend(u) + if backend == nil { + return fmt.Errorf("no backend configured for %s", u) } var requestUrl *url.URL @@ -160,10 +165,22 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } // Add checksum so the backend can validate the request. - AddBackendChecksum(req, data.Bytes(), secret) + AddBackendChecksum(req, data.Bytes(), backend.Secret()) + start := time.Now() resp, err := c.Do(req) + end := time.Now() + duration := end.Sub(start) + statsBackendClientRequests.WithLabelValues(backend.Url()).Inc() + statsBackendClientDuration.WithLabelValues(backend.Url()).Observe(duration.Seconds()) if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + statsBackendClientError.WithLabelValues(backend.Url(), "timeout").Inc() + } else if errors.Is(err, context.Canceled) { + statsBackendClientError.WithLabelValues(backend.Url(), "canceled").Inc() + } else { + statsBackendClientError.WithLabelValues(backend.Url(), "unknown").Inc() + } log.Printf("Could not send request %s to %s: %s", data.String(), req.URL, err) return err } @@ -172,12 +189,14 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ ct := resp.Header.Get("Content-Type") if !strings.HasPrefix(ct, "application/json") { log.Printf("Received unsupported content-type from %s: %s (%s)", req.URL, ct, resp.Status) + statsBackendClientError.WithLabelValues(backend.Url(), "invalid_content_type").Inc() return ErrUnsupportedContentType } body, err := b.buffers.ReadAll(resp.Body) if err != nil { log.Printf("Could not read response body from %s: %s", req.URL, err) + statsBackendClientError.WithLabelValues(backend.Url(), "error_reading_body").Inc() return err } @@ -195,24 +214,29 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ var ocs OcsResponse if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { log.Printf("Could not decode OCS response %s from %s: %s", body.String(), req.URL, err) + statsBackendClientError.WithLabelValues(backend.Url(), "error_decoding_ocs").Inc() return err } else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 { log.Printf("Incomplete OCS response %s from %s", body.String(), req.URL) + statsBackendClientError.WithLabelValues(backend.Url(), "error_incomplete_ocs").Inc() return ErrIncompleteResponse } switch ocs.Ocs.Meta.StatusCode { case http.StatusTooManyRequests: log.Printf("Throttled OCS response %s from %s", body.String(), req.URL) + statsBackendClientError.WithLabelValues(backend.Url(), "throttled").Inc() return ErrThrottledResponse } if err := json.Unmarshal(ocs.Ocs.Data, response); err != nil { log.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), req.URL, err) + statsBackendClientError.WithLabelValues(backend.Url(), "error_decoding_ocs_data").Inc() return err } } else if err := json.Unmarshal(body.Bytes(), response); err != nil { log.Printf("Could not decode response body %s from %s: %s", body.String(), req.URL, err) + statsBackendClientError.WithLabelValues(backend.Url(), "error_decoding_body").Inc() return err } return nil diff --git a/backend_client_stats_prometheus.go b/backend_client_stats_prometheus.go new file mode 100644 index 0000000..b406df0 --- /dev/null +++ b/backend_client_stats_prometheus.go @@ -0,0 +1,58 @@ +/** + * 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 signaling + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +var ( + statsBackendClientRequests = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "backend_client", + Name: "requests_total", + Help: "The total number of backend client requests", + }, []string{"backend"}) + statsBackendClientDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "signaling", + Subsystem: "backend_client", + Name: "requests_duration", + Help: "The duration of backend client requests in seconds", + Buckets: prometheus.ExponentialBucketsRange(0.01, 30, 30), + }, []string{"backend"}) + statsBackendClientError = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "backend_client", + Name: "requests_errors_total", + Help: "The total number of backend client requests that had an error", + }, []string{"backend", "error"}) + + backendClientStats = []prometheus.Collector{ + statsBackendClientRequests, + statsBackendClientDuration, + statsBackendClientError, + } +) + +func RegisterBackendClientStats() { + registerAll(backendClientStats...) +} From ed11ef775ccd15810de8be227ef1cbdf4835346a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Apr 2025 09:47:03 +0200 Subject: [PATCH 084/549] Fix flaky test "Test_GrpcClients_DnsDiscovery". Wait until initial hostname check has been performed before continuing test. Otherwise the initial check could be executed too late and breaks assumptions in the test. --- dns_monitor.go | 14 +++++++++++++- grpc_client_test.go | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dns_monitor.go b/dns_monitor.go index 072e01c..4121bbf 100644 --- a/dns_monitor.go +++ b/dns_monitor.go @@ -150,7 +150,8 @@ type DnsMonitor struct { cond *sync.Cond hostnames map[string]*dnsMonitorEntry - hasRemoved atomic.Bool + tickerWaiting atomic.Bool + hasRemoved atomic.Bool // Can be overwritten from tests. checkHostnames func() @@ -307,6 +308,7 @@ func (m *DnsMonitor) run() { } } + m.tickerWaiting.Store(true) select { case <-m.stopCtx.Done(): return @@ -341,3 +343,13 @@ func (m *DnsMonitor) checkHostname(entry *dnsMonitorEntry) { entry.setIPs(ips, false) } + +func (m *DnsMonitor) waitForTicker(ctx context.Context) error { + for !m.tickerWaiting.Load() { + time.Sleep(time.Millisecond) + if err := ctx.Err(); err != nil { + return err + } + } + return nil +} diff --git a/grpc_client_test.go b/grpc_client_test.go index b536a34..3c75506 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -217,6 +217,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) + require := require.New(t) lookup := newMockDnsLookupForTest(t) target := "testgrpc:12345" ip1 := net.ParseIP("192.168.0.1") @@ -230,6 +231,12 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() + // Wait for initial check to be done to make sure internal dnsmonitor goroutine is waiting. + if err := dnsMonitor.waitForTicker(ctx); err != nil { + require.NoError(err) + } + + drainWakeupChannel(ch) dnsMonitor.checkHostnames() if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(targetWithIp1, clients[0].Target()) From f2ce33478b43034a73f835cc6d2d77db9327e4ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 20:28:09 +0000 Subject: [PATCH 085/549] Bump github.com/nats-io/nats.go from 1.41.1 to 1.41.2 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.41.1 to 1.41.2. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.41.1...v1.41.2) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.41.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 307bcc5..38dcf2f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.11.1 - github.com/nats-io/nats.go v1.41.1 + github.com/nats-io/nats.go v1.41.2 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.11 @@ -58,7 +58,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/jwt/v2 v2.7.3 // indirect - github.com/nats-io/nkeys v0.4.10 // indirect + github.com/nats-io/nkeys v0.4.11 // 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 @@ -84,10 +84,10 @@ require ( go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect diff --git a/go.sum b/go.sum index 38f5a90..acfd70a 100644 --- a/go.sum +++ b/go.sum @@ -127,10 +127,10 @@ github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= github.com/nats-io/nats-server/v2 v2.11.1 h1:LwdauqMqMNhTxTN3+WFTX6wGDOKntHljgZ+7gL5HCnk= github.com/nats-io/nats-server/v2 v2.11.1/go.mod h1:leXySghbdtXSUmWem8K9McnJ6xbJOb0t9+NQ5HTRZjI= -github.com/nats-io/nats.go v1.41.1 h1:lCc/i5x7nqXbspxtmXaV4hRguMPHqE/kYltG9knrCdU= -github.com/nats-io/nats.go v1.41.1/go.mod h1:mzHiutcAdZrg6WLfYVKXGseqqow2fWmwlTEUOHsI4jY= -github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc= -github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U= +github.com/nats-io/nats.go v1.41.2 h1:5UkfLAtu/036s99AhFRlyNDI1Ieylb36qbGjJzHixos= +github.com/nats-io/nats.go v1.41.2/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= +github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8iFMHvQleYlF5M3PY6zpAbxsngImjE= @@ -229,8 +229,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -260,8 +260,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -270,12 +270,12 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 40be134746b7004da05862e05da2e40c442b11aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:19:08 +0000 Subject: [PATCH 086/549] Bump google.golang.org/grpc from 1.71.1 to 1.72.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.71.1 to 1.72.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.71.1...v1.72.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.72.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 307bcc5..cb47f4c 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.5.21 go.etcd.io/etcd/server/v3 v3.5.21 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.71.1 + google.golang.org/grpc v1.72.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) @@ -90,8 +90,8 @@ require ( golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // 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 diff --git a/go.sum b/go.sum index 38f5a90..5add991 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= @@ -252,8 +252,8 @@ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -298,18 +298,18 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From ea5eb424d66de2edc75f65105d0594ab20eb7236 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:00:43 +0200 Subject: [PATCH 087/549] docker: Support configuring compatibility backends. --- docker/server/entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index dc3d08d..981e51d 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -266,6 +266,8 @@ if [ ! -f "$CONFIG" ]; then fi echo >> "$CONFIG" done + elif [ -n "$BACKENDS_COMPAT_ALLOWED" ]; then + sed -i "s|#backends =.*|allowed = $BACKENDS_COMPAT_ALLOWED\nsecret = $BACKENDS_COMPAT_SECRET|" "$CONFIG" fi fi From 403a4417cb7bc275803359686774fc4abb1961b5 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:18:36 +0200 Subject: [PATCH 088/549] docker: Allow configuring read/write timeouts. --- docker/README.md | 4 ++++ docker/server/entrypoint.sh | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/docker/README.md b/docker/README.md index b4003e4..d1c69f0 100644 --- a/docker/README.md +++ b/docker/README.md @@ -15,7 +15,11 @@ The running container can be configured through different environment variables: - `CONFIG`: Optional name of configuration file to use. - `HTTP_LISTEN`: Address of HTTP listener. +- `HTTP_READ_TIMEOUT`: HTTP socket read timeout in seconds. +- `HTTP_WRITE_TIMEOUT`: HTTP socket write timeout in seconds. - `HTTPS_LISTEN`: Address of HTTPS listener. +- `HTTPS_READ_TIMEOUT`: HTTPS socket read timeout in seconds. +- `HTTPS_WRITE_TIMEOUT`: HTTPS socket write timeout in seconds. - `HTTPS_CERTIFICATE`: Name of certificate file for the HTTPS listener. - `HTTPS_KEY`: Name of private key file for the HTTPS listener. - `HASH_KEY`: Secret value used to generate checksums of sessions (32 or 64 bytes). diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 981e51d..d878cd2 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -39,8 +39,21 @@ if [ ! -f "$CONFIG" ]; then if [ -n "$HTTP_LISTEN" ]; then sed -i "s|#listen = 127.0.0.1:8080|listen = $HTTP_LISTEN|" "$CONFIG" fi + if [ -n "$HTTP_READ_TIMEOUT" ]; then + sed -i "/HTTP socket/,/HTTP socket/ s|#readtimeout =.*|readtimeout = $HTTP_READ_TIMEOUT|" "$CONFIG" + fi + if [ -n "$HTTP_WRITE_TIMEOUT" ]; then + sed -i "/HTTP socket/,/HTTP socket/ s|#writetimeout =.*|writetimeout = $HTTP_WRITE_TIMEOUT|" "$CONFIG" + fi + if [ -n "$HTTPS_LISTEN" ]; then sed -i "s|#listen = 127.0.0.1:8443|listen = $HTTPS_LISTEN|" "$CONFIG" + if [ -n "$HTTPS_READ_TIMEOUT" ]; then + sed -i "/HTTPS socket/,/HTTPS socket/ s|#readtimeout =.*|readtimeout = $HTTPS_READ_TIMEOUT|" "$CONFIG" + fi + if [ -n "$HTTPS_WRITE_TIMEOUT" ]; then + sed -i "/HTTPS socket/,/HTTPS socket/ s|#writetimeout =.*|writetimeout = $HTTPS_WRITE_TIMEOUT|" "$CONFIG" + fi if [ -n "$HTTPS_CERTIFICATE" ]; then sed -i "s|certificate = /etc/nginx/ssl/server.crt|certificate = $HTTPS_CERTIFICATE|" "$CONFIG" From 7aad88e19241b2796ddc1fb5d53023d99c661ad2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:27:45 +0200 Subject: [PATCH 089/549] docker: Make federation timeout configurable. --- docker/README.md | 1 + docker/server/entrypoint.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docker/README.md b/docker/README.md index d1c69f0..07f86b8 100644 --- a/docker/README.md +++ b/docker/README.md @@ -33,6 +33,7 @@ The running container can be configured through different environment variables: - `BACKEND__SESSION_LIMIT`: Optional session limit for backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__MAX_STREAM_BITRATE`: Optional maximum bitrate for audio/video streams in backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__MAX_SCREEN_BITRATE`: Optional maximum bitrate for screensharing streams in backend `ID` (where `ID` is the uppercase backend id). +- `FEDERATION_TIMEOUT`: Timeout for requests to federation targets in seconds. - `NATS_URL`: Optional URL of NATS server. - `ETCD_ENDPOINTS`: Static list of etcd endpoints (if etcd should be used). - `ETCD_DISCOVERY_SRV`: Alternative domain to use for DNS SRV configuration of etcd endpoints (if etcd should be used). diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index d878cd2..46b3979 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -77,6 +77,9 @@ if [ ! -f "$CONFIG" ]; then else sed -i "s|#url = nats://localhost:4222|url = nats://loopback|" "$CONFIG" fi + if [ -n "$FEDERATION_TIMEOUT" ]; then + sed -i "/federation/,/federation/ s|#timeout =.*|timeout = $FEDERATION_TIMEOUT|" "$CONFIG" + fi HAS_ETCD= if [ -n "$ETCD_ENDPOINTS" ]; then From 0daf48ae2b025c1876195dfcc88d5fdece8156b4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:28:08 +0200 Subject: [PATCH 090/549] docker: Make timeout for backend requests configurable. --- docker/README.md | 1 + docker/server/entrypoint.sh | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docker/README.md b/docker/README.md index 07f86b8..06db59f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -28,6 +28,7 @@ The running container can be configured through different environment variables: - `BACKENDS_ALLOWALL`: Allow all backends. Extremly insecure - use only for development! - `BACKENDS_ALLOWALL_SECRET`: Secret when `BACKENDS_ALLOWALL` is enabled. - `BACKENDS`: Space-separated list of backend ids. +- `BACKENDS_TIMEOUT`: Timeout in seconds for requests to backends. - `BACKEND__URL`: Url of backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__SHARED_SECRET`: Shared secret for backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__SESSION_LIMIT`: Optional session limit for backend `ID` (where `ID` is the uppercase backend id). diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 46b3979..dfd073d 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -248,6 +248,10 @@ if [ ! -f "$CONFIG" ]; then sed -i "s|#secret = the-shared-secret-for-allowall|secret = $BACKENDS_ALLOWALL_SECRET|" "$CONFIG" fi + if [ -n "$BACKENDS_TIMEOUT" ]; then + sed -i "/requests to the backend/,/requests to the backend/ s|^timeout =.*|timeout = $BACKENDS_TIMEOUT|" "$CONFIG" + fi + if [ -n "$BACKENDS" ]; then BACKENDS_CONFIG=${BACKENDS// /,} sed -i "s|#backends = .*|backends = $BACKENDS_CONFIG|" "$CONFIG" From 1ce202f987bde433fd06eb6b29170f491c4da987 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:29:27 +0200 Subject: [PATCH 091/549] docker: Make connections per host configurable. --- docker/README.md | 1 + docker/server/entrypoint.sh | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docker/README.md b/docker/README.md index 06db59f..fdc6c18 100644 --- a/docker/README.md +++ b/docker/README.md @@ -29,6 +29,7 @@ The running container can be configured through different environment variables: - `BACKENDS_ALLOWALL_SECRET`: Secret when `BACKENDS_ALLOWALL` is enabled. - `BACKENDS`: Space-separated list of backend ids. - `BACKENDS_TIMEOUT`: Timeout in seconds for requests to backends. +- `CONNECTIONS_PER_HOST`: Maximum number of concurrent backend connections per host. - `BACKEND__URL`: Url of backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__SHARED_SECRET`: Shared secret for backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__SESSION_LIMIT`: Optional session limit for backend `ID` (where `ID` is the uppercase backend id). diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index dfd073d..f9e4a16 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -252,6 +252,10 @@ if [ ! -f "$CONFIG" ]; then sed -i "/requests to the backend/,/requests to the backend/ s|^timeout =.*|timeout = $BACKENDS_TIMEOUT|" "$CONFIG" fi + if [ -n "$CONNECTIONS_PER_HOST" ]; then + sed -i "s|connectionsperhost =.*|connectionsperhost = $CONNECTIONS_PER_HOST|" "$CONFIG" + fi + if [ -n "$BACKENDS" ]; then BACKENDS_CONFIG=${BACKENDS// /,} sed -i "s|#backends = .*|backends = $BACKENDS_CONFIG|" "$CONFIG" From d6ce4adaa66c155b8f776609b8710220028990f3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:31:15 +0200 Subject: [PATCH 092/549] docker: Make proxy timeout configurable. --- docker/README.md | 1 + docker/server/entrypoint.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docker/README.md b/docker/README.md index fdc6c18..9732cd2 100644 --- a/docker/README.md +++ b/docker/README.md @@ -48,6 +48,7 @@ The running container can be configured through different environment variables: - `USE_PROXY`: Set to `1` if proxy servers should be used as WebRTC backends. - `PROXY_TOKEN_ID`: Id of the token to use when connecting to proxy servers. - `PROXY_TOKEN_KEY`: Private key for the configured token id. +- `PROXY_TIMEOUT`: Timeout in seconds for requests to the proxy server. - `PROXY_URLS`: Space-separated list of proxy URLs to connect to. - `PROXY_DNS_DISCOVERY`: Enable DNS discovery on hostnames of configured static URLs. - `PROXY_ETCD`: Set to `1` if etcd should be used to configure proxy connections. diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index f9e4a16..fefee50 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -119,6 +119,9 @@ if [ ! -f "$CONFIG" ]; then if [ -n "$PROXY_TOKEN_KEY" ]; then sed -i "s|#token_key =.*|token_key = $PROXY_TOKEN_KEY|" "$CONFIG" fi + if [ -n "$PROXY_TIMEOUT" ]; then + sed -i "s|#proxytimeout =.*|proxytimeout = $PROXY_TIMEOUT|" "$CONFIG" + fi if [ -n "$PROXY_ETCD" ]; then if [ -z "$HAS_ETCD" ]; then From d6e5975f9493b414db82bcfa0a19f176d6a61237 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Apr 2025 08:37:30 +0200 Subject: [PATCH 093/549] Fix shellcheck error. --- scripts/get-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/get-version.sh b/scripts/get-version.sh index e1fc562..28074ab 100755 --- a/scripts/get-version.sh +++ b/scripts/get-version.sh @@ -26,4 +26,4 @@ if [ -z "$VERSION" ]; then VERSION=unknown fi -echo $VERSION +echo "$VERSION" From 01c5ec131c78bb0d670b2a41a7115a5aa324e686 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 20:28:52 +0000 Subject: [PATCH 094/549] Bump github.com/nats-io/nats-server/v2 from 2.11.1 to 2.11.3 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.1 to 2.11.3. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.1...v2.11.3) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e3cbb59..55eb5c6 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.1 + github.com/nats-io/nats-server/v2 v2.11.3 github.com/nats-io/nats.go v1.41.2 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -57,7 +57,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/jwt/v2 v2.7.3 // indirect + github.com/nats-io/jwt/v2 v2.7.4 // indirect github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pion/randutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index e3ef406..6b650d0 100644 --- a/go.sum +++ b/go.sum @@ -123,10 +123,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= -github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= -github.com/nats-io/nats-server/v2 v2.11.1 h1:LwdauqMqMNhTxTN3+WFTX6wGDOKntHljgZ+7gL5HCnk= -github.com/nats-io/nats-server/v2 v2.11.1/go.mod h1:leXySghbdtXSUmWem8K9McnJ6xbJOb0t9+NQ5HTRZjI= +github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= +github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= +github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc= +github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY= github.com/nats-io/nats.go v1.41.2 h1:5UkfLAtu/036s99AhFRlyNDI1Ieylb36qbGjJzHixos= github.com/nats-io/nats.go v1.41.2/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= From 722c7e077c2d85974e96782adecd196dfa3f2055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 06:49:08 +0000 Subject: [PATCH 095/549] Bump github.com/nats-io/nats.go from 1.41.2 to 1.42.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.41.2 to 1.42.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.41.2...v1.42.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 55eb5c6..70e21dc 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.11.3 - github.com/nats-io/nats.go v1.41.2 + github.com/nats-io/nats.go v1.42.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.11 diff --git a/go.sum b/go.sum index 6b650d0..8ac038e 100644 --- a/go.sum +++ b/go.sum @@ -127,8 +127,8 @@ github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc= github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY= -github.com/nats-io/nats.go v1.41.2 h1:5UkfLAtu/036s99AhFRlyNDI1Ieylb36qbGjJzHixos= -github.com/nats-io/nats.go v1.41.2/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM= +github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From 5e7a1df2b668b3293db42bd5e562a954f48d2ac2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 20:53:06 +0000 Subject: [PATCH 096/549] Bump golangci/golangci-lint-action from 7.0.0 to 8.0.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7.0.0 to 8.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v7.0.0...v8.0.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 43c1f03..0275269 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: go-version: "1.23" - name: lint - uses: golangci/golangci-lint-action@v7.0.0 + uses: golangci/golangci-lint-action@v8.0.0 with: version: latest args: --timeout=2m0s From 47f05c9163757ac9008a94e72bb2b9a1bfc0099a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 4 May 2025 02:02:14 +0200 Subject: [PATCH 097/549] Fix subscribers not closed when publisher is closed in Janus 1.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Janus 0.x when a publisher is closed the subscribers are also automatically closed (if configured to do so, which is the default). However, in Janus 1.x, as a subscriber can be connected to several publishers, when a publisher is closed its subscribers just receive an "updated" event with the new state of the streams. As multistream connections are not used yet in Talk it still makes sense to close its subscribers when a publisher is closed, so subscribers are now automatically closed if they receive an "updated" event and all the media streams are inactive (note that data channels will be always active). Note that, although it should not be received with the current usage of Janus API, an "updated" event without streams could be received if the subscriber was updated but there were no changes in the streams, so in that case the subscriber should not be closed. Signed-off-by: Daniel Calviño Sánchez --- mcu_janus_subscriber.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index a60afb1..a77d401 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -47,6 +47,23 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { case "destroyed": log.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId) go p.Close(ctx) + case "updated": + streams, ok := getPluginValue(event.Plugindata, pluginVideoRoom, "streams").([]interface{}) + if !ok || len(streams) == 0 { + // The streams list will be empty if no stream was changed. + return + } + + for _, stream := range streams { + if stream, ok := stream.(map[string]interface{}); ok { + if (stream["type"] == "audio" || stream["type"] == "video") && stream["active"] != false { + return + } + } + } + + log.Printf("Subscriber %d: received updated event with no active media streams, closing", p.handleId) + go p.Close(ctx) case "event": // Handle renegotiations, but ignore other events like selected // substream / temporal layer. From e8c4a02945271b4c11775d45d3499d81440b4928 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 6 May 2025 16:54:38 +0200 Subject: [PATCH 098/549] Provide access to public id of publishers. --- mcu_common.go | 2 ++ mcu_janus_publisher.go | 4 ++++ mcu_proxy.go | 4 ++++ mcu_test.go | 4 ++++ proxy/proxy_server_test.go | 4 ++++ 5 files changed, 18 insertions(+) diff --git a/mcu_common.go b/mcu_common.go index 7d78690..b2649e4 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -211,6 +211,8 @@ type McuClient interface { type McuPublisher interface { McuClient + PublisherId() string + HasMedia(MediaType) bool SetMedia(MediaType) diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index fa0b509..92896c6 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -56,6 +56,10 @@ type mcuJanusPublisher struct { answerSdp atomic.Pointer[sdp.SessionDescription] } +func (p *mcuJanusPublisher) PublisherId() string { + return p.id +} + func (p *mcuJanusPublisher) handleEvent(event *janus.EventMsg) { if videoroom := getPluginStringValue(event.Plugindata, pluginVideoRoom, "videoroom"); videoroom != "" { ctx := context.TODO() diff --git a/mcu_proxy.go b/mcu_proxy.go index 72fab9a..04f9fd2 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -153,6 +153,10 @@ func newMcuProxyPublisher(id string, sid string, streamType StreamType, maxBitra } } +func (p *mcuProxyPublisher) PublisherId() string { + return p.id +} + func (p *mcuProxyPublisher) HasMedia(mt MediaType) bool { return (p.settings.MediaTypes & mt) == mt } diff --git a/mcu_test.go b/mcu_test.go index 9097267..36cb21f 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -184,6 +184,10 @@ type TestMCUPublisher struct { sdp string } +func (p *TestMCUPublisher) PublisherId() string { + return p.id +} + func (p *TestMCUPublisher) HasMedia(mt MediaType) bool { return (p.settings.MediaTypes & mt) == mt } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 25eff8d..866560a 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -401,6 +401,10 @@ func (p *TestMCUPublisher) Id() string { return p.id } +func (p *TestMCUPublisher) PublisherId() string { + return p.id +} + func (p *TestMCUPublisher) Sid() string { return p.sid } From 5c4ffdc7a29aab74b8bc6e4c2c00d6a81a21d87e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 6 May 2025 17:01:18 +0200 Subject: [PATCH 099/549] Close subscriber if remote publisher was closed. --- proxy/proxy_remote.go | 8 +++++++- proxy/proxy_server.go | 11 ++++++++++- proxy/proxy_session.go | 32 +++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index fadc4d0..4c738d4 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -61,6 +61,7 @@ var ( type RemoteConnection struct { mu sync.Mutex + p *ProxyServer url *url.URL conn *websocket.Conn closer *signaling.Closer @@ -82,13 +83,14 @@ type RemoteConnection struct { messageCallbacks map[string]chan *signaling.ProxyServerMessage } -func NewRemoteConnection(proxyUrl string, tokenId string, tokenKey *rsa.PrivateKey, tlsConfig *tls.Config) (*RemoteConnection, error) { +func NewRemoteConnection(p *ProxyServer, proxyUrl string, tokenId string, tokenKey *rsa.PrivateKey, tlsConfig *tls.Config) (*RemoteConnection, error) { u, err := url.Parse(proxyUrl) if err != nil { return nil, err } result := &RemoteConnection{ + p: p, url: u, closer: signaling.NewCloser(), @@ -456,6 +458,10 @@ func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { switch msg.Event.Type { case "update-load": + // Ignore + case "publisher-closed": + log.Printf("Remote publisher %s was closed on %s", msg.Event.ClientId, c) + c.p.RemotePublisherDeleted(msg.Event.ClientId) default: log.Printf("Received unsupported event %+v from %s", msg, c) } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index c51ad97..0ef6173 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -1595,7 +1595,7 @@ func (s *ProxyServer) getRemoteConnection(url string) (*RemoteConnection, error) return conn, nil } - conn, err := NewRemoteConnection(url, s.tokenId, s.tokenKey, s.remoteTlsConfig) + conn, err := NewRemoteConnection(s, url, s.tokenId, s.tokenKey, s.remoteTlsConfig) if err != nil { return nil, err } @@ -1612,3 +1612,12 @@ func (s *ProxyServer) PublisherDeleted(publisher signaling.McuPublisher) { session.OnPublisherDeleted(publisher) } } + +func (s *ProxyServer) RemotePublisherDeleted(publisherId string) { + s.sessionsLock.RLock() + defer s.sessionsLock.RUnlock() + + for _, session := range s.sessions { + session.OnRemotePublisherDeleted(publisherId) + } +} diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index de6645b..2e3621e 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -38,6 +38,7 @@ const ( ) type remotePublisherData struct { + id string hostname string port int rtcpPort int @@ -400,6 +401,7 @@ func (s *ProxySession) AddRemotePublisher(publisher signaling.McuPublisher, host } data := &remotePublisherData{ + id: publisher.PublisherId(), hostname: hostname, port: port, rtcpPort: rtcpPort, @@ -431,5 +433,33 @@ func (s *ProxySession) OnPublisherDeleted(publisher signaling.McuPublisher) { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() - delete(s.remotePublishers, publisher) + if entries, found := s.remotePublishers[publisher]; found { + delete(s.remotePublishers, publisher) + + for _, entry := range entries { + msg := &signaling.ProxyServerMessage{ + Type: "event", + Event: &signaling.EventProxyServerMessage{ + Type: "publisher-closed", + ClientId: entry.id, + }, + } + s.sendMessage(msg) + } + } +} + +func (s *ProxySession) OnRemotePublisherDeleted(publisherId string) { + s.subscribersLock.Lock() + defer s.subscribersLock.Unlock() + + for id, sub := range s.subscribers { + if sub.Publisher() == publisherId { + delete(s.subscribers, id) + delete(s.subscriberIds, sub) + + log.Printf("Remote subscriber %s was closed, closing %s subscriber %s", publisherId, sub.StreamType(), sub.Id()) + go sub.Close(context.Background()) + } + } } From 6fa6dcc533f1ba314cfddd6aa0c079658061c5b3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 7 May 2025 09:55:01 +0200 Subject: [PATCH 100/549] Handle "bye" in remote connections. --- proxy/proxy_remote.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 4c738d4..9cd81e6 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -450,6 +450,13 @@ func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { switch msg.Type { case "event": c.processEvent(msg) + case "bye": + log.Printf("Connection to %s was closed: %s", c, msg.Bye.Reason) + if msg.Bye.Reason == "session_expired" { + // Don't try to resume expired session. + c.sessionId = "" + } + c.scheduleReconnect() default: log.Printf("Received unsupported message %+v from %s", msg, c) } From 420f7eb0ba18a3ae40e42f8165ae271516894d2d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 7 May 2025 10:28:49 +0200 Subject: [PATCH 101/549] Close connection if last remote publisher is removed. --- proxy/proxy_remote.go | 16 +++++++++++++-- proxy/proxy_server.go | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 9cd81e6..0e5822f 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -28,6 +28,7 @@ import ( "encoding/json" "errors" "log" + "net" "net/http" "net/url" "strconv" @@ -205,6 +206,10 @@ func (c *RemoteConnection) sendClose() error { c.mu.Lock() defer c.mu.Unlock() + return c.sendCloseLocked() +} + +func (c *RemoteConnection) sendCloseLocked() error { if c.conn == nil { return ErrNotConnected } @@ -231,7 +236,12 @@ func (c *RemoteConnection) Close() error { return nil } - c.sendClose() + if !c.closed.CompareAndSwap(false, true) { + // Already closed + return nil + } + + c.closer.Close() err1 := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{}) err2 := c.conn.Close() c.conn = nil @@ -317,7 +327,9 @@ func (c *RemoteConnection) readPump(conn *websocket.Conn) { websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { - log.Printf("Error reading from %s: %v", c, err) + if !errors.Is(err, net.ErrClosed) || !c.closed.Load() { + log.Printf("Error reading from %s: %v", c, err) + } } break } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 0ef6173..7571121 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -145,6 +145,7 @@ type ProxyServer struct { remoteHostname string remoteConnections map[string]*RemoteConnection remoteConnectionsLock sync.Mutex + remotePublishers map[string]map[*proxyRemotePublisher]bool } func IsPublicIP(IP net.IP) bool { @@ -364,6 +365,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* remoteTlsConfig: remoteTlsConfig, remoteHostname: remoteHostname, remoteConnections: make(map[string]*RemoteConnection), + remotePublishers: make(map[string]map[*proxyRemotePublisher]bool), } result.maxIncoming.Store(int64(maxIncoming) * 1024 * 1024) @@ -858,6 +860,8 @@ func (p *proxyRemotePublisher) StartPublishing(ctx context.Context, publisher si } func (p *proxyRemotePublisher) StopPublishing(ctx context.Context, publisher signaling.McuRemotePublisherProperties) error { + defer p.proxy.removeRemotePublisher(p) + conn, err := p.proxy.getRemoteConnection(p.remoteUrl) if err != nil { return err @@ -899,6 +903,46 @@ func (p *proxyRemotePublisher) GetStreams(ctx context.Context) ([]signaling.Publ return response.Command.Streams, nil } +func (s *ProxyServer) addRemotePublisher(publisher *proxyRemotePublisher) { + s.remoteConnectionsLock.Lock() + defer s.remoteConnectionsLock.Unlock() + + publishers, found := s.remotePublishers[publisher.remoteUrl] + if !found { + publishers = make(map[*proxyRemotePublisher]bool) + s.remotePublishers[publisher.remoteUrl] = publishers + } + + publishers[publisher] = true + log.Printf("Add remote publisher to %s", publisher.remoteUrl) +} + +func (s *ProxyServer) removeRemotePublisher(publisher *proxyRemotePublisher) { + s.remoteConnectionsLock.Lock() + defer s.remoteConnectionsLock.Unlock() + + log.Printf("Removing remote publisher to %s", publisher.remoteUrl) + publishers, found := s.remotePublishers[publisher.remoteUrl] + if !found { + return + } + + delete(publishers, publisher) + if len(publishers) > 0 { + return + } + + delete(s.remotePublishers, publisher.remoteUrl) + if conn, found := s.remoteConnections[publisher.remoteUrl]; found { + delete(s.remoteConnections, publisher.remoteUrl) + if err := conn.Close(); err != nil { + log.Printf("Error closing remote connection to %s: %s", publisher.remoteUrl, err) + } else { + log.Printf("Remote connection to %s closed", publisher.remoteUrl) + } + } +} + func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, session *ProxySession, message *signaling.ProxyClientMessage) { cmd := message.Command @@ -1017,6 +1061,8 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s go publisher.Close(context.Background()) }() + s.addRemotePublisher(controller) + subscriber, err = remoteMcu.NewRemoteSubscriber(subCtx, session, publisher) if err != nil { handleCreateError(err) From e0e6cb6e9ddc90829581aee88be6829235a9bb53 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 7 May 2025 11:18:52 +0200 Subject: [PATCH 102/549] Add jitter to reconnect intervals. This prevents all servers from reconnecting at the same time in case of network interruptions or service restarts. --- mcu_janus.go | 2 +- mcu_proxy.go | 6 +++++- proxy/proxy_remote.go | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mcu_janus.go b/mcu_janus.go index ea08371..d90fd13 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -45,7 +45,7 @@ const ( screenPublisherUserId = 2 initialReconnectInterval = 1 * time.Second - maxReconnectInterval = 32 * time.Second + maxReconnectInterval = 16 * time.Second ) var ( diff --git a/mcu_proxy.go b/mcu_proxy.go index 04f9fd2..715535d 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -29,6 +29,7 @@ import ( "errors" "fmt" "log" + "math/rand/v2" "net" "net/http" "net/url" @@ -749,7 +750,10 @@ func (c *mcuProxyConnection) scheduleReconnect() { } interval := c.reconnectInterval.Load() - c.reconnectTimer.Reset(time.Duration(interval)) + // Prevent all servers from reconnecting at the same time in case of an + // interrupted connection to the proxy or a restart. + jitter := rand.Int64N(interval) - (interval / 2) + c.reconnectTimer.Reset(time.Duration(interval + jitter)) interval = interval * 2 if interval > int64(maxReconnectInterval) { diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 0e5822f..b16ade2 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -28,6 +28,7 @@ import ( "encoding/json" "errors" "log" + "math/rand/v2" "net" "net/http" "net/url" @@ -44,7 +45,7 @@ import ( const ( initialReconnectInterval = 1 * time.Second - maxReconnectInterval = 32 * time.Second + maxReconnectInterval = 16 * time.Second // Time allowed to write a message to the peer. writeWait = 10 * time.Second @@ -170,7 +171,10 @@ func (c *RemoteConnection) scheduleReconnect() { c.close() interval := c.reconnectInterval.Load() - c.reconnectTimer.Reset(time.Duration(interval)) + // Prevent all servers from reconnecting at the same time in case of an + // interrupted connection to the proxy or a restart. + jitter := rand.Int64N(interval) - (interval / 2) + c.reconnectTimer.Reset(time.Duration(interval + jitter)) interval = interval * 2 if interval > int64(maxReconnectInterval) { From af69bbf1e8af6dcd079c24333214e1e33e9da006 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 7 May 2025 11:46:11 +0200 Subject: [PATCH 103/549] Update changelog for 2.0.3 --- CHANGELOG.md | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f67415..6889d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,139 @@ All notable changes to this project will be documented in this file. +## 2.0.3 - 2025-05-07 + +### Added +- Allow using environment variables for sessions and clients secrets + [#910](https://github.com/strukturag/nextcloud-spreed-signaling/pull/910) +- Allow using environment variables for backend secrets + [#912](https://github.com/strukturag/nextcloud-spreed-signaling/pull/912) +- Add serverinfo API + [#937](https://github.com/strukturag/nextcloud-spreed-signaling/pull/937) +- Add metrics for backend client requests. + [#973](https://github.com/strukturag/nextcloud-spreed-signaling/pull/973) + +### Changed +- Drop support for Go 1.22 + [#969](https://github.com/strukturag/nextcloud-spreed-signaling/pull/969) +- Do not log nats url credentials + [#911](https://github.com/strukturag/nextcloud-spreed-signaling/pull/911) +- Migrate cache-control parsing to https://github.com/pquerna/cachecontrol + [#916](https://github.com/strukturag/nextcloud-spreed-signaling/pull/916) +- CI: Test with Golang 1.24 + [#922](https://github.com/strukturag/nextcloud-spreed-signaling/pull/922) +- Add "/usr/lib64" to systemd ExecPath + [#963](https://github.com/strukturag/nextcloud-spreed-signaling/pull/963) +- Improve memory allocations + [#870](https://github.com/strukturag/nextcloud-spreed-signaling/pull/870) +- Speedup tests + [#972](https://github.com/strukturag/nextcloud-spreed-signaling/pull/972) +- docker: Make more settings configurable + [#980](https://github.com/strukturag/nextcloud-spreed-signaling/pull/980) +- Add jitter to reconnect intervals. + [#988](https://github.com/strukturag/nextcloud-spreed-signaling/pull/988) + +### Fixed +- nats: Reconnect client indefinitely. + [#935](https://github.com/strukturag/nextcloud-spreed-signaling/pull/935) +- Explicitly set TMPDIR to ensure that it is an executable path + [#956](https://github.com/strukturag/nextcloud-spreed-signaling/pull/956) +- Close subscribers on errors during initial connection. + [#959](https://github.com/strukturag/nextcloud-spreed-signaling/pull/959) +- Fix formatting of errors in "assert.Fail" calls. + [#970](https://github.com/strukturag/nextcloud-spreed-signaling/pull/970) +- Fix race condition in flaky certificate/CA reload tests. + [#971](https://github.com/strukturag/nextcloud-spreed-signaling/pull/971) +- Fix flaky test "Test_GrpcClients_DnsDiscovery". + [#976](https://github.com/strukturag/nextcloud-spreed-signaling/pull/976) +- Fix subscribers not closed when publisher is closed in Janus 1.x + [#986](https://github.com/strukturag/nextcloud-spreed-signaling/pull/986) +- Close subscriber if remote publisher was closed. + [#987](https://github.com/strukturag/nextcloud-spreed-signaling/pull/987) + +### Dependencies +- Bump google.golang.org/grpc from 1.69.4 to 1.70.0 + [#904](https://github.com/strukturag/nextcloud-spreed-signaling/pull/904) +- Bump the etcd group with 4 updates + [#907](https://github.com/strukturag/nextcloud-spreed-signaling/pull/907) +- Bump coverallsapp/github-action from 2.3.4 to 2.3.6 + [#909](https://github.com/strukturag/nextcloud-spreed-signaling/pull/909) +- Bump google.golang.org/protobuf from 1.36.3 to 1.36.4 + [#908](https://github.com/strukturag/nextcloud-spreed-signaling/pull/908) +- Bump github.com/nats-io/nats-server/v2 from 2.10.24 to 2.10.25 + [#903](https://github.com/strukturag/nextcloud-spreed-signaling/pull/903) +- Bump golangci/golangci-lint-action from 6.2.0 to 6.3.0 + [#913](https://github.com/strukturag/nextcloud-spreed-signaling/pull/913) +- Bump github.com/nats-io/nats.go from 1.38.0 to 1.39.0 + [#915](https://github.com/strukturag/nextcloud-spreed-signaling/pull/915) +- Bump golangci/golangci-lint-action from 6.3.0 to 6.3.2 + [#918](https://github.com/strukturag/nextcloud-spreed-signaling/pull/918) +- Bump google.golang.org/protobuf from 1.36.4 to 1.36.5 + [#917](https://github.com/strukturag/nextcloud-spreed-signaling/pull/917) +- build(deps): bump golang from 1.23-alpine to 1.24-alpine in /docker/proxy + [#921](https://github.com/strukturag/nextcloud-spreed-signaling/pull/921) +- build(deps): bump golang from 1.23-alpine to 1.24-alpine in /docker/server + [#919](https://github.com/strukturag/nextcloud-spreed-signaling/pull/919) +- build(deps): bump sphinx from 8.1.3 to 8.2.0 in /docs + [#928](https://github.com/strukturag/nextcloud-spreed-signaling/pull/928) +- build(deps): bump github.com/prometheus/client_golang from 1.20.5 to 1.21.0 + [#927](https://github.com/strukturag/nextcloud-spreed-signaling/pull/927) +- build(deps): bump sphinx from 8.2.0 to 8.2.1 in /docs + [#929](https://github.com/strukturag/nextcloud-spreed-signaling/pull/929) +- build(deps): bump github.com/nats-io/nats.go from 1.39.0 to 1.39.1 + [#926](https://github.com/strukturag/nextcloud-spreed-signaling/pull/926) +- build(deps): bump sphinx from 8.2.1 to 8.2.3 in /docs + [#932](https://github.com/strukturag/nextcloud-spreed-signaling/pull/932) +- build(deps): bump google.golang.org/grpc from 1.70.0 to 1.71.0 + [#933](https://github.com/strukturag/nextcloud-spreed-signaling/pull/933) +- build(deps): bump golangci/golangci-lint-action from 6.3.3 to 6.5.0 + [#925](https://github.com/strukturag/nextcloud-spreed-signaling/pull/925) +- build(deps): bump jinja2 from 3.1.5 to 3.1.6 in /docs + [#938](https://github.com/strukturag/nextcloud-spreed-signaling/pull/938) +- build(deps): bump github.com/pion/sdp/v3 from 3.0.10 to 3.0.11 + [#939](https://github.com/strukturag/nextcloud-spreed-signaling/pull/939) +- build(deps): bump golangci/golangci-lint-action from 6.5.0 to 6.5.1 + [#940](https://github.com/strukturag/nextcloud-spreed-signaling/pull/940) +- build(deps): bump golangci/golangci-lint-action from 6.5.1 to 6.5.2 + [#946](https://github.com/strukturag/nextcloud-spreed-signaling/pull/946) +- build(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 + [#948](https://github.com/strukturag/nextcloud-spreed-signaling/pull/948) +- build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 + [#949](https://github.com/strukturag/nextcloud-spreed-signaling/pull/949) +- build(deps): bump golangci/golangci-lint-action from 6.5.2 to 7.0.0 + [#951](https://github.com/strukturag/nextcloud-spreed-signaling/pull/951) +- build(deps): bump google.golang.org/protobuf from 1.36.5 to 1.36.6 + [#952](https://github.com/strukturag/nextcloud-spreed-signaling/pull/952) +- Bump markdown from 3.7 to 3.8 in /docs + [#966](https://github.com/strukturag/nextcloud-spreed-signaling/pull/966) +- Bump golang.org/x/crypto from 0.32.0 to 0.35.0 + [#967](https://github.com/strukturag/nextcloud-spreed-signaling/pull/967) +- Bump github.com/nats-io/nats.go from 1.39.1 to 1.41.1 + [#964](https://github.com/strukturag/nextcloud-spreed-signaling/pull/964) +- Bump google.golang.org/grpc from 1.71.0 to 1.71.1 + [#957](https://github.com/strukturag/nextcloud-spreed-signaling/pull/957) +- build(deps): bump golang.org/x/net from 0.34.0 to 0.36.0 + [#941](https://github.com/strukturag/nextcloud-spreed-signaling/pull/941) +- build(deps): bump the etcd group with 4 updates + [#936](https://github.com/strukturag/nextcloud-spreed-signaling/pull/936) +- Bump github.com/nats-io/nats-server/v2 from 2.10.25 to 2.11.1 + [#962](https://github.com/strukturag/nextcloud-spreed-signaling/pull/962) +- Bump github.com/prometheus/client_golang from 1.21.1 to 1.22.0 + [#974](https://github.com/strukturag/nextcloud-spreed-signaling/pull/974) +- Bump github.com/fsnotify/fsnotify from 1.8.0 to 1.9.0 + [#975](https://github.com/strukturag/nextcloud-spreed-signaling/pull/975) +- Bump google.golang.org/grpc from 1.71.1 to 1.72.0 + [#978](https://github.com/strukturag/nextcloud-spreed-signaling/pull/978) +- Bump github.com/nats-io/nats.go from 1.41.1 to 1.41.2 + [#977](https://github.com/strukturag/nextcloud-spreed-signaling/pull/977) +- Bump github.com/nats-io/nats-server/v2 from 2.11.1 to 2.11.3 + [#982](https://github.com/strukturag/nextcloud-spreed-signaling/pull/982) +- Bump github.com/nats-io/nats.go from 1.41.2 to 1.42.0 + [#983](https://github.com/strukturag/nextcloud-spreed-signaling/pull/983) +- Bump golangci/golangci-lint-action from 7.0.0 to 8.0.0 + [#985](https://github.com/strukturag/nextcloud-spreed-signaling/pull/985) + + ## 2.0.2 - 2025-01-22 ### Added From 38f403f88c1a9a0a10a3ae2000846e6b946c0a57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 20:32:56 +0000 Subject: [PATCH 104/549] Bump google.golang.org/grpc from 1.72.0 to 1.72.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.0 to 1.72.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.72.0...v1.72.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.72.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 70e21dc..f716ae8 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.5.21 go.etcd.io/etcd/server/v3 v3.5.21 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.72.0 + google.golang.org/grpc v1.72.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index 8ac038e..b6334d6 100644 --- a/go.sum +++ b/go.sum @@ -308,8 +308,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From 8814fc811bee7c76a601e842dda5bd318a6217b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 20:46:10 +0000 Subject: [PATCH 105/549] Bump github.com/pion/sdp/v3 from 3.0.11 to 3.0.12 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.11 to 3.0.12. - [Release notes](https://github.com/pion/sdp/releases) - [Changelog](https://github.com/pion/sdp/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/sdp/compare/v3.0.11...v3.0.12) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f716ae8..9768306 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/nats-io/nats.go v1.42.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/sdp/v3 v3.0.11 + github.com/pion/sdp/v3 v3.0.12 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index b6334d6..e38f68d 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI= -github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/sdp/v3 v3.0.12 h1:pwUpHQ4W8LhQR37kRVC9YvWa5/GSPqfgxr8ejnwyaL0= +github.com/pion/sdp/v3 v3.0.12/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From dfdfe1b62abaedf2d7a6ed1711edb2d400641b4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 05:51:36 +0000 Subject: [PATCH 106/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.5.21 to 3.6.0 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.21...v3.6.0) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.5.21 to 3.6.0 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.21...v3.6.0) Updates `go.etcd.io/etcd/client/v3` from 3.5.21 to 3.6.0 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.21...v3.6.0) Updates `go.etcd.io/etcd/server/v3` from 3.5.21 to 3.6.0 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.5.21...v3.6.0) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 62 ++++++++--------- go.sum | 214 +++++++++++++++------------------------------------------ 2 files changed, 83 insertions(+), 193 deletions(-) diff --git a/go.mod b/go.mod index 9768306..c807e82 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.5.21 - go.etcd.io/etcd/client/pkg/v3 v3.5.21 - go.etcd.io/etcd/client/v3 v3.5.21 - go.etcd.io/etcd/server/v3 v3.5.21 + go.etcd.io/etcd/api/v3 v3.6.0 + go.etcd.io/etcd/client/pkg/v3 v3.6.0 + go.etcd.io/etcd/client/v3 v3.6.0 + go.etcd.io/etcd/server/v3 v3.6.0 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.72.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -31,31 +31,27 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-tpm v0.9.3 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/minio/highwayhash v1.0.3 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/jwt/v2 v2.7.4 // indirect github.com/nats-io/nkeys v0.4.11 // indirect @@ -67,33 +63,31 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.3.11 // indirect - go.etcd.io/etcd/client/v2 v2.305.21 // indirect - go.etcd.io/etcd/pkg/v3 v3.5.21 // indirect - go.etcd.io/etcd/raft/v3 v3.5.21 // indirect + go.etcd.io/bbolt v1.4.0 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.0 // indirect + go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect - go.uber.org/multierr v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index e38f68d..e265c5a 100644 --- a/go.sum +++ b/go.sum @@ -1,83 +1,47 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 h1:I8/Qu5NTaiXi1TsEYmTeLDUlf7u9pEdbG+azjDvx8Vg= github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490/go.mod h1:jWlUIP63OLr0cV2FGN2IEzSFsMAe58if8rk/SAE0JRE= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= -github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -86,26 +50,20 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -116,11 +74,6 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= @@ -135,42 +88,33 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8iFMHvQleYlF5M3PY6zpAbxsngImjE= github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/sdp/v3 v3.0.12 h1:pwUpHQ4W8LhQR37kRVC9YvWa5/GSPqfgxr8ejnwyaL0= github.com/pion/sdp/v3 v3.0.12/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -181,32 +125,30 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= -go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= -go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= -go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= -go.etcd.io/etcd/client/v2 v2.305.21 h1:eLiFfexc2mE+pTLz9WwnoEsX5JTTpLCYVivKkmVXIRA= -go.etcd.io/etcd/client/v2 v2.305.21/go.mod h1:OKkn4hlYNf43hpjEM3Ke3aRdUkhSl8xjKjSf8eCq2J8= -go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= -go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= -go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk= -go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU= -go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk= -go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs= -go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU= -go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.etcd.io/etcd/api/v3 v3.6.0 h1:vdbkcUBGLf1vfopoGE/uS3Nv0KPyIpUV/HM6w9yx2kM= +go.etcd.io/etcd/api/v3 v3.6.0/go.mod h1:Wt5yZqEmxgTNJGHob7mTVBJDZNXiHPtXTcPab37iFOw= +go.etcd.io/etcd/client/pkg/v3 v3.6.0 h1:nchnPqpuxvv3UuGGHaz0DQKYi5EIW5wOYsgUNRc365k= +go.etcd.io/etcd/client/pkg/v3 v3.6.0/go.mod h1:Jv5SFWMnGvIBn8o3OaBq/PnT0jjsX8iNokAUessNjoA= +go.etcd.io/etcd/client/v3 v3.6.0 h1:/yjKzD+HW5v/3DVj9tpwFxzNbu8hjcKID183ug9duWk= +go.etcd.io/etcd/client/v3 v3.6.0/go.mod h1:Jzk/Knqe06pkOZPHXsQ0+vNDvMQrgIqJ0W8DwPdMJMg= +go.etcd.io/etcd/pkg/v3 v3.6.0 h1:0o70c/NR4OZNO5mOtRFBATtMv6xjEoTVZjFtn6MlsNE= +go.etcd.io/etcd/pkg/v3 v3.6.0/go.mod h1:pFym9TwvGyAp9VHK/0LoJ1n2D+sX4ukzP15ZqN5gYO8= +go.etcd.io/etcd/server/v3 v3.6.0 h1:YcYxiJzmFCpjzzd7d/XmQE09p60248OzaaOaySRJyt0= +go.etcd.io/etcd/server/v3 v3.6.0/go.mod h1:y8PLrWY4upkE79xxRCkbWmCmGUmTeAG0RmzfzDhHO/E= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= @@ -215,15 +157,12 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -231,42 +170,22 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -279,10 +198,6 @@ golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -290,24 +205,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= @@ -317,17 +218,12 @@ google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From b952cb58de90eac577369f4fcac07f86cc675c7c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 22 May 2025 09:37:34 +0200 Subject: [PATCH 107/549] Fix deadlock when setting transient data while removing listener. --- transient_data.go | 13 ++++++++--- transient_data_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/transient_data.go b/transient_data.go index 120a454..57fd029 100644 --- a/transient_data.go +++ b/transient_data.go @@ -44,6 +44,13 @@ func NewTransientData() *TransientData { return &TransientData{} } +func (t *TransientData) sendMessageToListener(listener TransientListener, message *ServerMessage) { + t.mu.Unlock() + defer t.mu.Lock() + + listener.SendMessage(message) +} + func (t *TransientData) notifySet(key string, prev, value interface{}) { msg := &ServerMessage{ Type: "transient", @@ -55,7 +62,7 @@ func (t *TransientData) notifySet(key string, prev, value interface{}) { }, } for listener := range t.listeners { - listener.SendMessage(msg) + t.sendMessageToListener(listener, msg) } } @@ -69,7 +76,7 @@ func (t *TransientData) notifyDeleted(key string, prev interface{}) { }, } for listener := range t.listeners { - listener.SendMessage(msg) + t.sendMessageToListener(listener, msg) } } @@ -90,7 +97,7 @@ func (t *TransientData) AddListener(listener TransientListener) { Data: t.data, }, } - listener.SendMessage(msg) + t.sendMessageToListener(listener, msg) } } diff --git a/transient_data_test.go b/transient_data_test.go index 4523323..08b840d 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -23,6 +23,7 @@ package signaling import ( "context" + "sync" "testing" "time" @@ -84,6 +85,55 @@ func Test_TransientData(t *testing.T) { assert.Nil(data.GetData()["test"]) } +type MockTransientListener struct { + mu sync.Mutex + sending chan struct{} + done chan struct{} + + data *TransientData +} + +func (l *MockTransientListener) SendMessage(message *ServerMessage) bool { + close(l.sending) + + time.Sleep(10 * time.Millisecond) + + l.mu.Lock() + defer l.mu.Unlock() + defer close(l.done) + + time.Sleep(10 * time.Millisecond) + + return true +} + +func (l *MockTransientListener) Close() { + l.mu.Lock() + defer l.mu.Unlock() + + l.data.RemoveListener(l) +} + +func Test_TransientDataDeadlock(t *testing.T) { + data := NewTransientData() + + listener := &MockTransientListener{ + sending: make(chan struct{}), + done: make(chan struct{}), + + data: data, + } + data.AddListener(listener) + + go func() { + <-listener.sending + listener.Close() + }() + + data.Set("foo", "bar") + <-listener.done +} + func Test_TransientMessages(t *testing.T) { t.Parallel() CatchLogForTest(t) From b1162cc2da15790c9468637a124eaece82203dd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 20:06:17 +0000 Subject: [PATCH 108/549] Bump github.com/nats-io/nats-server/v2 from 2.11.3 to 2.11.4 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.3 to 2.11.4. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.3...v2.11.4) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index c807e82..8169b0b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.3 + github.com/nats-io/nats-server/v2 v2.11.4 github.com/nats-io/nats.go v1.42.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -43,7 +43,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-tpm v0.9.3 // indirect + github.com/google/go-tpm v0.9.5 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect @@ -79,10 +79,10 @@ require ( go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.37.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index e265c5a..a8eacb1 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= -github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= +github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc= -github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY= +github.com/nats-io/nats-server/v2 v2.11.4 h1:oQhvy6He6ER926sGqIKBKuYHH4BGnUQCNb0Y5Qa+M54= +github.com/nats-io/nats-server/v2 v2.11.4/go.mod h1:jFnKKwbNeq6IfLHq+OMnl7vrFRihQ/MkhRbiWfjLdjU= github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM= github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -168,8 +168,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -182,19 +182,19 @@ golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 6be0fb6828a45e8186f377ba8747b3baa13e1265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 20:16:54 +0000 Subject: [PATCH 109/549] Bump github.com/pion/sdp/v3 from 3.0.12 to 3.0.13 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.12 to 3.0.13. - [Release notes](https://github.com/pion/sdp/releases) - [Changelog](https://github.com/pion/sdp/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/sdp/compare/v3.0.12...v3.0.13) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c807e82..a45cdfc 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/nats-io/nats.go v1.42.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/sdp/v3 v3.0.12 + github.com/pion/sdp/v3 v3.0.13 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index e265c5a..9e3e97a 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.12 h1:pwUpHQ4W8LhQR37kRVC9YvWa5/GSPqfgxr8ejnwyaL0= -github.com/pion/sdp/v3 v3.0.12/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4= +github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= From eced2ffdd7a9cf3da52c56554fb8d8fa112073ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:47:57 +0000 Subject: [PATCH 110/549] Bump google.golang.org/grpc from 1.72.1 to 1.72.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.1 to 1.72.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.72.1...v1.72.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.72.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c807e82..03fc9be 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.0 go.etcd.io/etcd/server/v3 v3.6.0 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.72.1 + google.golang.org/grpc v1.72.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index e265c5a..ec8f32a 100644 --- a/go.sum +++ b/go.sum @@ -209,8 +209,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1: google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From 865ddc308a0f86221d803c97128e004fe7f32aaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 20:50:45 +0000 Subject: [PATCH 111/549] Bump github.com/nats-io/nats.go from 1.42.0 to 1.43.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.42.0 to 1.43.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.42.0...v1.43.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 35b7959..0ffa84c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.11.4 - github.com/nats-io/nats.go v1.42.0 + github.com/nats-io/nats.go v1.43.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.13 diff --git a/go.sum b/go.sum index 4827122..f5d491d 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.11.4 h1:oQhvy6He6ER926sGqIKBKuYHH4BGnUQCNb0Y5Qa+M54= github.com/nats-io/nats-server/v2 v2.11.4/go.mod h1:jFnKKwbNeq6IfLHq+OMnl7vrFRihQ/MkhRbiWfjLdjU= -github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM= -github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug= +github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From cdb3472c13e5902c2de0dd9ffd2ee52ce467be88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 20:37:02 +0000 Subject: [PATCH 112/549] Bump google.golang.org/grpc from 1.72.2 to 1.73.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.2 to 1.73.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.72.2...v1.73.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.73.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 0ffa84c..5cfd077 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.0 go.etcd.io/etcd/server/v3 v3.6.0 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.72.2 + google.golang.org/grpc v1.73.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) @@ -71,12 +71,12 @@ require ( go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.38.0 // indirect @@ -84,8 +84,8 @@ require ( golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index f5d491d..42448e5 100644 --- a/go.sum +++ b/go.sum @@ -143,20 +143,20 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -205,12 +205,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From 4c33de41a3efd69308adfa4edaf103646418d119 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:38:03 +0000 Subject: [PATCH 113/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.0 to 3.6.1 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.0...v3.6.1) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.0 to 3.6.1 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.0...v3.6.1) Updates `go.etcd.io/etcd/client/v3` from 3.6.0 to 3.6.1 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.0...v3.6.1) Updates `go.etcd.io/etcd/server/v3` from 3.6.0 to 3.6.1 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.0...v3.6.1) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 0ffa84c..617ce16 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.6.0 - go.etcd.io/etcd/client/pkg/v3 v3.6.0 - go.etcd.io/etcd/client/v3 v3.6.0 - go.etcd.io/etcd/server/v3 v3.6.0 + go.etcd.io/etcd/api/v3 v3.6.1 + go.etcd.io/etcd/client/pkg/v3 v3.6.1 + go.etcd.io/etcd/client/v3 v3.6.1 + go.etcd.io/etcd/server/v3 v3.6.1 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.72.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -67,7 +67,7 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.0 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.0 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.1 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index f5d491d..c9352d7 100644 --- a/go.sum +++ b/go.sum @@ -127,16 +127,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= -go.etcd.io/etcd/api/v3 v3.6.0 h1:vdbkcUBGLf1vfopoGE/uS3Nv0KPyIpUV/HM6w9yx2kM= -go.etcd.io/etcd/api/v3 v3.6.0/go.mod h1:Wt5yZqEmxgTNJGHob7mTVBJDZNXiHPtXTcPab37iFOw= -go.etcd.io/etcd/client/pkg/v3 v3.6.0 h1:nchnPqpuxvv3UuGGHaz0DQKYi5EIW5wOYsgUNRc365k= -go.etcd.io/etcd/client/pkg/v3 v3.6.0/go.mod h1:Jv5SFWMnGvIBn8o3OaBq/PnT0jjsX8iNokAUessNjoA= -go.etcd.io/etcd/client/v3 v3.6.0 h1:/yjKzD+HW5v/3DVj9tpwFxzNbu8hjcKID183ug9duWk= -go.etcd.io/etcd/client/v3 v3.6.0/go.mod h1:Jzk/Knqe06pkOZPHXsQ0+vNDvMQrgIqJ0W8DwPdMJMg= -go.etcd.io/etcd/pkg/v3 v3.6.0 h1:0o70c/NR4OZNO5mOtRFBATtMv6xjEoTVZjFtn6MlsNE= -go.etcd.io/etcd/pkg/v3 v3.6.0/go.mod h1:pFym9TwvGyAp9VHK/0LoJ1n2D+sX4ukzP15ZqN5gYO8= -go.etcd.io/etcd/server/v3 v3.6.0 h1:YcYxiJzmFCpjzzd7d/XmQE09p60248OzaaOaySRJyt0= -go.etcd.io/etcd/server/v3 v3.6.0/go.mod h1:y8PLrWY4upkE79xxRCkbWmCmGUmTeAG0RmzfzDhHO/E= +go.etcd.io/etcd/api/v3 v3.6.1 h1:yJ9WlDih9HT457QPuHt/TH/XtsdN2tubyxyQHSHPsEo= +go.etcd.io/etcd/api/v3 v3.6.1/go.mod h1:lnfuqoGsXMlZdTJlact3IB56o3bWp1DIlXPIGKRArto= +go.etcd.io/etcd/client/pkg/v3 v3.6.1 h1:CxDVv8ggphmamrXM4Of8aCC8QHzDM4tGcVr9p2BSoGk= +go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc= +go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0= +go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY= +go.etcd.io/etcd/pkg/v3 v3.6.1 h1:Qpshk3/SLra217k7FxcFGaH2niFAxFf1Dug57f0IUiw= +go.etcd.io/etcd/pkg/v3 v3.6.1/go.mod h1:nS0ahQoZZ9qXjQAtYGDt80IEHKl9YOF7mv6J0lQmBoQ= +go.etcd.io/etcd/server/v3 v3.6.1 h1:Y/mh94EeImzXyTBIMVgR0v5H+ANtRFDY4g1s5sxOZGE= +go.etcd.io/etcd/server/v3 v3.6.1/go.mod h1:nCqJGTP9c2WlZluJB59j3bqxZEI/GYBfQxno0MguVjE= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From 6b04363faf8629cee3f42bebcfc1fa3355aaf8a2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Jun 2025 08:46:48 +0200 Subject: [PATCH 114/549] Comment / document possible error responses. --- docs/standalone-signaling-api-v1.md | 17 ++++++++++---- hub.go | 35 ++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 1487d35..899f1fa 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -285,13 +285,18 @@ authorized, the backend returns an error and the hello request will be rejected. ### Error codes -- `unsupported-version`: The requested version is not supported. -- `auth-failed`: The session could not be authenticated. -- `too-many-sessions`: Too many sessions exist for this user id. +- `invalid_request`: The backend request for the v1 hello could not be authenticated. Check your shared secret. +- `invalid_hello_version`: The requested `hello` version is not supported. +- `auth_failed`: The session could not be authenticated. - `invalid_backend`: The requested backend URL is not supported. - `invalid_client_type`: The [client type](#client-types) is not supported. +- `invalid_ticket`: The passed ticket in the v1 hello is invalid. +- `no_such_user`: The user id in the v1 hello does not exists. - `invalid_token`: The passed token is invalid (can happen for - [client type `internal`](#client-type-internal)). + [client type `internal`](#client-type-internal) or v2 requests). +- `token_not_valid_yet`: The token could be authenticated but is not valid yet. +- `token_expired`: The token could be authenticated but is expired. +- `too_many_requests`: Too many failed requests from this client. ### Client types @@ -389,6 +394,7 @@ server will return an error and a normal `hello` handshake has to be performed. ### Error codes - `no_such_session`: The session id is no longer valid. +- `too_many_requests`: Too many failed requests from this client. ## Releasing sessions @@ -521,8 +527,11 @@ user, the backend returns an error and the room request will be rejected. ### Error codes +- `invalid_request`: The backend request could not be authenticated. Check your shared secret. - `no_such_room`: The requested room does not exist or the user is not invited to the room. +- `duplicate_session`: The given session already joined the room. +- `room_join_failed`: The Talk backend returned an unexpected response while joining the room. ## Join federated room diff --git a/hub.go b/hub.go index 27d2bfd..89b147c 100644 --- a/hub.go +++ b/hub.go @@ -54,18 +54,31 @@ import ( ) var ( - DuplicateClient = NewError("duplicate_client", "Client already registered.") - HelloExpected = NewError("hello_expected", "Expected Hello request.") + // HelloExpected is returned if a client sends a message before the "hello" request. + HelloExpected = NewError("hello_expected", "Expected Hello request.") + // InvalidHelloVersion is returned if the version in the "hello" message is not supported. InvalidHelloVersion = NewError("invalid_hello_version", "The hello version is not supported.") - UserAuthFailed = NewError("auth_failed", "The user could not be authenticated.") - RoomJoinFailed = NewError("room_join_failed", "Could not join the room.") - InvalidClientType = NewError("invalid_client_type", "The client type is not supported.") - InvalidBackendUrl = NewError("invalid_backend", "The backend URL is not supported.") - InvalidToken = NewError("invalid_token", "The passed token is invalid.") - NoSuchSession = NewError("no_such_session", "The session to resume does not exist.") - TokenNotValidYet = NewError("token_not_valid_yet", "The token is not valid yet.") - TokenExpired = NewError("token_expired", "The token is expired.") - TooManyRequests = NewError("too_many_requests", "Too many requests.") + // UserAuthFailed is returned if the Talk response to a v1 hello is not an error but also not a valid auth response. + UserAuthFailed = NewError("auth_failed", "The user could not be authenticated.") + // RoomJoinFailed is returned if the Talk response to a room join request is not an error and not a valid join response. + RoomJoinFailed = NewError("room_join_failed", "Could not join the room.") + // InvalidClientType is returned if the client type in the "hello" request is not supported. + InvalidClientType = NewError("invalid_client_type", "The client type is not supported.") + // InvalidBackendUrl is returned if no backend is configured for URL in the "hello" request. + InvalidBackendUrl = NewError("invalid_backend", "The backend URL is not supported.") + // InvalidToken is returned if the token in a "hello" request could not be validated. + InvalidToken = NewError("invalid_token", "The passed token is invalid.") + // NoSuchSession is returned if the session to be resumed is unknown or expired. + NoSuchSession = NewError("no_such_session", "The session to resume does not exist.") + // TokenNotValidYet is returned if the token in a "hello" request could be authenticated but is not valid yet. + // This hints to a mismatch in the time between the server running Talk and the server running the signaling server. + TokenNotValidYet = NewError("token_not_valid_yet", "The token is not valid yet.") + // TokenExpired is returned if the token in a "hello" request could be authenticated but is expired. + // This hints to a mismatch in the time between the server running Talk and the server running the signaling server, + // but could also be a client trying to connect with an old token. + TokenExpired = NewError("token_expired", "The token is expired.") + // TooManyRequests is returned if brute force detection reports too many failed "hello" requests. + TooManyRequests = NewError("too_many_requests", "Too many requests.") // Maximum number of concurrent requests to a backend. defaultMaxConcurrentRequestsPerHost = 8 From df757be9cfbd4af455321fe52fd6b17247a9713b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Jun 2025 08:23:33 +0200 Subject: [PATCH 115/549] Add "String()" method to BackendRoomDialoutRequest. --- api_backend.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api_backend.go b/api_backend.go index 5ba210a..b0aa36a 100644 --- a/api_backend.go +++ b/api_backend.go @@ -203,6 +203,14 @@ func (r *BackendRoomDialoutRequest) ValidateNumber() *Error { return nil } +func (r *BackendRoomDialoutRequest) String() string { + data, err := json.Marshal(r) + if err != nil { + return fmt.Sprintf("Could not serialize %#v: %s", r, err) + } + return string(data) +} + type TransientAction string const ( From ac35a0449d9cc8f2c534a9085824a7138f650803 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Jun 2025 08:24:13 +0200 Subject: [PATCH 116/549] Support multiple sessions for dialout. Will try to start request with all of them until one succeeds. Returns first received error if all failed. --- backend_server.go | 81 +++++++++++++++++++--------- backend_server_test.go | 116 +++++++++++++++++++++++++++++++++++++++++ hub.go | 6 +-- 3 files changed, 174 insertions(+), 29 deletions(-) diff --git a/backend_server.go b/backend_server.go index 477f04b..f344807 100644 --- a/backend_server.go +++ b/backend_server.go @@ -53,6 +53,8 @@ const ( randomUsernameLength = 32 sessionIdNotInMeeting = "0" + + startDialoutTimeout = 45 * time.Second ) type BackendServer struct { @@ -698,20 +700,7 @@ func isNumeric(s string) bool { return checkNumeric.MatchString(s) } -func (b *BackendServer) startDialout(roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { - if err := request.Dialout.ValidateNumber(); err != nil { - return returnDialoutError(http.StatusBadRequest, err) - } - - if !isNumeric(roomid) { - return returnDialoutError(http.StatusBadRequest, NewError("invalid_roomid", "The room id must be numeric.")) - } - - session := b.hub.GetDialoutSession(roomid, backend) - if session == nil { - return returnDialoutError(http.StatusNotFound, NewError("no_client_available", "No available client found to trigger dialout.")) - } - +func (b *BackendServer) startDialoutInSession(ctx context.Context, session *ClientSession, roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { url := backend.Url() if url == "" { // Old-style compat backend, use client-provided URL. @@ -734,7 +723,7 @@ func (b *BackendServer) startDialout(roomid string, backend *Backend, backendUrl }, } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + subCtx, cancel := context.WithTimeout(ctx, startDialoutTimeout) defer cancel() var response atomic.Pointer[DialoutInternalClientMessage] @@ -748,26 +737,30 @@ func (b *BackendServer) startDialout(roomid string, backend *Backend, backendUrl defer session.ClearResponseHandler(id) if !session.SendMessage(msg) { - return returnDialoutError(http.StatusBadGateway, NewError("error_notify", "Could not notify about new dialout.")) + return nil, NewError("error_notify", "Could not notify about new dialout.") } - <-ctx.Done() - if err := ctx.Err(); err != nil && !errors.Is(err, context.Canceled) { - return returnDialoutError(http.StatusGatewayTimeout, NewError("timeout", "Timeout while waiting for dialout to start.")) + <-subCtx.Done() + if err := subCtx.Err(); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return nil, NewError("timeout", "Timeout while waiting for dialout to start.") + } else if errors.Is(err, context.Canceled) && errors.Is(ctx.Err(), context.Canceled) { + // Upstream request was cancelled. + return nil, err + } } dialout := response.Load() if dialout == nil { - return returnDialoutError(http.StatusBadGateway, NewError("error_notify", "No dialout response received.")) + return nil, NewError("error_notify", "No dialout response received.") } switch dialout.Type { case "error": - return returnDialoutError(http.StatusBadGateway, dialout.Error) + return nil, dialout.Error case "status": if dialout.Status.Status != DialoutStatusAccepted { - log.Printf("Received unsupported dialout status when triggering dialout: %+v", dialout) - return returnDialoutError(http.StatusBadGateway, NewError("unsupported_status", "Unsupported dialout status received.")) + return nil, NewError("unsupported_status", fmt.Sprintf("Unsupported dialout status received: %+v", dialout)) } return &BackendServerRoomResponse{ @@ -776,10 +769,46 @@ func (b *BackendServer) startDialout(roomid string, backend *Backend, backendUrl CallId: dialout.Status.CallId, }, }, nil + default: + return nil, NewError("unsupported_type", fmt.Sprintf("Unsupported dialout type received: %+v", dialout)) + } +} + +func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { + if err := request.Dialout.ValidateNumber(); err != nil { + return returnDialoutError(http.StatusBadRequest, err) } - log.Printf("Received unsupported dialout type when triggering dialout: %+v", dialout) - return returnDialoutError(http.StatusBadGateway, NewError("unsupported_type", "Unsupported dialout type received.")) + if !isNumeric(roomid) { + return returnDialoutError(http.StatusBadRequest, NewError("invalid_roomid", "The room id must be numeric.")) + } + + var sessionError *Error + sessions := b.hub.GetDialoutSessions(roomid, backend) + for _, session := range sessions { + if ctx.Err() != nil { + // Upstream request was cancelled. + break + } + + response, err := b.startDialoutInSession(ctx, session, roomid, backend, backendUrl, request) + if err != nil { + log.Printf("Error starting dialout request %+v in session %s: %+v", request.Dialout, session.PublicId(), err) + var e *Error + if sessionError == nil && errors.As(err, &e) { + sessionError = e + } + continue + } + + return response, nil + } + + if sessionError != nil { + return returnDialoutError(http.StatusBadGateway, sessionError) + } + + return returnDialoutError(http.StatusNotFound, NewError("no_client_available", "No available client found to trigger dialout.")) } func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body []byte) { @@ -879,7 +908,7 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body case "switchto": err = b.sendRoomSwitchTo(roomid, backend, &request) case "dialout": - response, err = b.startDialout(roomid, backend, backendUrl, &request) + response, err = b.startDialout(r.Context(), roomid, backend, backendUrl, &request) default: http.Error(w, "Unsupported request type: "+request.Type, http.StatusBadRequest) return diff --git a/backend_server_test.go b/backend_server_test.go index b68b923..147d193 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -37,6 +37,7 @@ import ( "net/url" "strings" "sync" + "sync/atomic" "testing" "time" @@ -1791,3 +1792,118 @@ func TestBackendServer_DialoutRejected(t *testing.T) { } } } + +func TestBackendServer_DialoutFirstFailed(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + _, _, _, hub, _, server := CreateBackendServerForTest(t) + + client1 := NewTestClient(t, server, hub) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloInternalWithFeatures([]string{"start-dialout"})) + + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloInternalWithFeatures([]string{"start-dialout"})) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + _, err := client1.RunUntilHello(ctx) + require.NoError(err) + + _, err = client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "12345" + callId := "call-123" + + var returnedError atomic.Bool + + var wg sync.WaitGroup + runClient := func(client *TestClient) { + defer wg.Done() + + msg, err := client.RunUntilMessage(ctx) + if !assert.NoError(err) { + return + } + + if !assert.Equal("internal", msg.Type) || + !assert.NotNil(msg.Internal) || + !assert.Equal("dialout", msg.Internal.Type) || + !assert.NotNil(msg.Internal.Dialout) { + return + } + + assert.Equal(roomId, msg.Internal.Dialout.RoomId) + assert.Equal(server.URL+"/", msg.Internal.Dialout.Backend) + + var dialout *DialoutInternalClientMessage + // The first session should return an error to make sure the second is retried afterwards. + if returnedError.CompareAndSwap(false, true) { + errorCode := "error-code" + errorMessage := "rejected call" + + dialout = &DialoutInternalClientMessage{ + Type: "error", + Error: NewError(errorCode, errorMessage), + } + } else { + dialout = &DialoutInternalClientMessage{ + Type: "status", + RoomId: msg.Internal.Dialout.RoomId, + Status: &DialoutStatusInternalClientMessage{ + Status: "accepted", + CallId: callId, + }, + } + } + + response := &ClientMessage{ + Id: msg.Id, + Type: "internal", + Internal: &InternalClientMessage{ + Type: "dialout", + Dialout: dialout, + }, + } + assert.NoError(client.WriteJSON(response)) + } + + wg.Add(1) + go runClient(client1) + wg.Add(1) + go runClient(client2) + + defer func() { + wg.Wait() + }() + + msg := &BackendServerRoomRequest{ + Type: "dialout", + Dialout: &BackendRoomDialoutRequest{ + Number: "+1234567890", + }, + } + + data, err := json.Marshal(msg) + require.NoError(err) + res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) + require.NoError(err) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + assert.NoError(err) + require.Equal(http.StatusOK, res.StatusCode, "Expected success, got %s", string(body)) + + var response BackendServerRoomResponse + if err := json.Unmarshal(body, &response); assert.NoError(err) { + assert.Equal("dialout", response.Type) + if assert.NotNil(response.Dialout) { + assert.Nil(response.Dialout.Error, "expected dialout success, got %s", string(body)) + assert.Equal(callId, response.Dialout.CallId) + } + } +} diff --git a/hub.go b/hub.go index 89b147c..4255cfe 100644 --- a/hub.go +++ b/hub.go @@ -650,7 +650,7 @@ func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId string) (string, error) return h.roomSessions.GetSessionId(roomSessionId) } -func (h *Hub) GetDialoutSession(roomId string, backend *Backend) *ClientSession { +func (h *Hub) GetDialoutSessions(roomId string, backend *Backend) (result []*ClientSession) { url := backend.Url() h.mu.RLock() @@ -661,11 +661,11 @@ func (h *Hub) GetDialoutSession(roomId string, backend *Backend) *ClientSession } if session.GetClient() != nil { - return session + result = append(result, session) } } - return nil + return } func (h *Hub) GetBackend(u *url.URL) *Backend { From b35d7e9c57e10d110b78ba9971c9818576ce243e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 4 Jun 2025 11:16:52 +0200 Subject: [PATCH 117/549] make: Remove (empty) easyjson files from optimization step. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index b228d7a..99a7549 100644 --- a/Makefile +++ b/Makefile @@ -144,6 +144,7 @@ common: $(EASYJSON_GO_FILES) $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES) for file in $(EASYJSON_FILES); do \ rm -f easyjson-bootstrap*.go; \ TMPDIR=$(TMPDIR) PATH="$(GODIR)":$(PATH) "$(GOPATHBIN)/easyjson" -all $$file; \ + rm -f *_easyjson_easyjson.go; \ done $(BINDIR): From c7cbfcdce22c961376c313f96f8cf2740fe8d241 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 4 Jun 2025 14:49:17 +0200 Subject: [PATCH 118/549] Support filtering candidates received by clients. Can be customized through an allow- and blocklist. This can be used to prevent servers from trying to access private IPs when finding candidate pairs. --- api_signaling.go | 143 ++++++++++++++++- api_signaling_test.go | 168 ++++++++++++++++++++ go.mod | 8 + go.sum | 16 ++ hub.go | 62 +++++++- mcu_janus.go | 33 +++- mcu_janus_publisher.go | 36 +++-- mcu_janus_subscriber.go | 16 ++ mcu_janus_test.go | 335 ++++++++++++++++++++++++++++++++++++++-- mock_data_test.go | 57 +++++++ proxy.conf.in | 7 + proxy/proxy_server.go | 5 + server.conf.in | 7 + 13 files changed, 853 insertions(+), 40 deletions(-) diff --git a/api_signaling.go b/api_signaling.go index d3e55f7..0074b5e 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -26,12 +26,15 @@ import ( "errors" "fmt" "log" + "net" "net/url" + "slices" "sort" "strings" "time" "github.com/golang-jwt/jwt/v5" + "github.com/pion/ice/v4" "github.com/pion/sdp/v3" ) @@ -49,6 +52,11 @@ const ( var ( ErrNoSdp = NewError("no_sdp", "Payload does not contain a SDP.") ErrInvalidSdp = NewError("invalid_sdp", "Payload does not contain a valid SDP.") + + ErrNoCandidate = NewError("no_candidate", "Payload does not contain a candidate.") + ErrInvalidCandidate = NewError("invalid_candidate", "Payload does not contain a valid candidate.") + + ErrCandidateFiltered = errors.New("candidate was filtered") ) func makePtr[T any](v T) *T { @@ -718,13 +726,54 @@ type MessageClientMessageData struct { offerSdp *sdp.SessionDescription // Only set if Type == "offer" answerSdp *sdp.SessionDescription // Only set if Type == "answer" + candidate ice.Candidate // Only set if Type == "candidate" } +func (m *MessageClientMessageData) String() string { + data, err := json.Marshal(m) + if err != nil { + return fmt.Sprintf("Could not serialize %#v: %s", m, err) + } + return string(data) +} + +func parseSDP(s string) (*sdp.SessionDescription, error) { + var sdp sdp.SessionDescription + if err := sdp.UnmarshalString(s); err != nil { + return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", map[string]interface{}{ + "error": err.Error(), + }) + } + + for _, m := range sdp.MediaDescriptions { + for idx, a := range m.Attributes { + if !a.IsICECandidate() { + continue + } + + if _, err := ice.UnmarshalCandidate(a.Value); err != nil { + return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", map[string]interface{}{ + "media": m.MediaName.Media, + "idx": idx, + "error": err.Error(), + }) + } + } + } + + return &sdp, nil +} + +var ( + emptyCandidate = &ice.CandidateHost{} +) + func (m *MessageClientMessageData) CheckValid() error { if m.RoomType != "" && !IsValidStreamType(m.RoomType) { return fmt.Errorf("invalid room type: %s", m.RoomType) } - if m.Type == "offer" || m.Type == "answer" { + switch m.Type { + case "offer", "answer": sdpValue, found := m.Payload["sdp"] if !found { return ErrNoSdp @@ -734,23 +783,101 @@ func (m *MessageClientMessageData) CheckValid() error { return ErrInvalidSdp } - var sdp sdp.SessionDescription - if err := sdp.Unmarshal([]byte(sdpText)); err != nil { - return NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", map[string]interface{}{ - "error": err.Error(), - }) + sdp, err := parseSDP(sdpText) + if err != nil { + return err } switch m.Type { case "offer": - m.offerSdp = &sdp + m.offerSdp = sdp case "answer": - m.answerSdp = &sdp + m.answerSdp = sdp + } + case "candidate": + candValue, found := m.Payload["candidate"] + if !found { + return ErrNoCandidate + } + candItem, ok := candValue.(map[string]interface{}) + if !ok { + return ErrInvalidCandidate + } + candValue, ok = candItem["candidate"] + if !ok { + return ErrInvalidCandidate + } + candText, ok := candValue.(string) + if !ok { + return ErrInvalidCandidate + } + + if candText == "" { + m.candidate = emptyCandidate + } else { + cand, err := ice.UnmarshalCandidate(candText) + if err != nil { + return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", map[string]interface{}{ + "error": err.Error(), + }) + } + m.candidate = cand } } return nil } +func FilterCandidate(c ice.Candidate, allowed *AllowedIps, blocked *AllowedIps) bool { + switch c { + case nil: + return true + case emptyCandidate: + return false + } + + ip := net.ParseIP(c.Address()) + if len(ip) == 0 || ip.IsUnspecified() { + return true + } + + // Whitelist has preference. + if allowed != nil && allowed.Allowed(ip) { + return false + } + + // Check if address is blocked manually. + if blocked != nil && blocked.Allowed(ip) { + return true + } + + return false +} + +func FilterSDPCandidates(s *sdp.SessionDescription, allowed *AllowedIps, blocked *AllowedIps) bool { + modified := false + for _, m := range s.MediaDescriptions { + m.Attributes = slices.DeleteFunc(m.Attributes, func(a sdp.Attribute) bool { + if !a.IsICECandidate() { + return false + } + + if a.Value == "" { + return false + } + + c, err := ice.UnmarshalCandidate(a.Value) + if err != nil || FilterCandidate(c, allowed, blocked) { + modified = true + return true + } + + return false + }) + } + + return modified +} + func (m *MessageClientMessage) CheckValid() error { if len(m.Data) == 0 { return fmt.Errorf("message empty") diff --git a/api_signaling_test.go b/api_signaling_test.go index b33169b..3b08fc0 100644 --- a/api_signaling_test.go +++ b/api_signaling_test.go @@ -25,9 +25,12 @@ import ( "encoding/json" "fmt" "sort" + "strings" "testing" + "github.com/pion/ice/v4" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type testCheckValid interface { @@ -424,3 +427,168 @@ func Test_Welcome_AddRemoveFeature(t *testing.T) { msg.RemoveFeature("three", "one") assertEqualStrings(t, []string{"two"}, msg.Features) } + +func TestFilterCandidates(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + testcases := []struct { + candidate string + allowed string + blocked string + expectFiltered bool + }{ + // IPs can be filtered. + { + "candidate:1696121226 1 udp 2122129151 10.1.2.3 12345 typ host generation 0 ufrag YOs/ network-id 1", + "", + "10.0.0.0/8", + true, + }, + { + "candidate:1696121226 1 udp 2122129151 1.2.3.4 12345 typ host generation 0 ufrag YOs/ network-id 1", + "", + "10.0.0.0/8", + false, + }, + // IPs can be allowed. + { + "candidate:1696121226 1 udp 2122129151 10.1.2.3 12345 typ host generation 0 ufrag YOs/ network-id 1", + "10.1.0.0/16", + "10.0.0.0/8", + false, + }, + // IPs can be blocked. + { + "candidate:1696121226 1 udp 2122129151 1.2.3.4 12345 typ host generation 0 ufrag YOs/ network-id 1", + "", + "1.2.0.0/16", + true, + }, + } + + for idx, tc := range testcases { + candidate, err := ice.UnmarshalCandidate(tc.candidate) + if !assert.NoError(err, "parsing candidate %s failed in testcase %d", tc.candidate, idx) { + continue + } + + var allowed *AllowedIps + if tc.allowed != "" { + allowed, err = ParseAllowedIps(tc.allowed) + if !assert.NoError(err, "parsing allowed list %s failed in testcase %d", tc.allowed, idx) { + continue + } + } + + var blocked *AllowedIps + if tc.blocked != "" { + blocked, err = ParseAllowedIps(tc.blocked) + if !assert.NoError(err, "parsing blocked list %s failed in testcase %d", tc.blocked, idx) { + continue + } + } + + filtered := FilterCandidate(candidate, allowed, blocked) + assert.Equal(tc.expectFiltered, filtered, "failed in testcase %d", idx) + } +} + +func TestFilterSDPCandidates(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + s, err := parseSDP(MockSdpOfferAudioOnly) + require.NoError(err) + if encoded, err := s.Marshal(); assert.NoError(err) { + assert.Equal(MockSdpOfferAudioOnly, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + } + + expectedBefore := map[string]int{ + "audio": 4, + } + for _, m := range s.MediaDescriptions { + count := 0 + for _, a := range m.Attributes { + if a.IsICECandidate() { + count++ + } + } + + assert.EqualValues(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + } + + blocked, err := ParseAllowedIps("192.0.0.0/24, 192.168.0.0/16") + require.NoError(err) + + expectedAfter := map[string]int{ + "audio": 2, + } + if filtered := FilterSDPCandidates(s, nil, blocked); assert.True(filtered, "should have filtered") { + for _, m := range s.MediaDescriptions { + count := 0 + for _, a := range m.Attributes { + if a.IsICECandidate() { + count++ + } + } + + assert.EqualValues(expectedAfter[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + } + } + + if encoded, err := s.Marshal(); assert.NoError(err) { + assert.NotEqual(MockSdpOfferAudioOnly, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + } +} + +func TestNoFilterSDPCandidates(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + s, err := parseSDP(MockSdpOfferAudioOnlyNoFilter) + require.NoError(err) + if encoded, err := s.Marshal(); assert.NoError(err) { + assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + } + + expectedBefore := map[string]int{ + "audio": 2, + } + for _, m := range s.MediaDescriptions { + count := 0 + for _, a := range m.Attributes { + if a.IsICECandidate() { + count++ + } + } + + assert.EqualValues(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + } + + blocked, err := ParseAllowedIps("192.0.0.0/24, 192.168.0.0/16") + require.NoError(err) + + expectedAfter := map[string]int{ + "audio": 2, + } + if filtered := FilterSDPCandidates(s, nil, blocked); assert.False(filtered, "should not have filtered") { + for _, m := range s.MediaDescriptions { + count := 0 + for _, a := range m.Attributes { + if a.IsICECandidate() { + count++ + } + } + + assert.EqualValues(expectedAfter[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + } + } + + if encoded, err := s.Marshal(); assert.NoError(err) { + assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + } +} diff --git a/go.mod b/go.mod index 69c0b54..3728813 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/nats-io/nats.go v1.43.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 + github.com/pion/ice/v4 v4.0.10 github.com/pion/sdp/v3 v3.0.13 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 @@ -56,7 +57,13 @@ require ( github.com/nats-io/jwt/v2 v2.7.4 // indirect github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect + github.com/pion/dtls/v3 v3.0.6 // indirect + github.com/pion/logging v0.2.3 // indirect + github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect + github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/transport/v3 v3.0.7 // indirect + github.com/pion/turn/v4 v4.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect @@ -65,6 +72,7 @@ require ( github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect + github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.etcd.io/etcd/pkg/v3 v3.6.1 // indirect diff --git a/go.sum b/go.sum index 16838b4..620e4fe 100644 --- a/go.sum +++ b/go.sum @@ -90,10 +90,24 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= +github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= +github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= +github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= +github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= +github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= +github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= +github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= +github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4= github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= +github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= @@ -121,6 +135,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/hub.go b/hub.go index 4255cfe..1705204 100644 --- a/hub.go +++ b/hub.go @@ -200,6 +200,9 @@ type Hub struct { skipFederationVerify bool federationTimeout time.Duration + + allowedCandidates atomic.Pointer[AllowedIps] + blockedCandidates atomic.Pointer[AllowedIps] } func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { @@ -388,6 +391,29 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer skipFederationVerify: skipFederationVerify, federationTimeout: federationTimeout, } + if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { + allowed, err := ParseAllowedIps(value) + if err != nil { + return nil, fmt.Errorf("invalid allowedcandidates: %w", err) + } + + log.Printf("Candidates allowlist: %s", allowed) + hub.allowedCandidates.Store(allowed) + } else { + log.Printf("No candidates allowlist") + } + if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { + blocked, err := ParseAllowedIps(value) + if err != nil { + return nil, fmt.Errorf("invalid blockedcandidates: %w", err) + } + + log.Printf("Candidates blocklist: %s", blocked) + hub.blockedCandidates.Store(blocked) + } else { + log.Printf("No candidates blocklist") + } + hub.trustedProxies.Store(trustedProxiesIps) if len(geoipOverrides) > 0 { hub.geoipOverrides.Store(&geoipOverrides) @@ -540,6 +566,29 @@ func (h *Hub) Reload(config *goconf.ConfigFile) { h.geoipOverrides.Store(nil) } + if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { + if allowed, err := ParseAllowedIps(value); err != nil { + log.Printf("invalid allowedcandidates: %s", err) + } else { + log.Printf("Candidates allowlist: %s", allowed) + h.allowedCandidates.Store(allowed) + } + } else { + log.Printf("No candidates allowlist") + h.allowedCandidates.Store(nil) + } + if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { + if blocked, err := ParseAllowedIps(value); err != nil { + log.Printf("invalid blockedcandidates: %s", err) + } else { + log.Printf("Candidates blocklist: %s", blocked) + h.blockedCandidates.Store(blocked) + } + } else { + log.Printf("No candidates blocklist") + h.blockedCandidates.Store(nil) + } + if h.mcu != nil { h.mcu.Reload(config) } @@ -2662,6 +2711,11 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe clientType = "subscriber" mc = session.GetSubscriber(message.Recipient.SessionId, StreamType(data.RoomType)) default: + if data.Type == "candidate" && FilterCandidate(data.candidate, h.allowedCandidates.Load(), h.blockedCandidates.Load()) { + // Silently ignore filtered candidates. + return + } + if session.PublicId() == message.Recipient.SessionId { if err := session.IsAllowedToSend(data); err != nil { log.Printf("Session %s is not allowed to send candidate for %s, ignoring (%s)", session.PublicId(), data.RoomType, err) @@ -2688,10 +2742,12 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe mc.SendMessage(session.Context(), message, data, func(err error, response map[string]interface{}) { if err != nil { - log.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) - sendMcuProcessingFailed(session, client_message) + if !errors.Is(err, ErrCandidateFiltered) { + log.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) + sendMcuProcessingFailed(session, client_message) + } return - } else if response == nil { + } else if len(response) == 0 { // No response received return } diff --git a/mcu_janus.go b/mcu_janus.go index d90fd13..e3779a7 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -137,9 +137,12 @@ type clientInterface interface { type mcuJanusSettings struct { mcuCommonSettings + + allowedCandidates atomic.Pointer[AllowedIps] + blockedCandidates atomic.Pointer[AllowedIps] } -func newMcuJanusSettings(config *goconf.ConfigFile) (McuSettings, error) { +func newMcuJanusSettings(config *goconf.ConfigFile) (*mcuJanusSettings, error) { settings := &mcuJanusSettings{} if err := settings.load(config); err != nil { return nil, err @@ -160,6 +163,32 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { mcuTimeout := time.Duration(mcuTimeoutSeconds) * time.Second log.Printf("Using a timeout of %s for MCU requests", mcuTimeout) s.setTimeout(mcuTimeout) + + if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { + allowed, err := ParseAllowedIps(value) + if err != nil { + return fmt.Errorf("invalid allowedcandidates: %w", err) + } + + log.Printf("Candidates allowlist: %s", allowed) + s.allowedCandidates.Store(allowed) + } else { + log.Printf("No candidates allowlist") + s.allowedCandidates.Store(nil) + } + if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { + blocked, err := ParseAllowedIps(value) + if err != nil { + return fmt.Errorf("invalid blockedcandidates: %w", err) + } + + log.Printf("Candidates blocklist: %s", blocked) + s.blockedCandidates.Store(blocked) + } else { + log.Printf("No candidates blocklist") + s.blockedCandidates.Store(nil) + } + return nil } @@ -173,7 +202,7 @@ type mcuJanus struct { url string mu sync.Mutex - settings McuSettings + settings *mcuJanusSettings createJanusGateway func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 92896c6..d555aa7 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -179,6 +179,17 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli return } + if FilterSDPCandidates(data.offerSdp, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + // Update request with filtered SDP. + marshalled, err := data.offerSdp.Marshal() + if err != nil { + go callback(fmt.Errorf("could not marshal filtered offer: %w", err), nil) + return + } + + jsep_msg["sdp"] = string(marshalled) + } + p.offerSdp.Store(data.offerSdp) p.sdpFlags.Add(sdpHasOffer) if p.sdpFlags.Get() == sdpHasAnswer|sdpHasOffer { @@ -203,18 +214,16 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli sdpString, ok := sdpData.(string) if !ok { log.Printf("Invalid sdp found in answer %+v", jsep) + } else if answerSdp, err := parseSDP(sdpString); err != nil { + log.Printf("Error parsing answer sdp %+v: %s", sdpString, err) + p.answerSdp.Store(nil) + p.sdpFlags.Remove(sdpHasAnswer) } else { - var answerSdp sdp.SessionDescription - if err := answerSdp.UnmarshalString(sdpString); err != nil { - log.Printf("Error parsing answer sdp %+v: %s", sdpString, err) - p.answerSdp.Store(nil) - p.sdpFlags.Remove(sdpHasAnswer) - } else { - p.answerSdp.Store(&answerSdp) - p.sdpFlags.Add(sdpHasAnswer) - if p.sdpFlags.Get() == sdpHasAnswer|sdpHasOffer { - p.sdpReady.Close() - } + // Note: we don't need to filter the SDP received from Janus. + p.answerSdp.Store(answerSdp) + p.sdpFlags.Add(sdpHasAnswer) + if p.sdpFlags.Get() == sdpHasAnswer|sdpHasOffer { + p.sdpReady.Close() } } } @@ -223,6 +232,11 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli }) } case "candidate": + if FilterCandidate(data.candidate, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + go callback(ErrCandidateFiltered, nil) + return + } + p.deferred <- func() { msgctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index a77d401..9e75967 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -294,6 +294,17 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl } case "answer": p.deferred <- func() { + if FilterSDPCandidates(data.answerSdp, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + // Update request with filtered SDP. + marshalled, err := data.answerSdp.Marshal() + if err != nil { + go callback(fmt.Errorf("could not marshal filtered answer: %w", err), nil) + return + } + + jsep_msg["sdp"] = string(marshalled) + } + msgctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() @@ -304,6 +315,11 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl } } case "candidate": + if FilterCandidate(data.candidate, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + go callback(ErrCandidateFiltered, nil) + return + } + p.deferred <- func() { msgctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() diff --git a/mcu_janus_test.go b/mcu_janus_test.go index f7407d8..ba67cc2 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "strings" "sync" "sync/atomic" "testing" @@ -43,7 +44,7 @@ type TestJanusRoom struct { id uint64 } -type TestJanusHandler func(room *TestJanusRoom, body map[string]interface{}) (interface{}, *janus.ErrorMsg) +type TestJanusHandler func(room *TestJanusRoom, body map[string]interface{}, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) type TestJanusGateway struct { t *testing.T @@ -59,6 +60,8 @@ type TestJanusGateway struct { handles map[uint64]*TestJanusHandle rooms map[uint64]*TestJanusRoom handlers map[string]TestJanusHandler + + handleRooms map[*TestJanusHandle]*TestJanusRoom } func NewTestJanusGateway(t *testing.T) *TestJanusGateway { @@ -70,6 +73,8 @@ func NewTestJanusGateway(t *testing.T) *TestJanusGateway { handles: make(map[uint64]*TestJanusHandle), rooms: make(map[uint64]*TestJanusRoom), handlers: make(map[string]TestJanusHandler), + + handleRooms: make(map[*TestJanusHandle]*TestJanusRoom), } t.Cleanup(func() { @@ -80,6 +85,7 @@ func NewTestJanusGateway(t *testing.T) *TestJanusGateway { assert.Len(gateway.transactions, 0) assert.Len(gateway.handles, 0) assert.Len(gateway.rooms, 0) + assert.Len(gateway.handleRooms, 0) }) return gateway @@ -128,7 +134,7 @@ func (g *TestJanusGateway) Close() error { return nil } -func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body map[string]interface{}) interface{} { +func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body map[string]interface{}, jsep map[string]interface{}) interface{} { request := body["request"].(string) switch request { case "create": @@ -158,6 +164,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan } assert.Equal(g.t, "publisher", body["ptype"]) + g.handleRooms[handle] = room return &janus.EventMsg{ Session: session.Id, Handle: handle.id, @@ -180,7 +187,13 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan } } + assert.EqualValues(g.t, room.id, uint64(rid)) delete(g.rooms, uint64(rid)) + for h, r := range g.handleRooms { + if r.id == room.id { + delete(g.handleRooms, h) + } + } return &janus.SuccessMsg{ PluginData: janus.PluginData{ @@ -189,23 +202,38 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan }, } default: - rid := body["room"].(float64) - room := g.rooms[uint64(rid)] - if room == nil { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, - Reason: "Room not found", - }, + var room *TestJanusRoom + if roomId, found := body["room"]; found { + rid := roomId.(float64) + if room = g.rooms[uint64(rid)]; room == nil { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, + Reason: "Room not found", + }, + } + } + } else { + if room, found = g.handleRooms[handle]; !found { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: JANUS_VIDEOROOM_ERROR_INVALID_REQUEST, + Reason: "No joined to a room yet.", + }, + } } } handler, found := g.handlers[request] if found { var err *janus.ErrorMsg - result, err := handler(room, body) + result, err := handler(room, body, jsep) if err != nil { result = err + } else { + if request == "start" { + g.handleRooms[handle] = room + } } return result } @@ -265,7 +293,7 @@ func (g *TestJanusGateway) processRequest(msg map[string]interface{}) interface{ case "destroy": delete(g.sessions, session.Id) return &janus.AckMsg{} - case "message": + case "message", "trickle": hid := msg["handle_id"].(float64) handle, found := g.handles[uint64(hid)] if !found { @@ -277,8 +305,45 @@ func (g *TestJanusGateway) processRequest(msg map[string]interface{}) interface{ } } - body := msg["body"].(map[string]interface{}) - return g.processMessage(session, handle, body) + var result interface{} + switch method { + case "message": + body := msg["body"].(map[string]interface{}) + if jsep, found := msg["jsep"]; found { + result = g.processMessage(session, handle, body, jsep.(map[string]interface{})) + } else { + result = g.processMessage(session, handle, body, nil) + } + case "trickle": + room, found := g.handleRooms[handle] + if !found { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: JANUS_VIDEOROOM_ERROR_INVALID_REQUEST, + Reason: "No joined to a room yet.", + }, + } + } + + handler, found := g.handlers[method.(string)] + if found { + var err *janus.ErrorMsg + result, err = handler(room, msg, nil) + if err != nil { + result = err + } + } + } + + if ev, ok := result.(*janus.EventMsg); ok { + if ev.Session == 0 { + ev.Session = uint64(sid) + } + if ev.Handle == 0 { + ev.Handle = handle.id + } + } + return result } return nil @@ -331,6 +396,9 @@ func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { gateway := NewTestJanusGateway(t) config := goconf.NewConfigFile() + if strings.Contains(t.Name(), "Filter") { + config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") + } mcu, err := NewMcuJanus(context.Background(), "", config) require.NoError(t, err) t.Cleanup(func() { @@ -415,6 +483,239 @@ func (i *TestMcuInitiator) Country() string { return i.country } +func Test_JanusPublisherFilterOffer(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + if assert.NotNil(jsep) { + // The SDP received by Janus will be filtered from blocked candidates. + if sdpValue, found := jsep["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "sdp": MockSdpAnswerAudioOnly, + }, + }, nil + }, + "trickle": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.AckMsg{}, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "publisher-id" + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + // Send offer containing candidates that will be blocked / filtered. + data := &MessageClientMessageData{ + Type: "offer", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioOnly, + }, + } + require.NoError(data.CheckValid()) + + var wg sync.WaitGroup + wg.Add(1) + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer wg.Done() + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + wg.Wait() + + data = &MessageClientMessageData{ + Type: "candidate", + Payload: map[string]interface{}{ + "candidate": map[string]interface{}{ + "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer wg.Done() + + assert.ErrorContains(err, "filtered") + assert.Empty(m) + }) + wg.Wait() + + data = &MessageClientMessageData{ + Type: "candidate", + Payload: map[string]interface{}{ + "candidate": map[string]interface{}{ + "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer wg.Done() + + assert.NoError(err) + assert.Empty(m) + }) + wg.Wait() +} + +func Test_JanusSubscriberFilterAnswer(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "start": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + if assert.NotNil(jsep) { + // The SDP received by Janus will be filtered from blocked candidates. + if sdpValue, found := jsep["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpAnswerAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: map[string]interface{}{ + "room": room.id, + "started": true, + "videoroom": "event", + }, + }, + }, nil + }, + "trickle": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.AckMsg{}, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "publisher-id" + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + listener2 := &TestMcuListener{ + id: pubId, + } + + initiator2 := &TestMcuInitiator{ + country: "DE", + } + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) + require.NoError(err) + defer sub.Close(context.Background()) + + // Send answer containing candidates that will be blocked / filtered. + data := &MessageClientMessageData{ + Type: "answer", + Payload: map[string]interface{}{ + "sdp": MockSdpAnswerAudioOnly, + }, + } + require.NoError(data.CheckValid()) + + var wg sync.WaitGroup + wg.Add(1) + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer wg.Done() + + if assert.NoError(err) { + assert.Empty(m) + } + }) + wg.Wait() + + data = &MessageClientMessageData{ + Type: "candidate", + Payload: map[string]interface{}{ + "candidate": map[string]interface{}{ + "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer wg.Done() + + assert.ErrorContains(err, "filtered") + assert.Empty(m) + }) + wg.Wait() + + data = &MessageClientMessageData{ + Type: "candidate", + Payload: map[string]interface{}{ + "candidate": map[string]interface{}{ + "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer wg.Done() + + assert.NoError(err) + assert.Empty(m) + }) + wg.Wait() +} + func Test_JanusPublisherSubscriber(t *testing.T) { CatchLogForTest(t) t.Parallel() @@ -512,8 +813,9 @@ func Test_JanusRemotePublisher(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "add_remote_publisher": func(room *TestJanusRoom, body map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "add_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { assert.EqualValues(1, room.id) + assert.Nil(jsep) if streams := body["streams"].([]interface{}); assert.Len(streams, 1) { stream := streams[0].(map[string]interface{}) assert.Equal("0", stream["mid"]) @@ -533,8 +835,9 @@ func Test_JanusRemotePublisher(t *testing.T) { }, }, nil }, - "remove_remote_publisher": func(room *TestJanusRoom, body map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "remove_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { assert.EqualValues(1, room.id) + assert.Nil(jsep) removed.Add(1) return &janus.SuccessMsg{ PluginData: janus.PluginData{ diff --git a/mock_data_test.go b/mock_data_test.go index f90927a..495b2d0 100644 --- a/mock_data_test.go +++ b/mock_data_test.go @@ -168,5 +168,62 @@ a=rtcp-fb:99 nack a=rtcp-fb:99 nack pli a=rtcp-fb:99 ccm fir a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid +` + + MockSdpOfferAudioOnlyNoFilter = `v=0 +o=- 20518 0 IN IP4 0.0.0.0 +s=- +t=0 0 +a=group:BUNDLE audio-D.ietf-mmusic-sdp-bundle-negotiation +a=ice-options:trickle-D.ietf-mmusic-trickle-ice +m=audio 54609 UDP/TLS/RTP/SAVPF 109 0 8 +c=IN IP4 192.168.0.1 +a=mid:audio +a=msid:ma ta +a=sendrecv +a=rtpmap:109 opus/48000/2 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=maxptime:120 +a=ice-ufrag:074c6550 +a=ice-pwd:a28a397a4c3f31747d1ee3474af08a068 +a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 +a=setup:actpass +a=tls-id:1 +a=rtcp-mux +a=rtcp:60065 IN IP4 192.168.0.1 +a=rtcp-rsize +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid +a=candidate:0 1 UDP 2122194687 192.0.2.4 61665 typ host +a=candidate:0 2 UDP 2122194687 192.0.2.4 61667 typ host +a=end-of-candidates +` + MockSdpAnswerAudioOnlyNoFilter = `v=0 +o=- 16833 0 IN IP4 0.0.0.0 +s=- +t=0 0 +a=group:BUNDLE audio +a=ice-options:trickle +m=audio 49203 UDP/TLS/RTP/SAVPF 109 0 8 +c=IN IP4 192.168.0.1 +a=mid:audio +a=msid:ma ta +a=sendrecv +a=rtpmap:109 opus/48000/2 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=maxptime:120 +a=ice-ufrag:05067423 +a=ice-pwd:1747d1ee3474a28a397a4c3f3af08a068 +a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08 +a=setup:active +a=tls-id:1 +a=rtcp-mux +a=rtcp-rsize +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid +a=candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host +a=end-of-candidates ` ) diff --git a/proxy.conf.in b/proxy.conf.in index 5cbb3c4..22174ac 100644 --- a/proxy.conf.in +++ b/proxy.conf.in @@ -82,6 +82,13 @@ url = ws://localhost:8188/ # Default is 2 mbit/sec. #maxscreenbitrate = 2097152 +# List of IP addresses / subnets that are allowed to be used by clients in +# candidates. The allowed list has preference over the blocked list below. +#allowedcandidates = 10.0.0.0/8 + +# List of IP addresses / subnets to filter from candidates received by clients. +#blockedcandidates = 1.2.3.0/24 + [stats] # Comma-separated list of IP addresses that are allowed to access the stats # endpoint. Leave empty (or commented) to only allow access from "127.0.0.1". diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 7571121..21b5ed8 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -1346,6 +1346,11 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response map[string]interface{}) { var responseMsg *signaling.ProxyServerMessage + if errors.Is(err, signaling.ErrCandidateFiltered) { + // Silently ignore filtered candidates. + err = nil + } + if err != nil { log.Printf("Error sending %+v to %s client %s: %s", mcuData, mcuClient.StreamType(), payload.ClientId, err) responseMsg = message.NewWrappedErrorServerMessage(err) diff --git a/server.conf.in b/server.conf.in index 85630d5..6703531 100644 --- a/server.conf.in +++ b/server.conf.in @@ -179,6 +179,13 @@ connectionsperhost = 8 # proxy server that is used. #maxscreenbitrate = 2097152 +# List of IP addresses / subnets that are allowed to be used by clients in +# candidates. The allowed list has preference over the blocked list below. +#allowedcandidates = 10.0.0.0/8 + +# List of IP addresses / subnets to filter from candidates received by clients. +#blockedcandidates = 1.2.3.0/24 + # For type "proxy": timeout in seconds for requests to the proxy server. #proxytimeout = 2 From 47a7a9591aa4624986843fc60d4cf80f82a5d6c6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 4 Jun 2025 20:25:59 +0200 Subject: [PATCH 119/549] docker: Make candidates lists configurable. --- docker/README.md | 4 ++++ docker/proxy/entrypoint.sh | 6 ++++++ docker/server/entrypoint.sh | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/docker/README.md b/docker/README.md index 9732cd2..26cf4df 100644 --- a/docker/README.md +++ b/docker/README.md @@ -55,6 +55,8 @@ The running container can be configured through different environment variables: - `PROXY_KEY_PREFIX`: Key prefix of proxy entries. - `MAX_STREAM_BITRATE`: Optional global maximum bitrate for audio/video streams. - `MAX_SCREEN_BITRATE`: Optional global maximum bitrate for screensharing streams. +- `ALLOWED_CANDIDATES`: List of IP addresses / subnets that are allowed to be used by clients in candidates. The allowed list has preference over the blocked list below. +- `BLOCKED_CANDIDATES`: List of IP addresses / subnets to filter from candidates received by clients. - `TURN_API_KEY`: API key that Janus will need to send when requesting TURN credentials. - `TURN_SECRET`: The shared secret to use for generating TURN credentials. - `TURN_SERVERS`: A comma-separated list of TURN servers to use. @@ -117,6 +119,8 @@ The running container can be configured through different environment variables: - `JANUS_URL`: Url to Janus server. - `MAX_STREAM_BITRATE`: Optional maximum bitrate for audio/video streams. - `MAX_SCREEN_BITRATE`: Optional maximum bitrate for screensharing streams. +- `ALLOWED_CANDIDATES`: List of IP addresses / subnets that are allowed to be used by clients in candidates. The allowed list has preference over the blocked list below. +- `BLOCKED_CANDIDATES`: List of IP addresses / subnets to filter from candidates received by clients. - `STATS_IPS`: Comma-separated list of IP addresses that are allowed to access the stats endpoint. - `TRUSTED_PROXIES`: Comma-separated list of IPs / networks that are trusted proxies. - `ETCD_ENDPOINTS`: Static list of etcd endpoints (if etcd should be used). diff --git a/docker/proxy/entrypoint.sh b/docker/proxy/entrypoint.sh index 5a2d2ff..116a9dd 100755 --- a/docker/proxy/entrypoint.sh +++ b/docker/proxy/entrypoint.sh @@ -96,6 +96,12 @@ if [ ! -f "$CONFIG" ]; then if [ -n "$MAX_SCREEN_BITRATE" ]; then sed -i "s|#maxscreenbitrate =.*|maxscreenbitrate = $MAX_SCREEN_BITRATE|" "$CONFIG" fi + if [ -n "$ALLOWED_CANDIDATES" ]; then + sed -i "s|#allowedcandidates =.*|allowedcandidates = $ALLOWED_CANDIDATES|" "$CONFIG" + fi + if [ -n "$BLOCKED_CANDIDATES" ]; then + sed -i "s|#blockedcandidates =.*|blockedcandidates = $BLOCKED_CANDIDATES|" "$CONFIG" + fi if [ -n "$TOKENS_ETCD" ]; then if [ -z "$HAS_ETCD" ]; then diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index fefee50..6b1195f 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -150,6 +150,12 @@ if [ ! -f "$CONFIG" ]; then if [ -n "$MAX_SCREEN_BITRATE" ]; then sed -i "s|#maxscreenbitrate =.*|maxscreenbitrate = $MAX_SCREEN_BITRATE|" "$CONFIG" fi + if [ -n "$ALLOWED_CANDIDATES" ]; then + sed -i "s|#allowedcandidates =.*|allowedcandidates = $ALLOWED_CANDIDATES|" "$CONFIG" + fi + if [ -n "$BLOCKED_CANDIDATES" ]; then + sed -i "s|#blockedcandidates =.*|blockedcandidates = $BLOCKED_CANDIDATES|" "$CONFIG" + fi if [ -n "$SKIP_VERIFY" ]; then sed -i "s|#skipverify =.*|skipverify = $SKIP_VERIFY|" "$CONFIG" From dbd13da119bbc80aefe311b9b997eaf79f09515f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 4 Jun 2025 20:39:45 +0200 Subject: [PATCH 120/549] Increase test coverage on publisher "GetStreams". --- mcu_janus_test.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index ba67cc2..7d93dce 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -716,6 +716,177 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { wg.Wait() } +func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + if assert.NotNil(jsep) { + if sdpValue, found := jsep["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpOfferAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "sdp": MockSdpAnswerAudioOnly, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "publisher-id" + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + data := &MessageClientMessageData{ + Type: "offer", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioOnly, + }, + } + require.NoError(data.CheckValid()) + + done := make(chan struct{}) + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + <-done + + if streams, err := pub.GetStreams(ctx); assert.NoError(err) { + if assert.Len(streams, 1) { + stream := streams[0] + assert.Equal("audio", stream.Type) + assert.Equal("audio", stream.Mid) + assert.EqualValues(0, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("opus", stream.Codec) + assert.False(stream.Stereo) + assert.False(stream.Fec) + assert.False(stream.Dtx) + } + } +} + +func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + if assert.NotNil(jsep) { + _, found := jsep["sdp"] + assert.True(found) + } + + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "publisher-id" + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + data := &MessageClientMessageData{ + Type: "offer", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioAndVideo, + }, + } + require.NoError(data.CheckValid()) + + // Defer sending of offer / answer so "GetStreams" will wait. + go func() { + done := make(chan struct{}) + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + <-done + }() + + if streams, err := pub.GetStreams(ctx); assert.NoError(err) { + if assert.Len(streams, 2) { + stream := streams[0] + assert.Equal("audio", stream.Type) + assert.Equal("audio", stream.Mid) + assert.EqualValues(0, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("opus", stream.Codec) + assert.False(stream.Stereo) + assert.False(stream.Fec) + assert.False(stream.Dtx) + + stream = streams[1] + assert.Equal("video", stream.Type) + assert.Equal("video", stream.Mid) + assert.EqualValues(1, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("H264", stream.Codec) + assert.Equal("4d0028", stream.ProfileH264) + } + } +} + func Test_JanusPublisherSubscriber(t *testing.T) { CatchLogForTest(t) t.Parallel() From 91b03d3d25a41b175bf1b19618e090436780548e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 4 Jun 2025 21:17:30 +0200 Subject: [PATCH 121/549] Increase test coverage for "requestoffer". --- mcu_janus_test.go | 184 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 170 insertions(+), 14 deletions(-) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 7d93dce..1169dc4 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -38,10 +38,14 @@ import ( type TestJanusHandle struct { id uint64 + + sdp atomic.Value } type TestJanusRoom struct { id uint64 + + publisher atomic.Pointer[TestJanusHandle] } type TestJanusHandler func(room *TestJanusRoom, body map[string]interface{}, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) @@ -155,25 +159,57 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan rid := body["room"].(float64) room := g.rooms[uint64(rid)] if room == nil { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, - Reason: "Room not found", + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: map[string]interface{}{ + "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, + }, }, } } - assert.Equal(g.t, "publisher", body["ptype"]) g.handleRooms[handle] = room - return &janus.EventMsg{ - Session: session.Id, - Handle: handle.id, - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: map[string]interface{}{ - "room": room.id, + switch body["ptype"] { + case "publisher": + if !assert.True(g.t, room.publisher.CompareAndSwap(nil, handle)) { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED, + Reason: "Already publisher in this room", + }, + } + } + + return &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: map[string]interface{}{ + "room": room.id, + }, }, - }, + } + case "subscriber": + publisher := room.publisher.Load() + if publisher == nil || publisher.sdp.Load() == nil { + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: map[string]interface{}{ + "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED, + }, + }, + } + } + + sdp := publisher.sdp.Load() + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "sdp": sdp.(string), + }, + } } case "destroy": rid := body["room"].(float64) @@ -231,8 +267,26 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan if err != nil { result = err } else { - if request == "start" { + switch request { + case "start": g.handleRooms[handle] = room + case "configure": + if sdp, found := jsep["sdp"]; found { + handle.sdp.Store(sdp.(string)) + // Simulate "connected" event. + go func() { + time.Sleep(10 * time.Millisecond) + session.Lock() + h, found := session.Handles[handle.id] + session.Unlock() + if found { + h.Events <- &janus.WebRTCUpMsg{ + Session: session.Id, + Handle: h.Id, + } + } + }() + } } } return result @@ -973,6 +1027,108 @@ func Test_JanusSubscriberPublisher(t *testing.T) { <-done } +func Test_JanusSubscriberRequestOffer(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + var originalOffer atomic.Value + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + if assert.NotNil(jsep) { + if sdp, found := jsep["sdp"]; assert.True(found) { + originalOffer.Store(strings.ReplaceAll(sdp.(string), "\r\n", "\n")) + } + } + + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "publisher-id" + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + listener2 := &TestMcuListener{ + id: pubId, + } + + initiator2 := &TestMcuInitiator{ + country: "DE", + } + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) + require.NoError(err) + defer sub.Close(context.Background()) + + go func() { + data := &MessageClientMessageData{ + Type: "offer", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioAndVideo, + }, + } + require.NoError(data.CheckValid()) + + done := make(chan struct{}) + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + <-done + }() + + data := &MessageClientMessageData{ + Type: "requestoffer", + } + require.NoError(data.CheckValid()) + + done := make(chan struct{}) + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + if sdp := originalOffer.Load(); assert.NotNil(sdp) { + assert.Equal(sdp.(string), strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + } + }) + <-done +} + func Test_JanusRemotePublisher(t *testing.T) { CatchLogForTest(t) t.Parallel() From 1ec2d9d83b74410e1c38f187eab2649cba373186 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:07:51 +0000 Subject: [PATCH 122/549] Bump markdown from 3.8 to 3.8.2 in /docs Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.8 to 3.8.2. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.8...3.8.2) --- updated-dependencies: - dependency-name: markdown dependency-version: 3.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c42a3ea..6d9e4ec 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ jinja2==3.1.6 -markdown==3.8 +markdown==3.8.2 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==8.2.3 From af87f11f3c437658227aeb20f901a432e9df1335 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 23 Jun 2025 13:47:41 +0200 Subject: [PATCH 123/549] Only forward actor id / -type in "addsession" request if both are given. --- hub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub.go b/hub.go index 4255cfe..fbd03f2 100644 --- a/hub.go +++ b/hub.go @@ -2339,7 +2339,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { return } - if msg.Options != nil { + if msg.Options != nil && msg.Options.ActorId != "" && msg.Options.ActorType != "" { request := NewBackendClientRoomRequest(room.Id(), msg.UserId, publicSessionId) request.Room.ActorId = msg.Options.ActorId request.Room.ActorType = msg.Options.ActorType From 4ab2fc0227d8af6b764b88c8622eb4f9d79678e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:52:42 +0000 Subject: [PATCH 124/549] Bump github.com/pion/sdp/v3 from 3.0.13 to 3.0.14 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.13 to 3.0.14. - [Release notes](https://github.com/pion/sdp/releases) - [Changelog](https://github.com/pion/sdp/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/sdp/compare/v3.0.13...v3.0.14) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3728813..96e7bd9 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 - github.com/pion/sdp/v3 v3.0.13 + github.com/pion/sdp/v3 v3.0.14 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 620e4fe..9748134 100644 --- a/go.sum +++ b/go.sum @@ -100,8 +100,8 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4= -github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= +github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= From 6dfd31a030982ef923d4f7a199e42e55e1320b86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:10:52 +0000 Subject: [PATCH 125/549] Bump github.com/nats-io/nats-server/v2 from 2.11.4 to 2.11.5 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.4 to 2.11.5. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.4...v2.11.5) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 96e7bd9..e6272c9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.4 + github.com/nats-io/nats-server/v2 v2.11.5 github.com/nats-io/nats.go v1.43.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -87,11 +87,11 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.38.0 // indirect + golang.org/x/crypto v0.39.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.11.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 9748134..51378ea 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.4 h1:oQhvy6He6ER926sGqIKBKuYHH4BGnUQCNb0Y5Qa+M54= -github.com/nats-io/nats-server/v2 v2.11.4/go.mod h1:jFnKKwbNeq6IfLHq+OMnl7vrFRihQ/MkhRbiWfjLdjU= +github.com/nats-io/nats-server/v2 v2.11.5 h1:yxwFASM5VrbHky6bCCame6g6fXZaayLoh7WFPWU9EEg= +github.com/nats-io/nats-server/v2 v2.11.5/go.mod h1:2xoztlcb4lDL5Blh1/BiukkKELXvKQ5Vy29FPVRBUYs= github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug= github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -184,8 +184,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -198,8 +198,8 @@ golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -209,10 +209,10 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From ec9755d10fc0f2149e4fbc0610cc0b4dcfdcf372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 20:05:21 +0000 Subject: [PATCH 126/549] Bump github.com/nats-io/nats-server/v2 from 2.11.5 to 2.11.6 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.5 to 2.11.6. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.5...v2.11.6) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e6272c9..1231bd6 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.5 + github.com/nats-io/nats-server/v2 v2.11.6 github.com/nats-io/nats.go v1.43.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 diff --git a/go.sum b/go.sum index 51378ea..0e93d12 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.5 h1:yxwFASM5VrbHky6bCCame6g6fXZaayLoh7WFPWU9EEg= -github.com/nats-io/nats-server/v2 v2.11.5/go.mod h1:2xoztlcb4lDL5Blh1/BiukkKELXvKQ5Vy29FPVRBUYs= +github.com/nats-io/nats-server/v2 v2.11.6 h1:4VXRjbTUFKEB+7UoaKL3F5Y83xC7MxPoIONOnGgpkHw= +github.com/nats-io/nats-server/v2 v2.11.6/go.mod h1:2xoztlcb4lDL5Blh1/BiukkKELXvKQ5Vy29FPVRBUYs= github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug= github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= From 29a40bf4b9ed8de6ebd515a3456dc51e935a136f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:00:12 +0000 Subject: [PATCH 127/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.1 to 3.6.2 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.1...v3.6.2) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.1 to 3.6.2 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.1...v3.6.2) Updates `go.etcd.io/etcd/client/v3` from 3.6.1 to 3.6.2 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.1...v3.6.2) Updates `go.etcd.io/etcd/server/v3` from 3.6.1 to 3.6.2 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.1...v3.6.2) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 1231bd6..4548a0e 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.6.1 - go.etcd.io/etcd/client/pkg/v3 v3.6.1 - go.etcd.io/etcd/client/v3 v3.6.1 - go.etcd.io/etcd/server/v3 v3.6.1 + go.etcd.io/etcd/api/v3 v3.6.2 + go.etcd.io/etcd/client/pkg/v3 v3.6.2 + go.etcd.io/etcd/client/v3 v3.6.2 + go.etcd.io/etcd/server/v3 v3.6.2 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.73.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -74,8 +74,8 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.4.0 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.1 // indirect + go.etcd.io/bbolt v1.4.2 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.2 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index 0e93d12..92d74d5 100644 --- a/go.sum +++ b/go.sum @@ -141,18 +141,18 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= -go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= -go.etcd.io/etcd/api/v3 v3.6.1 h1:yJ9WlDih9HT457QPuHt/TH/XtsdN2tubyxyQHSHPsEo= -go.etcd.io/etcd/api/v3 v3.6.1/go.mod h1:lnfuqoGsXMlZdTJlact3IB56o3bWp1DIlXPIGKRArto= -go.etcd.io/etcd/client/pkg/v3 v3.6.1 h1:CxDVv8ggphmamrXM4Of8aCC8QHzDM4tGcVr9p2BSoGk= -go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc= -go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0= -go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY= -go.etcd.io/etcd/pkg/v3 v3.6.1 h1:Qpshk3/SLra217k7FxcFGaH2niFAxFf1Dug57f0IUiw= -go.etcd.io/etcd/pkg/v3 v3.6.1/go.mod h1:nS0ahQoZZ9qXjQAtYGDt80IEHKl9YOF7mv6J0lQmBoQ= -go.etcd.io/etcd/server/v3 v3.6.1 h1:Y/mh94EeImzXyTBIMVgR0v5H+ANtRFDY4g1s5sxOZGE= -go.etcd.io/etcd/server/v3 v3.6.1/go.mod h1:nCqJGTP9c2WlZluJB59j3bqxZEI/GYBfQxno0MguVjE= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd/api/v3 v3.6.2 h1:25aCkIMjUmiiOtnBIp6PhNj4KdcURuBak0hU2P1fgRc= +go.etcd.io/etcd/api/v3 v3.6.2/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.2 h1:zw+HRghi/G8fKpgKdOcEKpnBTE4OO39T6MegA0RopVU= +go.etcd.io/etcd/client/pkg/v3 v3.6.2/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.2 h1:RgmcLJxkpHqpFvgKNwAQHX3K+wsSARMXKgjmUSpoSKQ= +go.etcd.io/etcd/client/v3 v3.6.2/go.mod h1:PL7e5QMKzjybn0FosgiWvCUDzvdChpo5UgGR4Sk4Gzc= +go.etcd.io/etcd/pkg/v3 v3.6.2 h1:Ds+VYdvqEnKq8/iAy1fMo0FnJ9qzB0iOCe3f/9rko5k= +go.etcd.io/etcd/pkg/v3 v3.6.2/go.mod h1:b8Ag+HsYLuP6GgoExrswupAG6jXakxcvoNnIRhO17ZA= +go.etcd.io/etcd/server/v3 v3.6.2 h1:LY2tRcK8Rfby4aT2X9cWlRSk48FlLtP571nzc7tIhzc= +go.etcd.io/etcd/server/v3 v3.6.2/go.mod h1:EQ1Y6Q1ZbggsF3kYMGact0V0YPoJ7XVT2o5r1dorNXs= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From d2d411a7ca05536b8024446d7e9d0562384c7f2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:46:52 +0000 Subject: [PATCH 128/549] Bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3 Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.2 to 5.2.3. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.2...v5.2.3) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-version: 5.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4548a0e..80d550c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 github.com/fsnotify/fsnotify v1.9.0 - github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/golang-jwt/jwt/v5 v5.2.3 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/securecookie v1.1.2 diff --git a/go.sum b/go.sum index 92d74d5..f56f59b 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= +github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= From 7b909f4ec6d2569c6c192e0265dfe4a4b2007429 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Jul 2025 16:14:45 +0200 Subject: [PATCH 129/549] Avoid duplicate wakeups if file in current folder is watched and modified. --- file_watcher.go | 8 +++++--- file_watcher_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/file_watcher.go b/file_watcher.go index 6d3a923..8faa330 100644 --- a/file_watcher.go +++ b/file_watcher.go @@ -112,20 +112,22 @@ func (f *FileWatcher) run() { return } + filename := path.Clean(event.Name) + // Use timer to deduplicate multiple events for the same file. mu.Lock() - t, found := timers[event.Name] + t, found := timers[filename] mu.Unlock() if !found { t = time.AfterFunc(deduplicate, func() { f.callback(f.filename) mu.Lock() - delete(timers, event.Name) + delete(timers, filename) mu.Unlock() }) mu.Lock() - timers[event.Name] = t + timers[filename] = t mu.Unlock() } else { t.Reset(deduplicate) diff --git a/file_watcher_test.go b/file_watcher_test.go index 14dec15..03551b8 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -86,6 +86,48 @@ func TestFileWatcher_File(t *testing.T) { }) } +func TestFileWatcher_CurrentDir(t *testing.T) { + ensureNoGoroutinesLeak(t, func(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + tmpdir := t.TempDir() + require.NoError(os.Chdir(tmpdir)) + filename := path.Join(tmpdir, "test.txt") + require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) + + modified := make(chan struct{}) + w, err := NewFileWatcher("./"+path.Base(filename), func(filename string) { + modified <- struct{}{} + }) + require.NoError(err) + defer w.Close() + + require.NoError(os.WriteFile(filename, []byte("Updated"), 0644)) + <-modified + + ctxTimeout, cancel := context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + assert.Fail("should not have received another event") + case <-ctxTimeout.Done(): + } + + require.NoError(os.WriteFile(filename, []byte("Updated"), 0644)) + <-modified + + ctxTimeout, cancel = context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + assert.Fail("should not have received another event") + case <-ctxTimeout.Done(): + } + }) +} + func TestFileWatcher_Rename(t *testing.T) { require := require.New(t) assert := assert.New(t) From bdb2a816f8894add631627605beeaee0e60fdd98 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 16 Jul 2025 16:50:47 +0200 Subject: [PATCH 130/549] Handle case where watched file is in a symlinked versioned subfolder. This is what k8s is doing for mounted Secrets / ConfigMaps. --- file_watcher.go | 45 +++++++++++++++++------ file_watcher_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 12 deletions(-) diff --git a/file_watcher.go b/file_watcher.go index 8faa330..a26d0a7 100644 --- a/file_watcher.go +++ b/file_watcher.go @@ -61,21 +61,11 @@ type FileWatcher struct { } func NewFileWatcher(filename string, callback FileWatcherCallback) (*FileWatcher, error) { - realFilename, err := filepath.EvalSymlinks(filename) - if err != nil { - return nil, err - } - watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err } - if err := watcher.Add(realFilename); err != nil { - watcher.Close() // nolint - return nil, err - } - if err := watcher.Add(path.Dir(filename)); err != nil { watcher.Close() // nolint return nil, err @@ -85,17 +75,35 @@ func NewFileWatcher(filename string, callback FileWatcherCallback) (*FileWatcher w := &FileWatcher{ filename: filename, - target: realFilename, callback: callback, watcher: watcher, closeCtx: closeCtx, closeFunc: closeFunc, } + if err := w.updateWatcher(); err != nil { + watcher.Close() // nolint + return nil, err + } + go w.run() return w, nil } +func (f *FileWatcher) updateWatcher() error { + realFilename, err := filepath.EvalSymlinks(f.filename) + if err != nil { + return err + } + + if err := f.watcher.Add(realFilename); err != nil { + return err + } + + f.target = realFilename + return nil +} + func (f *FileWatcher) Close() error { f.closeFunc() return f.watcher.Close() @@ -137,7 +145,20 @@ func (f *FileWatcher) run() { for { select { case event := <-f.watcher.Events: - if !event.Has(fsnotify.Write) && !event.Has(fsnotify.Create) && !event.Has(fsnotify.Rename) { + if !event.Has(fsnotify.Write) && !event.Has(fsnotify.Create) && !event.Has(fsnotify.Rename) && !event.Has(fsnotify.Remove) { + continue + } + + if event.Has(fsnotify.Remove) { + // Watched target has been deleted, assume it was symlinked and try to watch new target. + if event.Name != f.target { + continue + } + + triggerEvent(event) + if err := f.updateWatcher(); err != nil { + log.Printf("Error updating watcher after %s is deleted: %s", event.Name, err) + } continue } diff --git a/file_watcher_test.go b/file_watcher_test.go index 03551b8..8dfa48c 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -305,3 +305,90 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { case <-ctxTimeout.Done(): } } + +func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { + // This mimics what k8s is doing with configmaps / secrets. + require := require.New(t) + assert := assert.New(t) + tmpdir := t.TempDir() + + // File is in a versioned folder. + version1Path := path.Join(tmpdir, "version1") + require.NoError(os.Mkdir(version1Path, 0755)) + sourceFilename1 := path.Join(version1Path, "test.txt") + require.NoError(os.WriteFile(sourceFilename1, []byte("Hello world!"), 0644)) + + // Versioned folder is symlinked to a generic "data" folder. + dataPath := path.Join(tmpdir, "data") + require.NoError(os.Symlink("version1", dataPath)) + + // File in root is symlinked from generic "data" folder. + filename := path.Join(tmpdir, "test.txt") + require.NoError(os.Symlink("data/test.txt", filename)) + + modified := make(chan struct{}) + w, err := NewFileWatcher(filename, func(filename string) { + modified <- struct{}{} + }) + require.NoError(err) + defer w.Close() + + // New file is created in a new versioned subfolder. + version2Path := path.Join(tmpdir, "version2") + require.NoError(os.Mkdir(version2Path, 0755)) + + sourceFilename2 := path.Join(version2Path, "test.txt") + require.NoError(os.WriteFile(sourceFilename2, []byte("Updated"), 0644)) + + ctxTimeout, cancel := context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + assert.Fail("should not have received another event") + case <-ctxTimeout.Done(): + } + + // Create temporary symlink to new versioned subfolder... + require.NoError(os.Symlink("version2", dataPath+".tmp")) + // ...atomically update generic "data" symlink... + require.NoError(os.Rename(dataPath+".tmp", dataPath)) + // ...and old versioned subfolder is removed (this will trigger the event). + require.NoError(os.RemoveAll(version1Path)) + + <-modified + + // Another new file is created in a new versioned subfolder. + version3Path := path.Join(tmpdir, "version3") + require.NoError(os.Mkdir(version3Path, 0755)) + + sourceFilename3 := path.Join(version3Path, "test.txt") + require.NoError(os.WriteFile(sourceFilename3, []byte("Updated again"), 0644)) + + ctxTimeout, cancel = context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + assert.Fail("should not have received another event") + case <-ctxTimeout.Done(): + } + + // Create temporary symlink to new versioned subfolder... + require.NoError(os.Symlink("version3", dataPath+".tmp")) + // ...atomically update generic "data" symlink... + require.NoError(os.Rename(dataPath+".tmp", dataPath)) + // ...and old versioned subfolder is removed (this will trigger the event). + require.NoError(os.RemoveAll(version2Path)) + + <-modified + + ctxTimeout, cancel = context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + assert.Fail("should not have received another event") + case <-ctxTimeout.Done(): + } +} From 667f29507aefb5cc6d34d73dcc8553810dcc75e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 20:42:45 +0000 Subject: [PATCH 131/549] Bump google.golang.org/grpc from 1.73.0 to 1.74.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.73.0 to 1.74.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.73.0...v1.74.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.74.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++--------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 80d550c..2f2ca25 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.2 go.etcd.io/etcd/server/v3 v3.6.2 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.73.0 + google.golang.org/grpc v1.74.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) @@ -38,7 +38,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -79,21 +79,21 @@ require ( go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index f56f59b..d112e26 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -159,20 +159,20 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -193,8 +193,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -221,12 +221,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY= +google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From 899a9b6a61aec1e78db9cbe451a7b8a702b671bb Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Jul 2025 08:04:04 +0200 Subject: [PATCH 132/549] Describe how to pass caller information for outgoing calls. --- docs/standalone-signaling-api-v1.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 899f1fa..1f903bb 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1849,7 +1849,7 @@ Message format (Backend -> Server) "dialout" { "number": "e164-target-number", "options": { - ...arbitrary options that will be sent back to validate... + ...additional options... } } } @@ -1857,6 +1857,26 @@ Message format (Backend -> Server) Please note that this requires a connected internal client that supports dialout (e.g. the SIP bridge). +The `options` will be sent to Nextcloud Talk for validation of the dialout +request. A field `caller` can be included containing the data that should be +sent as `From` header in the outgoing call, or a field `anonymous` with value +`true` to trigger an anonymous outgoing call (CLIR). + +Example request (dialout to `+49123456789` and use `+491122334455` as caller): + + { + "type": "dialout" + "dialout" { + "number": "+49123456789", + "options": { + "attendeeId": "abcdef", + "actorType": "actor-type", + "actorId": "the-actor", + "caller": "+491122334455" + } + } + } + Message format (Server -> Backend, request was accepted) { From 6133563fe2350a887862af663af1a3be93154512 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Jul 2025 08:11:24 +0200 Subject: [PATCH 133/549] Use backend id in backend client stats to match other stats. --- backend_client.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/backend_client.go b/backend_client.go index 7d6f4d0..f61da41 100644 --- a/backend_client.go +++ b/backend_client.go @@ -171,15 +171,15 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ resp, err := c.Do(req) end := time.Now() duration := end.Sub(start) - statsBackendClientRequests.WithLabelValues(backend.Url()).Inc() - statsBackendClientDuration.WithLabelValues(backend.Url()).Observe(duration.Seconds()) + statsBackendClientRequests.WithLabelValues(backend.Id()).Inc() + statsBackendClientDuration.WithLabelValues(backend.Id()).Observe(duration.Seconds()) if err != nil { if errors.Is(err, context.DeadlineExceeded) { - statsBackendClientError.WithLabelValues(backend.Url(), "timeout").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "timeout").Inc() } else if errors.Is(err, context.Canceled) { - statsBackendClientError.WithLabelValues(backend.Url(), "canceled").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "canceled").Inc() } else { - statsBackendClientError.WithLabelValues(backend.Url(), "unknown").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "unknown").Inc() } log.Printf("Could not send request %s to %s: %s", data.String(), req.URL, err) return err @@ -189,14 +189,14 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ ct := resp.Header.Get("Content-Type") if !strings.HasPrefix(ct, "application/json") { log.Printf("Received unsupported content-type from %s: %s (%s)", req.URL, ct, resp.Status) - statsBackendClientError.WithLabelValues(backend.Url(), "invalid_content_type").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "invalid_content_type").Inc() return ErrUnsupportedContentType } body, err := b.buffers.ReadAll(resp.Body) if err != nil { log.Printf("Could not read response body from %s: %s", req.URL, err) - statsBackendClientError.WithLabelValues(backend.Url(), "error_reading_body").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "error_reading_body").Inc() return err } @@ -214,29 +214,29 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ var ocs OcsResponse if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { log.Printf("Could not decode OCS response %s from %s: %s", body.String(), req.URL, err) - statsBackendClientError.WithLabelValues(backend.Url(), "error_decoding_ocs").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_ocs").Inc() return err } else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 { log.Printf("Incomplete OCS response %s from %s", body.String(), req.URL) - statsBackendClientError.WithLabelValues(backend.Url(), "error_incomplete_ocs").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "error_incomplete_ocs").Inc() return ErrIncompleteResponse } switch ocs.Ocs.Meta.StatusCode { case http.StatusTooManyRequests: log.Printf("Throttled OCS response %s from %s", body.String(), req.URL) - statsBackendClientError.WithLabelValues(backend.Url(), "throttled").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "throttled").Inc() return ErrThrottledResponse } if err := json.Unmarshal(ocs.Ocs.Data, response); err != nil { log.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), req.URL, err) - statsBackendClientError.WithLabelValues(backend.Url(), "error_decoding_ocs_data").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_ocs_data").Inc() return err } } else if err := json.Unmarshal(body.Bytes(), response); err != nil { log.Printf("Could not decode response body %s from %s: %s", body.String(), req.URL, err) - statsBackendClientError.WithLabelValues(backend.Url(), "error_decoding_body").Inc() + statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_body").Inc() return err } return nil From 6f30f0268e3eded7f566e98e2d102617843cda38 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 3 Jul 2024 10:09:06 +0200 Subject: [PATCH 134/549] Log request data in error cases. --- backend_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend_client.go b/backend_client.go index f61da41..51febb6 100644 --- a/backend_client.go +++ b/backend_client.go @@ -188,14 +188,14 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ ct := resp.Header.Get("Content-Type") if !strings.HasPrefix(ct, "application/json") { - log.Printf("Received unsupported content-type from %s: %s (%s)", req.URL, ct, resp.Status) + log.Printf("Received unsupported content-type from %s for %s: %s (%s)", req.URL, data.String(), ct, resp.Status) statsBackendClientError.WithLabelValues(backend.Id(), "invalid_content_type").Inc() return ErrUnsupportedContentType } body, err := b.buffers.ReadAll(resp.Body) if err != nil { - log.Printf("Could not read response body from %s: %s", req.URL, err) + log.Printf("Could not read response body from %s for %s: %s", req.URL, data.String(), err) statsBackendClientError.WithLabelValues(backend.Id(), "error_reading_body").Inc() return err } From 5da0a5d4b0b33ac1b9cd04012678caf878690478 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 3 Jul 2024 10:18:13 +0200 Subject: [PATCH 135/549] Prepare internal APIs for multiple backend urls. --- api_signaling.go | 16 ++++++- backend_configuration.go | 34 ++++++++------ backend_configuration_test.go | 20 ++++----- backend_server.go | 22 ++++++--- backend_storage_etcd.go | 7 ++- backend_storage_static.go | 19 ++++---- clientsession.go | 26 +++-------- federation.go | 3 ++ grpc_client.go | 24 +++++----- grpc_server.go | 85 +++++++++++++++++++++-------------- grpc_sessions.proto | 3 +- hub.go | 20 ++++----- hub_test.go | 2 +- room.go | 22 +++++++-- virtualsession.go | 8 +++- virtualsession_test.go | 9 ++-- 16 files changed, 187 insertions(+), 133 deletions(-) diff --git a/api_signaling.go b/api_signaling.go index 0074b5e..45f139a 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -378,11 +378,17 @@ type ClientTypeInternalAuthParams struct { func (p *ClientTypeInternalAuthParams) CheckValid() error { if p.Backend == "" { return fmt.Errorf("backend missing") - } else if u, err := url.Parse(p.Backend); err != nil { + } + + if p.Backend[len(p.Backend)-1] != '/' { + p.Backend += "/" + } + if u, err := url.Parse(p.Backend); err != nil { return err } else { if strings.Contains(u.Host, ":") && hasStandardPort(u) { u.Host = u.Hostname() + p.Backend = u.String() } p.parsedBackend = u @@ -483,11 +489,17 @@ func (m *HelloClientMessage) CheckValid() error { case HelloClientTypeFederation: if m.Auth.Url == "" { return fmt.Errorf("url missing") - } else if u, err := url.ParseRequestURI(m.Auth.Url); err != nil { + } + + if m.Auth.Url[len(m.Auth.Url)-1] != '/' { + m.Auth.Url += "/" + } + if u, err := url.ParseRequestURI(m.Auth.Url); err != nil { return err } else { if strings.Contains(u.Host, ":") && hasStandardPort(u) { u.Host = u.Hostname() + m.Auth.Url = u.String() } m.Auth.parsedUrl = u diff --git a/backend_configuration.go b/backend_configuration.go index fa6b45b..33d5eb8 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -42,11 +42,9 @@ var ( ) type Backend struct { - id string - url string - parsedUrl *url.URL - secret []byte - compat bool + id string + urls []string + secret []byte allowHttp bool @@ -67,7 +65,7 @@ func (b *Backend) Secret() []byte { } func (b *Backend) IsCompat() bool { - return b.compat + return len(b.urls) == 0 } func (b *Backend) IsUrlAllowed(u *url.URL) bool { @@ -81,12 +79,23 @@ func (b *Backend) IsUrlAllowed(u *url.URL) bool { } } -func (b *Backend) Url() string { - return b.url +func (b *Backend) HasUrl(url string) bool { + if b.IsCompat() { + // Old-style configuration, only hosts are configured. + return true + } + + for _, u := range b.urls { + if strings.HasPrefix(url, u) { + return true + } + } + + return false } -func (b *Backend) ParsedUrl() *url.URL { - return b.parsedUrl +func (b *Backend) Urls() []string { + return b.urls } func (b *Backend) Limit() int { @@ -173,10 +182,7 @@ func (s *backendStorageCommon) getBackendLocked(u *url.URL) *Backend { continue } - if entry.url == "" { - // Old-style configuration, only hosts are configured. - return entry - } else if strings.HasPrefix(url, entry.url) { + if entry.HasUrl(url) { return entry } } diff --git a/backend_configuration_test.go b/backend_configuration_test.go index a657733..c6b701e 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -491,7 +491,7 @@ func TestBackendConfiguration_Etcd(t *testing.T) { require.NoError(storage.WaitForInitialized(ctx)) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && - assert.Equal(url1, backends[0].url) && + assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(initialSecret1, string(backends[0].secret)) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) @@ -502,7 +502,7 @@ func TestBackendConfiguration_Etcd(t *testing.T) { SetEtcdValue(etcd, "/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+secret1+"\"}")) <-ch if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && - assert.Equal(url1, backends[0].url) && + assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) @@ -516,9 +516,9 @@ func TestBackendConfiguration_Etcd(t *testing.T) { SetEtcdValue(etcd, "/backends/2_two", []byte("{\"url\":\""+url2+"\",\"secret\":\""+secret2+"\"}")) <-ch if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && - assert.Equal(url1, backends[0].url) && + assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) && - assert.Equal(url2, backends[1].url) && + assert.Equal([]string{url2}, backends[1].urls) && assert.Equal(secret2, string(backends[1].secret)) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) @@ -534,11 +534,11 @@ func TestBackendConfiguration_Etcd(t *testing.T) { SetEtcdValue(etcd, "/backends/3_three", []byte("{\"url\":\""+url3+"\",\"secret\":\""+secret3+"\"}")) <-ch if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 3) && - assert.Equal(url1, backends[0].url) && + assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) && - assert.Equal(url2, backends[1].url) && + assert.Equal([]string{url2}, backends[1].urls) && assert.Equal(secret2, string(backends[1].secret)) && - assert.Equal(url3, backends[2].url) && + assert.Equal([]string{url3}, backends[2].urls) && assert.Equal(secret3, string(backends[2].secret)) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) @@ -553,9 +553,9 @@ func TestBackendConfiguration_Etcd(t *testing.T) { DeleteEtcdValue(etcd, "/backends/1_one") <-ch if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) { - assert.Equal(url2, backends[0].url) + assert.Equal([]string{url2}, backends[0].urls) assert.Equal(secret2, string(backends[0].secret)) - assert.Equal(url3, backends[1].url) + assert.Equal([]string{url3}, backends[1].urls) assert.Equal(secret3, string(backends[1].secret)) } @@ -563,7 +563,7 @@ func TestBackendConfiguration_Etcd(t *testing.T) { DeleteEtcdValue(etcd, "/backends/2_two") <-ch if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { - assert.Equal(url3, backends[0].url) + assert.Equal([]string{url3}, backends[0].urls) assert.Equal(secret3, string(backends[0].secret)) } diff --git a/backend_server.go b/backend_server.go index f344807..5336eb4 100644 --- a/backend_server.go +++ b/backend_server.go @@ -701,12 +701,22 @@ func isNumeric(s string) bool { } func (b *BackendServer) startDialoutInSession(ctx context.Context, session *ClientSession, roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { - url := backend.Url() - if url == "" { - // Old-style compat backend, use client-provided URL. - url = backendUrl - if url != "" && url[len(url)-1] != '/' { - url += "/" + url := backendUrl + if url != "" && url[len(url)-1] != '/' { + url += "/" + } + if urls := backend.Urls(); len(urls) > 0 { + // Check if client-provided URL is registered for backend and use that. + found := false + for _, u := range urls { + if strings.HasPrefix(url, u) { + found = true + break + } + } + + if !found { + url = urls[0] } } id := newRandomString(32) diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index ce82bef..64a3571 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -179,10 +179,9 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data } backend := &Backend{ - id: key, - url: info.Url, - parsedUrl: info.parsedUrl, - secret: []byte(info.Secret), + id: key, + urls: []string{info.Url}, + secret: []byte(info.Secret), allowHttp: info.parsedUrl.Scheme == "http", diff --git a/backend_storage_static.go b/backend_storage_static.go index 9d9ab53..06883a5 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -55,7 +55,6 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) compatBackend = &Backend{ id: "compat", secret: []byte(commonSecret), - compat: true, allowHttp: allowHttp, @@ -70,7 +69,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) for host, configuredBackends := range getConfiguredHosts(backendIds, config, commonSecret) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { - log.Printf("Backend %s added for %s", be.id, be.url) + log.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) updateBackendStats(be) } numBackends += len(configuredBackends) @@ -95,7 +94,6 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) compatBackend = &Backend{ id: "compat", secret: []byte(commonSecret), - compat: true, allowHttp: allowHttp, @@ -140,7 +138,7 @@ func (s *backendStorageStatic) Close() { func (s *backendStorageStatic) RemoveBackendsForHost(host string) { if oldBackends := s.backends[host]; len(oldBackends) > 0 { for _, backend := range oldBackends { - log.Printf("Backend %s removed for %s", backend.id, backend.url) + log.Printf("Backend %s removed for %s", backend.id, strings.Join(backend.urls, ", ")) deleteBackendStats(backend) } statsBackendsCurrent.Sub(float64(len(oldBackends))) @@ -161,7 +159,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend) { found = true s.backends[host][existingIndex] = newBackend backends = append(backends[:index], backends[index+1:]...) - log.Printf("Backend %s updated for %s", newBackend.id, newBackend.url) + log.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) updateBackendStats(newBackend) break } @@ -169,7 +167,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend) { } if !found { removed := s.backends[host][existingIndex] - log.Printf("Backend %s removed for %s", removed.id, removed.url) + log.Printf("Backend %s removed for %s", removed.id, strings.Join(removed.urls, ", ")) s.backends[host] = append(s.backends[host][:existingIndex], s.backends[host][existingIndex+1:]...) deleteBackendStats(removed) statsBackendsCurrent.Dec() @@ -178,7 +176,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend) { s.backends[host] = append(s.backends[host], backends...) for _, added := range backends { - log.Printf("Backend %s added for %s", added.id, added.url) + log.Printf("Backend %s added for %s", added.id, strings.Join(added.urls, ", ")) updateBackendStats(added) } statsBackendsCurrent.Add(float64(len(backends))) @@ -254,10 +252,9 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr } hosts[parsed.Host] = append(hosts[parsed.Host], &Backend{ - id: id, - url: u, - parsedUrl: parsed, - secret: []byte(secret), + id: id, + urls: []string{u}, + secret: []byte(secret), allowHttp: parsed.Scheme == "http", diff --git a/clientsession.go b/clientsession.go index c59920a..0e75d2d 100644 --- a/clientsession.go +++ b/clientsession.go @@ -39,6 +39,8 @@ var ( // Warn if a session has 32 or more pending messages. warnPendingMessagesCount = 32 + // The "/api/v1/signaling/" URL will be changed to use "v3" as the "signaling-v3" + // feature is returned by the capabilities endpoint. PathToOcsSignalingBackend = "ocs/v2.php/apps/spreed/api/v1/signaling/backend" ) @@ -130,24 +132,6 @@ func NewClientSession(hub *Hub, privateId string, publicId string, data *Session s.backendUrl = hello.Auth.Url s.parsedBackendUrl = hello.Auth.parsedUrl } - if !strings.Contains(s.backendUrl, "/ocs/v2.php/") { - backendUrl := s.backendUrl - if !strings.HasSuffix(backendUrl, "/") { - backendUrl += "/" - } - backendUrl += PathToOcsSignalingBackend - u, err := url.Parse(backendUrl) - if err != nil { - return nil, err - } - - if strings.Contains(u.Host, ":") && hasStandardPort(u) { - u.Host = u.Hostname() - } - - s.backendUrl = backendUrl - s.parsedBackendUrl = u - } if err := s.SubscribeEvents(); err != nil { return nil, err @@ -307,6 +291,10 @@ func (s *ClientSession) ParsedBackendUrl() *url.URL { return s.parsedBackendUrl } +func (s *ClientSession) ParsedBackendOcsUrl() *url.URL { + return s.parsedBackendUrl.JoinPath(PathToOcsSignalingBackend) +} + func (s *ClientSession) AuthUserId() string { return s.userId } @@ -563,7 +551,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { request.Room.UpdateFromSession(s) request.Room.Action = "leave" var response map[string]interface{} - if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendUrl(), request, &response); err != nil { + if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { log.Printf("Could not notify about room session %s left room %s: %s", sid, room.Id(), err) } else { log.Printf("Removed room session %s: %+v", sid, response) diff --git a/federation.go b/federation.go index d1ac58b..053c7c0 100644 --- a/federation.go +++ b/federation.go @@ -65,6 +65,8 @@ func getCloudUrl(s string) string { } if pos := strings.Index(s, "/ocs/v"); pos != -1 { s = s[:pos] + } else { + s = strings.TrimSuffix(s, "/") } return s } @@ -606,6 +608,7 @@ func (c *FederationClient) updateEventUsers(users []map[string]interface{}, loca localCloudUrlLen := len(localCloudUrl) remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) checkSessionId := true + log.Printf("XXX local=%s remote=%s", localCloudUrl, remoteCloudUrl) for _, u := range users { if actorType, found := getStringMapEntry[string](u, "actorType"); found { if actorId, found := getStringMapEntry[string](u, "actorId"); found { diff --git a/grpc_client.go b/grpc_client.go index 7aad2a2..9cf0dec 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -29,7 +29,6 @@ import ( "io" "log" "net" - "net/url" "strings" "sync" "sync/atomic" @@ -240,14 +239,14 @@ func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId string, return sessionId, nil } -func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId string, room *Room) (bool, error) { +func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId string, room *Room, backendUrl string) (bool, error) { statsGrpcClientCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging log.Printf("Check if session %s is in call %s on %s", sessionId, room.Id(), c.Target()) response, err := c.impl.IsSessionInCall(ctx, &IsSessionInCallRequest{ SessionId: sessionId, RoomId: room.Id(), - BackendUrl: room.Backend().url, + BackendUrl: backendUrl, }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { return false, nil @@ -258,13 +257,18 @@ func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId string, room return response.GetInCall(), nil } -func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backend *Backend) (internal map[string]*InternalSessionData, virtual map[string]*VirtualSessionData, err error) { +func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[string]*InternalSessionData, virtual map[string]*VirtualSessionData, err error) { statsGrpcClientCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging - log.Printf("Get internal sessions for %s@%s on %s", roomId, backend.Id(), c.Target()) + log.Printf("Get internal sessions for %s on %s", roomId, c.Target()) + var backendUrl string + if len(backendUrls) > 0 { + backendUrl = backendUrls[0] + } response, err := c.impl.GetInternalSessions(ctx, &GetInternalSessionsRequest{ - RoomId: roomId, - BackendUrl: backend.Url(), + RoomId: roomId, + BackendUrl: backendUrl, + BackendUrls: backendUrls, }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { return nil, nil, nil @@ -305,12 +309,12 @@ func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId string, strea return response.GetPublisherId(), response.GetProxyUrl(), net.ParseIP(response.GetIp()), nil } -func (c *GrpcClient) GetSessionCount(ctx context.Context, u *url.URL) (uint32, error) { +func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, error) { statsGrpcClientCalls.WithLabelValues("GetSessionCount").Inc() // TODO: Remove debug logging - log.Printf("Get session count for %s on %s", u, c.Target()) + log.Printf("Get session count for %s on %s", url, c.Target()) response, err := c.impl.GetSessionCount(ctx, &GetSessionCountRequest{ - Url: u.String(), + Url: url, }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { return 0, nil diff --git a/grpc_server.go b/grpc_server.go index c0ca8b0..17327fd 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -178,7 +178,7 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa result := &IsSessionInCallReply{} room := session.GetRoom() - if room == nil || room.Id() != request.GetRoomId() || room.Backend().url != request.GetBackendUrl() || + if room == nil || room.Id() != request.GetRoomId() || !room.Backend().HasUrl(request.GetBackendUrl()) || (session.ClientType() != HelloClientTypeInternal && !room.IsSessionInCall(session)) { // Recipient is not in a room, a different room or not in the call. result.InCall = false @@ -191,44 +191,63 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetInternalSessionsRequest) (*GetInternalSessionsReply, error) { statsGrpcServerCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging - log.Printf("Get internal sessions from %s on %s", request.RoomId, request.BackendUrl) + log.Printf("Get internal sessions from %s on %v (fallback %s)", request.RoomId, request.BackendUrls, request.BackendUrl) - var u *url.URL - if request.BackendUrl != "" { - var err error - u, err = url.Parse(request.BackendUrl) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "invalid url") - } - } - - backend := s.hub.GetBackend(u) - if backend == nil { - return nil, status.Error(codes.NotFound, "no such backend") - } - - room := s.hub.GetRoomForBackend(request.RoomId, backend) - if room == nil { - return nil, status.Error(codes.NotFound, "no such room") + var backendUrls []string + if len(request.BackendUrls) > 0 { + backendUrls = request.BackendUrls + } else if request.BackendUrl != "" { + backendUrls = append(backendUrls, request.BackendUrl) + } else { + // Only compat backend. + backendUrls = []string{""} } result := &GetInternalSessionsReply{} - room.mu.RLock() - defer room.mu.RUnlock() + processed := make(map[string]bool) + for _, bu := range backendUrls { + var parsed *url.URL + if bu != "" { + var err error + parsed, err = url.Parse(bu) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid url") + } + } - for session := range room.internalSessions { - result.InternalSessions = append(result.InternalSessions, &InternalSessionData{ - SessionId: session.PublicId(), - InCall: uint32(session.GetInCall()), - Features: session.GetFeatures(), - }) - } + backend := s.hub.GetBackend(parsed) + if backend == nil { + return nil, status.Error(codes.NotFound, "no such backend") + } - for session := range room.virtualSessions { - result.VirtualSessions = append(result.VirtualSessions, &VirtualSessionData{ - SessionId: session.PublicId(), - InCall: uint32(session.GetInCall()), - }) + // Only process each backend once. + if processed[backend.Id()] { + continue + } + processed[backend.Id()] = true + + room := s.hub.GetRoomForBackend(request.RoomId, backend) + if room == nil { + return nil, status.Error(codes.NotFound, "no such room") + } + + room.mu.RLock() + defer room.mu.RUnlock() + + for session := range room.internalSessions { + result.InternalSessions = append(result.InternalSessions, &InternalSessionData{ + SessionId: session.PublicId(), + InCall: uint32(session.GetInCall()), + Features: session.GetFeatures(), + }) + } + + for session := range room.virtualSessions { + result.VirtualSessions = append(result.VirtualSessions, &VirtualSessionData{ + SessionId: session.PublicId(), + InCall: uint32(session.GetInCall()), + }) + } } return result, nil diff --git a/grpc_sessions.proto b/grpc_sessions.proto index 3d29dfe..0503553 100644 --- a/grpc_sessions.proto +++ b/grpc_sessions.proto @@ -63,7 +63,8 @@ message IsSessionInCallReply { message GetInternalSessionsRequest { string roomId = 1; - string backendUrl = 2; + string backendUrl = 2 [deprecated = true]; + repeated string backendUrls = 3; } message InternalSessionData { diff --git a/hub.go b/hub.go index abe26d0..5e3b4da 100644 --- a/hub.go +++ b/hub.go @@ -700,12 +700,10 @@ func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId string) (string, error) } func (h *Hub) GetDialoutSessions(roomId string, backend *Backend) (result []*ClientSession) { - url := backend.Url() - h.mu.RLock() defer h.mu.RUnlock() for session := range h.dialoutSessions { - if session.backend.Url() != url { + if !backend.HasUrl(session.BackendUrl()) { continue } @@ -957,14 +955,14 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * go func(c *GrpcClient) { defer wg.Done() - count, err := c.GetSessionCount(ctx, backend.ParsedUrl()) + count, err := c.GetSessionCount(ctx, session.BackendUrl()) if err != nil { - log.Printf("Received error while getting session count for %s from %s: %s", backend.Url(), c.Target(), err) + log.Printf("Received error while getting session count for %s from %s: %s", session.BackendUrl(), c.Target(), err) return } if count > 0 { - log.Printf("%d sessions connected for %s on %s", count, backend.Url(), c.Target()) + log.Printf("%d sessions connected for %s on %s", count, session.BackendUrl(), c.Target()) totalCount.Add(count) } }(client) @@ -1308,6 +1306,8 @@ func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message return nil, nil, InvalidBackendUrl } + url = url.JoinPath(PathToOcsSignalingBackend) + // Run in timeout context to prevent blocking too long. ctx, cancel := context.WithTimeout(ctx, h.backendTimeout) defer cancel() @@ -1767,7 +1767,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { } request := NewBackendClientRoomRequest(roomId, session.UserId(), sessionId) request.Room.UpdateFromSession(session) - if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendUrl(), request, &room); err != nil { + if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &room); err != nil { session.SendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -2395,7 +2395,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { request.Room.InCall = sess.GetInCall() var response BackendClientResponse - if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendUrl(), request, &response); err != nil { + if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() log.Printf("Could not join virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) reply := message.NewErrorServerMessage(NewError("add_failed", "Could not join virtual session.")) @@ -2413,7 +2413,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { } else { request := NewBackendClientSessionRequest(room.Id(), "add", publicSessionId, msg) var response BackendClientSessionResponse - if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendUrl(), request, &response); err != nil { + if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() log.Printf("Could not add virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) reply := message.NewErrorServerMessage(NewError("add_failed", "Could not add virtual session.")) @@ -2620,7 +2620,7 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi go func(client *GrpcClient) { defer wg.Done() - inCall, err := client.IsSessionInCall(rpcCtx, recipientSessionId, senderRoom) + inCall, err := client.IsSessionInCall(rpcCtx, recipientSessionId, senderRoom, senderSession.BackendUrl()) if errors.Is(err, context.Canceled) { return } else if err != nil { diff --git a/hub_test.go b/hub_test.go index b8cda73..fec0fb1 100644 --- a/hub_test.go +++ b/hub_test.go @@ -2019,7 +2019,7 @@ func TestClientHelloClient_V3Api(t *testing.T) { } // The "/api/v1/signaling/" URL will be changed to use "v3" as the "signaling-v3" // feature is returned by the capabilities endpoint. - require.NoError(client.SendHelloParams(server.URL+"/ocs/v2.php/apps/spreed/api/v1/signaling/backend", HelloVersionV1, "client", nil, params)) + require.NoError(client.SendHelloParams(server.URL, HelloVersionV1, "client", nil, params)) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() diff --git a/room.go b/room.go index 46f6cff..4fe09cd 100644 --- a/room.go +++ b/room.go @@ -29,6 +29,7 @@ import ( "log" "net/url" "strconv" + "strings" "sync" "time" @@ -603,7 +604,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[string]*Inter go func(c *GrpcClient) { defer wg.Done() - clientInternal, clientVirtual, err := c.GetInternalSessions(ctx, r.Id(), r.Backend()) + clientInternal, clientVirtual, err := c.GetInternalSessions(ctx, r.Id(), r.Backend().Urls()) if err != nil { log.Printf("Received error while getting internal sessions for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) return @@ -1043,6 +1044,20 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { continue } + u += PathToOcsSignalingBackend + parsed, err := url.Parse(u) + if err != nil { + log.Printf("Could not parse backend url %s: %s", u, err) + continue + } + + if strings.Contains(parsed.Host, ":") && hasStandardPort(parsed) { + parsed.Host = parsed.Hostname() + } + + u = parsed.String() + parsedBackendUrl := parsed + var sid string var uid string switch sess := session.(type) { @@ -1062,12 +1077,11 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { } e, found := entries[u] if !found { - p := session.ParsedBackendUrl() - if p == nil { + if parsedBackendUrl == nil { // Should not happen, invalid URLs should get rejected earlier. continue } - urls[u] = p + urls[u] = parsedBackendUrl } entries[u] = append(e, BackendPingEntry{ diff --git a/virtualsession.go b/virtualsession.go index cfbd435..8dff084 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -132,6 +132,10 @@ func (s *VirtualSession) ParsedBackendUrl() *url.URL { return s.session.ParsedBackendUrl() } +func (s *VirtualSession) ParsedBackendOcsUrl() *url.URL { + return s.session.ParsedBackendOcsUrl() +} + func (s *VirtualSession) UserId() string { return s.userId } @@ -197,7 +201,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa } var response BackendClientResponse - if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendUrl(), request, &response); err != nil { + if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { virtualSessionId := GetVirtualSessionId(s.session, s.PublicId()) log.Printf("Could not leave virtual session %s at backend %s: %s", virtualSessionId, s.BackendUrl(), err) if session != nil && message != nil { @@ -222,7 +226,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa User: s.userData, }) var response BackendClientSessionResponse - err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendUrl(), request, &response) + err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response) if err != nil { log.Printf("Could not remove virtual session %s from backend %s: %s", s.PublicId(), s.BackendUrl(), err) if session != nil && message != nil { diff --git a/virtualsession_test.go b/virtualsession_test.go index bc9ca10..ad84415 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -42,8 +42,7 @@ func TestVirtualSession(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") backend := &Backend{ - id: "compat", - compat: true, + id: "compat", } room, err := hub.createRoom(roomId, emptyProperties, backend) require.NoError(err) @@ -270,8 +269,7 @@ func TestVirtualSessionCustomInCall(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") backend := &Backend{ - id: "compat", - compat: true, + id: "compat", } room, err := hub.createRoom(roomId, emptyProperties, backend) require.NoError(err) @@ -425,8 +423,7 @@ func TestVirtualSessionCleanup(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") backend := &Backend{ - id: "compat", - compat: true, + id: "compat", } room, err := hub.createRoom(roomId, emptyProperties, backend) require.NoError(err) From f537711e140668444bbc5baa8652e9867f113852 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 15 Jul 2024 10:57:37 +0200 Subject: [PATCH 136/549] Add support for multiple URLs per backend. --- api_backend.go | 50 +++++++---- api_signaling.go | 4 + backend_configuration.go | 18 ++++ backend_configuration_test.go | 2 +- backend_storage_etcd.go | 118 +++++++++++++++----------- backend_storage_static.go | 154 ++++++++++++++++++++++++---------- 6 files changed, 239 insertions(+), 107 deletions(-) diff --git a/api_backend.go b/api_backend.go index b0aa36a..1a17ed8 100644 --- a/api_backend.go +++ b/api_backend.go @@ -32,6 +32,7 @@ import ( "net/http" "net/url" "regexp" + "slices" "strings" "time" ) @@ -440,10 +441,12 @@ type TurnCredentials struct { // Information on a backend in the etcd cluster. type BackendInformationEtcd struct { - parsedUrl *url.URL + // Compat setting. + Url string `json:"url,omitempty"` - Url string `json:"url"` - Secret string `json:"secret"` + Urls []string `json:"urls,omitempty"` + parsedUrls []*url.URL + Secret string `json:"secret"` MaxStreamBitrate int `json:"maxstreambitrate,omitempty"` MaxScreenBitrate int `json:"maxscreenbitrate,omitempty"` @@ -452,24 +455,41 @@ type BackendInformationEtcd struct { } func (p *BackendInformationEtcd) CheckValid() error { - if p.Url == "" { - return fmt.Errorf("url missing") - } if p.Secret == "" { return fmt.Errorf("secret missing") } - parsedUrl, err := url.Parse(p.Url) - if err != nil { - return fmt.Errorf("invalid url: %w", err) + if len(p.Urls) > 0 { + slices.Sort(p.Urls) + p.Urls = slices.Compact(p.Urls) + for idx, u := range p.Urls { + parsedUrl, err := url.Parse(u) + if err != nil { + return fmt.Errorf("invalid url %s: %w", u, err) + } + if strings.Contains(parsedUrl.Host, ":") && hasStandardPort(parsedUrl) { + parsedUrl.Host = parsedUrl.Hostname() + p.Urls[idx] = parsedUrl.String() + } + + p.parsedUrls = append(p.parsedUrls, parsedUrl) + } + } else if p.Url != "" { + parsedUrl, err := url.Parse(p.Url) + if err != nil { + return fmt.Errorf("invalid url: %w", err) + } + if strings.Contains(parsedUrl.Host, ":") && hasStandardPort(parsedUrl) { + parsedUrl.Host = parsedUrl.Hostname() + p.Url = parsedUrl.String() + } + + p.Urls = append(p.Urls, p.Url) + p.parsedUrls = append(p.parsedUrls, parsedUrl) + } else { + return fmt.Errorf("urls missing") } - if strings.Contains(parsedUrl.Host, ":") && hasStandardPort(parsedUrl) { - parsedUrl.Host = parsedUrl.Hostname() - p.Url = parsedUrl.String() - } - - p.parsedUrl = parsedUrl return nil } diff --git a/api_signaling.go b/api_signaling.go index 45f139a..45c830d 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -494,6 +494,10 @@ func (m *HelloClientMessage) CheckValid() error { if m.Auth.Url[len(m.Auth.Url)-1] != '/' { m.Auth.Url += "/" } + if pos := strings.Index(m.Auth.Url, "ocs/v2.php/apps/spreed/"); pos != -1 { + m.Auth.Url = m.Auth.Url[:pos] + } + if u, err := url.ParseRequestURI(m.Auth.Url); err != nil { return err } else { diff --git a/backend_configuration.go b/backend_configuration.go index 33d5eb8..32d1001 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -22,8 +22,10 @@ package signaling import ( + "bytes" "fmt" "net/url" + "slices" "strings" "sync" @@ -68,6 +70,22 @@ func (b *Backend) IsCompat() bool { return len(b.urls) == 0 } +func (b *Backend) Equal(other *Backend) bool { + if b == other { + return true + } else if b == nil || other == nil { + return false + } + + return b.id == other.id && + b.allowHttp == other.allowHttp && + b.maxStreamBitrate == other.maxStreamBitrate && + b.maxScreenBitrate == other.maxScreenBitrate && + b.sessionLimit == other.sessionLimit && + bytes.Equal(b.secret, other.secret) && + slices.Equal(b.urls, other.urls) +} + func (b *Backend) IsUrlAllowed(u *url.URL) bool { switch u.Scheme { case "https": diff --git a/backend_configuration_test.go b/backend_configuration_test.go index c6b701e..6602630 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -461,7 +461,7 @@ func mustParse(s string) *url.URL { return p } -func TestBackendConfiguration_Etcd(t *testing.T) { +func TestBackendConfiguration_EtcdCompat(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 64a3571..3be7919 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -28,6 +28,7 @@ import ( "fmt" "log" "net/url" + "slices" "time" "github.com/dlintw/goconf" @@ -178,53 +179,58 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data return } + allowHttp := slices.ContainsFunc(info.parsedUrls, func(u *url.URL) bool { + return u.Scheme == "http" + }) + backend := &Backend{ id: key, - urls: []string{info.Url}, + urls: info.Urls, secret: []byte(info.Secret), - allowHttp: info.parsedUrl.Scheme == "http", + allowHttp: allowHttp, maxStreamBitrate: info.MaxStreamBitrate, maxScreenBitrate: info.MaxScreenBitrate, sessionLimit: info.SessionLimit, } - host := info.parsedUrl.Host - s.mu.Lock() defer s.mu.Unlock() s.keyInfos[key] = &info - entries, found := s.backends[host] - if !found { - // Simple case, first backend for this host - log.Printf("Added backend %s (from %s)", info.Url, key) - s.backends[host] = []*Backend{backend} - updateBackendStats(backend) - statsBackendsCurrent.Inc() - s.wakeupForTesting() - return - } - - // Was the backend changed? - replaced := false - for idx, entry := range entries { - if entry.id == key { - log.Printf("Updated backend %s (from %s)", info.Url, key) + for idx, u := range info.parsedUrls { + host := u.Host + entries, found := s.backends[host] + if !found { + // Simple case, first backend for this host + log.Printf("Added backend %s (from %s)", info.Urls[idx], key) + s.backends[host] = []*Backend{backend} updateBackendStats(backend) - entries[idx] = backend - replaced = true - break + statsBackendsCurrent.Inc() + s.wakeupForTesting() + continue } - } - if !replaced { - // New backend, add to list. - log.Printf("Added backend %s (from %s)", info.Url, key) - s.backends[host] = append(entries, backend) - updateBackendStats(backend) - statsBackendsCurrent.Inc() + // Was the backend changed? + replaced := false + for idx, entry := range entries { + if entry.id == key { + log.Printf("Updated backend %s (from %s)", info.Urls[idx], key) + updateBackendStats(backend) + entries[idx] = backend + replaced = true + break + } + } + + if !replaced { + // New backend, add to list. + log.Printf("Added backend %s (from %s)", info.Urls[idx], key) + s.backends[host] = append(entries, backend) + updateBackendStats(backend) + statsBackendsCurrent.Inc() + } } s.wakeupForTesting() } @@ -239,27 +245,43 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev } delete(s.keyInfos, key) - host := info.parsedUrl.Host - entries, found := s.backends[host] - if !found { - return - } - - log.Printf("Removing backend %s (from %s)", info.Url, key) - newEntries := make([]*Backend, 0, len(entries)-1) - for _, entry := range entries { - if entry.id == key { - updateBackendStats(entry) - statsBackendsCurrent.Dec() + var deleted map[string][]*Backend + for idx, u := range info.parsedUrls { + host := u.Host + entries, found := s.backends[host] + if !found { + if d, ok := deleted[host]; ok { + if slices.ContainsFunc(d, func(b *Backend) bool { + return slices.Contains(b.urls, u.String()) + }) { + log.Printf("Removing backend %s (from %s)", info.Urls[idx], key) + } + } continue } - newEntries = append(newEntries, entry) - } - if len(newEntries) > 0 { - s.backends[host] = newEntries - } else { - delete(s.backends, host) + log.Printf("Removing backend %s (from %s)", info.Urls[idx], key) + newEntries := make([]*Backend, 0, len(entries)-1) + for _, entry := range entries { + if entry.id == key { + if len(info.parsedUrls) > 1 { + if deleted == nil { + deleted = make(map[string][]*Backend) + } + deleted[host] = append(deleted[host], entry) + } + updateBackendStats(entry) + statsBackendsCurrent.Dec() + continue + } + + newEntries = append(newEntries, entry) + } + if len(newEntries) > 0 { + s.backends[host] = newEntries + } else { + delete(s.backends, host) + } } s.wakeupForTesting() } diff --git a/backend_storage_static.go b/backend_storage_static.go index 06883a5..26cd58a 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -24,7 +24,7 @@ package signaling import ( "log" "net/url" - "reflect" + "slices" "strings" "github.com/dlintw/goconf" @@ -66,14 +66,18 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) updateBackendStats(compatBackend) numBackends++ } else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { + added := make(map[string]*Backend) for host, configuredBackends := range getConfiguredHosts(backendIds, config, commonSecret) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { - log.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) - updateBackendStats(be) + added[be.id] = be } - numBackends += len(configuredBackends) } + for _, be := range added { + log.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) + updateBackendStats(be) + } + numBackends += len(added) } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { // Old-style configuration, only hosts are configured and are using a common secret. allowMap := make(map[string]bool) @@ -135,23 +139,30 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) func (s *backendStorageStatic) Close() { } -func (s *backendStorageStatic) RemoveBackendsForHost(host string) { +func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[string]bool) { if oldBackends := s.backends[host]; len(oldBackends) > 0 { + deleted := 0 for _, backend := range oldBackends { + if seen[backend.Id()] { + continue + } + + seen[backend.Id()] = true log.Printf("Backend %s removed for %s", backend.id, strings.Join(backend.urls, ", ")) deleteBackendStats(backend) + deleted++ } - statsBackendsCurrent.Sub(float64(len(oldBackends))) + statsBackendsCurrent.Sub(float64(deleted)) } delete(s.backends, host) } -func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend) { +func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen map[string]bool) { for existingIndex, existingBackend := range s.backends[host] { found := false index := 0 for _, newBackend := range backends { - if reflect.DeepEqual(existingBackend, newBackend) { // otherwise we could manually compare the struct members here + if existingBackend.Equal(newBackend) { found = true backends = append(backends[:index], backends[index+1:]...) break @@ -159,27 +170,41 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend) { found = true s.backends[host][existingIndex] = newBackend backends = append(backends[:index], backends[index+1:]...) - log.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) - updateBackendStats(newBackend) + if !seen[newBackend.id] { + seen[newBackend.id] = true + log.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) + updateBackendStats(newBackend) + } break } index++ } if !found { removed := s.backends[host][existingIndex] - log.Printf("Backend %s removed for %s", removed.id, strings.Join(removed.urls, ", ")) s.backends[host] = append(s.backends[host][:existingIndex], s.backends[host][existingIndex+1:]...) - deleteBackendStats(removed) - statsBackendsCurrent.Dec() + if !seen[removed.id] { + seen[removed.id] = true + log.Printf("Backend %s removed for %s", removed.id, strings.Join(removed.urls, ", ")) + deleteBackendStats(removed) + statsBackendsCurrent.Dec() + } } } s.backends[host] = append(s.backends[host], backends...) + + addedBackends := 0 for _, added := range backends { + if seen[added.id] { + continue + } + + seen[added.id] = true log.Printf("Backend %s added for %s", added.id, strings.Join(added.urls, ", ")) updateBackendStats(added) + addedBackends++ } - statsBackendsCurrent.Add(float64(len(backends))) + statsBackendsCurrent.Add(float64(addedBackends)) } func getConfiguredBackendIDs(backendIds string) (ids []string) { @@ -201,35 +226,26 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } +func MapIf[T any](s []T, f func(T) (T, bool)) []T { + result := make([]T, 0, len(s)) + for _, v := range s { + if v, ok := f(v); ok { + result = append(result, v) + } + } + return result +} + func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { hosts = make(map[string][]*Backend) + seenUrls := make(map[string]string) for _, id := range getConfiguredBackendIDs(backendIds) { - u, _ := GetStringOptionWithEnv(config, id, "url") - if u == "" { - log.Printf("Backend %s is missing or incomplete, skipping", id) - continue - } - - if u[len(u)-1] != '/' { - u += "/" - } - parsed, err := url.Parse(u) - if err != nil { - log.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err) - continue - } - - if strings.Contains(parsed.Host, ":") && hasStandardPort(parsed) { - parsed.Host = parsed.Hostname() - u = parsed.String() - } - secret, _ := GetStringOptionWithEnv(config, id, "secret") if secret == "" && commonSecret != "" { log.Printf("Backend %s has no own shared secret set, using common shared secret", id) secret = commonSecret } - if u == "" || secret == "" { + if secret == "" { log.Printf("Backend %s is missing or incomplete, skipping", id) continue } @@ -251,18 +267,69 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr maxScreenBitrate = 0 } - hosts[parsed.Host] = append(hosts[parsed.Host], &Backend{ - id: id, - urls: []string{u}, - secret: []byte(secret), + var urls []string + if u, _ := GetStringOptionWithEnv(config, id, "urls"); u != "" { + urls = strings.Split(u, ",") + urls = MapIf(urls, func(s string) (string, bool) { + s = strings.TrimSpace(s) + return s, len(s) > 0 + }) + slices.Sort(urls) + urls = slices.Compact(urls) + } else if u, _ := GetStringOptionWithEnv(config, id, "url"); u != "" { + if u = strings.TrimSpace(u); u != "" { + urls = []string{u} + } + } - allowHttp: parsed.Scheme == "http", + if len(urls) == 0 { + log.Printf("Backend %s is missing or incomplete, skipping", id) + continue + } + + backend := &Backend{ + id: id, + secret: []byte(secret), maxStreamBitrate: maxStreamBitrate, maxScreenBitrate: maxScreenBitrate, sessionLimit: uint64(sessionLimit), - }) + } + + added := make(map[string]bool) + for _, u := range urls { + if u[len(u)-1] != '/' { + u += "/" + } + + parsed, err := url.Parse(u) + if err != nil { + log.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err) + continue + } + + if strings.Contains(parsed.Host, ":") && hasStandardPort(parsed) { + parsed.Host = parsed.Hostname() + u = parsed.String() + } + + if prev, found := seenUrls[u]; found { + log.Printf("Url %s in backend %s was already used in backend %s, skipping", u, id, prev) + continue + } + + seenUrls[u] = id + backend.urls = append(backend.urls, u) + if parsed.Scheme == "http" { + backend.allowHttp = true + } + + if !added[parsed.Host] { + hosts[parsed.Host] = append(hosts[parsed.Host], backend) + added[parsed.Host] = true + } + } } return hosts @@ -283,15 +350,16 @@ func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { configuredHosts := getConfiguredHosts(backendIds, config, commonSecret) // remove backends that are no longer configured + seen := make(map[string]bool) for hostname := range s.backends { if _, ok := configuredHosts[hostname]; !ok { - s.RemoveBackendsForHost(hostname) + s.RemoveBackendsForHost(hostname, seen) } } // rewrite backends adding newly configured ones and rewriting existing ones for hostname, configuredBackends := range configuredHosts { - s.UpsertHost(hostname, configuredBackends) + s.UpsertHost(hostname, configuredBackends, seen) } } } From 762ed8fe59dcd34d1d59ca862353bc29ee25da73 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 19 Dec 2024 16:46:06 +0100 Subject: [PATCH 137/549] Update generated files. --- api_backend_easyjson.go | 76 +++++++++++++++++++++++++++++++++-------- grpc_sessions.pb.go | 25 ++++++++++---- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 5284f0a..1905387 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -3706,6 +3706,29 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle switch key { case "url": out.Url = string(in.String()) + case "urls": + if in.IsNull() { + in.Skip() + out.Urls = nil + } else { + in.Delim('[') + if out.Urls == nil { + if !in.IsDelim(']') { + out.Urls = make([]string, 0, 4) + } else { + out.Urls = []string{} + } + } else { + out.Urls = (out.Urls)[:0] + } + for !in.IsDelim(']') { + var v74 string + v74 = string(in.String()) + out.Urls = append(out.Urls, v74) + in.WantComma() + } + in.Delim(']') + } case "secret": out.Secret = string(in.String()) case "maxstreambitrate": @@ -3728,14 +3751,39 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw out.RawByte('{') first := true _ = first - { + if in.Url != "" { const prefix string = ",\"url\":" + first = false out.RawString(prefix[1:]) out.String(string(in.Url)) } + if len(in.Urls) != 0 { + const prefix string = ",\"urls\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + { + out.RawByte('[') + for v75, v76 := range in.Urls { + if v75 > 0 { + out.RawByte(',') + } + out.String(string(v76)) + } + out.RawByte(']') + } + } { const prefix string = ",\"secret\":" - out.RawString(prefix) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } out.String(string(in.Secret)) } if in.MaxStreamBitrate != 0 { @@ -4009,9 +4057,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle *out.Permissions = (*out.Permissions)[:0] } for !in.IsDelim(']') { - var v74 Permission - v74 = Permission(in.String()) - *out.Permissions = append(*out.Permissions, v74) + var v77 Permission + v77 = Permission(in.String()) + *out.Permissions = append(*out.Permissions, v77) in.WantComma() } in.Delim(']') @@ -4058,11 +4106,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw out.RawString("null") } else { out.RawByte('[') - for v75, v76 := range *in.Permissions { - if v75 > 0 { + for v78, v79 := range *in.Permissions { + if v78 > 0 { out.RawByte(',') } - out.String(string(v76)) + out.String(string(v79)) } out.RawByte(']') } @@ -4587,9 +4635,9 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle out.Entries = (out.Entries)[:0] } for !in.IsDelim(']') { - var v77 BackendPingEntry - (v77).UnmarshalEasyJSON(in) - out.Entries = append(out.Entries, v77) + var v80 BackendPingEntry + (v80).UnmarshalEasyJSON(in) + out.Entries = append(out.Entries, v80) in.WantComma() } in.Delim(']') @@ -4625,11 +4673,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jw out.RawString("null") } else { out.RawByte('[') - for v78, v79 := range in.Entries { - if v78 > 0 { + for v81, v82 := range in.Entries { + if v81 > 0 { out.RawByte(',') } - (v79).MarshalEasyJSON(out) + (v82).MarshalEasyJSON(out) } out.RawByte(']') } diff --git a/grpc_sessions.pb.go b/grpc_sessions.pb.go index 7dbd91e..3d9fbc8 100644 --- a/grpc_sessions.pb.go +++ b/grpc_sessions.pb.go @@ -329,9 +329,11 @@ func (x *IsSessionInCallReply) GetInCall() bool { } type GetInternalSessionsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - RoomId string `protobuf:"bytes,1,opt,name=roomId,proto3" json:"roomId,omitempty"` - BackendUrl string `protobuf:"bytes,2,opt,name=backendUrl,proto3" json:"backendUrl,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + RoomId string `protobuf:"bytes,1,opt,name=roomId,proto3" json:"roomId,omitempty"` + // Deprecated: Marked as deprecated in grpc_sessions.proto. + BackendUrl string `protobuf:"bytes,2,opt,name=backendUrl,proto3" json:"backendUrl,omitempty"` + BackendUrls []string `protobuf:"bytes,3,rep,name=backendUrls,proto3" json:"backendUrls,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -373,6 +375,7 @@ func (x *GetInternalSessionsRequest) GetRoomId() string { return "" } +// Deprecated: Marked as deprecated in grpc_sessions.proto. func (x *GetInternalSessionsRequest) GetBackendUrl() string { if x != nil { return x.BackendUrl @@ -380,6 +383,13 @@ func (x *GetInternalSessionsRequest) GetBackendUrl() string { return "" } +func (x *GetInternalSessionsRequest) GetBackendUrls() []string { + if x != nil { + return x.BackendUrls + } + return nil +} + type InternalSessionData struct { state protoimpl.MessageState `protogen:"open.v1"` SessionId string `protobuf:"bytes,1,opt,name=sessionId,proto3" json:"sessionId,omitempty"` @@ -653,12 +663,13 @@ const file_grpc_sessions_proto_rawDesc = "" + "backendUrl\x18\x03 \x01(\tR\n" + "backendUrl\".\n" + "\x14IsSessionInCallReply\x12\x16\n" + - "\x06inCall\x18\x01 \x01(\bR\x06inCall\"T\n" + + "\x06inCall\x18\x01 \x01(\bR\x06inCall\"z\n" + "\x1aGetInternalSessionsRequest\x12\x16\n" + - "\x06roomId\x18\x01 \x01(\tR\x06roomId\x12\x1e\n" + + "\x06roomId\x18\x01 \x01(\tR\x06roomId\x12\"\n" + "\n" + - "backendUrl\x18\x02 \x01(\tR\n" + - "backendUrl\"g\n" + + "backendUrl\x18\x02 \x01(\tB\x02\x18\x01R\n" + + "backendUrl\x12 \n" + + "\vbackendUrls\x18\x03 \x03(\tR\vbackendUrls\"g\n" + "\x13InternalSessionData\x12\x1c\n" + "\tsessionId\x18\x01 \x01(\tR\tsessionId\x12\x16\n" + "\x06inCall\x18\x02 \x01(\rR\x06inCall\x12\x1a\n" + From 9fea05769f8cd511c20b396f2754794c4baa67a0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Jul 2025 11:43:52 +0200 Subject: [PATCH 138/549] Add more tests for configuring / using multiple URLs. --- backend_configuration_test.go | 164 ++++++++++++++++++++++++++++++++++ hub_test.go | 145 ++++++++++++++++++++++++++++++ testclient_test.go | 2 +- 3 files changed, 310 insertions(+), 1 deletion(-) diff --git a/backend_configuration_test.go b/backend_configuration_test.go index 6602630..8742ead 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -26,6 +26,7 @@ import ( "net/url" "reflect" "sort" + "strings" "testing" "github.com/dlintw/goconf" @@ -611,3 +612,166 @@ func TestBackendCommonSecret(t *testing.T) { assert.Equal(string(testBackendSecret), string(b2.Secret())) } } + +func TestBackendChangeUrls(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + u1, err := url.Parse("http://domain1.invalid/") + require.NoError(err) + u2, err := url.Parse("http://domain2.invalid/") + require.NoError(err) + original_config := goconf.NewConfigFile() + original_config.AddOption("backend", "backends", "backend1,backend2") + original_config.AddOption("backend", "secret", string(testBackendSecret)) + original_config.AddOption("backend1", "urls", u1.String()) + original_config.AddOption("backend2", "urls", u2.String()) + cfg, err := NewBackendConfiguration(original_config, nil) + require.NoError(err) + + if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { + assert.Equal("backend1", b1.Id()) + assert.Equal(string(testBackendSecret), string(b1.Secret())) + assert.Equal([]string{u1.String()}, b1.Urls()) + } + if b2 := cfg.GetBackend(u2); assert.NotNil(b2) { + assert.Equal("backend2", b2.Id()) + assert.Equal(string(testBackendSecret), string(b2.Secret())) + assert.Equal([]string{u2.String()}, b2.Urls()) + } + + // Add url. + updated_config := goconf.NewConfigFile() + updated_config.AddOption("backend", "backends", "backend1") + updated_config.AddOption("backend", "secret", string(testBackendSecret)) + updated_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") + updated_config.AddOption("backend1", "urls", strings.Join([]string{u1.String(), u2.String()}, ",")) + cfg.Reload(updated_config) + + if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { + assert.Equal("backend1", b1.Id()) + assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) + assert.Equal([]string{u1.String(), u2.String()}, b1.Urls()) + } + if b1 := cfg.GetBackend(u2); assert.NotNil(b1) { + assert.Equal("backend1", b1.Id()) + assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) + assert.Equal([]string{u1.String(), u2.String()}, b1.Urls()) + } + + // No change reload. + cfg.Reload(updated_config) + if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { + assert.Equal("backend1", b1.Id()) + assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) + assert.Equal([]string{u1.String(), u2.String()}, b1.Urls()) + } + if b1 := cfg.GetBackend(u2); assert.NotNil(b1) { + assert.Equal("backend1", b1.Id()) + assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) + assert.Equal([]string{u1.String(), u2.String()}, b1.Urls()) + } + + // Remove url. + updated_config = goconf.NewConfigFile() + updated_config.AddOption("backend", "backends", "backend1") + updated_config.AddOption("backend", "secret", string(testBackendSecret)) + updated_config.AddOption("backend1", "urls", u2.String()) + cfg.Reload(updated_config) + + if b1 := cfg.GetBackend(u2); assert.NotNil(b1) { + assert.Equal("backend1", b1.Id()) + assert.Equal(string(testBackendSecret), string(b1.Secret())) + assert.Equal([]string{u2.String()}, b1.Urls()) + } +} + +func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + etcd, client := NewEtcdClientForTest(t) + + url1 := "https://domain1.invalid/foo" + initialSecret1 := string(testBackendSecret) + "-backend1-initial" + secret1 := string(testBackendSecret) + "-backend1" + + SetEtcdValue(etcd, "/backends/1_one", []byte("{\"urls\":[\""+url1+"\"],\"secret\":\""+initialSecret1+"\"}")) + + config := goconf.NewConfigFile() + config.AddOption("backend", "backendtype", "etcd") + config.AddOption("backend", "backendprefix", "/backends") + + cfg, err := NewBackendConfiguration(config, client) + require.NoError(err) + defer cfg.Close() + + storage := cfg.storage.(*backendStorageEtcd) + ch := storage.getWakeupChannelForTesting() + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + require.NoError(storage.WaitForInitialized(ctx)) + + if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && + assert.Equal([]string{url1}, backends[0].Urls()) && + assert.Equal(initialSecret1, string(backends[0].Secret())) { + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } + } + + url2 := "https://domain1.invalid/bar" + + drainWakeupChannel(ch) + SetEtcdValue(etcd, "/backends/1_one", []byte("{\"urls\":[\""+url1+"\",\""+url2+"\"],\"secret\":\""+secret1+"\"}")) + <-ch + if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && + assert.Equal([]string{url2, url1}, backends[0].Urls()) && + assert.Equal(secret1, string(backends[0].Secret())) { + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } + if backend := cfg.GetBackend(mustParse(url2)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } + } + + url3 := "https://domain2.invalid/foo" + secret3 := string(testBackendSecret) + "-backend3" + + drainWakeupChannel(ch) + SetEtcdValue(etcd, "/backends/3_three", []byte("{\"urls\":[\""+url3+"\"],\"secret\":\""+secret3+"\"}")) + <-ch + if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && + assert.Equal([]string{url2, url1}, backends[0].Urls()) && + assert.Equal(secret1, string(backends[0].Secret())) && + assert.Equal([]string{url3}, backends[1].Urls()) && + assert.Equal(secret3, string(backends[1].Secret())) { + if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } else if backend := cfg.GetBackend(mustParse(url2)); assert.NotNil(backend) { + assert.Equal(backends[0], backend) + } else if backend := cfg.GetBackend(mustParse(url3)); assert.NotNil(backend) { + assert.Equal(backends[1], backend) + } + } + + drainWakeupChannel(ch) + DeleteEtcdValue(etcd, "/backends/1_one") + <-ch + if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { + assert.Equal([]string{url3}, backends[0].Urls()) + assert.Equal(secret3, string(backends[0].Secret())) + } + + drainWakeupChannel(ch) + DeleteEtcdValue(etcd, "/backends/3_three") + <-ch + + _, found := storage.backends["domain1.invalid"] + assert.False(found, "Should have removed host information") +} diff --git a/hub_test.go b/hub_test.go index fec0fb1..c6db2ec 100644 --- a/hub_test.go +++ b/hub_test.go @@ -131,6 +131,21 @@ func getTestConfigWithMultipleBackends(server *httptest.Server) (*goconf.ConfigF return config, nil } +func getTestConfigWithMultipleUrls(server *httptest.Server) (*goconf.ConfigFile, error) { + config, err := getTestConfig(server) + if err != nil { + return nil, err + } + + config.RemoveOption("backend", "allowed") + config.RemoveOption("backend", "secret") + config.AddOption("backend", "backends", "backend1") + + config.AddOption("backend1", "urls", strings.Join([]string{server.URL + "/one", server.URL + "/two/"}, ",")) + config.AddOption("backend1", "secret", string(testBackendSecret)) + return config, nil +} + func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { require := require.New(t) r := mux.NewRouter() @@ -173,6 +188,13 @@ func CreateHubWithMultipleBackendsForTest(t *testing.T) (*Hub, AsyncEvents, *mux return h, events, r, server } +func CreateHubWithMultipleUrlsForTest(t *testing.T) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { + h, events, r, server := CreateHubForTestWithConfig(t, getTestConfigWithMultipleUrls) + registerBackendHandlerUrl(t, r, "/one") + registerBackendHandlerUrl(t, r, "/two") + return h, events, r, server +} + func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, *Hub, *mux.Router, *mux.Router, *httptest.Server, *httptest.Server) { require := require.New(t) r1 := mux.NewRouter() @@ -4442,6 +4464,59 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { } } +func TestSendBetweenDifferentUrls(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubWithMultipleUrlsForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1 := NewTestClient(t, server, hub) + defer client1.CloseWithBye() + + params1 := TestBackendClientAuthParams{ + UserId: "user1", + } + require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + + params2 := TestBackendClientAuthParams{ + UserId: "user2", + } + require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + recipient1 := MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + } + recipient2 := MessageClientMessageRecipient{ + Type: "session", + SessionId: hello2.Hello.SessionId, + } + + data1 := "from-1-to-2" + client1.SendMessage(recipient2, data1) // nolint + data2 := "from-2-to-1" + client2.SendMessage(recipient1, data2) // nolint + + var payload string + if err := checkReceiveClientMessage(ctx, client1, "session", hello2.Hello, &payload); assert.NoError(err) { + assert.Equal(data2, payload) + } + if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { + assert.Equal(data1, payload) + } +} + func TestNoSameRoomOnDifferentBackends(t *testing.T) { t.Parallel() CatchLogForTest(t) @@ -4527,6 +4602,76 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { } } +func TestSameRoomOnDifferentUrls(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubWithMultipleUrlsForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1 := NewTestClient(t, server, hub) + defer client1.CloseWithBye() + + params1 := TestBackendClientAuthParams{ + UserId: "user1", + } + require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + + params2 := TestBackendClientAuthParams{ + UserId: "user2", + } + require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + // Join room by id. + roomId := "test-room" + roomMsg, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + roomMsg, err = client2.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + hub.ru.RLock() + var rooms []*Room + for _, room := range hub.rooms { + defer room.Close() + rooms = append(rooms, room) + } + hub.ru.RUnlock() + + assert.Len(rooms, 1) + + recipient := MessageClientMessageRecipient{ + Type: "room", + } + + data1 := "from-1-to-2" + client1.SendMessage(recipient, data1) // nolint + data2 := "from-2-to-1" + client2.SendMessage(recipient, data2) // nolint + + var payload string + if err := checkReceiveClientMessage(ctx, client1, "room", hello2.Hello, &payload); assert.NoError(err) { + assert.Equal(data2, payload) + } + if err := checkReceiveClientMessage(ctx, client2, "room", hello1.Hello, &payload); assert.NoError(err) { + assert.Equal(data1, payload) + } +} + func TestClientSendOffer(t *testing.T) { CatchLogForTest(t) for _, subtest := range clusteredTests { diff --git a/testclient_test.go b/testclient_test.go index 836fb6a..ed97215 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -126,7 +126,7 @@ func checkMessageType(message *ServerMessage, expectedType string) error { func checkMessageSender(hub *Hub, sender *MessageServerMessageSender, senderType string, hello *HelloServerMessage) error { if sender.Type != senderType { - return fmt.Errorf("Expected sender type %s, got %s", senderType, sender.SessionId) + return fmt.Errorf("Expected sender type %s, got %s", senderType, sender.Type) } else if sender.SessionId != hello.SessionId { return fmt.Errorf("Expected session id %+v, got %+v", getPubliceSessionIdData(hub, hello.SessionId), getPubliceSessionIdData(hub, sender.SessionId)) From 8375b985e864cf47b565d47cf9871720c234cee1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Jul 2025 12:51:04 +0200 Subject: [PATCH 139/549] Improve detecting duplicate backend URLs in etcd configuration. --- api_backend.go | 22 +++++++-- api_backend_test.go | 107 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/api_backend.go b/api_backend.go index 1a17ed8..e20d5bf 100644 --- a/api_backend.go +++ b/api_backend.go @@ -454,7 +454,7 @@ type BackendInformationEtcd struct { SessionLimit uint64 `json:"sessionlimit,omitempty"` } -func (p *BackendInformationEtcd) CheckValid() error { +func (p *BackendInformationEtcd) CheckValid() (err error) { if p.Secret == "" { return fmt.Errorf("secret missing") } @@ -462,17 +462,31 @@ func (p *BackendInformationEtcd) CheckValid() error { if len(p.Urls) > 0 { slices.Sort(p.Urls) p.Urls = slices.Compact(p.Urls) - for idx, u := range p.Urls { + seen := make(map[string]bool) + outIdx := 0 + for _, u := range p.Urls { parsedUrl, err := url.Parse(u) if err != nil { return fmt.Errorf("invalid url %s: %w", u, err) } + if strings.Contains(parsedUrl.Host, ":") && hasStandardPort(parsedUrl) { parsedUrl.Host = parsedUrl.Hostname() - p.Urls[idx] = parsedUrl.String() + u = parsedUrl.String() + p.Urls[outIdx] = u + } else { + p.Urls[outIdx] = u } - + if seen[u] { + continue + } + seen[u] = true p.parsedUrls = append(p.parsedUrls, parsedUrl) + outIdx++ + } + if len(p.Urls) != outIdx { + clear(p.Urls[outIdx:]) + p.Urls = p.Urls[:outIdx] } } else if p.Url != "" { parsedUrl, err := url.Parse(p.Url) diff --git a/api_backend_test.go b/api_backend_test.go index 724075d..1a46212 100644 --- a/api_backend_test.go +++ b/api_backend_test.go @@ -72,3 +72,110 @@ func TestValidNumbers(t *testing.T) { assert.False(isValidNumber(number), "number %s should not be valid", number) } } + +func TestValidateBackendInformationEtcd(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + testcases := []struct { + b BackendInformationEtcd + expectedError string + expectedUrls []string + }{ + { + b: BackendInformationEtcd{}, + expectedError: "secret missing", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + }, + expectedError: "urls missing", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Url: "https://foo\n", + }, + expectedError: "invalid url", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo\n"}, + }, + expectedError: "invalid url", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo", "https://foo\n"}, + }, + expectedError: "invalid url", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Url: "https://foo:443", + }, + expectedUrls: []string{"https://foo"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo:443"}, + }, + expectedUrls: []string{"https://foo"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Url: "https://foo:8443", + }, + expectedUrls: []string{"https://foo:8443"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo:8443"}, + }, + expectedUrls: []string{"https://foo:8443"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo", "https://bar", "https://foo"}, + }, + expectedUrls: []string{"https://bar", "https://foo"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo", "https://bar", "https://foo:443", "https://zaz"}, + }, + expectedUrls: []string{"https://bar", "https://foo", "https://zaz"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo:443", "https://bar", "https://foo", "https://zaz"}, + }, + expectedUrls: []string{"https://bar", "https://foo", "https://zaz"}, + }, + } + + for idx, tc := range testcases { + if tc.expectedError == "" { + if assert.NoError(tc.b.CheckValid(), "failed for testcase %d", idx) { + assert.Equal(tc.expectedUrls, tc.b.Urls, "failed for testcase %d", idx) + var urls []string + for _, u := range tc.b.parsedUrls { + urls = append(urls, u.String()) + } + assert.Equal(tc.expectedUrls, urls, "failed for testcase %d", idx) + } + } else { + assert.ErrorContains(tc.b.CheckValid(), tc.expectedError, "failed for testcase %d, got %+v", idx, tc.b.parsedUrls) + } + } +} From ddad70b4c5b2d83b5bc1619b6922b9ca98cbeb08 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Jul 2025 13:52:39 +0200 Subject: [PATCH 140/549] Update default configuration and Docker scripts for backend urls. --- docker/README.md | 3 ++- docker/server/entrypoint.sh | 10 ++++++++-- server.conf.in | 15 ++++++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docker/README.md b/docker/README.md index 26cf4df..83760cd 100644 --- a/docker/README.md +++ b/docker/README.md @@ -30,7 +30,8 @@ The running container can be configured through different environment variables: - `BACKENDS`: Space-separated list of backend ids. - `BACKENDS_TIMEOUT`: Timeout in seconds for requests to backends. - `CONNECTIONS_PER_HOST`: Maximum number of concurrent backend connections per host. -- `BACKEND__URL`: Url of backend `ID` (where `ID` is the uppercase backend id). +- `BACKEND__URLS`: Comma-separated list of urls of backend `ID` (where `ID` is the uppercase backend id). +- `BACKEND__URL`: Url of backend `ID` (where `ID` is the uppercase backend id, deprecated). - `BACKEND__SHARED_SECRET`: Shared secret for backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__SESSION_LIMIT`: Optional session limit for backend `ID` (where `ID` is the uppercase backend id). - `BACKEND__MAX_STREAM_BITRATE`: Optional maximum bitrate for audio/video streams in backend `ID` (where `ID` is the uppercase backend id). diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 6b1195f..03d9996 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -273,9 +273,15 @@ if [ ! -f "$CONFIG" ]; then for backend in $BACKENDS; do echo "[$backend]" >> "$CONFIG" - declare var="BACKEND_${backend^^}_URL" + declare var="BACKEND_${backend^^}_URLS" if [ -n "${!var}" ]; then - echo "url = ${!var}" >> "$CONFIG" + echo "urls = ${!var}" >> "$CONFIG" + else + declare var_compat="BACKEND_${backend^^}_URL" + if [ -n "${!var_compat}" ]; then + echo "Variable $var_compat is deprecated, use $var instead." + echo "urls = ${!var_compat}" >> "$CONFIG" + fi fi declare var="BACKEND_${backend^^}_SHARED_SECRET" diff --git a/server.conf.in b/server.conf.in index 6703531..8f437e3 100644 --- a/server.conf.in +++ b/server.conf.in @@ -84,7 +84,8 @@ internalsecret = the-shared-secret-for-internal-clients # For backend type "etcd": # Key prefix of backend entries. All keys below will be watched and assumed to # contain a JSON document with the following entries: -# - "url": Url of the Nextcloud instance. +# - "urls": List of urls of the Nextcloud instance. +# - "url": Url of the Nextcloud instance (deprecated). # - "secret": Shared secret for requests from and to the backend servers. # # Additional optional entries: @@ -93,8 +94,8 @@ internalsecret = the-shared-secret-for-internal-clients # - "sessionlimit": Number of sessions that are allowed to connect. # # Example: -# "/signaling/backend/one" -> {"url": "https://nextcloud.domain1.invalid", ...} -# "/signaling/backend/two" -> {"url": "https://domain2.invalid/nextcloud", ...} +# "/signaling/backend/one" -> {"urls": ["https://nextcloud.domain1.invalid"], ...} +# "/signaling/backend/two" -> {"urls": ["https://domain2.invalid/nextcloud"], ...} #backendprefix = /signaling/backend # Allow any hostname as backend endpoint. This is extremely insecure and should @@ -122,8 +123,8 @@ connectionsperhost = 8 # Backend configurations as defined in the "[backend]" section above. The # section names must match the ids used in "backends" above. #[backend-id] -# URL of the Nextcloud instance -#url = https://cloud.domain.invalid +# Comma-separated list of urls of the Nextcloud instance +#urls = https://cloud.domain.invalid # Shared secret for requests from and to the backend servers. Leave empty to use # the common shared secret from above. @@ -143,8 +144,8 @@ connectionsperhost = 8 #maxscreenbitrate = 2097152 #[another-backend] -# URL of the Nextcloud instance -#url = https://cloud.otherdomain.invalid +# Comma-separated list of urls of the Nextcloud instance +#urls = https://cloud.otherdomain.invalid # Shared secret for requests from and to the backend servers. Leave empty to use # the common shared secret from above. From ac900616a54412a1e3a93c74f46cae49d87169ff Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 17 Jul 2025 16:13:44 +0200 Subject: [PATCH 141/549] Fix counting of backends for metrics. --- backend_configuration.go | 8 +++ backend_configuration_test.go | 108 +++++++++++++++++++++++----------- backend_storage_etcd.go | 21 ++++--- backend_storage_static.go | 99 +++++++++++++++++++++++++------ stats_prometheus_test.go | 16 ++++- 5 files changed, 190 insertions(+), 62 deletions(-) diff --git a/backend_configuration.go b/backend_configuration.go index 32d1001..154d4c9 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -56,6 +56,8 @@ type Backend struct { sessionLimit uint64 sessionsLock sync.Mutex sessions map[string]bool + + counted bool } func (b *Backend) Id() string { @@ -179,6 +181,12 @@ func (s *backendStorageCommon) GetBackends() []*Backend { for _, entries := range s.backends { result = append(result, entries...) } + slices.SortFunc(result, func(a, b *Backend) int { + return strings.Compare(a.Id(), b.Id()) + }) + result = slices.CompactFunc(result, func(a, b *Backend) bool { + return a.Id() == b.Id() + }) return result } diff --git a/backend_configuration_test.go b/backend_configuration_test.go index 8742ead..d51ba2f 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -30,7 +30,6 @@ import ( "testing" "github.com/dlintw/goconf" - "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -230,9 +229,10 @@ func TestParseBackendIds(t *testing.T) { } func TestBackendReloadNoChange(t *testing.T) { + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) - current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -242,7 +242,7 @@ func TestBackendReloadNoChange(t *testing.T) { original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") o_cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") @@ -254,18 +254,19 @@ func TestBackendReloadNoChange(t *testing.T) { n_cfg, err := NewBackendConfiguration(new_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+4) + checkStatsValue(t, statsBackendsCurrent, 4) o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, current+4) + checkStatsValue(t, statsBackendsCurrent, 4) if !reflect.DeepEqual(n_cfg, o_cfg) { assert.Fail(t, "BackendConfiguration should be equal after Reload") } } func TestBackendReloadChangeExistingURL(t *testing.T) { + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) - current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -276,7 +277,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { o_cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -288,22 +289,23 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { n_cfg, err := NewBackendConfiguration(new_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+4) + checkStatsValue(t, statsBackendsCurrent, 4) original_config.RemoveOption("backend1", "url") original_config.AddOption("backend1", "url", "http://domain3.invalid") original_config.AddOption("backend1", "sessionlimit", "10") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, current+4) + checkStatsValue(t, statsBackendsCurrent, 4) if !reflect.DeepEqual(n_cfg, o_cfg) { assert.Fail(t, "BackendConfiguration should be equal after Reload") } } func TestBackendReloadChangeSecret(t *testing.T) { + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) - current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -314,7 +316,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { o_cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -325,21 +327,20 @@ func TestBackendReloadChangeSecret(t *testing.T) { n_cfg, err := NewBackendConfiguration(new_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+4) + checkStatsValue(t, statsBackendsCurrent, 4) original_config.RemoveOption("backend1", "secret") original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, current+4) - if !reflect.DeepEqual(n_cfg, o_cfg) { - assert.Fail(t, "BackendConfiguration should be equal after Reload") - } + checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(t, n_cfg, o_cfg, "BackendConfiguration should be equal after Reload") } func TestBackendReloadAddBackend(t *testing.T) { + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) - current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "allowall", "false") @@ -348,7 +349,7 @@ func TestBackendReloadAddBackend(t *testing.T) { o_cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+1) + checkStatsValue(t, statsBackendsCurrent, 1) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -360,7 +361,7 @@ func TestBackendReloadAddBackend(t *testing.T) { n_cfg, err := NewBackendConfiguration(new_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+3) + checkStatsValue(t, statsBackendsCurrent, 3) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend2", "url", "http://domain2.invalid") @@ -368,16 +369,17 @@ func TestBackendReloadAddBackend(t *testing.T) { original_config.AddOption("backend2", "sessionlimit", "10") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, current+4) + checkStatsValue(t, statsBackendsCurrent, 4) if !reflect.DeepEqual(n_cfg, o_cfg) { assert.Fail(t, "BackendConfiguration should be equal after Reload") } } func TestBackendReloadRemoveHost(t *testing.T) { + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) - current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -388,7 +390,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { o_cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "allowall", "false") @@ -397,22 +399,23 @@ func TestBackendReloadRemoveHost(t *testing.T) { n_cfg, err := NewBackendConfiguration(new_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+3) + checkStatsValue(t, statsBackendsCurrent, 3) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1") original_config.RemoveSection("backend2") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) if !reflect.DeepEqual(n_cfg, o_cfg) { assert.Fail(t, "BackendConfiguration should be equal after Reload") } } func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) - current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -423,7 +426,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { o_cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "allowall", "false") @@ -432,13 +435,13 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { n_cfg, err := NewBackendConfiguration(new_config, nil) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, current+3) + checkStatsValue(t, statsBackendsCurrent, 3) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1") original_config.RemoveSection("backend2") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, current+2) + checkStatsValue(t, statsBackendsCurrent, 2) if !reflect.DeepEqual(n_cfg, o_cfg) { assert.Fail(t, "BackendConfiguration should be equal after Reload") } @@ -463,7 +466,8 @@ func mustParse(s string) *url.URL { } func TestBackendConfiguration_EtcdCompat(t *testing.T) { - t.Parallel() + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -479,6 +483,8 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { config.AddOption("backend", "backendtype", "etcd") config.AddOption("backend", "backendprefix", "/backends") + checkStatsValue(t, statsBackendsCurrent, 0) + cfg, err := NewBackendConfiguration(config, client) require.NoError(err) defer cfg.Close() @@ -502,6 +508,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+secret1+"\"}")) <-ch + checkStatsValue(t, statsBackendsCurrent, 1) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) { @@ -516,6 +523,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/2_two", []byte("{\"url\":\""+url2+"\",\"secret\":\""+secret2+"\"}")) <-ch + checkStatsValue(t, statsBackendsCurrent, 2) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) && @@ -534,6 +542,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/3_three", []byte("{\"url\":\""+url3+"\",\"secret\":\""+secret3+"\"}")) <-ch + checkStatsValue(t, statsBackendsCurrent, 3) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 3) && assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) && @@ -553,6 +562,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { drainWakeupChannel(ch) DeleteEtcdValue(etcd, "/backends/1_one") <-ch + checkStatsValue(t, statsBackendsCurrent, 2) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) { assert.Equal([]string{url2}, backends[0].urls) assert.Equal(secret2, string(backends[0].secret)) @@ -563,6 +573,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { drainWakeupChannel(ch) DeleteEtcdValue(etcd, "/backends/2_two") <-ch + checkStatsValue(t, statsBackendsCurrent, 1) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { assert.Equal([]string{url3}, backends[0].urls) assert.Equal(secret3, string(backends[0].secret)) @@ -614,7 +625,8 @@ func TestBackendCommonSecret(t *testing.T) { } func TestBackendChangeUrls(t *testing.T) { - t.Parallel() + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -627,9 +639,13 @@ func TestBackendChangeUrls(t *testing.T) { original_config.AddOption("backend", "secret", string(testBackendSecret)) original_config.AddOption("backend1", "urls", u1.String()) original_config.AddOption("backend2", "urls", u2.String()) + + checkStatsValue(t, statsBackendsCurrent, 0) + cfg, err := NewBackendConfiguration(original_config, nil) require.NoError(err) + checkStatsValue(t, statsBackendsCurrent, 2) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret), string(b1.Secret())) @@ -649,6 +665,7 @@ func TestBackendChangeUrls(t *testing.T) { updated_config.AddOption("backend1", "urls", strings.Join([]string{u1.String(), u2.String()}, ",")) cfg.Reload(updated_config) + checkStatsValue(t, statsBackendsCurrent, 1) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) @@ -662,6 +679,7 @@ func TestBackendChangeUrls(t *testing.T) { // No change reload. cfg.Reload(updated_config) + checkStatsValue(t, statsBackendsCurrent, 1) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) @@ -680,15 +698,26 @@ func TestBackendChangeUrls(t *testing.T) { updated_config.AddOption("backend1", "urls", u2.String()) cfg.Reload(updated_config) + checkStatsValue(t, statsBackendsCurrent, 1) if b1 := cfg.GetBackend(u2); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret), string(b1.Secret())) assert.Equal([]string{u2.String()}, b1.Urls()) } + + updated_config = goconf.NewConfigFile() + updated_config.AddOption("backend", "backends", "") + updated_config.AddOption("backend", "secret", string(testBackendSecret)) + cfg.Reload(updated_config) + + checkStatsValue(t, statsBackendsCurrent, 0) + b1 := cfg.GetBackend(u2) + assert.Nil(b1) } func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { - t.Parallel() + ResetStatsValue(t, statsBackendsCurrent) + CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -704,6 +733,8 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { config.AddOption("backend", "backendtype", "etcd") config.AddOption("backend", "backendprefix", "/backends") + checkStatsValue(t, statsBackendsCurrent, 0) + cfg, err := NewBackendConfiguration(config, client) require.NoError(err) defer cfg.Close() @@ -716,6 +747,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { require.NoError(storage.WaitForInitialized(ctx)) + checkStatsValue(t, statsBackendsCurrent, 1) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal([]string{url1}, backends[0].Urls()) && assert.Equal(initialSecret1, string(backends[0].Secret())) { @@ -729,6 +761,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/1_one", []byte("{\"urls\":[\""+url1+"\",\""+url2+"\"],\"secret\":\""+secret1+"\"}")) <-ch + checkStatsValue(t, statsBackendsCurrent, 1) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal([]string{url2, url1}, backends[0].Urls()) && assert.Equal(secret1, string(backends[0].Secret())) { @@ -743,13 +776,16 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { url3 := "https://domain2.invalid/foo" secret3 := string(testBackendSecret) + "-backend3" + url4 := "https://domain3.invalid/foo" + drainWakeupChannel(ch) - SetEtcdValue(etcd, "/backends/3_three", []byte("{\"urls\":[\""+url3+"\"],\"secret\":\""+secret3+"\"}")) + SetEtcdValue(etcd, "/backends/3_three", []byte("{\"urls\":[\""+url3+"\",\""+url4+"\"],\"secret\":\""+secret3+"\"}")) <-ch + checkStatsValue(t, statsBackendsCurrent, 2) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && assert.Equal([]string{url2, url1}, backends[0].Urls()) && assert.Equal(secret1, string(backends[0].Secret())) && - assert.Equal([]string{url3}, backends[1].Urls()) && + assert.Equal([]string{url3, url4}, backends[1].Urls()) && assert.Equal(secret3, string(backends[1].Secret())) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) @@ -757,14 +793,17 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { assert.Equal(backends[0], backend) } else if backend := cfg.GetBackend(mustParse(url3)); assert.NotNil(backend) { assert.Equal(backends[1], backend) + } else if backend := cfg.GetBackend(mustParse(url4)); assert.NotNil(backend) { + assert.Equal(backends[1], backend) } } drainWakeupChannel(ch) DeleteEtcdValue(etcd, "/backends/1_one") <-ch + checkStatsValue(t, statsBackendsCurrent, 1) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { - assert.Equal([]string{url3}, backends[0].Urls()) + assert.Equal([]string{url3, url4}, backends[0].Urls()) assert.Equal(secret3, string(backends[0].Secret())) } @@ -772,6 +811,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { DeleteEtcdValue(etcd, "/backends/3_three") <-ch + checkStatsValue(t, statsBackendsCurrent, 0) _, found := storage.backends["domain1.invalid"] assert.False(found, "Should have removed host information") } diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 3be7919..654e725 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -199,6 +199,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data defer s.mu.Unlock() s.keyInfos[key] = &info + added := false for idx, u := range info.parsedUrls { host := u.Host entries, found := s.backends[host] @@ -206,9 +207,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data // Simple case, first backend for this host log.Printf("Added backend %s (from %s)", info.Urls[idx], key) s.backends[host] = []*Backend{backend} - updateBackendStats(backend) - statsBackendsCurrent.Inc() - s.wakeupForTesting() + added = true continue } @@ -217,7 +216,6 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data for idx, entry := range entries { if entry.id == key { log.Printf("Updated backend %s (from %s)", info.Urls[idx], key) - updateBackendStats(backend) entries[idx] = backend replaced = true break @@ -228,10 +226,13 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data // New backend, add to list. log.Printf("Added backend %s (from %s)", info.Urls[idx], key) s.backends[host] = append(entries, backend) - updateBackendStats(backend) - statsBackendsCurrent.Inc() + added = true } } + updateBackendStats(backend) + if added { + statsBackendsCurrent.Inc() + } s.wakeupForTesting() } @@ -246,6 +247,7 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev delete(s.keyInfos, key) var deleted map[string][]*Backend + seen := make(map[string]bool) for idx, u := range info.parsedUrls { host := u.Host entries, found := s.backends[host] @@ -270,8 +272,11 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev } deleted[host] = append(deleted[host], entry) } - updateBackendStats(entry) - statsBackendsCurrent.Dec() + if !seen[entry.Id()] { + seen[entry.Id()] = true + updateBackendStats(entry) + statsBackendsCurrent.Dec() + } continue } diff --git a/backend_storage_static.go b/backend_storage_static.go index 26cd58a..1ca6751 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -33,6 +33,8 @@ import ( type backendStorageStatic struct { backendStorageCommon + backendsById map[string]*Backend + // Deprecated allowAll bool commonSecret []byte @@ -48,6 +50,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) sessionLimit = 0 } backends := make(map[string][]*Backend) + backendsById := make(map[string]*Backend) var compatBackend *Backend numBackends := 0 if allowAll { @@ -59,11 +62,13 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) allowHttp: allowHttp, sessionLimit: uint64(sessionLimit), + counted: true, } if sessionLimit > 0 { log.Printf("Allow a maximum of %d sessions", sessionLimit) } updateBackendStats(compatBackend) + backendsById[compatBackend.id] = compatBackend numBackends++ } else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { added := make(map[string]*Backend) @@ -75,7 +80,9 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) } for _, be := range added { log.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) + backendsById[be.id] = be updateBackendStats(be) + be.counted = true } numBackends += len(added) } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { @@ -102,6 +109,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) allowHttp: allowHttp, sessionLimit: uint64(sessionLimit), + counted: true, } hosts := make([]string, 0, len(allowMap)) for host := range allowMap { @@ -116,6 +124,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) log.Printf("Allow a maximum of %d sessions", sessionLimit) } updateBackendStats(compatBackend) + backendsById[compatBackend.id] = compatBackend numBackends++ } } @@ -130,6 +139,8 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) backends: backends, }, + backendsById: backendsById, + allowAll: allowAll, commonSecret: []byte(commonSecret), compatBackend: compatBackend, @@ -139,25 +150,51 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) func (s *backendStorageStatic) Close() { } -func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[string]bool) { +func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[string]seenState) { if oldBackends := s.backends[host]; len(oldBackends) > 0 { deleted := 0 for _, backend := range oldBackends { - if seen[backend.Id()] { + if seen[backend.Id()] == seenDeleted { continue } - seen[backend.Id()] = true - log.Printf("Backend %s removed for %s", backend.id, strings.Join(backend.urls, ", ")) - deleteBackendStats(backend) - deleted++ + seen[backend.Id()] = seenDeleted + urls := filter(backend.urls, func(s string) bool { + return !strings.Contains(s, "://"+host) + }) + log.Printf("Backend %s removed for %s", backend.id, strings.Join(urls, ", ")) + if len(urls) == len(backend.urls) && backend.counted { + deleteBackendStats(backend) + delete(s.backendsById, backend.Id()) + deleted++ + backend.counted = false + } } statsBackendsCurrent.Sub(float64(deleted)) } delete(s.backends, host) } -func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen map[string]bool) { +func filter[T any](s []T, del func(T) bool) []T { + result := make([]T, 0, len(s)) + for _, e := range s { + if !del(e) { + result = append(result, e) + } + } + return result +} + +type seenState int + +const ( + seenNotSeen seenState = iota + seenAdded + seenUpdated + seenDeleted +) + +func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen map[string]seenState) { for existingIndex, existingBackend := range s.backends[host] { found := false index := 0 @@ -170,10 +207,12 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen found = true s.backends[host][existingIndex] = newBackend backends = append(backends[:index], backends[index+1:]...) - if !seen[newBackend.id] { - seen[newBackend.id] = true + if seen[newBackend.id] != seenUpdated { + seen[newBackend.id] = seenUpdated log.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) updateBackendStats(newBackend) + newBackend.counted = existingBackend.counted + s.backendsById[newBackend.id] = newBackend } break } @@ -182,11 +221,18 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen if !found { removed := s.backends[host][existingIndex] s.backends[host] = append(s.backends[host][:existingIndex], s.backends[host][existingIndex+1:]...) - if !seen[removed.id] { - seen[removed.id] = true - log.Printf("Backend %s removed for %s", removed.id, strings.Join(removed.urls, ", ")) - deleteBackendStats(removed) - statsBackendsCurrent.Dec() + if seen[removed.id] != seenDeleted { + seen[removed.id] = seenDeleted + urls := filter(removed.urls, func(s string) bool { + return !strings.Contains(s, "://"+host) + }) + log.Printf("Backend %s removed for %s", removed.id, strings.Join(urls, ", ")) + if len(urls) == len(removed.urls) && removed.counted { + deleteBackendStats(removed) + delete(s.backendsById, removed.Id()) + statsBackendsCurrent.Dec() + removed.counted = false + } } } } @@ -195,14 +241,23 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen addedBackends := 0 for _, added := range backends { - if seen[added.id] { + if seen[added.id] == seenAdded { continue } - seen[added.id] = true + seen[added.id] = seenAdded + if prev, found := s.backendsById[added.id]; found { + added.counted = prev.counted + } else { + s.backendsById[added.id] = added + } + log.Printf("Backend %s added for %s", added.id, strings.Join(added.urls, ", ")) - updateBackendStats(added) - addedBackends++ + if !added.counted { + updateBackendStats(added) + addedBackends++ + added.counted = true + } } statsBackendsCurrent.Add(float64(addedBackends)) } @@ -350,7 +405,7 @@ func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { configuredHosts := getConfiguredHosts(backendIds, config, commonSecret) // remove backends that are no longer configured - seen := make(map[string]bool) + seen := make(map[string]seenState) for hostname := range s.backends { if _, ok := configuredHosts[hostname]; !ok { s.RemoveBackendsForHost(hostname, seen) @@ -361,6 +416,12 @@ func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { for hostname, configuredBackends := range configuredHosts { s.UpsertHost(hostname, configuredBackends, seen) } + } else { + // remove all backends + seen := make(map[string]seenState) + for hostname := range s.backends { + s.RemoveBackendsForHost(hostname, seen) + } } } diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index 859e2f3..7783d26 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -32,7 +32,20 @@ import ( "github.com/stretchr/testify/assert" ) +func ResetStatsValue[T prometheus.Gauge](t *testing.T, collector T) { + // Make sure test is not executed with "t.Parallel()" + t.Setenv("PARALLEL_CHECK", "1") + + collector.Set(0) + t.Cleanup(func() { + collector.Set(0) + }) +} + func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64) { + // Make sure test is not executed with "t.Parallel()" + t.Setenv("PARALLEL_CHECK", "1") + ch := make(chan *prometheus.Desc, 1) collector.Describe(ch) desc := <-ch @@ -41,7 +54,8 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 assert := assert.New(t) pc := make([]uintptr, 10) n := runtime.Callers(2, pc) - if assert.NotEqualValues(0, n, "Expected value %f for %s, got %f", value, desc, v) { + if n == 0 { + assert.EqualValues(value, v, "failed for %s", desc) return } From 5c0ebc4435c4cd139be1a293baf8f3ae0758e261 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:04:52 +0000 Subject: [PATCH 142/549] Bump github.com/pion/sdp/v3 from 3.0.14 to 3.0.15 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.14 to 3.0.15. - [Release notes](https://github.com/pion/sdp/releases) - [Changelog](https://github.com/pion/sdp/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/sdp/compare/v3.0.14...v3.0.15) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.15 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2f2ca25..b339a90 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 - github.com/pion/sdp/v3 v3.0.14 + github.com/pion/sdp/v3 v3.0.15 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index d112e26..b7d03aa 100644 --- a/go.sum +++ b/go.sum @@ -100,8 +100,8 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= -github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk= +github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= From 80e3463ab3d4e76fb90573c5cfd6cefa653b3168 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 22 Jul 2025 16:58:25 +0200 Subject: [PATCH 143/549] Remove debug output. --- federation.go | 1 - 1 file changed, 1 deletion(-) diff --git a/federation.go b/federation.go index 053c7c0..6856af8 100644 --- a/federation.go +++ b/federation.go @@ -608,7 +608,6 @@ func (c *FederationClient) updateEventUsers(users []map[string]interface{}, loca localCloudUrlLen := len(localCloudUrl) remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) checkSessionId := true - log.Printf("XXX local=%s remote=%s", localCloudUrl, remoteCloudUrl) for _, u := range users { if actorType, found := getStringMapEntry[string](u, "actorType"); found { if actorId, found := getStringMapEntry[string](u, "actorId"); found { From f72e606628c6f30f7016baf7ee9b17b8cd678b4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:50:42 +0000 Subject: [PATCH 144/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.2 to 3.6.3 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.2...v3.6.3) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.2 to 3.6.3 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.2...v3.6.3) Updates `go.etcd.io/etcd/client/v3` from 3.6.2 to 3.6.3 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.2...v3.6.3) Updates `go.etcd.io/etcd/server/v3` from 3.6.2 to 3.6.3 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.2...v3.6.3) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b339a90..09cbd25 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.6.2 - go.etcd.io/etcd/client/pkg/v3 v3.6.2 - go.etcd.io/etcd/client/v3 v3.6.2 - go.etcd.io/etcd/server/v3 v3.6.2 + go.etcd.io/etcd/api/v3 v3.6.3 + go.etcd.io/etcd/client/pkg/v3 v3.6.3 + go.etcd.io/etcd/client/v3 v3.6.3 + go.etcd.io/etcd/server/v3 v3.6.3 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.74.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -75,7 +75,7 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.2 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.2 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.3 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index b7d03aa..0f47f90 100644 --- a/go.sum +++ b/go.sum @@ -143,16 +143,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.2 h1:25aCkIMjUmiiOtnBIp6PhNj4KdcURuBak0hU2P1fgRc= -go.etcd.io/etcd/api/v3 v3.6.2/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.2 h1:zw+HRghi/G8fKpgKdOcEKpnBTE4OO39T6MegA0RopVU= -go.etcd.io/etcd/client/pkg/v3 v3.6.2/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.2 h1:RgmcLJxkpHqpFvgKNwAQHX3K+wsSARMXKgjmUSpoSKQ= -go.etcd.io/etcd/client/v3 v3.6.2/go.mod h1:PL7e5QMKzjybn0FosgiWvCUDzvdChpo5UgGR4Sk4Gzc= -go.etcd.io/etcd/pkg/v3 v3.6.2 h1:Ds+VYdvqEnKq8/iAy1fMo0FnJ9qzB0iOCe3f/9rko5k= -go.etcd.io/etcd/pkg/v3 v3.6.2/go.mod h1:b8Ag+HsYLuP6GgoExrswupAG6jXakxcvoNnIRhO17ZA= -go.etcd.io/etcd/server/v3 v3.6.2 h1:LY2tRcK8Rfby4aT2X9cWlRSk48FlLtP571nzc7tIhzc= -go.etcd.io/etcd/server/v3 v3.6.2/go.mod h1:EQ1Y6Q1ZbggsF3kYMGact0V0YPoJ7XVT2o5r1dorNXs= +go.etcd.io/etcd/api/v3 v3.6.3 h1:4Lftl1e6VzBsj5HPhLu8GGybjeT5qg9mug70RxTHmQQ= +go.etcd.io/etcd/api/v3 v3.6.3/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.3 h1:1yE8p3PFZ+CWaVyTZk+6ngSyMK8TaG2589W3KGm22ao= +go.etcd.io/etcd/client/pkg/v3 v3.6.3/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.3 h1:yKpdrcVK6jTfr/VuVuH5VesaLmUi8PLI9eXHTy5kpTM= +go.etcd.io/etcd/client/v3 v3.6.3/go.mod h1:zDuGaiUvpECwqClZCUkHi6q2XSf2ejPbUB755QLXdL8= +go.etcd.io/etcd/pkg/v3 v3.6.3 h1:+hob8XhXIw69v4Iee1NafszplWY05xP9xc9ptZ0Yeso= +go.etcd.io/etcd/pkg/v3 v3.6.3/go.mod h1:BNPqrrrb2Ihmlrxw/aWlVC9AT55sL6UEPjKyH+JAky8= +go.etcd.io/etcd/server/v3 v3.6.3 h1:LtJkiGx8F3BWMG8dsDixKqCsng7ZYX4qhcMd6lRfCvk= +go.etcd.io/etcd/server/v3 v3.6.3/go.mod h1:VrBuQXPMTLa5R6GdOtP+nlR2HSHXhY42bV5vpWQVU+A= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From 46a20e5041c32e70c9726187b7588f703798486c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:50:48 +0000 Subject: [PATCH 145/549] Bump google.golang.org/grpc from 1.74.0 to 1.74.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.74.0 to 1.74.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.74.0...v1.74.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.74.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b339a90..6290fcb 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.2 go.etcd.io/etcd/server/v3 v3.6.2 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.74.0 + google.golang.org/grpc v1.74.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index b7d03aa..8f23c59 100644 --- a/go.sum +++ b/go.sum @@ -225,8 +225,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1: google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY= -google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From 9235b80125b36eb08902c8173b4d67958eb72e9c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 23 Jul 2025 13:57:27 +0200 Subject: [PATCH 146/549] Return connection / publisher tokens for remote publishers. This supports connecting to and subscribing streams from proxies that don't know the signaling server sending the request. --- grpc_client.go | 8 +- grpc_mcu.proto | 2 + grpc_server.go | 12 ++ hub.go | 9 ++ mcu_proxy.go | 56 +++++---- mcu_proxy_test.go | 284 +++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 316 insertions(+), 55 deletions(-) diff --git a/grpc_client.go b/grpc_client.go index 9cf0dec..fac9d0c 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -292,7 +292,7 @@ func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, bac return } -func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId string, streamType StreamType) (string, string, net.IP, error) { +func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId string, streamType StreamType) (string, string, net.IP, string, string, error) { statsGrpcClientCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging log.Printf("Get %s publisher id %s on %s", streamType, sessionId, c.Target()) @@ -301,12 +301,12 @@ func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId string, strea StreamType: string(streamType), }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { - return "", "", nil, nil + return "", "", nil, "", "", nil } else if err != nil { - return "", "", nil, err + return "", "", nil, "", "", err } - return response.GetPublisherId(), response.GetProxyUrl(), net.ParseIP(response.GetIp()), nil + return response.GetPublisherId(), response.GetProxyUrl(), net.ParseIP(response.GetIp()), response.GetConnectToken(), response.GetPublisherToken(), nil } func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, error) { diff --git a/grpc_mcu.proto b/grpc_mcu.proto index b2313d2..c766ddc 100644 --- a/grpc_mcu.proto +++ b/grpc_mcu.proto @@ -38,4 +38,6 @@ message GetPublisherIdReply { string publisherId = 1; string proxyUrl = 2; string ip = 3; + string connectToken = 4; + string publisherToken = 5; } diff --git a/grpc_server.go b/grpc_server.go index 17327fd..cee0ab7 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -41,6 +41,8 @@ import ( var ( GrpcServerId string + + ErrNoProxyMcu = errors.New("no proxy mcu") ) func init() { @@ -62,6 +64,7 @@ type GrpcServerHub interface { GetRoomForBackend(roomId string, backend *Backend) *Room GetBackend(u *url.URL) *Backend + CreateProxyToken(publisherId string) (string, error) } type GrpcServer struct { @@ -276,6 +279,15 @@ func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherId if ip := publisher.conn.ip; ip != nil { reply.Ip = ip.String() } + var err error + if reply.ConnectToken, err = s.hub.CreateProxyToken(""); err != nil && !errors.Is(err, ErrNoProxyMcu) { + log.Printf("Error creating proxy token for connection: %s", err) + return nil, status.Error(codes.Internal, "error creating proxy connect token") + } + if reply.PublisherToken, err = s.hub.CreateProxyToken(publisher.Id()); err != nil && !errors.Is(err, ErrNoProxyMcu) { + log.Printf("Error creating proxy token for publisher %s: %s", publisher.Id(), err) + return nil, status.Error(codes.Internal, "error creating proxy publisher token") + } return reply, nil } diff --git a/hub.go b/hub.go index 5e3b4da..f8800d9 100644 --- a/hub.go +++ b/hub.go @@ -722,6 +722,15 @@ func (h *Hub) GetBackend(u *url.URL) *Backend { return h.backend.GetBackend(u) } +func (h *Hub) CreateProxyToken(publisherId string) (string, error) { + proxy, ok := h.mcu.(*mcuProxy) + if !ok { + return "", ErrNoProxyMcu + } + + return proxy.createToken(publisherId) +} + func (h *Hub) checkExpiredSessions(now time.Time) { for session, expires := range h.expiredSessions { if now.After(expires) { diff --git a/mcu_proxy.go b/mcu_proxy.go index 715535d..1fad338 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -340,10 +340,11 @@ func (s *mcuProxySubscriber) ProcessEvent(msg *EventProxyServerMessage) { } type mcuProxyConnection struct { - proxy *mcuProxy - rawUrl string - url *url.URL - ip net.IP + proxy *mcuProxy + rawUrl string + url *url.URL + ip net.IP + connectToken string load atomic.Int64 bandwidth atomic.Pointer[EventProxyServerBandwidth] @@ -380,7 +381,7 @@ type mcuProxyConnection struct { subscribers map[string]*mcuProxySubscriber } -func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP) (*mcuProxyConnection, error) { +func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token string) (*mcuProxyConnection, error) { parsed, err := url.Parse(baseUrl) if err != nil { return nil, err @@ -391,6 +392,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP) (*mcuProx rawUrl: baseUrl, url: parsed, ip: ip, + connectToken: token, closer: NewCloser(), closedDone: NewCloser(), callbacks: make(map[string]func(*ProxyServerMessage)), @@ -1105,6 +1107,8 @@ func (c *mcuProxyConnection) sendHello() error { } if sessionId := c.SessionId(); sessionId != "" { msg.Hello.ResumeId = sessionId + } else if c.connectToken != "" { + msg.Hello.Token = c.connectToken } else { tokenString, err := c.proxy.createToken("") if err != nil { @@ -1238,14 +1242,16 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList return subscriber, nil } -func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId string, publisherSessionId string, streamType StreamType, publisherConn *mcuProxyConnection) (McuSubscriber, error) { +func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId string, publisherSessionId string, streamType StreamType, publisherConn *mcuProxyConnection, remoteToken string) (McuSubscriber, error) { if c == publisherConn { return c.newSubscriber(ctx, listener, publisherId, publisherSessionId, streamType) } - remoteToken, err := c.proxy.createToken(publisherId) - if err != nil { - return nil, err + if remoteToken == "" { + var err error + if remoteToken, err = c.proxy.createToken(publisherId); err != nil { + return nil, err + } } msg := &ProxyClientMessage{ @@ -1516,7 +1522,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e var conns []*mcuProxyConnection if len(ips) == 0 { - conn, err := newMcuProxyConnection(m, url, nil) + conn, err := newMcuProxyConnection(m, url, nil, "") if err != nil { if ignoreErrors { log.Printf("Could not create proxy connection to %s: %s", url, err) @@ -1529,7 +1535,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e conns = append(conns, conn) } else { for _, ip := range ips { - conn, err := newMcuProxyConnection(m, url, ip) + conn, err := newMcuProxyConnection(m, url, ip, "") if err != nil { if ignoreErrors { log.Printf("Could not create proxy connection to %s (%s): %s", url, ip, err) @@ -1974,12 +1980,13 @@ func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher str } type proxyPublisherInfo struct { - id string - conn *mcuProxyConnection - err error + id string + conn *mcuProxyConnection + token string + err error } -func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, id string, publisher string, streamType StreamType, publisherConn *mcuProxyConnection, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { +func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, info *proxyPublisherInfo, publisher string, streamType StreamType, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { for _, conn := range connections { if !isAllowed(conn) || conn.IsShutdownScheduled() || conn.IsTemporary() { continue @@ -1987,10 +1994,10 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i var subscriber McuSubscriber var err error - if conn == publisherConn { - subscriber, err = conn.newSubscriber(ctx, listener, id, publisher, streamType) + if conn == info.conn { + subscriber, err = conn.newSubscriber(ctx, listener, info.id, publisher, streamType) } else { - subscriber, err = conn.newRemoteSubscriber(ctx, listener, id, publisher, streamType, publisherConn) + subscriber, err = conn.newRemoteSubscriber(ctx, listener, info.id, publisher, streamType, info.conn, info.token) } if err != nil { log.Printf("Could not create subscriber for %s publisher %s on %s: %s", streamType, publisher, conn, err) @@ -2056,7 +2063,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ wg.Add(1) go func(client *GrpcClient) { defer wg.Done() - id, url, ip, err := client.GetPublisherId(getctx, publisher, streamType) + id, url, ip, connectToken, publisherToken, err := client.GetPublisherId(getctx, publisher, streamType) if errors.Is(err, context.Canceled) { return } else if err != nil { @@ -2085,7 +2092,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ } if publisherConn == nil { - publisherConn, err = newMcuProxyConnection(m, url, ip) + publisherConn, err = newMcuProxyConnection(m, url, ip, connectToken) if err != nil { log.Printf("Could not create temporary connection to %s for %s publisher %s: %s", url, streamType, publisher, err) return @@ -2112,8 +2119,9 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ } ch <- &proxyPublisherInfo{ - id: id, - conn: publisherConn, + id: id, + conn: publisherConn, + token: publisherToken, } }(client) } @@ -2145,7 +2153,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ connections := m.getSortedConnections(initiator) if !allowOutgoing || len(connections) > 0 && !connections[0].IsSameCountry(publisherInfo.conn) { // Connect to remote publisher through "closer" gateway. - subscriber := m.createSubscriber(ctx, listener, publisherInfo.id, publisher, streamType, publisherInfo.conn, connections, func(c *mcuProxyConnection) bool { + subscriber := m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections, func(c *mcuProxyConnection) bool { bw := c.Bandwidth() return bw == nil || bw.AllowOutgoing() }) @@ -2180,7 +2188,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ } return 0 }) - subscriber = m.createSubscriber(ctx, listener, publisherInfo.id, publisher, streamType, publisherInfo.conn, connections2, func(c *mcuProxyConnection) bool { + subscriber = m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections2, func(c *mcuProxyConnection) bool { return true }) } diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index d68e217..96bab02 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -40,6 +40,7 @@ import ( "time" "github.com/dlintw/goconf" + "github.com/golang-jwt/jwt/v5" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -214,6 +215,26 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer return nil, fmt.Errorf("expected hello, got %+v", msg) } + token, err := jwt.ParseWithClaims(msg.Hello.Token, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + claims, ok := token.Claims.(*TokenClaims) + if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { + return nil, errors.New("unsupported claims type") + } + + key, found := c.server.tokens[claims.Issuer] + if !assert.True(c.t, found) { + return nil, fmt.Errorf("no key found for issuer") + } + + return key, nil + }) + if assert.NoError(c.t, err) { + if assert.True(c.t, token.Valid) { + _, ok := token.Claims.(*TokenClaims) + assert.True(c.t, ok) + } + } + response := &ProxyServerMessage{ Id: msg.Id, Type: "hello", @@ -295,6 +316,25 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( continue } + token, err := jwt.ParseWithClaims(msg.Command.RemoteToken, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + claims, ok := token.Claims.(*TokenClaims) + if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { + return nil, errors.New("unsupported claims type") + } + + key, found := server.tokens[claims.Issuer] + if !assert.True(c.t, found) { + return nil, fmt.Errorf("no key found for issuer") + } + + return key, nil + }) + if assert.NoError(c.t, err) { + if claims, ok := token.Claims.(*TokenClaims); assert.True(c.t, token.Valid) && assert.True(c.t, ok) { + assert.Equal(c.t, msg.Command.PublisherId, claims.Subject) + } + } + pub = server.getPublisher(msg.Command.PublisherId) break } @@ -450,6 +490,7 @@ type TestProxyServerHandler struct { URL string server *httptest.Server servers []*TestProxyServerHandler + tokens map[string]*rsa.PublicKey upgrader *websocket.Upgrader country string @@ -637,6 +678,7 @@ func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler upgrader := websocket.Upgrader{} proxyHandler := &TestProxyServerHandler{ t: t, + tokens: make(map[string]*rsa.PublicKey), upgrader: &upgrader, country: country, clients: make(map[string]*testProxyServerClient), @@ -663,7 +705,7 @@ type proxyTestOptions struct { servers []*TestProxyServerHandler } -func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions) *mcuProxy { +func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int) *mcuProxy { t.Helper() require := require.New(t) if options.etcd == nil { @@ -689,13 +731,15 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions) *mcuP NewProxyServerForTest(t, "DE"), } } + tokenId := fmt.Sprintf("test-token-%d", idx) for _, s := range options.servers { s.servers = options.servers + s.tokens[tokenId] = &tokenKey.PublicKey urls = append(urls, s.URL) waitingMap[s.URL] = true } cfg.AddOption("mcu", "url", strings.Join(urls, " ")) - cfg.AddOption("mcu", "token_id", "test-token") + cfg.AddOption("mcu", "token_id", tokenId) cfg.AddOption("mcu", "token_key", privkeyFile) etcdConfig := goconf.NewConfigFile() @@ -744,25 +788,25 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions) *mcuP return proxy } -func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler) *mcuProxy { +func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int) *mcuProxy { t.Helper() return newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: servers, - }) + }, idx) } -func newMcuProxyForTest(t *testing.T) *mcuProxy { +func newMcuProxyForTest(t *testing.T, idx int) *mcuProxy { t.Helper() server := NewProxyServerForTest(t, "DE") - return newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{server}) + return newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{server}, idx) } func Test_ProxyPublisherSubscriber(t *testing.T) { CatchLogForTest(t) t.Parallel() - mcu := newMcuProxyForTest(t) + mcu := newMcuProxyForTest(t, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -798,7 +842,7 @@ func Test_ProxyPublisherSubscriber(t *testing.T) { func Test_ProxyPublisherCodecs(t *testing.T) { CatchLogForTest(t) t.Parallel() - mcu := newMcuProxyForTest(t) + mcu := newMcuProxyForTest(t, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -825,7 +869,7 @@ func Test_ProxyPublisherCodecs(t *testing.T) { func Test_ProxyWaitForPublisher(t *testing.T) { CatchLogForTest(t) t.Parallel() - mcu := newMcuProxyForTest(t) + mcu := newMcuProxyForTest(t, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -880,7 +924,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ server1, server2, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -950,7 +994,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ server1, server2, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1023,7 +1067,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ server1, server2, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1073,7 +1117,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1121,7 +1165,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1169,7 +1213,7 @@ func Test_ProxySubscriberCountry(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1213,7 +1257,7 @@ func Test_ProxySubscriberContinent(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1257,7 +1301,7 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1321,7 +1365,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }) + }, 0) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1379,6 +1423,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { } type mockGrpcServerHub struct { + proxy atomic.Pointer[mcuProxy] sessionsLock sync.Mutex sessionByPublicId map[string]Session } @@ -1420,6 +1465,15 @@ func (h *mockGrpcServerHub) GetRoomForBackend(roomId string, backend *Backend) * return nil } +func (h *mockGrpcServerHub) CreateProxyToken(publisherId string) (string, error) { + proxy := h.proxy.Load() + if proxy == nil { + return "", errors.New("not a proxy mcu") + } + + return proxy.createToken(publisherId) +} + func Test_ProxyRemotePublisher(t *testing.T) { CatchLogForTest(t) t.Parallel() @@ -1446,14 +1500,16 @@ func Test_ProxyRemotePublisher(t *testing.T) { server1, server2, }, - }) + }, 1) + hub1.proxy.Store(mcu1) mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, server2, }, - }) + }, 2) + hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1530,7 +1586,8 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2, server3, }, - }) + }, 1) + hub1.proxy.Store(mcu1) mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ @@ -1538,7 +1595,8 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2, server3, }, - }) + }, 2) + hub2.proxy.Store(mcu2) mcu3 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ @@ -1546,7 +1604,8 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2, server3, }, - }) + }, 3) + hub3.proxy.Store(mcu3) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1628,14 +1687,16 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { server1, server2, }, - }) + }, 1) + hub1.proxy.Store(mcu1) mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, server2, }, - }) + }, 2) + hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1721,13 +1782,15 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { servers: []*TestProxyServerHandler{ server1, }, - }) + }, 1) + hub1.proxy.Store(mcu1) mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, }, - }) + }, 2) + hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1802,3 +1865,170 @@ loop: } } } + +func Test_ProxyConnectToken(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + + etcd := NewEtcdForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := NewProxyServerForTest(t, "DE") + server2 := NewProxyServerForTest(t, "DE") + + // Signaling server instances are in a cluster but don't share their proxies, + // i.e. they are only known to their local proxy, not the one of the other + // signaling server - so the connection token must be passed between them. + mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + etcd: etcd, + servers: []*TestProxyServerHandler{ + server1, + }, + }, 1) + hub1.proxy.Store(mcu1) + mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + etcd: etcd, + servers: []*TestProxyServerHandler{ + server2, + }, + }, 2) + hub2.proxy.Store(mcu2) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "the-publisher" + pubSid := "1234567890" + pubListener := &MockMcuListener{ + publicId: pubId + "-public", + } + pubInitiator := &MockMcuInitiator{ + country: "DE", + } + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[StreamType]McuPublisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ + MediaTypes: MediaTypeVideo | MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + subListener := &MockMcuListener{ + publicId: "subscriber-public", + } + subInitiator := &MockMcuInitiator{ + country: "DE", + } + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} + +func Test_ProxyPublisherToken(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + + etcd := NewEtcdForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := NewProxyServerForTest(t, "DE") + server2 := NewProxyServerForTest(t, "US") + + // Signaling server instances are in a cluster but don't share their proxies, + // i.e. they are only known to their local proxy, not the one of the other + // signaling server - so the connection token must be passed between them. + // Also the subscriber is connecting from a different country, so a remote + // stream will be created that needs a valid token from the remote proxy. + mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + etcd: etcd, + servers: []*TestProxyServerHandler{ + server1, + }, + }, 1) + hub1.proxy.Store(mcu1) + mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + etcd: etcd, + servers: []*TestProxyServerHandler{ + server2, + }, + }, 2) + hub2.proxy.Store(mcu2) + // Support remote subscribers for the tests. + server1.servers = append(server1.servers, server2) + server2.servers = append(server2.servers, server1) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "the-publisher" + pubSid := "1234567890" + pubListener := &MockMcuListener{ + publicId: pubId + "-public", + } + pubInitiator := &MockMcuInitiator{ + country: "DE", + } + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[StreamType]McuPublisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ + MediaTypes: MediaTypeVideo | MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + subListener := &MockMcuListener{ + publicId: "subscriber-public", + } + subInitiator := &MockMcuInitiator{ + country: "US", + } + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} From 9c7b3b9547fd571345ed920ee2ad19649d480e25 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 23 Jul 2025 14:40:35 +0200 Subject: [PATCH 147/549] Update generated files. --- grpc_mcu.pb.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/grpc_mcu.pb.go b/grpc_mcu.pb.go index ee447ef..add128c 100644 --- a/grpc_mcu.pb.go +++ b/grpc_mcu.pb.go @@ -92,12 +92,14 @@ func (x *GetPublisherIdRequest) GetStreamType() string { } type GetPublisherIdReply struct { - state protoimpl.MessageState `protogen:"open.v1"` - PublisherId string `protobuf:"bytes,1,opt,name=publisherId,proto3" json:"publisherId,omitempty"` - ProxyUrl string `protobuf:"bytes,2,opt,name=proxyUrl,proto3" json:"proxyUrl,omitempty"` - Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PublisherId string `protobuf:"bytes,1,opt,name=publisherId,proto3" json:"publisherId,omitempty"` + ProxyUrl string `protobuf:"bytes,2,opt,name=proxyUrl,proto3" json:"proxyUrl,omitempty"` + Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"` + ConnectToken string `protobuf:"bytes,4,opt,name=connectToken,proto3" json:"connectToken,omitempty"` + PublisherToken string `protobuf:"bytes,5,opt,name=publisherToken,proto3" json:"publisherToken,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetPublisherIdReply) Reset() { @@ -151,6 +153,20 @@ func (x *GetPublisherIdReply) GetIp() string { return "" } +func (x *GetPublisherIdReply) GetConnectToken() string { + if x != nil { + return x.ConnectToken + } + return "" +} + +func (x *GetPublisherIdReply) GetPublisherToken() string { + if x != nil { + return x.PublisherToken + } + return "" +} + var File_grpc_mcu_proto protoreflect.FileDescriptor const file_grpc_mcu_proto_rawDesc = "" + @@ -160,11 +176,13 @@ const file_grpc_mcu_proto_rawDesc = "" + "\tsessionId\x18\x01 \x01(\tR\tsessionId\x12\x1e\n" + "\n" + "streamType\x18\x02 \x01(\tR\n" + - "streamType\"c\n" + + "streamType\"\xaf\x01\n" + "\x13GetPublisherIdReply\x12 \n" + "\vpublisherId\x18\x01 \x01(\tR\vpublisherId\x12\x1a\n" + "\bproxyUrl\x18\x02 \x01(\tR\bproxyUrl\x12\x0e\n" + - "\x02ip\x18\x03 \x01(\tR\x02ip2^\n" + + "\x02ip\x18\x03 \x01(\tR\x02ip\x12\"\n" + + "\fconnectToken\x18\x04 \x01(\tR\fconnectToken\x12&\n" + + "\x0epublisherToken\x18\x05 \x01(\tR\x0epublisherToken2^\n" + "\x06RpcMcu\x12T\n" + "\x0eGetPublisherId\x12 .signaling.GetPublisherIdRequest\x1a\x1e.signaling.GetPublisherIdReply\"\x00B Date: Thu, 24 Jul 2025 09:44:33 +0200 Subject: [PATCH 148/549] Only forward actor details in leave virtual sessions request if both are given. Missing from previous change in #1009 --- hub.go | 6 +++--- virtualsession.go | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/hub.go b/hub.go index f8800d9..259e87b 100644 --- a/hub.go +++ b/hub.go @@ -2397,10 +2397,10 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { return } - if msg.Options != nil && msg.Options.ActorId != "" && msg.Options.ActorType != "" { + if options := msg.Options; options != nil && options.ActorId != "" && options.ActorType != "" { request := NewBackendClientRoomRequest(room.Id(), msg.UserId, publicSessionId) - request.Room.ActorId = msg.Options.ActorId - request.Room.ActorType = msg.Options.ActorType + request.Room.ActorId = options.ActorId + request.Room.ActorType = options.ActorType request.Room.InCall = sess.GetInCall() var response BackendClientResponse diff --git a/virtualsession.go b/virtualsession.go index 8dff084..4ed1016 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -192,13 +192,11 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa ctx, cancel := context.WithTimeout(context.Background(), s.hub.backendTimeout) defer cancel() - if options := s.Options(); options != nil { + if options := s.Options(); options != nil && options.ActorId != "" && options.ActorType != "" { request := NewBackendClientRoomRequest(room.Id(), s.UserId(), s.PublicId()) request.Room.Action = "leave" - if options != nil { - request.Room.ActorId = options.ActorId - request.Room.ActorType = options.ActorType - } + request.Room.ActorId = options.ActorId + request.Room.ActorType = options.ActorType var response BackendClientResponse if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { From 85b85feeb8444690f894dae9360f7a0bd5829d4c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Jul 2025 09:58:15 +0200 Subject: [PATCH 149/549] Add test to check passing of virtual actor information. --- hub_test.go | 5 + virtualsession_test.go | 208 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) diff --git a/hub_test.go b/hub_test.go index c6db2ec..8c5d215 100644 --- a/hub_test.go +++ b/hub_test.go @@ -429,6 +429,11 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re assert.Empty(request.Room.ActorType) assert.Empty(request.Room.ActorId) } + } else if strings.Contains(t.Name(), "VirtualSessionActorInformation") && request.Room.UserId == "user1" { + if request.Room.Action == "" || request.Room.Action == "join" || request.Room.Action == "leave" { + assert.Equal("actor-type", request.Room.ActorType, "failed for %+v", request.Room) + assert.Equal("actor-id", request.Room.ActorId, "failed for %+v", request.Room) + } } // Allow joining any room. diff --git a/virtualsession_test.go b/virtualsession_test.go index ad84415..f9fe06b 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -236,6 +236,214 @@ func TestVirtualSession(t *testing.T) { } } +func TestVirtualSessionActorInformation(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + + roomId := "the-room-id" + emptyProperties := json.RawMessage("{}") + backend := &Backend{ + id: "compat", + } + room, err := hub.createRoom(roomId, emptyProperties, backend) + require.NoError(err) + defer room.Close() + + clientInternal := NewTestClient(t, server, hub) + defer clientInternal.CloseWithBye() + require.NoError(clientInternal.SendHelloInternal()) + + client := NewTestClient(t, server, hub) + defer client.CloseWithBye() + require.NoError(client.SendHello(testDefaultUserId)) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + if hello, err := clientInternal.RunUntilHello(ctx); assert.NoError(err) { + assert.Empty(hello.Hello.UserId) + assert.NotEmpty(hello.Hello.SessionId) + assert.NotEmpty(hello.Hello.ResumeId) + } + hello, err := client.RunUntilHello(ctx) + assert.NoError(err) + + roomMsg, err := client.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Ignore "join" events. + assert.NoError(client.DrainMessages(ctx)) + + internalSessionId := "session1" + userId := "user1" + msgAdd := &ClientMessage{ + Type: "internal", + Internal: &InternalClientMessage{ + Type: "addsession", + AddSession: &AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: internalSessionId, + RoomId: roomId, + }, + UserId: userId, + Flags: FLAG_MUTED_SPEAKING, + Options: &AddSessionOptions{ + ActorId: "actor-id", + ActorType: "actor-type", + }, + }, + }, + } + require.NoError(clientInternal.WriteJSON(msgAdd)) + + msg1, err := client.RunUntilMessage(ctx) + require.NoError(err) + // The public session id will be generated by the server, so don't check for it. + require.NoError(client.checkMessageJoinedSession(msg1, "", userId)) + sessionId := msg1.Event.Join[0].SessionId + session := hub.GetSessionByPublicId(sessionId) + if assert.NotNil(session, "Could not get virtual session %s", sessionId) { + assert.Equal(HelloClientTypeVirtual, session.ClientType()) + sid := session.(*VirtualSession).SessionId() + assert.Equal(internalSessionId, sid) + } + + // Also a participants update event will be triggered for the virtual user. + msg2, err := client.RunUntilMessage(ctx) + require.NoError(err) + if updateMsg, err := checkMessageParticipantsInCall(msg2); assert.NoError(err) { + assert.Equal(roomId, updateMsg.RoomId) + if assert.Len(updateMsg.Users, 1) { + assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) + assert.Equal(true, updateMsg.Users[0]["virtual"]) + assert.EqualValues((FlagInCall | FlagWithPhone), updateMsg.Users[0]["inCall"]) + } + } + + msg3, err := client.RunUntilMessage(ctx) + require.NoError(err) + + if flagsMsg, err := checkMessageParticipantFlags(msg3); assert.NoError(err) { + assert.Equal(roomId, flagsMsg.RoomId) + assert.Equal(sessionId, flagsMsg.SessionId) + assert.EqualValues(FLAG_MUTED_SPEAKING, flagsMsg.Flags) + } + + newFlags := uint32(FLAG_TALKING) + msgFlags := &ClientMessage{ + Type: "internal", + Internal: &InternalClientMessage{ + Type: "updatesession", + UpdateSession: &UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: internalSessionId, + RoomId: roomId, + }, + Flags: &newFlags, + }, + }, + } + require.NoError(clientInternal.WriteJSON(msgFlags)) + + msg4, err := client.RunUntilMessage(ctx) + require.NoError(err) + + if flagsMsg, err := checkMessageParticipantFlags(msg4); assert.NoError(err) { + assert.Equal(roomId, flagsMsg.RoomId) + assert.Equal(sessionId, flagsMsg.SessionId) + assert.EqualValues(newFlags, flagsMsg.Flags) + } + + // A new client will receive the initial flags of the virtual session. + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + require.NoError(client2.SendHello(testDefaultUserId + "2")) + + _, err = client2.RunUntilHello(ctx) + require.NoError(err) + + roomMsg, err = client2.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + gotFlags := false + var receivedMessages []*ServerMessage + for !gotFlags { + messages, err := client2.GetPendingMessages(ctx) + if err != nil { + assert.NoError(err) + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + break + } + } + + receivedMessages = append(receivedMessages, messages...) + for _, msg := range messages { + if msg.Type != "event" || msg.Event.Target != "participants" || msg.Event.Type != "flags" { + continue + } + + if assert.Equal(roomId, msg.Event.Flags.RoomId) && + assert.Equal(sessionId, msg.Event.Flags.SessionId) && + assert.EqualValues(newFlags, msg.Event.Flags.Flags) { + gotFlags = true + break + } + } + } + assert.True(gotFlags, "Didn't receive initial flags in %+v", receivedMessages) + + // Ignore "join" messages from second client + assert.NoError(client.DrainMessages(ctx)) + + // When sending to a virtual session, the message is sent to the actual + // client and contains a "Recipient" block with the internal session id. + recipient := MessageClientMessageRecipient{ + Type: "session", + SessionId: sessionId, + } + + data := "from-client-to-virtual" + require.NoError(client.SendMessage(recipient, data)) + + msg2, err = clientInternal.RunUntilMessage(ctx) + require.NoError(err) + require.NoError(checkMessageType(msg2, "message")) + require.NoError(checkMessageSender(hub, msg2.Message.Sender, "session", hello.Hello)) + + if assert.NotNil(msg2.Message.Recipient) { + assert.Equal("session", msg2.Message.Recipient.Type) + assert.Equal(internalSessionId, msg2.Message.Recipient.SessionId) + } + + var payload string + if err := json.Unmarshal(msg2.Message.Data, &payload); assert.NoError(err) { + assert.Equal(data, payload) + } + + msgRemove := &ClientMessage{ + Type: "internal", + Internal: &InternalClientMessage{ + Type: "removesession", + RemoveSession: &RemoveSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: internalSessionId, + RoomId: roomId, + }, + }, + }, + } + require.NoError(clientInternal.WriteJSON(msgRemove)) + + if msg5, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client.checkMessageRoomLeaveSession(msg5, sessionId)) + } +} + func checkHasEntryWithInCall(message *RoomEventServerMessage, sessionId string, entryType string, inCall int) error { found := false for _, entry := range message.Users { From d363620120ebc2b9821b5db4dda79aac829240c6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Jul 2025 12:08:37 +0200 Subject: [PATCH 150/549] Add helper method to wait for error message. --- testclient_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/testclient_test.go b/testclient_test.go index ed97215..915a95b 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -690,6 +690,23 @@ func (c *TestClient) RunUntilMessage(ctx context.Context) (message *ServerMessag return } +func (c *TestClient) RunUntilError(ctx context.Context, code string) (*Error, error) { + message, err := c.RunUntilMessage(ctx) + if err != nil { + return nil, err + } + if err := checkUnexpectedClose(err); err != nil { + return nil, err + } + if err := checkMessageType(message, "error"); err != nil { + return nil, err + } + if message.Error.Code != code { + return nil, fmt.Errorf("expected error %s, got %s", code, message.Error.Code) + } + return message.Error, nil +} + func (c *TestClient) RunUntilHello(ctx context.Context) (message *ServerMessage, err error) { if message, err = c.RunUntilMessage(ctx); err != nil { return nil, err From bcc5bccbf02684c2397c1e804a08104088817841 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 24 Jul 2025 12:10:30 +0200 Subject: [PATCH 151/549] Fix updating metric "signaling_mcu_subscribers" in various error cases. --- mcu_janus_subscriber.go | 15 +- mcu_janus_test.go | 389 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 400 insertions(+), 4 deletions(-) diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 9e75967..492b471 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -127,6 +127,15 @@ func (p *mcuJanusSubscriber) NotifyReconnected() { log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId) } +func (p *mcuJanusSubscriber) closeClient(ctx context.Context) bool { + if !p.mcuJanusClient.closeClient(ctx) { + return false + } + + statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Dec() + return true +} + func (p *mcuJanusSubscriber) Close(ctx context.Context) { p.mu.Lock() closed := p.closeClient(ctx) @@ -134,7 +143,6 @@ func (p *mcuJanusSubscriber) Close(ctx context.Context) { if closed { p.mcu.SubscriberDisconnected(p.Id(), p.publisher, p.streamType) - statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Dec() } p.mcu.unregisterClient(p) p.listener.SubscriberClosed(p) @@ -206,6 +214,7 @@ retry: p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) p.closeChan = make(chan struct{}, 1) + statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Inc() go p.run(p.handle, p.closeChan) log.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId) goto retry @@ -215,7 +224,7 @@ retry: switch error_code { case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: log.Printf("Publisher %s not created yet for %s, not joining room %d as subscriber", p.publisher, p.streamType, p.roomId) - p.listener.SubscriberClosed(p) + go p.Close(context.Background()) callback(fmt.Errorf("Publisher %s not created yet for %s", p.publisher, p.streamType), nil) return case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: @@ -228,7 +237,7 @@ retry: } if err := waiter.Wait(ctx); err != nil { - p.listener.SubscriberClosed(p) + go p.Close(context.Background()) callback(err, nil) return } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 1169dc4..be0fcc8 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -65,6 +65,9 @@ type TestJanusGateway struct { rooms map[uint64]*TestJanusRoom handlers map[string]TestJanusHandler + attachCount atomic.Int32 + joinCount atomic.Int32 + handleRooms map[*TestJanusHandle]*TestJanusRoom } @@ -158,6 +161,33 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan case "join": rid := body["room"].(float64) room := g.rooms[uint64(rid)] + error_code := JANUS_OK + if body["ptype"] == "subscriber" { + if strings.Contains(g.t.Name(), "NoSuchRoom") { + if g.joinCount.Add(1) == 1 { + error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM + } + } else if strings.Contains(g.t.Name(), "AlreadyJoined") { + if g.joinCount.Add(1) == 1 { + error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED + } + } else if strings.Contains(g.t.Name(), "SubscriberTimeout") { + if g.joinCount.Add(1) == 1 { + error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED + } + } + } + if error_code != JANUS_OK { + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: map[string]interface{}{ + "error_code": error_code, + }, + }, + } + } + if room == nil { return &janus.EventMsg{ Plugindata: janus.PluginData{ @@ -207,7 +237,8 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan sdp := publisher.sdp.Load() return &janus.EventMsg{ Jsep: map[string]interface{}{ - "sdp": sdp.(string), + "type": "offer", + "sdp": sdp.(string), }, } } @@ -275,6 +306,10 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan handle.sdp.Store(sdp.(string)) // Simulate "connected" event. go func() { + if strings.Contains(g.t.Name(), "SubscriberTimeout") { + return + } + time.Sleep(10 * time.Millisecond) session.Lock() h, found := session.Handles[handle.id] @@ -317,6 +352,17 @@ func (g *TestJanusGateway) processRequest(msg map[string]interface{}) interface{ switch method { case "attach": + if strings.Contains(g.t.Name(), "AlreadyJoinedAttachError") { + if g.attachCount.Add(1) == 4 { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: JANUS_ERROR_UNKNOWN, + Reason: "Fail for test", + }, + } + } + } + handle := &TestJanusHandle{ id: g.hid.Add(1), } @@ -1212,3 +1258,344 @@ func Test_JanusRemotePublisher(t *testing.T) { assert.EqualValues(1, added.Load()) assert.EqualValues(1, removed.Load()) } + +func Test_JanusSubscriberNoSuchRoom(t *testing.T) { + ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) + defer checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "type": "answer", + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1 := NewTestClient(t, server, hub) + defer client1.CloseWithBye() + require.NoError(client1.SendHello(testDefaultUserId + "1")) + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + require.NoError(client2.SendHello(testDefaultUserId + "2")) + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg, err = client2.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []map[string]interface{}{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) + assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + + require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + _, err = client2.RunUntilError(ctx, "processing_failed") + require.NoError(err) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) +} + +func test_JanusSubscriberAlreadyJoined(t *testing.T) { + ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) + defer checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "type": "answer", + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1 := NewTestClient(t, server, hub) + defer client1.CloseWithBye() + require.NoError(client1.SendHello(testDefaultUserId + "1")) + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + require.NoError(client2.SendHello(testDefaultUserId + "2")) + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg, err = client2.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []map[string]interface{}{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) + assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + + require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + if strings.Contains(t.Name(), "AttachError") { + _, err = client2.RunUntilError(ctx, "processing_failed") + require.NoError(err) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + } + + require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) +} + +func Test_JanusSubscriberAlreadyJoined(t *testing.T) { + test_JanusSubscriberAlreadyJoined(t) +} + +func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { + test_JanusSubscriberAlreadyJoined(t) +} + +func Test_JanusSubscriberTimeout(t *testing.T) { + ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) + defer checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.EventMsg{ + Jsep: map[string]interface{}{ + "type": "answer", + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1 := NewTestClient(t, server, hub) + defer client1.CloseWithBye() + require.NoError(client1.SendHello(testDefaultUserId + "1")) + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + client2 := NewTestClient(t, server, hub) + defer client2.CloseWithBye() + require.NoError(client2.SendHello(testDefaultUserId + "2")) + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg, err = client2.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []map[string]interface{}{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) + assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + + require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + + oldTimeout := mcu.settings.timeout.Swap(100 * int64(time.Millisecond)) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + _, err = client2.RunUntilError(ctx, "processing_failed") + require.NoError(err) + + mcu.settings.timeout.Store(oldTimeout) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) +} From 5bc0c30c1f8a1d4954f6735fe2d6749c3f7d8b44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 20:31:58 +0000 Subject: [PATCH 152/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.3 to 3.6.4 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.3...v3.6.4) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.3 to 3.6.4 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.3...v3.6.4) Updates `go.etcd.io/etcd/client/v3` from 3.6.3 to 3.6.4 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.3...v3.6.4) Updates `go.etcd.io/etcd/server/v3` from 3.6.3 to 3.6.4 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.3...v3.6.4) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 25a153c..cd43b6e 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 - go.etcd.io/etcd/api/v3 v3.6.3 - go.etcd.io/etcd/client/pkg/v3 v3.6.3 - go.etcd.io/etcd/client/v3 v3.6.3 - go.etcd.io/etcd/server/v3 v3.6.3 + go.etcd.io/etcd/api/v3 v3.6.4 + go.etcd.io/etcd/client/pkg/v3 v3.6.4 + go.etcd.io/etcd/client/v3 v3.6.4 + go.etcd.io/etcd/server/v3 v3.6.4 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.74.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -75,7 +75,7 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.2 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.3 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.4 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index 6f51341..9b46709 100644 --- a/go.sum +++ b/go.sum @@ -143,16 +143,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.3 h1:4Lftl1e6VzBsj5HPhLu8GGybjeT5qg9mug70RxTHmQQ= -go.etcd.io/etcd/api/v3 v3.6.3/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.3 h1:1yE8p3PFZ+CWaVyTZk+6ngSyMK8TaG2589W3KGm22ao= -go.etcd.io/etcd/client/pkg/v3 v3.6.3/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.3 h1:yKpdrcVK6jTfr/VuVuH5VesaLmUi8PLI9eXHTy5kpTM= -go.etcd.io/etcd/client/v3 v3.6.3/go.mod h1:zDuGaiUvpECwqClZCUkHi6q2XSf2ejPbUB755QLXdL8= -go.etcd.io/etcd/pkg/v3 v3.6.3 h1:+hob8XhXIw69v4Iee1NafszplWY05xP9xc9ptZ0Yeso= -go.etcd.io/etcd/pkg/v3 v3.6.3/go.mod h1:BNPqrrrb2Ihmlrxw/aWlVC9AT55sL6UEPjKyH+JAky8= -go.etcd.io/etcd/server/v3 v3.6.3 h1:LtJkiGx8F3BWMG8dsDixKqCsng7ZYX4qhcMd6lRfCvk= -go.etcd.io/etcd/server/v3 v3.6.3/go.mod h1:VrBuQXPMTLa5R6GdOtP+nlR2HSHXhY42bV5vpWQVU+A= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= +go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= +go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= +go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From ad78c817b9833d5557c4edb9afb21729dbb52c03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:41:27 +0000 Subject: [PATCH 153/549] Bump github.com/nats-io/nats.go from 1.43.0 to 1.44.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.43.0 to 1.44.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.43.0...v1.44.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.44.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cd43b6e..0b2db89 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.11.6 - github.com/nats-io/nats.go v1.43.0 + github.com/nats-io/nats.go v1.44.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 diff --git a/go.sum b/go.sum index 9b46709..b85725e 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.11.6 h1:4VXRjbTUFKEB+7UoaKL3F5Y83xC7MxPoIONOnGgpkHw= github.com/nats-io/nats-server/v2 v2.11.6/go.mod h1:2xoztlcb4lDL5Blh1/BiukkKELXvKQ5Vy29FPVRBUYs= -github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug= -github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M= +github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From f07a42529b15780bd7bebb7295f60b40c249d22e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 30 Jul 2025 08:50:25 +0200 Subject: [PATCH 154/549] Delete (unused) proxy publisher/subscriber created after local timeout. Also fixes using the configured proxy timeout when creating subscribers. --- mcu_proxy.go | 179 ++++++++++++++++++++++++++++++++++++++++------ mcu_proxy_test.go | 128 +++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 23 deletions(-) diff --git a/mcu_proxy.go b/mcu_proxy.go index 1fad338..d10bb53 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -184,7 +184,7 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { }, } - if response, err := p.conn.performSyncRequest(ctx, msg); err != nil { + if response, _, err := p.conn.performSyncRequest(ctx, msg); err != nil { log.Printf("Could not delete publisher %s at %s: %s", p.proxyId, p.conn, err) return } else if response.Type == "error" { @@ -284,7 +284,7 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { }, } - if response, err := s.conn.performSyncRequest(ctx, msg); err != nil { + if response, _, err := s.conn.performSyncRequest(ctx, msg); err != nil { if s.publisherConn != nil { log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", s.proxyId, s.conn, s.publisherConn, err) } else { @@ -339,6 +339,8 @@ func (s *mcuProxySubscriber) ProcessEvent(msg *EventProxyServerMessage) { } } +type mcuProxyCallback func(response *ProxyServerMessage) + type mcuProxyConnection struct { proxy *mcuProxy rawUrl string @@ -371,7 +373,8 @@ type mcuProxyConnection struct { version atomic.Value features atomic.Value - callbacks map[string]func(*ProxyServerMessage) + callbacks map[string]mcuProxyCallback + deferredCallbacks map[string]mcuProxyCallback publishersLock sync.RWMutex publishers map[string]*mcuProxyPublisher @@ -395,7 +398,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str connectToken: token, closer: NewCloser(), closedDone: NewCloser(), - callbacks: make(map[string]func(*ProxyServerMessage)), + callbacks: make(map[string]mcuProxyCallback), publishers: make(map[string]*mcuProxyPublisher), publisherIds: make(map[string]string), subscribers: make(map[string]*mcuProxySubscriber), @@ -917,6 +920,7 @@ func (c *mcuProxyConnection) clearCallbacks() { defer c.mu.Unlock() clear(c.callbacks) + clear(c.deferredCallbacks) } func (c *mcuProxyConnection) getCallback(id string) func(*ProxyServerMessage) { @@ -930,6 +934,33 @@ func (c *mcuProxyConnection) getCallback(id string) func(*ProxyServerMessage) { return callback } +func (c *mcuProxyConnection) registerDeferredCallback(msgId string, callback mcuProxyCallback) { + if msgId == "" { + return + } + + c.mu.Lock() + defer c.mu.Unlock() + + delete(c.callbacks, msgId) + if c.deferredCallbacks == nil { + c.deferredCallbacks = make(map[string]mcuProxyCallback) + } + c.deferredCallbacks[msgId] = callback +} + +func (c *mcuProxyConnection) getDeferredCallback(msgId string) mcuProxyCallback { + c.mu.Lock() + defer c.mu.Unlock() + + result, found := c.deferredCallbacks[msgId] + if found { + delete(c.deferredCallbacks, msgId) + } + + return result +} + func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { if c.helloMsgId != "" && msg.Id == c.helloMsgId { c.helloMsgId = "" @@ -991,10 +1022,12 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { if proxyDebugMessages { log.Printf("Received from %s: %+v", c, msg) } - callback := c.getCallback(msg.Id) - if callback != nil { + if callback := c.getCallback(msg.Id); callback != nil { callback(msg) return + } else if callback := c.getDeferredCallback(msg.Id); callback != nil { + go callback(msg) + return } switch msg.Type { @@ -1138,7 +1171,7 @@ func (c *mcuProxyConnection) sendMessageLocked(msg *ProxyClientMessage) error { return c.conn.WriteJSON(msg) } -func (c *mcuProxyConnection) performAsyncRequest(ctx context.Context, msg *ProxyClientMessage, callback func(err error, response *ProxyServerMessage)) { +func (c *mcuProxyConnection) performAsyncRequest(ctx context.Context, msg *ProxyClientMessage, callback func(err error, response *ProxyServerMessage)) string { msgId := strconv.FormatInt(c.msgId.Add(1), 10) msg.Id = msgId @@ -1150,18 +1183,27 @@ func (c *mcuProxyConnection) performAsyncRequest(ctx context.Context, msg *Proxy if err := c.sendMessageLocked(msg); err != nil { delete(c.callbacks, msgId) go callback(err, nil) - return + return "" } + + context.AfterFunc(ctx, func() { + c.mu.Lock() + defer c.mu.Unlock() + + delete(c.callbacks, msgId) + }) + + return msgId } -func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyClientMessage) (*ProxyServerMessage, error) { +func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyClientMessage) (*ProxyServerMessage, string, error) { if err := ctx.Err(); err != nil { - return nil, err + return nil, "", err } errChan := make(chan error, 1) responseChan := make(chan *ProxyServerMessage, 1) - c.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { + msgId := c.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { if err != nil { errChan <- err } else { @@ -1171,14 +1213,44 @@ func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyC select { case <-ctx.Done(): - return nil, ctx.Err() + return nil, msgId, ctx.Err() case err := <-errChan: - return nil, err + return nil, msgId, err case response := <-responseChan: - return response, nil + return response, msgId, nil } } +func (c *mcuProxyConnection) deferredDeletePublisher(id string, streamType StreamType, response *ProxyServerMessage) { + if response.Type == "error" { + log.Printf("Publisher for %s was not created at %s: %s", id, c, response.Error) + return + } + + proxyId := response.Command.Id + log.Printf("Created unused %s publisher %s on %s for %s", streamType, proxyId, c, id) + msg := &ProxyClientMessage{ + Type: "command", + Command: &CommandProxyClientMessage{ + Type: "delete-publisher", + ClientId: proxyId, + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), c.proxy.settings.Timeout()) + defer cancel() + + if response, _, err := c.performSyncRequest(ctx, msg); err != nil { + log.Printf("Could not delete publisher %s at %s: %s", proxyId, c, err) + return + } else if response.Type == "error" { + log.Printf("Could not delete publisher %s at %s: %s", proxyId, c, response.Error) + return + } + + log.Printf("Deleted publisher %s at %s", proxyId, c) +} + func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { msg := &ProxyClientMessage{ Type: "command", @@ -1193,9 +1265,13 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe }, } - response, err := c.performSyncRequest(ctx, msg) + response, msgId, err := c.performSyncRequest(ctx, msg) if err != nil { - // TODO: Cancel request + if errors.Is(err, context.DeadlineExceeded) { + c.registerDeferredCallback(msgId, func(response *ProxyServerMessage) { + c.deferredDeletePublisher(id, streamType, response) + }) + } return nil, err } else if response.Type == "error" { return nil, fmt.Errorf("error creating %s publisher for %s on %s: %+v", streamType, id, c, response.Error) @@ -1213,6 +1289,49 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe return publisher, nil } +func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId string, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { + if response.Type == "error" { + log.Printf("Subscriber for %s was not created at %s: %s", publisherSessionId, c, response.Error) + return + } + + proxyId := response.Command.Id + log.Printf("Created unused %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) + + msg := &ProxyClientMessage{ + Type: "command", + Command: &CommandProxyClientMessage{ + Type: "delete-subscriber", + ClientId: proxyId, + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), c.proxy.settings.Timeout()) + defer cancel() + + if response, _, err := c.performSyncRequest(ctx, msg); err != nil { + if publisherConn != nil { + log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", proxyId, c, publisherConn, err) + } else { + log.Printf("Could not delete subscriber %s at %s: %s", proxyId, c, err) + } + return + } else if response.Type == "error" { + if publisherConn != nil { + log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", proxyId, c, publisherConn, response.Error) + } else { + log.Printf("Could not delete subscriber %s at %s: %s", proxyId, c, response.Error) + } + return + } + + if publisherConn != nil { + log.Printf("Deleted remote subscriber %s at %s (forwarded to %s)", proxyId, c, publisherConn) + } else { + log.Printf("Deleted subscriber %s at %s", proxyId, c) + } +} + func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuListener, publisherId string, publisherSessionId string, streamType StreamType) (McuSubscriber, error) { msg := &ProxyClientMessage{ Type: "command", @@ -1223,9 +1342,13 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList }, } - response, err := c.performSyncRequest(ctx, msg) + response, msgId, err := c.performSyncRequest(ctx, msg) if err != nil { - // TODO: Cancel request + if errors.Is(err, context.DeadlineExceeded) { + c.registerDeferredCallback(msgId, func(response *ProxyServerMessage) { + c.deferredDeleteSubscriber(publisherSessionId, streamType, nil, response) + }) + } return nil, err } else if response.Type == "error" { return nil, fmt.Errorf("error creating %s subscriber for %s on %s: %+v", streamType, publisherSessionId, c, response.Error) @@ -1266,9 +1389,13 @@ func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener M }, } - response, err := c.performSyncRequest(ctx, msg) + response, msgId, err := c.performSyncRequest(ctx, msg) if err != nil { - // TODO: Cancel request + if errors.Is(err, context.DeadlineExceeded) { + c.registerDeferredCallback(msgId, func(response *ProxyServerMessage) { + c.deferredDeleteSubscriber(publisherSessionId, streamType, publisherConn, response) + }) + } return nil, err } else if response.Type == "error" { return nil, fmt.Errorf("error creating remote %s subscriber for %s on %s (forwarded to %s): %+v", streamType, publisherSessionId, c, publisherConn, response.Error) @@ -1992,12 +2119,15 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i continue } + subctx, cancel := context.WithTimeout(ctx, m.settings.Timeout()) + defer cancel() + var subscriber McuSubscriber var err error if conn == info.conn { - subscriber, err = conn.newSubscriber(ctx, listener, info.id, publisher, streamType) + subscriber, err = conn.newSubscriber(subctx, listener, info.id, publisher, streamType) } else { - subscriber, err = conn.newRemoteSubscriber(ctx, listener, info.id, publisher, streamType, info.conn, info.token) + subscriber, err = conn.newRemoteSubscriber(subctx, listener, info.id, publisher, streamType, info.conn, info.token) } if err != nil { log.Printf("Could not create subscriber for %s publisher %s on %s: %s", streamType, publisher, conn, err) @@ -2198,7 +2328,10 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ } } - subscriber, err := publisherInfo.conn.newSubscriber(ctx, listener, publisherInfo.id, publisher, streamType) + subctx, cancel := context.WithTimeout(ctx, m.settings.Timeout()) + defer cancel() + + subscriber, err := publisherInfo.conn.newSubscriber(subctx, listener, publisherInfo.id, publisher, streamType) if err != nil { if publisherInfo.conn.IsTemporary() { publisherInfo.conn.closeIfEmpty() diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 96bab02..10562b4 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -47,6 +47,10 @@ import ( "go.etcd.io/etcd/server/v3/embed" ) +const ( + timeoutTestTimeout = 100 * time.Millisecond +) + func TestMcuProxyStats(t *testing.T) { collectAndLint(t, proxyMcuStats...) } @@ -270,6 +274,10 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( var response *ProxyServerMessage switch msg.Command.Type { case "create-publisher": + if strings.Contains(c.t.Name(), "ProxyPublisherTimeout") { + time.Sleep(2 * timeoutTestTimeout) + defer c.server.Wakeup() + } pub := c.server.createPublisher() if assert.NotNil(c.t, msg.Command.PublisherSettings) { @@ -296,6 +304,10 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( } c.server.updateLoad(1) case "delete-publisher": + if strings.Contains(c.t.Name(), "ProxyPublisherTimeout") { + defer c.server.Wakeup() + } + if pub, found := c.server.deletePublisher(msg.Command.ClientId); !found { response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.ClientId)) } else { @@ -345,6 +357,10 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( if pub == nil { response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.PublisherId)) } else { + if strings.Contains(c.t.Name(), "ProxySubscriberTimeout") { + time.Sleep(2 * timeoutTestTimeout) + defer c.server.Wakeup() + } sub := c.server.createSubscriber(pub) response = &ProxyServerMessage{ Id: msg.Id, @@ -357,6 +373,9 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( c.server.updateLoad(1) } case "delete-subscriber": + if strings.Contains(c.t.Name(), "ProxySubscriberTimeout") { + defer c.server.Wakeup() + } if sub, found := c.server.deleteSubscriber(msg.Command.ClientId); !found { response = msg.NewWrappedErrorServerMessage(fmt.Errorf("subscriber %s not found", msg.Command.ClientId)) } else { @@ -501,6 +520,8 @@ type TestProxyServerHandler struct { clients map[string]*testProxyServerClient publishers map[string]*testProxyServerPublisher subscribers map[string]*testProxyServerSubscriber + + wakeupChan chan struct{} } func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { @@ -672,6 +693,19 @@ func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques go client.run() } +func (h *TestProxyServerHandler) Wakeup() { + h.wakeupChan <- struct{}{} +} + +func (h *TestProxyServerHandler) WaitForWakeup(ctx context.Context) error { + select { + case <-h.wakeupChan: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler { t.Helper() @@ -684,6 +718,7 @@ func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler clients: make(map[string]*testProxyServerClient), publishers: make(map[string]*testProxyServerPublisher), subscribers: make(map[string]*testProxyServerSubscriber), + wakeupChan: make(chan struct{}), } server := httptest.NewServer(proxyHandler) proxyHandler.server = server @@ -2032,3 +2067,96 @@ func Test_ProxyPublisherToken(t *testing.T) { defer sub.Close(context.Background()) } + +func Test_ProxyPublisherTimeout(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := NewProxyServerForTest(t, "DE") + mcu := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{server}, + }, 0) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "the-publisher" + pubSid := "1234567890" + pubListener := &MockMcuListener{ + publicId: pubId + "-public", + } + pubInitiator := &MockMcuInitiator{ + country: "DE", + } + + settings := mcu.settings.(*mcuProxySettings) + settings.timeout.Store(timeoutTestTimeout.Nanoseconds()) + + // Creating the publisher will timeout locally. + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ + MediaTypes: MediaTypeVideo | MediaTypeAudio, + }, pubInitiator) + if pub != nil { + defer pub.Close(context.Background()) + } + assert.ErrorContains(err, "no MCU connection available") + + // Wait for publisher to be created on the proxy side. + require.NoError(server.WaitForWakeup(ctx), "publisher not created") + + // The local side will remove the (unused) publisher from the proxy. + require.NoError(server.WaitForWakeup(ctx), "unused publisher not deleted") +} + +func Test_ProxySubscriberTimeout(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := NewProxyServerForTest(t, "DE") + mcu := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{server}, + }, 0) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := "the-publisher" + pubSid := "1234567890" + pubListener := &MockMcuListener{ + publicId: pubId + "-public", + } + pubInitiator := &MockMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ + MediaTypes: MediaTypeVideo | MediaTypeAudio, + }, pubInitiator) + require.NoError(err) + defer pub.Close(context.Background()) + + subListener := &MockMcuListener{ + publicId: "subscriber-public", + } + subInitiator := &MockMcuInitiator{ + country: "DE", + } + + settings := mcu.settings.(*mcuProxySettings) + settings.timeout.Store(timeoutTestTimeout.Nanoseconds()) + + // Creating the subscriber will timeout locally. + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) + if sub != nil { + defer sub.Close(context.Background()) + } + assert.ErrorIs(err, context.DeadlineExceeded) + + // Wait for subscriber to be created on the proxy side. + require.NoError(server.WaitForWakeup(ctx), "subscriber not created") + + // The local side will remove the (unused) subscriber from the proxy. + require.NoError(server.WaitForWakeup(ctx), "unused subscriber not deleted") +} From 613806be140161f7c14325f94627bb02ecefaafa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 30 Jul 2025 09:44:18 +0200 Subject: [PATCH 155/549] modernize: Replace "interface{}" with "any". --- .golangci.yml | 1 + api_backend.go | 14 ++-- api_proxy.go | 10 +-- api_signaling.go | 48 ++++++------ backend_client.go | 2 +- backend_server.go | 8 +- backend_server_test.go | 16 ++-- capabilities.go | 16 ++-- capabilities_test.go | 6 +- client.go | 2 +- clientsession.go | 20 ++--- clientsession_test.go | 4 +- deferred_executor.go | 2 +- federation.go | 6 +- federation_test.go | 20 ++--- hub.go | 22 +++--- hub_test.go | 94 +++++++++++----------- janus_client.go | 66 ++++++++-------- lru.go | 6 +- mcu_common.go | 8 +- mcu_common_test.go | 4 +- mcu_janus.go | 12 +-- mcu_janus_client.go | 16 ++-- mcu_janus_publisher.go | 10 +-- mcu_janus_remote_publisher.go | 4 +- mcu_janus_stream_selection.go | 4 +- mcu_janus_subscriber.go | 16 ++-- mcu_janus_test.go | 138 ++++++++++++++++----------------- mcu_proxy.go | 10 +-- mcu_proxy_test.go | 4 +- mcu_test.go | 12 +-- natsclient.go | 8 +- natsclient_loopback.go | 4 +- proxy/proxy_server.go | 8 +- proxy/proxy_server_test.go | 8 +- proxy/proxy_session.go | 8 +- proxy/proxy_testclient_test.go | 4 +- remotesession.go | 2 +- room.go | 28 +++---- room_test.go | 2 +- roomsessions_test.go | 2 +- session.go | 8 +- sessionid_codec.go | 4 +- testclient_test.go | 42 +++++----- transient_data.go | 32 ++++---- transient_data_test.go | 4 +- virtualsession.go | 6 +- 47 files changed, 386 insertions(+), 385 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6d822a2..20ae68c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,6 +29,7 @@ linters: - name: superfluous-else #- name: unused-parameter - name: unreachable-code + - name: use-any - name: redefines-builtin-id exclusions: generated: lax diff --git a/api_backend.go b/api_backend.go index e20d5bf..3ed14c2 100644 --- a/api_backend.go +++ b/api_backend.go @@ -143,15 +143,15 @@ type BackendRoomDeleteRequest struct { type BackendRoomInCallRequest struct { // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. - InCall json.RawMessage `json:"incall,omitempty"` - All bool `json:"all,omitempty"` - Changed []map[string]interface{} `json:"changed,omitempty"` - Users []map[string]interface{} `json:"users,omitempty"` + InCall json.RawMessage `json:"incall,omitempty"` + All bool `json:"all,omitempty"` + Changed []map[string]any `json:"changed,omitempty"` + Users []map[string]any `json:"users,omitempty"` } type BackendRoomParticipantsRequest struct { - Changed []map[string]interface{} `json:"changed,omitempty"` - Users []map[string]interface{} `json:"users,omitempty"` + Changed []map[string]any `json:"changed,omitempty"` + Users []map[string]any `json:"users,omitempty"` } type BackendRoomMessageRequest struct { @@ -222,7 +222,7 @@ const ( type BackendRoomTransientRequest struct { Action TransientAction `json:"action"` Key string `json:"key"` - Value interface{} `json:"value,omitempty"` + Value any `json:"value,omitempty"` TTL time.Duration `json:"ttl,omitempty"` } diff --git a/api_proxy.go b/api_proxy.go index 23acf35..d22646a 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -277,9 +277,9 @@ type CommandProxyServerMessage struct { type PayloadProxyClientMessage struct { Type string `json:"type"` - ClientId string `json:"clientId"` - Sid string `json:"sid,omitempty"` - Payload map[string]interface{} `json:"payload,omitempty"` + ClientId string `json:"clientId"` + Sid string `json:"sid,omitempty"` + Payload map[string]any `json:"payload,omitempty"` } func (m *PayloadProxyClientMessage) CheckValid() error { @@ -308,8 +308,8 @@ func (m *PayloadProxyClientMessage) CheckValid() error { type PayloadProxyServerMessage struct { Type string `json:"type"` - ClientId string `json:"clientId"` - Payload map[string]interface{} `json:"payload"` + ClientId string `json:"clientId"` + Payload map[string]any `json:"payload"` } // Type "event" diff --git a/api_signaling.go b/api_signaling.go index 45c830d..2d24941 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -63,7 +63,7 @@ func makePtr[T any](v T) *T { return &v } -func getStringMapEntry[T any](m map[string]interface{}, key string) (s T, ok bool) { +func getStringMapEntry[T any](m map[string]any, key string) (s T, ok bool) { var defaultValue T v, found := m[key] if !found { @@ -268,7 +268,7 @@ func NewError(code string, message string) *Error { return NewErrorDetail(code, message, nil) } -func NewErrorDetail(code string, message string, details interface{}) *Error { +func NewErrorDetail(code string, message string, details any) *Error { var rawDetails json.RawMessage if details != nil { var err error @@ -728,10 +728,10 @@ type MessageClientMessage struct { } type MessageClientMessageData struct { - Type string `json:"type"` - Sid string `json:"sid"` - RoomType string `json:"roomType"` - Payload map[string]interface{} `json:"payload"` + Type string `json:"type"` + Sid string `json:"sid"` + RoomType string `json:"roomType"` + Payload map[string]any `json:"payload"` // Only supported if Type == "offer" Bitrate int `json:"bitrate,omitempty"` @@ -756,7 +756,7 @@ func (m *MessageClientMessageData) String() string { func parseSDP(s string) (*sdp.SessionDescription, error) { var sdp sdp.SessionDescription if err := sdp.UnmarshalString(s); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", map[string]interface{}{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", map[string]any{ "error": err.Error(), }) } @@ -768,7 +768,7 @@ func parseSDP(s string) (*sdp.SessionDescription, error) { } if _, err := ice.UnmarshalCandidate(a.Value); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", map[string]interface{}{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", map[string]any{ "media": m.MediaName.Media, "idx": idx, "error": err.Error(), @@ -815,7 +815,7 @@ func (m *MessageClientMessageData) CheckValid() error { if !found { return ErrNoCandidate } - candItem, ok := candValue.(map[string]interface{}) + candItem, ok := candValue.(map[string]any) if !ok { return ErrInvalidCandidate } @@ -833,7 +833,7 @@ func (m *MessageClientMessageData) CheckValid() error { } else { cand, err := ice.UnmarshalCandidate(candText) if err != nil { - return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", map[string]interface{}{ + return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", map[string]any{ "error": err.Error(), }) } @@ -1142,9 +1142,9 @@ type RoomEventServerMessage struct { RoomId string `json:"roomid"` Properties json.RawMessage `json:"properties,omitempty"` // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. - InCall json.RawMessage `json:"incall,omitempty"` - Changed []map[string]interface{} `json:"changed,omitempty"` - Users []map[string]interface{} `json:"users,omitempty"` + InCall json.RawMessage `json:"incall,omitempty"` + Changed []map[string]any `json:"changed,omitempty"` + Users []map[string]any `json:"users,omitempty"` All bool `json:"all,omitempty"` } @@ -1179,7 +1179,7 @@ type RoomFlagsServerMessage struct { Flags uint32 `json:"flags"` } -type ChatComment map[string]interface{} +type ChatComment map[string]any type RoomEventMessageDataChat struct { Comment *ChatComment `json:"comment,omitempty"` @@ -1248,12 +1248,12 @@ type EventServerMessageSwitchTo struct { // MCU-related types type AnswerOfferMessage struct { - To string `json:"to"` - From string `json:"from"` - Type string `json:"type"` - RoomType string `json:"roomType"` - Payload map[string]interface{} `json:"payload"` - Sid string `json:"sid,omitempty"` + To string `json:"to"` + From string `json:"from"` + Type string `json:"type"` + RoomType string `json:"roomType"` + Payload map[string]any `json:"payload"` + Sid string `json:"sid,omitempty"` } // Type "transient" @@ -1284,8 +1284,8 @@ func (m *TransientDataClientMessage) CheckValid() error { type TransientDataServerMessage struct { Type string `json:"type"` - Key string `json:"key,omitempty"` - OldValue interface{} `json:"oldvalue,omitempty"` - Value interface{} `json:"value,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` + Key string `json:"key,omitempty"` + OldValue any `json:"oldvalue,omitempty"` + Value any `json:"value,omitempty"` + Data map[string]any `json:"data,omitempty"` } diff --git a/backend_client.go b/backend_client.go index 51febb6..769d02c 100644 --- a/backend_client.go +++ b/backend_client.go @@ -117,7 +117,7 @@ func isOcsRequest(u *url.URL) bool { // 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 interface{}, response interface{}) error { +func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, request any, response any) error { if u == nil { return fmt.Errorf("no url passed to perform JSON request %+v", request) } diff --git a/backend_server.go b/backend_server.go index 5336eb4..ee46d3c 100644 --- a/backend_server.go +++ b/backend_server.go @@ -427,7 +427,7 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId return sid, nil } -func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentStringStringMap, users []map[string]interface{}) []map[string]interface{} { +func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentStringStringMap, users []map[string]any) []map[string]any { if len(users) == 0 { return users } @@ -453,7 +453,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Add(1) - go func(roomSessionId string, u map[string]interface{}) { + go func(roomSessionId string, u map[string]any) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -468,7 +468,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Wait() - result := make([]map[string]interface{}, 0, len(users)) + result := make([]map[string]any, 0, len(users)) for _, user := range users { if _, found := user["sessionId"]; found { result = append(result, user) @@ -524,7 +524,7 @@ loop: } sessionId := user["sessionId"].(string) - permissionsList, ok := permissionsInterface.([]interface{}) + permissionsList, ok := permissionsInterface.([]any) if !ok { log.Printf("Received invalid permissions %+v (%s) for session %s", permissionsInterface, reflect.TypeOf(permissionsInterface), sessionId) continue diff --git a/backend_server_test.go b/backend_server_test.go index 147d193..1692614 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -829,7 +829,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]interface{}{ + Changed: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, @@ -839,7 +839,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, @@ -911,13 +911,13 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]interface{}{ + Changed: []map[string]any{ { "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{}, }, }, - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{}, @@ -988,7 +988,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), - Changed: []map[string]interface{}{ + Changed: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, @@ -998,7 +998,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { "inCall": 3, }, }, - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, @@ -1035,7 +1035,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), - Changed: []map[string]interface{}{ + Changed: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, @@ -1045,7 +1045,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { "inCall": 3, }, }, - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, diff --git a/capabilities.go b/capabilities.go index 993e60c..c89f1fd 100644 --- a/capabilities.go +++ b/capabilities.go @@ -64,7 +64,7 @@ type capabilitiesEntry struct { nextUpdate time.Time etag string mustRevalidate bool - capabilities map[string]interface{} + capabilities map[string]any } func newCapabilitiesEntry(c *Capabilities) *capabilitiesEntry { @@ -211,7 +211,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return false, nil } - var capa map[string]interface{} + var capa map[string]any if err := json.Unmarshal(capaObj, &capa); err != nil { log.Printf("Unsupported capabilities received for app spreed from %s: %+v", url, capaResponse) e.capabilities = nil @@ -223,7 +223,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return true, nil } -func (e *capabilitiesEntry) GetCapabilities() map[string]interface{} { +func (e *capabilitiesEntry) GetCapabilities() map[string]any { e.mu.RLock() defer e.mu.RUnlock() @@ -322,7 +322,7 @@ func (c *Capabilities) getKeyForUrl(u *url.URL) string { return key } -func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (map[string]interface{}, bool, error) { +func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (map[string]any, bool, error) { key := c.getKeyForUrl(u) entry, valid := c.getCapabilities(key) if valid { @@ -349,7 +349,7 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea return false } - features, ok := featuresInterface.([]interface{}) + features, ok := featuresInterface.([]any) if !ok { log.Printf("Invalid features list received for %s: %+v", u, featuresInterface) return false @@ -363,7 +363,7 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea return false } -func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (map[string]interface{}, bool, bool) { +func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (map[string]any, bool, bool) { caps, cached, err := c.loadCapabilities(ctx, u) if err != nil { log.Printf("Could not get capabilities for %s: %s", u, err) @@ -375,7 +375,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str return nil, cached, false } - config, ok := configInterface.(map[string]interface{}) + config, ok := configInterface.(map[string]any) if !ok { log.Printf("Invalid config mapping received from %s: %+v", u, configInterface) return nil, cached, false @@ -386,7 +386,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str return nil, cached, false } - groupConfig, ok := groupInterface.(map[string]interface{}) + groupConfig, ok := groupInterface.(map[string]any) if !ok { log.Printf("Invalid group mapping \"%s\" received from %s: %+v", group, u, groupInterface) return nil, cached, false diff --git a/capabilities_test.go b/capabilities_test.go index d8857b4..69cf6fc 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -66,14 +66,14 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie if strings.Contains(t.Name(), "V3Api") { features = append(features, "signaling-v3") } - signaling := map[string]interface{}{ + signaling := map[string]any{ "foo": "bar", "baz": 42, } - config := map[string]interface{}{ + config := map[string]any{ "signaling": signaling, } - spreedCapa, _ := json.Marshal(map[string]interface{}{ + spreedCapa, _ := json.Marshal(map[string]any{ "features": features, "config": config, }) diff --git a/client.go b/client.go index 3fd6ce3..06a267f 100644 --- a/client.go +++ b/client.go @@ -428,7 +428,7 @@ func (c *Client) writeInternal(message json.Marshaler) bool { 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 { + if m, ok := (any(message)).(easyjson.Marshaler); ok { _, err = easyjson.MarshalToWriter(m, writer) } else { err = json.NewEncoder(writer).Encode(message) diff --git a/clientsession.go b/clientsession.go index 0e75d2d..426335f 100644 --- a/clientsession.go +++ b/clientsession.go @@ -65,7 +65,7 @@ type ClientSession struct { userId string userData json.RawMessage - parseUserData func() (map[string]interface{}, error) + parseUserData func() (map[string]any, error) inCall Flags supportsPermissions bool @@ -315,7 +315,7 @@ func (s *ClientSession) UserData() json.RawMessage { return s.userData } -func (s *ClientSession) ParsedUserData() (map[string]interface{}, error) { +func (s *ClientSession) ParsedUserData() (map[string]any, error) { return s.parseUserData() } @@ -550,7 +550,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) request.Room.Action = "leave" - var response map[string]interface{} + var response map[string]any if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { log.Printf("Could not notify about room session %s left room %s: %s", sid, room.Id(), err) } else { @@ -614,7 +614,7 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { return prev } -func (s *ClientSession) sendOffer(client McuClient, sender string, streamType StreamType, offer map[string]interface{}) { +func (s *ClientSession) sendOffer(client McuClient, sender string, streamType StreamType, offer map[string]any) { offer_message := &AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -642,13 +642,13 @@ func (s *ClientSession) sendOffer(client McuClient, sender string, streamType St s.sendMessageUnlocked(response_message) } -func (s *ClientSession) sendCandidate(client McuClient, sender string, streamType StreamType, candidate interface{}) { +func (s *ClientSession) sendCandidate(client McuClient, sender string, streamType StreamType, candidate any) { candidate_message := &AnswerOfferMessage{ To: s.PublicId(), From: sender, Type: "candidate", RoomType: string(streamType), - Payload: map[string]interface{}{ + Payload: map[string]any{ "candidate": candidate, }, Sid: client.Sid(), @@ -713,7 +713,7 @@ func (s *ClientSession) SendMessages(messages []*ServerMessage) bool { return true } -func (s *ClientSession) OnUpdateOffer(client McuClient, offer map[string]interface{}) { +func (s *ClientSession) OnUpdateOffer(client McuClient, offer map[string]any) { s.mu.Lock() defer s.mu.Unlock() @@ -725,7 +725,7 @@ func (s *ClientSession) OnUpdateOffer(client McuClient, offer map[string]interfa } } -func (s *ClientSession) OnIceCandidate(client McuClient, candidate interface{}) { +func (s *ClientSession) OnIceCandidate(client McuClient, candidate any) { s.mu.Lock() defer s.mu.Unlock() @@ -1108,7 +1108,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { return } - mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response map[string]interface{}) { + mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response map[string]any) { if err != nil { log.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ @@ -1171,7 +1171,7 @@ func filterDisplayNames(events []*EventServerMessageSessionEntry) []*EventServer continue } - var userdata map[string]interface{} + var userdata map[string]any if err := json.Unmarshal(event.User, &userdata); err != nil { result = append(result, event) continue diff --git a/clientsession_test.go b/clientsession_test.go index e75b132..de034a8 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -171,7 +171,7 @@ func TestBandwidth_Client(t *testing.T) { Sid: "54321", RoomType: "video", Bitrate: bitrate, - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -245,7 +245,7 @@ func TestBandwidth_Backend(t *testing.T) { Sid: "54321", RoomType: string(streamType), Bitrate: bitrate, - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) diff --git a/deferred_executor.go b/deferred_executor.go index a6f46c7..c193eee 100644 --- a/deferred_executor.go +++ b/deferred_executor.go @@ -62,7 +62,7 @@ func (e *DeferredExecutor) run() { } } -func getFunctionName(i interface{}) string { +func getFunctionName(i any) string { return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() } diff --git a/federation.go b/federation.go index 6856af8..c1527cd 100644 --- a/federation.go +++ b/federation.go @@ -603,7 +603,7 @@ func (c *FederationClient) joinRoom() error { }) } -func (c *FederationClient) updateEventUsers(users []map[string]interface{}, localSessionId string, remoteSessionId string) { +func (c *FederationClient) updateEventUsers(users []map[string]any, localSessionId string, remoteSessionId string) { localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) localCloudUrlLen := len(localCloudUrl) remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) @@ -668,7 +668,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { 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{} + var data map[string]any 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 { @@ -864,7 +864,7 @@ func (c *FederationClient) sendMessageLocked(message *ClientMessage) error { 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 { + if m, ok := (any(message)).(easyjson.Marshaler); ok { _, err = easyjson.MarshalToWriter(m, writer) } else { err = json.NewEncoder(writer).Encode(message) diff --git a/federation_test.go b/federation_test.go index 255cc28..51d4c26 100644 --- a/federation_test.go +++ b/federation_test.go @@ -111,7 +111,7 @@ func Test_Federation(t *testing.T) { require.NotNil(room) now := time.Now() - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -359,7 +359,7 @@ func Test_Federation(t *testing.T) { } // Simulate request from the backend that a federated user joined the call. - users := []map[string]interface{}{ + users := []map[string]any{ { "sessionId": remoteSessionId, "inCall": 1, @@ -383,7 +383,7 @@ func Test_Federation(t *testing.T) { assert.Equal(federatedRoomId, event.Update.RoomId) // Simulate request from the backend that a local user joined the call. - users = []map[string]interface{}{ + users = []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -435,7 +435,7 @@ func Test_Federation(t *testing.T) { hello4, err := client4.RunUntilHello(ctx) require.NoError(err) - userdata = map[string]interface{}{ + userdata = map[string]any{ "displayname": "Federated user 2", "actorType": "federated_users", "actorId": "the-other-federated-user-id", @@ -532,7 +532,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -643,7 +643,7 @@ func Test_FederationChangeRoom(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -770,7 +770,7 @@ func Test_FederationMedia(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -821,7 +821,7 @@ func Test_FederationMedia(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "screen", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -867,7 +867,7 @@ func Test_FederationResume(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -1000,7 +1000,7 @@ func Test_FederationResumeNewSession(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", diff --git a/hub.go b/hub.go index 259e87b..b446a23 100644 --- a/hub.go +++ b/hub.go @@ -1355,20 +1355,20 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message default: return nil, nil, InvalidClientType } - token, err := jwt.ParseWithClaims(tokenString, tokenClaims, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, tokenClaims, func(token *jwt.Token) (any, error) { // Only public-private-key algorithms are supported. - var loadKeyFunc func([]byte) (interface{}, error) + var loadKeyFunc func([]byte) (any, error) switch token.Method.(type) { case *jwt.SigningMethodRSA: - loadKeyFunc = func(data []byte) (interface{}, error) { + loadKeyFunc = func(data []byte) (any, error) { return jwt.ParseRSAPublicKeyFromPEM(data) } case *jwt.SigningMethodECDSA: - loadKeyFunc = func(data []byte) (interface{}, error) { + loadKeyFunc = func(data []byte) (any, error) { return jwt.ParseECPublicKeyFromPEM(data) } case *jwt.SigningMethodEd25519: - loadKeyFunc = func(data []byte) (interface{}, error) { + loadKeyFunc = func(data []byte) (any, error) { if !bytes.HasPrefix(data, []byte("-----BEGIN ")) { // Nextcloud sends the Ed25519 key as base64-encoded public key data. decoded, err := base64.StdEncoding.DecodeString(string(data)) @@ -1673,7 +1673,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { return } - var details interface{} + var details any var ce *tls.CertificateVerificationError if errors.As(err, &ce) { details = map[string]string{ @@ -2155,7 +2155,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { return } - mc.SendMessage(session.Context(), msg, clientData, func(err error, response map[string]interface{}) { + mc.SendMessage(session.Context(), msg, clientData, func(err error, response map[string]any) { if err != nil { log.Printf("Could not send MCU message %+v for session %s to %s: %s", clientData, session.PublicId(), recipient.PublicId(), err) sendMcuProcessingFailed(session, message) @@ -2749,7 +2749,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe return } - mc.SendMessage(session.Context(), message, data, func(err error, response map[string]interface{}) { + mc.SendMessage(session.Context(), message, data, func(err error, response map[string]any) { if err != nil { if !errors.Is(err, ErrCandidateFiltered) { log.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) @@ -2765,7 +2765,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe }) } -func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response map[string]interface{}) { +func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response map[string]any) { var response_message *ServerMessage switch response["type"] { case "answer": @@ -2880,8 +2880,8 @@ func (h *Hub) processRoomParticipants(message *BackendServerRoomRequest) { room.PublishUsersChanged(message.Participants.Changed, message.Participants.Users) } -func (h *Hub) GetStats() map[string]interface{} { - result := make(map[string]interface{}) +func (h *Hub) GetStats() map[string]any { + result := make(map[string]any) h.ru.RLock() result["rooms"] = len(h.rooms) h.ru.RUnlock() diff --git a/hub_test.go b/hub_test.go index 8c5d215..0353fd7 100644 --- a/hub_test.go +++ b/hub_test.go @@ -631,7 +631,7 @@ func ensureAuthTokens(t *testing.T) (string, string) { return privateKey, publicKey } -func getPrivateAuthToken(t *testing.T) (key interface{}) { +func getPrivateAuthToken(t *testing.T) (key any) { private, _ := ensureAuthTokens(t) data, err := base64.StdEncoding.DecodeString(private) require.NoError(t, err) @@ -646,7 +646,7 @@ func getPrivateAuthToken(t *testing.T) (key interface{}) { return key } -func getPublicAuthToken(t *testing.T) (key interface{}) { +func getPublicAuthToken(t *testing.T) (key any) { _, public := ensureAuthTokens(t) data, err := base64.StdEncoding.DecodeString(public) require.NoError(t, err) @@ -698,11 +698,11 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "Federation") { features = append(features, "federation-v2") } - signaling := map[string]interface{}{ + signaling := map[string]any{ "foo": "bar", "baz": 42, } - config := map[string]interface{}{ + config := map[string]any{ "signaling": signaling, } if strings.Contains(t.Name(), "MultiRoom") { @@ -734,7 +734,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { signaling[ConfigKeyHelloV2TokenKey] = string(public) } } - spreedCapa, _ := json.Marshal(map[string]interface{}{ + spreedCapa, _ := json.Marshal(map[string]any{ "features": features, "config": config, }) @@ -1885,7 +1885,7 @@ func TestClientHelloResumeProxy(t *testing.T) { room2 := hub2.getRoom(roomId) require.Nil(room2, "Should not have gotten room %s", roomId) - users := []map[string]interface{}{ + users := []map[string]any{ { "sessionId": "the-session-id", "inCall": 1, @@ -2137,7 +2137,7 @@ func TestClientMessageToSessionId(t *testing.T) { SessionId: hello2.Hello.SessionId, } - data1 := map[string]interface{}{ + data1 := map[string]any{ "type": "test", "message": "from-1-to-2", } @@ -2149,7 +2149,7 @@ func TestClientMessageToSessionId(t *testing.T) { if err := checkReceiveClientMessage(ctx, client1, "session", hello2.Hello, &payload1); assert.NoError(err) { assert.Equal(data2, payload1) } - var payload2 map[string]interface{} + var payload2 map[string]any if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload2); assert.NoError(err) { assert.Equal(data1, payload2) } @@ -2653,7 +2653,7 @@ func TestClientMessageToCall(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []map[string]interface{}{ + users := []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2690,7 +2690,7 @@ func TestClientMessageToCall(t *testing.T) { } // Simulate request from the backend that somebody joined the call. - users = []map[string]interface{}{ + users = []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2774,7 +2774,7 @@ func TestClientControlToCall(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []map[string]interface{}{ + users := []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2811,7 +2811,7 @@ func TestClientControlToCall(t *testing.T) { } // Simulate request from the backend that somebody joined the call. - users = []map[string]interface{}{ + users = []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -3583,7 +3583,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { // The two chat messages should get combined into one when receiving pending messages. chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" - var data1 map[string]interface{} + var data1 map[string]any require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint client1.SendMessage(recipient2, data1) // nolint @@ -3600,7 +3600,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { assert.Equal(hello2.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) } - var payload map[string]interface{} + var payload map[string]any if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { assert.Equal(data1, payload) } @@ -3655,7 +3655,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []map[string]interface{}{ + users := []map[string]any{ { "sessionId": "the-session-id", "inCall": 1, @@ -3680,7 +3680,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { } chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" - var data1 map[string]interface{} + var data1 map[string]any require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint @@ -3697,7 +3697,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { // TODO(jojo): Check contents of message and try with multiple users. assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) - var payload map[string]interface{} + var payload map[string]any if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { assert.Equal(data1, payload) } @@ -3907,7 +3907,7 @@ func TestClientSendOfferPermissions(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "screen", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3985,7 +3985,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4002,7 +4002,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioOnly, }, })) @@ -4056,7 +4056,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4067,13 +4067,13 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]interface{}{ + Changed: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, @@ -4162,7 +4162,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4173,13 +4173,13 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]interface{}{ + Changed: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, @@ -4286,7 +4286,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "screen", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4330,7 +4330,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.NoError(checkMessageError(msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. - users1 := []map[string]interface{}{ + users1 := []map[string]any{ { "sessionId": hello2.Hello.SessionId, "inCall": 1, @@ -4357,7 +4357,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.NoError(checkMessageError(msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. - users2 := []map[string]interface{}{ + users2 := []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -4388,7 +4388,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { Type: "answer", Sid: "12345", RoomType: "screen", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpAnswerAudioAndVideo, }, })) @@ -4746,7 +4746,7 @@ func TestClientSendOffer(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4820,7 +4820,7 @@ func TestClientUnshareScreen(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "screen", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioOnly, }, })) @@ -5273,7 +5273,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: []byte("0"), - Users: []map[string]interface{}{ + Users: []map[string]any{ { "sessionId": virtualSession.PublicId(), "participantPermissions": 246, @@ -5387,7 +5387,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } -func DoTestSwitchToOne(t *testing.T, details map[string]interface{}) { +func DoTestSwitchToOne(t *testing.T, details map[string]any) { CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { @@ -5440,7 +5440,7 @@ func DoTestSwitchToOne(t *testing.T, details map[string]interface{}) { roomId2 := "test-room-2" var sessions json.RawMessage if details != nil { - sessions, err = json.Marshal(map[string]interface{}{ + sessions, err = json.Marshal(map[string]any{ roomSessionId1: details, }) require.NoError(err) @@ -5491,7 +5491,7 @@ func DoTestSwitchToOne(t *testing.T, details map[string]interface{}) { } func TestSwitchToOneMap(t *testing.T) { - DoTestSwitchToOne(t, map[string]interface{}{ + DoTestSwitchToOne(t, map[string]any{ "foo": "bar", }) } @@ -5500,7 +5500,7 @@ func TestSwitchToOneList(t *testing.T) { DoTestSwitchToOne(t, nil) } -func DoTestSwitchToMultiple(t *testing.T, details1 map[string]interface{}, details2 map[string]interface{}) { +func DoTestSwitchToMultiple(t *testing.T, details1 map[string]any, details2 map[string]any) { CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { @@ -5553,7 +5553,7 @@ func DoTestSwitchToMultiple(t *testing.T, details1 map[string]interface{}, detai roomId2 := "test-room-2" var sessions json.RawMessage if details1 != nil || details2 != nil { - sessions, err = json.Marshal(map[string]interface{}{ + sessions, err = json.Marshal(map[string]any{ roomSessionId1: details1, roomSessionId2: details2, }) @@ -5603,9 +5603,9 @@ func DoTestSwitchToMultiple(t *testing.T, details1 map[string]interface{}, detai } func TestSwitchToMultipleMap(t *testing.T) { - DoTestSwitchToMultiple(t, map[string]interface{}{ + DoTestSwitchToMultiple(t, map[string]any{ "foo": "bar", - }, map[string]interface{}{ + }, map[string]any{ "bar": "baz", }) } @@ -5615,7 +5615,7 @@ func TestSwitchToMultipleList(t *testing.T) { } func TestSwitchToMultipleMixed(t *testing.T) { - DoTestSwitchToMultiple(t, map[string]interface{}{ + DoTestSwitchToMultiple(t, map[string]any{ "foo": "bar", }, nil) } @@ -5746,7 +5746,7 @@ func TestDialoutStatus(t *testing.T) { key := "callstatus_" + callId if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(msg, key, map[string]interface{}{ + assert.NoError(checkMessageTransientSet(msg, key, map[string]any{ "callid": callId, "status": "accepted", }, nil)) @@ -5762,10 +5762,10 @@ func TestDialoutStatus(t *testing.T) { })) if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(msg, key, map[string]interface{}{ + assert.NoError(checkMessageTransientSet(msg, key, map[string]any{ "callid": callId, "status": "ringing", - }, map[string]interface{}{ + }, map[string]any{ "callid": callId, "status": "accepted", })) @@ -5789,11 +5789,11 @@ func TestDialoutStatus(t *testing.T) { })) if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(msg, key, map[string]interface{}{ + assert.NoError(checkMessageTransientSet(msg, key, map[string]any{ "callid": callId, "status": "cleared", "cause": clearedCause, - }, map[string]interface{}{ + }, map[string]any{ "callid": callId, "status": "ringing", })) @@ -5803,7 +5803,7 @@ func TestDialoutStatus(t *testing.T) { defer cancel() if msg, err := client.RunUntilMessage(ctx2); assert.NoError(err) { - assert.NoError(checkMessageTransientRemove(msg, key, map[string]interface{}{ + assert.NoError(checkMessageTransientRemove(msg, key, map[string]any{ "callid": callId, "status": "cleared", "cause": clearedCause, diff --git a/janus_client.go b/janus_client.go index 33a7ec2..10d48c5 100644 --- a/janus_client.go +++ b/janus_client.go @@ -125,19 +125,19 @@ var ( } ) -var msgtypes = map[string]func() interface{}{ - "error": func() interface{} { return &janus.ErrorMsg{} }, - "success": func() interface{} { return &janus.SuccessMsg{} }, - "detached": func() interface{} { return &janus.DetachedMsg{} }, - "server_info": func() interface{} { return &InfoMsg{} }, - "ack": func() interface{} { return &janus.AckMsg{} }, - "event": func() interface{} { return &janus.EventMsg{} }, - "webrtcup": func() interface{} { return &janus.WebRTCUpMsg{} }, - "media": func() interface{} { return &janus.MediaMsg{} }, - "hangup": func() interface{} { return &janus.HangupMsg{} }, - "slowlink": func() interface{} { return &janus.SlowLinkMsg{} }, - "timeout": func() interface{} { return &janus.TimeoutMsg{} }, - "trickle": func() interface{} { return &TrickleMsg{} }, +var msgtypes = map[string]func() any{ + "error": func() any { return &janus.ErrorMsg{} }, + "success": func() any { return &janus.SuccessMsg{} }, + "detached": func() any { return &janus.DetachedMsg{} }, + "server_info": func() any { return &InfoMsg{} }, + "ack": func() any { return &janus.AckMsg{} }, + "event": func() any { return &janus.EventMsg{} }, + "webrtcup": func() any { return &janus.WebRTCUpMsg{} }, + "media": func() any { return &janus.MediaMsg{} }, + "hangup": func() any { return &janus.HangupMsg{} }, + "slowlink": func() any { return &janus.SlowLinkMsg{} }, + "timeout": func() any { return &janus.TimeoutMsg{} }, + "trickle": func() any { return &TrickleMsg{} }, } type InfoMsg struct { @@ -171,8 +171,8 @@ func unexpected(request string) error { } type transaction struct { - ch chan interface{} - incoming chan interface{} + ch chan any + incoming chan any closer *Closer } @@ -187,7 +187,7 @@ func (t *transaction) run() { } } -func (t *transaction) add(msg interface{}) { +func (t *transaction) add(msg any) { t.incoming <- msg } @@ -197,15 +197,15 @@ func (t *transaction) quit() { func newTransaction() *transaction { t := &transaction{ - ch: make(chan interface{}, 1), - incoming: make(chan interface{}, 8), + ch: make(chan any, 1), + incoming: make(chan any, 8), closer: NewCloser(), } return t } -func newRequest(method string) (map[string]interface{}, *transaction) { - req := make(map[string]interface{}, 8) +func newRequest(method string) (map[string]any, *transaction) { + req := make(map[string]any, 8) req["janus"] = method return req, newTransaction() } @@ -225,7 +225,7 @@ type JanusGatewayInterface interface { Create(context.Context) (*JanusSession, error) Close() error - send(map[string]interface{}, *transaction) (uint64, error) + send(map[string]any, *transaction) (uint64, error) removeTransaction(uint64) removeSession(*JanusSession) @@ -263,7 +263,7 @@ type JanusGateway struct { // gateway := new(Gateway) // //gateway.conn = conn -// gateway.transactions = make(map[uint64]chan interface{}) +// gateway.transactions = make(map[uint64]chan any) // gateway.Sessions = make(map[uint64]*JanusSession) // go gateway.recv() @@ -338,7 +338,7 @@ func (gateway *JanusGateway) removeTransaction(id uint64) { } } -func (gateway *JanusGateway) send(msg map[string]interface{}, t *transaction) (uint64, error) { +func (gateway *JanusGateway) send(msg map[string]any, t *transaction) (uint64, error) { id := gateway.nextTransaction.Add(1) msg["transaction"] = strconv.FormatUint(id, 10) data, err := json.Marshal(msg) @@ -367,7 +367,7 @@ func (gateway *JanusGateway) send(msg map[string]interface{}, t *transaction) (u return id, nil } -func passMsg(ch chan interface{}, msg interface{}) { +func passMsg(ch chan any, msg any) { ch <- msg } @@ -508,7 +508,7 @@ func (gateway *JanusGateway) recv() { } } -func waitForMessage(ctx context.Context, t *transaction) (interface{}, error) { +func waitForMessage(ctx context.Context, t *transaction) (any, error) { select { case <-ctx.Done(): return nil, ctx.Err() @@ -599,7 +599,7 @@ type JanusSession struct { gateway JanusGatewayInterface } -func (session *JanusSession) send(msg map[string]interface{}, t *transaction) (uint64, error) { +func (session *JanusSession) send(msg map[string]any, t *transaction) (uint64, error) { msg["session_id"] = session.Id return session.gateway.send(msg, t) } @@ -631,7 +631,7 @@ func (session *JanusSession) Attach(ctx context.Context, plugin string) (*JanusH handle := new(JanusHandle) handle.session = session handle.Id = success.Data.ID - handle.Events = make(chan interface{}, 8) + handle.Events = make(chan any, 8) session.Lock() session.Handles[handle.Id] = handle @@ -706,18 +706,18 @@ type JanusHandle struct { // Events is a receive only channel that can be used to receive events // related to this handle from the gateway. - Events chan interface{} + Events chan any session *JanusSession } -func (handle *JanusHandle) send(msg map[string]interface{}, t *transaction) (uint64, error) { +func (handle *JanusHandle) send(msg map[string]any, t *transaction) (uint64, error) { msg["handle_id"] = handle.Id return handle.session.send(msg, t) } // send sync request -func (handle *JanusHandle) Request(ctx context.Context, body interface{}) (*janus.SuccessMsg, error) { +func (handle *JanusHandle) Request(ctx context.Context, body any) (*janus.SuccessMsg, error) { req, ch := newRequest("message") if body != nil { req["body"] = body @@ -746,7 +746,7 @@ func (handle *JanusHandle) Request(ctx context.Context, body interface{}) (*janu // body should be the plugin data to be passed to the plugin, and jsep should // contain an optional SDP offer/answer to establish a WebRTC PeerConnection. // On success, an EventMsg will be returned and error will be nil. -func (handle *JanusHandle) Message(ctx context.Context, body, jsep interface{}) (*janus.EventMsg, error) { +func (handle *JanusHandle) Message(ctx context.Context, body, jsep any) (*janus.EventMsg, error) { req, ch := newRequest("message") if body != nil { req["body"] = body @@ -787,7 +787,7 @@ GetMessage: // No tears.. // } // // On success, an AckMsg will be returned and error will be nil. -func (handle *JanusHandle) Trickle(ctx context.Context, candidate interface{}) (*janus.AckMsg, error) { +func (handle *JanusHandle) Trickle(ctx context.Context, candidate any) (*janus.AckMsg, error) { req, ch := newRequest("trickle") req["candidate"] = candidate id, err := handle.send(req, ch) @@ -814,7 +814,7 @@ func (handle *JanusHandle) Trickle(ctx context.Context, candidate interface{}) ( // a new PeerConnection with a plugin. // candidates should be an array of ICE candidates. // On success, an AckMsg will be returned and error will be nil. -func (handle *JanusHandle) TrickleMany(ctx context.Context, candidates interface{}) (*janus.AckMsg, error) { +func (handle *JanusHandle) TrickleMany(ctx context.Context, candidates any) (*janus.AckMsg, error) { req, ch := newRequest("trickle") req["candidates"] = candidates id, err := handle.send(req, ch) diff --git a/lru.go b/lru.go index 1563d01..d46234a 100644 --- a/lru.go +++ b/lru.go @@ -28,7 +28,7 @@ import ( type cacheEntry struct { key string - value interface{} + value any } type LruCache struct { @@ -46,7 +46,7 @@ func NewLruCache(size int) *LruCache { } } -func (c *LruCache) Set(key string, value interface{}) { +func (c *LruCache) Set(key string, value any) { c.mu.Lock() if v, found := c.data[key]; found { c.entries.MoveToFront(v) @@ -66,7 +66,7 @@ func (c *LruCache) Set(key string, value interface{}) { c.mu.Unlock() } -func (c *LruCache) Get(key string) interface{} { +func (c *LruCache) Get(key string) any { c.mu.Lock() if v, found := c.data[key]; found { c.entries.MoveToFront(v) diff --git a/mcu_common.go b/mcu_common.go index b2649e4..65cdfef 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -56,9 +56,9 @@ const ( type McuListener interface { PublicId() string - OnUpdateOffer(client McuClient, offer map[string]interface{}) + OnUpdateOffer(client McuClient, offer map[string]any) - OnIceCandidate(client McuClient, candidate interface{}) + OnIceCandidate(client McuClient, candidate any) OnIceCompleted(client McuClient) SubscriberSidUpdated(subscriber McuSubscriber) @@ -127,7 +127,7 @@ type Mcu interface { SetOnConnected(func()) SetOnDisconnected(func()) - GetStats() interface{} + GetStats() any GetServerInfoSfu() *BackendServerInfoSfu NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) @@ -205,7 +205,7 @@ type McuClient interface { Close(ctx context.Context) - SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) + SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) } type McuPublisher interface { diff --git a/mcu_common_test.go b/mcu_common_test.go index 6304638..285dd94 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -37,11 +37,11 @@ func (m *MockMcuListener) PublicId() string { return m.publicId } -func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer map[string]interface{}) { +func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer map[string]any) { } -func (m *MockMcuListener) OnIceCandidate(client McuClient, candidate interface{}) { +func (m *MockMcuListener) OnIceCandidate(client McuClient, candidate any) { } diff --git a/mcu_janus.go b/mcu_janus.go index e3779a7..5a49314 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -61,7 +61,7 @@ func getStreamId(publisherId string, streamType StreamType) string { return fmt.Sprintf("%s|%s", publisherId, streamType) } -func getPluginValue(data janus.PluginData, pluginName string, key string) interface{} { +func getPluginValue(data janus.PluginData, pluginName string, key string) any { if data.Plugin != pluginName { return nil } @@ -69,7 +69,7 @@ func getPluginValue(data janus.PluginData, pluginName string, key string) interf return data.Data[key] } -func convertIntValue(value interface{}) (uint64, error) { +func convertIntValue(value any) (uint64, error) { switch t := value.(type) { case float64: if t < 0 { @@ -520,7 +520,7 @@ type mcuJanusConnectionStats struct { Uptime *time.Time `json:"uptime,omitempty"` } -func (m *mcuJanus) GetStats() interface{} { +func (m *mcuJanus) GetStats() any { result := mcuJanusConnectionStats{ Url: m.url, } @@ -568,7 +568,7 @@ func (m *mcuJanus) SubscriberDisconnected(id string, publisher string, streamTyp } func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id string, streamType StreamType, settings NewPublisherSettings) (uint64, int, error) { - create_msg := map[string]interface{}{ + create_msg := map[string]any{ "request": "create", "description": getStreamId(id, streamType), // We publish every stream in its own Janus room. @@ -642,7 +642,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id string, st return nil, 0, 0, 0, err } - msg := map[string]interface{}{ + msg := map[string]any{ "request": "join", "ptype": "publisher", "room": roomId, @@ -834,7 +834,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re return nil, err } - response, err := handle.Request(ctx, map[string]interface{}{ + response, err := handle.Request(ctx, map[string]any{ "request": "add_remote_publisher", "room": roomId, "id": streamTypeUserIds[streamType], diff --git a/mcu_janus_client.go b/mcu_janus_client.go index f1d254b..cceb046 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -75,7 +75,7 @@ func (c *mcuJanusClient) MaxBitrate() int { func (c *mcuJanusClient) Close(ctx context.Context) { } -func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { } func (c *mcuJanusClient) closeClient(ctx context.Context) bool { @@ -124,14 +124,14 @@ loop: } } -func (c *mcuJanusClient) sendOffer(ctx context.Context, offer map[string]interface{}, callback func(error, map[string]interface{})) { +func (c *mcuJanusClient) sendOffer(ctx context.Context, offer map[string]any, callback func(error, map[string]any)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) return } - configure_msg := map[string]interface{}{ + configure_msg := map[string]any{ "request": "configure", "audio": true, "video": true, @@ -146,14 +146,14 @@ func (c *mcuJanusClient) sendOffer(ctx context.Context, offer map[string]interfa callback(nil, answer_msg.Jsep) } -func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer map[string]interface{}, callback func(error, map[string]interface{})) { +func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer map[string]any, callback func(error, map[string]any)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) return } - start_msg := map[string]interface{}{ + start_msg := map[string]any{ "request": "start", "room": c.roomId, } @@ -166,7 +166,7 @@ func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer map[string]inter callback(nil, nil) } -func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate interface{}, callback func(error, map[string]interface{})) { +func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, map[string]any)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) @@ -188,7 +188,7 @@ func (c *mcuJanusClient) handleTrickle(event *TrickleMsg) { } } -func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, map[string]interface{})) { +func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, map[string]any)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) @@ -200,7 +200,7 @@ func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelecti return } - configure_msg := map[string]interface{}{ + configure_msg := map[string]any{ "request": "configure", } if stream != nil { diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index d555aa7..1e330f6 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -139,7 +139,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { notify := false p.mu.Lock() if handle := p.handle; handle != nil && p.roomId != 0 { - destroy_msg := map[string]interface{}{ + destroy_msg := map[string]any{ "request": "destroy", "room": p.roomId, } @@ -167,7 +167,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { @@ -201,7 +201,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli msgctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() - p.sendOffer(msgctx, jsep_msg, func(err error, jsep map[string]interface{}) { + p.sendOffer(msgctx, jsep_msg, func(err error, jsep map[string]any) { if err != nil { callback(err, jsep) return @@ -403,7 +403,7 @@ func getPublisherRemoteId(id string, remoteId string, hostname string, port int, } func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { - msg := map[string]interface{}{ + msg := map[string]any{ "request": "publish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], @@ -440,7 +440,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId string, } func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { - msg := map[string]interface{}{ + msg := map[string]any{ "request": "unpublish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], diff --git a/mcu_janus_remote_publisher.go b/mcu_janus_remote_publisher.go index 9a3575b..f622efa 100644 --- a/mcu_janus_remote_publisher.go +++ b/mcu_janus_remote_publisher.go @@ -124,7 +124,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { p.mu.Lock() if handle := p.handle; handle != nil { - response, err := p.handle.Request(ctx, map[string]interface{}{ + response, err := p.handle.Request(ctx, map[string]any{ "request": "remove_remote_publisher", "room": p.roomId, "id": streamTypeUserIds[p.streamType], @@ -135,7 +135,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { log.Printf("Removed remote publisher: %+v", response) } if p.roomId != 0 { - destroy_msg := map[string]interface{}{ + destroy_msg := map[string]any{ "request": "destroy", "room": p.roomId, } diff --git a/mcu_janus_stream_selection.go b/mcu_janus_stream_selection.go index db12a88..3d31bb4 100644 --- a/mcu_janus_stream_selection.go +++ b/mcu_janus_stream_selection.go @@ -37,7 +37,7 @@ func (s *streamSelection) HasValues() bool { return s.substream.Valid || s.temporal.Valid || s.audio.Valid || s.video.Valid } -func (s *streamSelection) AddToMessage(message map[string]interface{}) { +func (s *streamSelection) AddToMessage(message map[string]any) { if s.substream.Valid { message["substream"] = s.substream.Int16 } @@ -52,7 +52,7 @@ func (s *streamSelection) AddToMessage(message map[string]interface{}) { } } -func parseStreamSelection(payload map[string]interface{}) (*streamSelection, error) { +func parseStreamSelection(payload map[string]any) (*streamSelection, error) { var stream streamSelection if value, found := payload["substream"]; found { switch value := value.(type) { diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 492b471..6c75788 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -48,14 +48,14 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { log.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId) go p.Close(ctx) case "updated": - streams, ok := getPluginValue(event.Plugindata, pluginVideoRoom, "streams").([]interface{}) + streams, ok := getPluginValue(event.Plugindata, pluginVideoRoom, "streams").([]any) if !ok || len(streams) == 0 { // The streams list will be empty if no stream was changed. return } for _, stream := range streams { - if stream, ok := stream.(map[string]interface{}); ok { + if stream, ok := stream.(map[string]any); ok { if (stream["type"] == "audio" || stream["type"] == "video") && stream["active"] != false { return } @@ -149,7 +149,7 @@ func (p *mcuJanusSubscriber) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, map[string]interface{})) { +func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, map[string]any)) { handle := p.handle if handle == nil { callback(ErrNotConnected, nil) @@ -161,13 +161,13 @@ func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelecti loggedNotPublishingYet := false retry: - join_msg := map[string]interface{}{ + join_msg := map[string]any{ "request": "join", "ptype": "subscriber", "room": p.roomId, } if p.mcu.isMultistream() { - join_msg["streams"] = []map[string]interface{}{ + join_msg["streams"] = []map[string]any{ { "feed": streamTypeUserIds[p.streamType], }, @@ -255,14 +255,14 @@ retry: callback(nil, join_response.Jsep) } -func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, map[string]interface{})) { +func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, map[string]any)) { handle := p.handle if handle == nil { callback(ErrNotConnected, nil) return } - configure_msg := map[string]interface{}{ + configure_msg := map[string]any{ "request": "configure", "update": true, } @@ -278,7 +278,7 @@ func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection callback(nil, configure_response.Jsep) } -func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { diff --git a/mcu_janus_test.go b/mcu_janus_test.go index be0fcc8..8d25f64 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -48,7 +48,7 @@ type TestJanusRoom struct { publisher atomic.Pointer[TestJanusHandle] } -type TestJanusHandler func(room *TestJanusRoom, body map[string]interface{}, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) +type TestJanusHandler func(room *TestJanusRoom, body map[string]any, jsep map[string]any) (any, *janus.ErrorMsg) type TestJanusGateway struct { t *testing.T @@ -141,7 +141,7 @@ func (g *TestJanusGateway) Close() error { return nil } -func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body map[string]interface{}, jsep map[string]interface{}) interface{} { +func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body map[string]any, jsep map[string]any) any { request := body["request"].(string) switch request { case "create": @@ -153,7 +153,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "room": room.id, }, }, @@ -181,7 +181,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "error_code": error_code, }, }, @@ -192,7 +192,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, }, }, @@ -216,7 +216,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan Handle: handle.id, Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "room": room.id, }, }, @@ -227,7 +227,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED, }, }, @@ -236,7 +236,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan sdp := publisher.sdp.Load() return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "type": "offer", "sdp": sdp.(string), }, @@ -265,7 +265,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{}, + Data: map[string]any{}, }, } default: @@ -331,7 +331,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return nil } -func (g *TestJanusGateway) processRequest(msg map[string]interface{}) interface{} { +func (g *TestJanusGateway) processRequest(msg map[string]any) any { method, found := msg["janus"] if !found { return nil @@ -405,12 +405,12 @@ func (g *TestJanusGateway) processRequest(msg map[string]interface{}) interface{ } } - var result interface{} + var result any switch method { case "message": - body := msg["body"].(map[string]interface{}) + body := msg["body"].(map[string]any) if jsep, found := msg["jsep"]; found { - result = g.processMessage(session, handle, body, jsep.(map[string]interface{})) + result = g.processMessage(session, handle, body, jsep.(map[string]any)) } else { result = g.processMessage(session, handle, body, nil) } @@ -449,7 +449,7 @@ func (g *TestJanusGateway) processRequest(msg map[string]interface{}) interface{ return nil } -func (g *TestJanusGateway) send(msg map[string]interface{}, t *transaction) (uint64, error) { +func (g *TestJanusGateway) send(msg map[string]any, t *transaction) (uint64, error) { tid := g.tid.Add(1) data, err := json.Marshal(msg) @@ -521,11 +521,11 @@ func (t *TestMcuListener) PublicId() string { return t.id } -func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer map[string]interface{}) { +func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer map[string]any) { } -func (t *TestMcuListener) OnIceCandidate(client McuClient, candidate interface{}) { +func (t *TestMcuListener) OnIceCandidate(client McuClient, candidate any) { } @@ -591,7 +591,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { // The SDP received by Janus will be filtered from blocked candidates. @@ -604,12 +604,12 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "sdp": MockSdpAnswerAudioOnly, }, }, nil }, - "trickle": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "trickle": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.AckMsg{}, nil }, @@ -635,7 +635,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { // Send offer containing candidates that will be blocked / filtered. data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioOnly, }, } @@ -643,7 +643,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer wg.Done() if assert.NoError(err) { @@ -659,15 +659,15 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]interface{}{ - "candidate": map[string]interface{}{ + Payload: map[string]any{ + "candidate": map[string]any{ "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -677,15 +677,15 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]interface{}{ - "candidate": map[string]interface{}{ + Payload: map[string]any{ + "candidate": map[string]any{ "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer wg.Done() assert.NoError(err) @@ -702,7 +702,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "start": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "start": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { // The SDP received by Janus will be filtered from blocked candidates. @@ -717,7 +717,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "room": room.id, "started": true, "videoroom": "event", @@ -725,7 +725,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { }, }, nil }, - "trickle": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "trickle": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.AckMsg{}, nil }, @@ -762,7 +762,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { // Send answer containing candidates that will be blocked / filtered. data := &MessageClientMessageData{ Type: "answer", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpAnswerAudioOnly, }, } @@ -770,7 +770,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer wg.Done() if assert.NoError(err) { @@ -781,15 +781,15 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]interface{}{ - "candidate": map[string]interface{}{ + Payload: map[string]any{ + "candidate": map[string]any{ "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -799,15 +799,15 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]interface{}{ - "candidate": map[string]interface{}{ + Payload: map[string]any{ + "candidate": map[string]any{ "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer wg.Done() assert.NoError(err) @@ -824,7 +824,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { if sdpValue, found := jsep["sdp"]; assert.True(found) { @@ -836,7 +836,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "sdp": MockSdpAnswerAudioOnly, }, }, nil @@ -862,14 +862,14 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioOnly, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer close(done) if assert.NoError(err) { @@ -906,7 +906,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { _, found := jsep["sdp"] @@ -914,7 +914,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "sdp": MockSdpAnswerAudioAndVideo, }, }, nil @@ -940,7 +940,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, } @@ -949,7 +949,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { // Defer sending of offer / answer so "GetStreams" will wait. go func() { done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer close(done) if assert.NoError(err) { @@ -1083,7 +1083,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { if sdp, found := jsep["sdp"]; assert.True(found) { @@ -1092,7 +1092,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "sdp": MockSdpAnswerAudioAndVideo, }, }, nil @@ -1130,14 +1130,14 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { go func() { data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer close(done) if assert.NoError(err) { @@ -1158,7 +1158,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { require.NoError(data.CheckValid()) done := make(chan struct{}) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]interface{}) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { defer close(done) if assert.NoError(err) { @@ -1186,11 +1186,11 @@ func Test_JanusRemotePublisher(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "add_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "add_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) assert.Nil(jsep) - if streams := body["streams"].([]interface{}); assert.Len(streams, 1) { - stream := streams[0].(map[string]interface{}) + if streams := body["streams"].([]any); assert.Len(streams, 1) { + stream := streams[0].(map[string]any) assert.Equal("0", stream["mid"]) assert.EqualValues(0, stream["mindex"]) assert.Equal("audio", stream["type"]) @@ -1200,7 +1200,7 @@ func Test_JanusRemotePublisher(t *testing.T) { return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{ + Data: map[string]any{ "id": 12345, "port": 10000, "rtcp_port": 10001, @@ -1208,14 +1208,14 @@ func Test_JanusRemotePublisher(t *testing.T) { }, }, nil }, - "remove_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "remove_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) assert.Nil(jsep) removed.Add(1) return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]interface{}{}, + Data: map[string]any{}, }, }, nil }, @@ -1269,10 +1269,10 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1317,7 +1317,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []map[string]interface{}{ + users1 := []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1339,7 +1339,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -1378,10 +1378,10 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1426,7 +1426,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []map[string]interface{}{ + users1 := []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1448,7 +1448,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -1497,10 +1497,10 @@ func Test_JanusSubscriberTimeout(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]interface{}) (interface{}, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: map[string]interface{}{ + Jsep: map[string]any{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1545,7 +1545,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []map[string]interface{}{ + users1 := []map[string]any{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1567,7 +1567,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: map[string]interface{}{ + Payload: map[string]any{ "sdp": MockSdpOfferAudioAndVideo, }, })) diff --git a/mcu_proxy.go b/mcu_proxy.go index d10bb53..f1b94f7 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -101,7 +101,7 @@ func (c *mcuProxyPubSubCommon) MaxBitrate() int { return c.maxBitrate } -func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, map[string]interface{})) { +func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, map[string]any)) { c.conn.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { if err != nil { callback(err, nil) @@ -124,7 +124,7 @@ func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClie func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadProxyServerMessage) { switch msg.Type { case "offer": - c.listener.OnUpdateOffer(client, msg.Payload["offer"].(map[string]interface{})) + c.listener.OnUpdateOffer(client, msg.Payload["offer"].(map[string]any)) case "candidate": c.listener.OnIceCandidate(client, msg.Payload["candidate"]) default: @@ -195,7 +195,7 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { log.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) } -func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ @@ -307,7 +307,7 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { } } -func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ @@ -1796,7 +1796,7 @@ type mcuProxyStats struct { Details []*mcuProxyConnectionStats `json:"details"` } -func (m *mcuProxy) GetStats() interface{} { +func (m *mcuProxy) GetStats() any { result := &mcuProxyStats{} m.connectionsMu.RLock() diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 10562b4..18571c8 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -219,7 +219,7 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer return nil, fmt.Errorf("expected hello, got %+v", msg) } - token, err := jwt.ParseWithClaims(msg.Hello.Token, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(msg.Hello.Token, &TokenClaims{}, func(token *jwt.Token) (any, error) { claims, ok := token.Claims.(*TokenClaims) if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { return nil, errors.New("unsupported claims type") @@ -328,7 +328,7 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( continue } - token, err := jwt.ParseWithClaims(msg.Command.RemoteToken, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(msg.Command.RemoteToken, &TokenClaims{}, func(token *jwt.Token) (any, error) { claims, ok := token.Claims.(*TokenClaims) if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { return nil, errors.New("unsupported claims type") diff --git a/mcu_test.go b/mcu_test.go index 36cb21f..a149ac5 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -66,7 +66,7 @@ func (m *TestMCU) SetOnConnected(f func()) { func (m *TestMCU) SetOnDisconnected(f func()) { } -func (m *TestMCU) GetStats() interface{} { +func (m *TestMCU) GetStats() any { return nil } @@ -196,7 +196,7 @@ func (p *TestMCUPublisher) SetMedia(mt MediaType) { p.settings.MediaTypes = mt } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { go func() { if p.isClosed() { callback(fmt.Errorf("Already closed"), nil) @@ -210,13 +210,13 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClie p.sdp = sdp switch sdp { case MockSdpOfferAudioOnly: - callback(nil, map[string]interface{}{ + callback(nil, map[string]any{ "type": "answer", "sdp": MockSdpAnswerAudioOnly, }) return case MockSdpOfferAudioAndVideo: - callback(nil, map[string]interface{}{ + callback(nil, map[string]any{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }) @@ -252,7 +252,7 @@ func (s *TestMCUSubscriber) Publisher() string { return s.publisher.id } -func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]interface{})) { +func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { go func() { if s.isClosed() { callback(fmt.Errorf("Already closed"), nil) @@ -269,7 +269,7 @@ func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageCli return } - callback(nil, map[string]interface{}{ + callback(nil, map[string]any{ "type": "offer", "sdp": sdp, }) diff --git a/natsclient.go b/natsclient.go index 66e8a84..ea0a364 100644 --- a/natsclient.go +++ b/natsclient.go @@ -51,9 +51,9 @@ type NatsClient interface { Close() Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) - Publish(subject string, message interface{}) error + Publish(subject string, message any) error - Decode(msg *nats.Msg, v interface{}) error + Decode(msg *nats.Msg, v any) error } // The NATS client doesn't work if a subject contains spaces. As the room id @@ -129,7 +129,7 @@ func (c *natsClient) Subscribe(subject string, ch chan *nats.Msg) (NatsSubscript return c.conn.ChanSubscribe(subject, ch) } -func (c *natsClient) Publish(subject string, message interface{}) error { +func (c *natsClient) Publish(subject string, message any) error { data, err := json.Marshal(message) if err != nil { return err @@ -138,7 +138,7 @@ func (c *natsClient) Publish(subject string, message interface{}) error { return c.conn.Publish(subject, data) } -func (c *natsClient) Decode(msg *nats.Msg, vPtr interface{}) (err error) { +func (c *natsClient) Decode(msg *nats.Msg, vPtr any) (err error) { switch arg := vPtr.(type) { case *string: // If they want a string and it is a JSON string, strip quotes diff --git a/natsclient_loopback.go b/natsclient_loopback.go index 56b6fb6..952f27a 100644 --- a/natsclient_loopback.go +++ b/natsclient_loopback.go @@ -145,7 +145,7 @@ func (c *LoopbackNatsClient) unsubscribe(s *loopbackNatsSubscription) { } } -func (c *LoopbackNatsClient) Publish(subject string, message interface{}) error { +func (c *LoopbackNatsClient) Publish(subject string, message any) error { if strings.HasSuffix(subject, ".") || strings.Contains(subject, " ") { return nats.ErrBadSubject } @@ -168,6 +168,6 @@ func (c *LoopbackNatsClient) Publish(subject string, message interface{}) error return nil } -func (c *LoopbackNatsClient) Decode(msg *nats.Msg, v interface{}) error { +func (c *LoopbackNatsClient) Decode(msg *nats.Msg, v any) error { return json.Unmarshal(msg.Data, v) } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 21b5ed8..9feca63 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -1344,7 +1344,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s ctx2, cancel := context.WithTimeout(ctx, s.mcuTimeout) defer cancel() - mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response map[string]interface{}) { + mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response map[string]any) { var responseMsg *signaling.ProxyServerMessage if errors.Is(err, signaling.ErrCandidateFiltered) { // Silently ignore filtered candidates. @@ -1377,7 +1377,7 @@ func (s *ProxyServer) processBye(ctx context.Context, client *ProxyClient, sessi func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, string, error) { reason := "auth-failed" - token, err := jwt.ParseWithClaims(tokenValue, &signaling.TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenValue, &signaling.TokenClaims{}, func(token *jwt.Token) (any, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { log.Printf("Unexpected signing method: %v", token.Header["alg"]) @@ -1586,8 +1586,8 @@ func (s *ProxyServer) GetClientId(client signaling.McuClient) string { return s.clientIds[client.Id()] } -func (s *ProxyServer) getStats() map[string]interface{} { - result := map[string]interface{}{ +func (s *ProxyServer) getStats() map[string]any { + result := map[string]any{ "sessions": s.GetSessionsCount(), "load": s.load.Load(), "mcu": s.mcu.GetStats(), diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 866560a..17b3b69 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -375,7 +375,7 @@ func (m *TestMCU) SetOnConnected(f func()) { func (m *TestMCU) SetOnDisconnected(f func()) { } -func (m *TestMCU) GetStats() interface{} { +func (m *TestMCU) GetStats() any { return nil } @@ -420,7 +420,7 @@ func (p *TestMCUPublisher) MaxBitrate() int { func (p *TestMCUPublisher) Close(ctx context.Context) { } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]interface{})) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]any)) { callback(errors.New("not implemented"), nil) } @@ -673,7 +673,7 @@ func (p *TestRemotePublisher) Close(ctx context.Context) { } } -func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]interface{})) { +func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]any)) { callback(errors.New("not implemented"), nil) } @@ -729,7 +729,7 @@ func (s *TestRemoteSubscriber) Close(ctx context.Context) { s.closeFunc() } -func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]interface{})) { +func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]any)) { callback(errors.New("not implemented"), nil) } diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 2e3621e..0c2e77b 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -158,7 +158,7 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient { return prev } -func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[string]interface{}) { +func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[string]any) { id := s.proxy.GetClientId(client) if id == "" { log.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client) @@ -170,7 +170,7 @@ func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[strin Payload: &signaling.PayloadProxyServerMessage{ Type: "offer", ClientId: id, - Payload: map[string]interface{}{ + Payload: map[string]any{ "offer": offer, }, }, @@ -178,7 +178,7 @@ func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[strin s.sendMessage(msg) } -func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate interface{}) { +func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate any) { id := s.proxy.GetClientId(client) if id == "" { log.Printf("Received candidate %+v from unknown %s client %s (%+v)", candidate, client.StreamType(), client.Id(), client) @@ -190,7 +190,7 @@ func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate inte Payload: &signaling.PayloadProxyServerMessage{ Type: "candidate", ClientId: id, - Payload: map[string]interface{}{ + Payload: map[string]any{ "candidate": candidate, }, }, diff --git a/proxy/proxy_testclient_test.go b/proxy/proxy_testclient_test.go index c08b5f3..7930545 100644 --- a/proxy/proxy_testclient_test.go +++ b/proxy/proxy_testclient_test.go @@ -125,7 +125,7 @@ func (c *ProxyTestClient) SendBye() error { return c.WriteJSON(hello) } -func (c *ProxyTestClient) WriteJSON(data interface{}) error { +func (c *ProxyTestClient) WriteJSON(data any) error { if msg, ok := data.(*signaling.ProxyClientMessage); ok { if err := msg.CheckValid(); err != nil { return err @@ -188,7 +188,7 @@ func checkMessageType(message *signaling.ProxyServerMessage, expectedType string return nil } -func (c *ProxyTestClient) SendHello(key interface{}) error { +func (c *ProxyTestClient) SendHello(key any) error { claims := &signaling.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), diff --git a/remotesession.go b/remotesession.go index 85c271f..3c56720 100644 --- a/remotesession.go +++ b/remotesession.go @@ -118,7 +118,7 @@ func (s *RemoteSession) sendProxyMessage(message []byte) error { return proxy.Send(msg) } -func (s *RemoteSession) sendMessage(message interface{}) error { +func (s *RemoteSession) sendMessage(message any) error { data, err := json.Marshal(message) if err != nil { return err diff --git a/room.go b/room.go index 4fe09cd..e12df91 100644 --- a/room.go +++ b/room.go @@ -80,7 +80,7 @@ type Room struct { statsRoomSessionsCurrent *prometheus.GaugeVec // Users currently in the room - users []map[string]interface{} + users []map[string]any // Timestamps of last backend requests for the different types. lastRoomRequests map[string]int64 @@ -458,7 +458,7 @@ func (r *Room) RemoveSession(session Session) bool { if virtualSession, ok := session.(*VirtualSession); ok { delete(r.virtualSessions, virtualSession) // Handle case where virtual session was also sent by Nextcloud. - users := make([]map[string]interface{}, 0, len(r.users)) + users := make([]map[string]any, 0, len(r.users)) for _, u := range r.users { if u["sessionId"] != sid { users = append(users, u) @@ -631,7 +631,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[string]*Inter return } -func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string]interface{} { +func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { now := time.Now().Unix() r.mu.RLock() defer r.mu.RUnlock() @@ -675,7 +675,7 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] } } for session := range r.internalSessions { - u := map[string]interface{}{ + u := map[string]any{ "inCall": session.GetInCall(), "sessionId": session.PublicId(), "lastPing": now, @@ -687,7 +687,7 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] users = append(users, u) } for _, session := range clusteredInternalSessions { - u := map[string]interface{}{ + u := map[string]any{ "inCall": session.GetInCall(), "sessionId": session.GetSessionId(), "lastPing": now, @@ -704,7 +704,7 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] continue } skipSession[sid] = true - users = append(users, map[string]interface{}{ + users = append(users, map[string]any{ "inCall": session.GetInCall(), "sessionId": sid, "lastPing": now, @@ -716,7 +716,7 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] continue } - users = append(users, map[string]interface{}{ + users = append(users, map[string]any{ "inCall": session.GetInCall(), "sessionId": sid, "lastPing": now, @@ -726,14 +726,14 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] return users } -func (r *Room) filterPermissions(users []map[string]interface{}) []map[string]interface{} { +func (r *Room) filterPermissions(users []map[string]any) []map[string]any { for _, user := range users { delete(user, "permissions") } return users } -func IsInCall(value interface{}) (bool, bool) { +func IsInCall(value any) (bool, bool) { switch value := value.(type) { case bool: return value, true @@ -753,7 +753,7 @@ func IsInCall(value interface{}) (bool, bool) { } } -func (r *Room) PublishUsersInCallChanged(changed []map[string]interface{}, users []map[string]interface{}) { +func (r *Room) PublishUsersInCallChanged(changed []map[string]any, users []map[string]any) { r.users = users for _, user := range changed { inCallInterface, found := user["inCall"] @@ -908,7 +908,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { } } -func (r *Room) PublishUsersChanged(changed []map[string]interface{}, users []map[string]interface{}) { +func (r *Room) PublishUsersChanged(changed []map[string]any, users []map[string]any) { changed = r.filterPermissions(changed) users = r.filterPermissions(users) @@ -929,7 +929,7 @@ func (r *Room) PublishUsersChanged(changed []map[string]interface{}, users []map } } -func (r *Room) getParticipantsUpdateMessage(users []map[string]interface{}) *ServerMessage { +func (r *Room) getParticipantsUpdateMessage(users []map[string]any) *ServerMessage { users = r.filterPermissions(users) message := &ServerMessage{ @@ -1206,11 +1206,11 @@ func (r *Room) notifyInternalRoomDeleted() { } } -func (r *Room) SetTransientData(key string, value interface{}) { +func (r *Room) SetTransientData(key string, value any) { r.transientData.Set(key, value) } -func (r *Room) SetTransientDataTTL(key string, value interface{}, ttl time.Duration) { +func (r *Room) SetTransientDataTTL(key string, value any, ttl time.Duration) { r.transientData.SetTTL(key, value, ttl) } diff --git a/room_test.go b/room_test.go index 2d170ea..b5b9c7d 100644 --- a/room_test.go +++ b/room_test.go @@ -39,7 +39,7 @@ import ( func TestRoom_InCall(t *testing.T) { type Testcase struct { - Value interface{} + Value any InCall bool Valid bool } diff --git a/roomsessions_test.go b/roomsessions_test.go index a2947b3..575713b 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -63,7 +63,7 @@ func (s *DummySession) UserData() json.RawMessage { return nil } -func (s *DummySession) ParsedUserData() (map[string]interface{}, error) { +func (s *DummySession) ParsedUserData() (map[string]any, error) { return nil, nil } diff --git a/session.go b/session.go index d08b8ec..5bf83f1 100644 --- a/session.go +++ b/session.go @@ -56,7 +56,7 @@ type Session interface { UserId() string UserData() json.RawMessage - ParsedUserData() (map[string]interface{}, error) + ParsedUserData() (map[string]any, error) Backend() *Backend BackendUrl() string @@ -74,13 +74,13 @@ type Session interface { SendMessage(message *ServerMessage) bool } -func parseUserData(data json.RawMessage) func() (map[string]interface{}, error) { - return sync.OnceValues(func() (map[string]interface{}, error) { +func parseUserData(data json.RawMessage) func() (map[string]any, error) { + return sync.OnceValues(func() (map[string]any, error) { if len(data) == 0 { return nil, nil } - var m map[string]interface{} + var m map[string]any if err := json.Unmarshal(data, &m); err != nil { return nil, err } diff --git a/sessionid_codec.go b/sessionid_codec.go index 81de6a8..f5d9930 100644 --- a/sessionid_codec.go +++ b/sessionid_codec.go @@ -32,7 +32,7 @@ import ( type protoSerializer struct { } -func (s *protoSerializer) Serialize(src interface{}) ([]byte, error) { +func (s *protoSerializer) Serialize(src any) ([]byte, error) { msg, ok := src.(proto.Message) if !ok { return nil, fmt.Errorf("can't serialize type %T", src) @@ -40,7 +40,7 @@ func (s *protoSerializer) Serialize(src interface{}) ([]byte, error) { return proto.Marshal(msg) } -func (s *protoSerializer) Deserialize(src []byte, dst interface{}) error { +func (s *protoSerializer) Deserialize(src []byte, dst any) error { msg, ok := dst.(proto.Message) if !ok { return fmt.Errorf("can't deserialize type %T", src) diff --git a/testclient_test.go b/testclient_test.go index 915a95b..77bf12a 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -137,7 +137,7 @@ func checkMessageSender(hub *Hub, sender *MessageServerMessageSender, senderType return nil } -func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload interface{}, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) error { +func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) error { message, err := client.RunUntilMessage(ctx) if err := checkUnexpectedClose(err); err != nil { return err @@ -159,15 +159,15 @@ func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, client return nil } -func checkReceiveClientMessageWithSender(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload interface{}, sender **MessageServerMessageSender) error { +func checkReceiveClientMessageWithSender(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) error { return checkReceiveClientMessageWithSenderAndRecipient(ctx, client, senderType, hello, payload, sender, nil) } -func checkReceiveClientMessage(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload interface{}) error { +func checkReceiveClientMessage(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any) error { return checkReceiveClientMessageWithSenderAndRecipient(ctx, client, senderType, hello, payload, nil, nil) } -func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload interface{}, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) error { +func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) error { message, err := client.RunUntilMessage(ctx) if err := checkUnexpectedClose(err); err != nil { return err @@ -189,11 +189,11 @@ func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, client return nil } -func checkReceiveClientControlWithSender(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload interface{}, sender **MessageServerMessageSender) error { // nolint +func checkReceiveClientControlWithSender(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) error { // nolint return checkReceiveClientControlWithSenderAndRecipient(ctx, client, senderType, hello, payload, sender, nil) } -func checkReceiveClientControl(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload interface{}) error { +func checkReceiveClientControl(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any) error { return checkReceiveClientControlWithSenderAndRecipient(ctx, client, senderType, hello, payload, nil, nil) } @@ -363,7 +363,7 @@ func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId string return nil } -func (c *TestClient) WriteJSON(data interface{}) error { +func (c *TestClient) WriteJSON(data any) error { if !strings.Contains(c.t.Name(), "HelloUnsupportedVersion") { if msg, ok := data.(*ClientMessage); ok { if err := msg.CheckValid(); err != nil { @@ -377,7 +377,7 @@ func (c *TestClient) WriteJSON(data interface{}) error { return c.conn.WriteJSON(data) } -func (c *TestClient) EnsuerWriteJSON(data interface{}) { +func (c *TestClient) EnsuerWriteJSON(data any) { require.NoError(c.t, c.WriteJSON(data), "Could not write JSON %+v", data) } @@ -401,7 +401,7 @@ func (c *TestClient) SendHelloV2WithFeatures(userid string, features []string) e return c.SendHelloV2WithTimesAndFeatures(userid, now, now.Add(time.Minute), features) } -func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time.Time, expiresAt time.Time, userdata map[string]interface{}) (string, error) { +func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time.Time, expiresAt time.Time, userdata map[string]any) (string, error) { data, err := json.Marshal(userdata) if err != nil { return "", err @@ -434,7 +434,7 @@ func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time } func (c *TestClient) CreateHelloV2Token(userid string, issuedAt time.Time, expiresAt time.Time) (string, error) { - userdata := map[string]interface{}{ + userdata := map[string]any{ "displayname": "Displayname " + userid, } @@ -497,7 +497,7 @@ func (c *TestClient) SendHelloInternalWithFeatures(features []string) error { return c.SendHelloParams("", HelloVersionV1, "internal", features, params) } -func (c *TestClient) SendHelloParams(url string, version string, clientType string, features []string, params interface{}) error { +func (c *TestClient) SendHelloParams(url string, version string, clientType string, features []string, params any) error { data, err := json.Marshal(params) require.NoError(c.t, err) @@ -526,7 +526,7 @@ func (c *TestClient) SendBye() error { return c.WriteJSON(hello) } -func (c *TestClient) SendMessage(recipient MessageClientMessageRecipient, data interface{}) error { +func (c *TestClient) SendMessage(recipient MessageClientMessageRecipient, data any) error { payload, err := json.Marshal(data) require.NoError(c.t, err) @@ -541,7 +541,7 @@ func (c *TestClient) SendMessage(recipient MessageClientMessageRecipient, data i return c.WriteJSON(message) } -func (c *TestClient) SendControl(recipient MessageClientMessageRecipient, data interface{}) error { +func (c *TestClient) SendControl(recipient MessageClientMessageRecipient, data any) error { payload, err := json.Marshal(data) require.NoError(c.t, err) @@ -606,7 +606,7 @@ func (c *TestClient) SendInternalDialout(msg *DialoutInternalClientMessage) erro return c.WriteJSON(message) } -func (c *TestClient) SetTransientData(key string, value interface{}, ttl time.Duration) error { +func (c *TestClient) SetTransientData(key string, value any, ttl time.Duration) error { payload, err := json.Marshal(value) require.NoError(c.t, err) @@ -998,7 +998,7 @@ func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) error { return err } - var data map[string]interface{} + var data map[string]any if err := json.Unmarshal(message.Message.Data, &data); err != nil { return err } @@ -1007,7 +1007,7 @@ func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) error { return fmt.Errorf("expected data type offer, got %+v", data) } - payload := data["payload"].(map[string]interface{}) + payload := data["payload"].(map[string]any) if payload["type"].(string) != "offer" { return fmt.Errorf("expected payload type offer, got %+v", payload) } @@ -1042,7 +1042,7 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string } } - var data map[string]interface{} + var data map[string]any if err := json.Unmarshal(message.Message.Data, &data); err != nil { return err } @@ -1051,7 +1051,7 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string return fmt.Errorf("expected data type answer, got %+v", data) } - payload := data["payload"].(map[string]interface{}) + payload := data["payload"].(map[string]any) if payload["type"].(string) != "answer" { return fmt.Errorf("expected payload type answer, got %+v", payload) } @@ -1062,7 +1062,7 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string return nil } -func checkMessageTransientSet(message *ServerMessage, key string, value interface{}, oldValue interface{}) error { +func checkMessageTransientSet(message *ServerMessage, key string, value any, oldValue any) error { if err := checkMessageType(message, "transient"); err != nil { return err } else if message.TransientData.Type != "set" { @@ -1078,7 +1078,7 @@ func checkMessageTransientSet(message *ServerMessage, key string, value interfac return nil } -func checkMessageTransientRemove(message *ServerMessage, key string, oldValue interface{}) error { +func checkMessageTransientRemove(message *ServerMessage, key string, oldValue any) error { if err := checkMessageType(message, "transient"); err != nil { return err } else if message.TransientData.Type != "remove" { @@ -1092,7 +1092,7 @@ func checkMessageTransientRemove(message *ServerMessage, key string, oldValue in return nil } -func checkMessageTransientInitial(message *ServerMessage, data map[string]interface{}) error { +func checkMessageTransientInitial(message *ServerMessage, data map[string]any) error { if err := checkMessageType(message, "transient"); err != nil { return err } else if message.TransientData.Type != "initial" { diff --git a/transient_data.go b/transient_data.go index 57fd029..d0969bc 100644 --- a/transient_data.go +++ b/transient_data.go @@ -33,7 +33,7 @@ type TransientListener interface { type TransientData struct { mu sync.Mutex - data map[string]interface{} + data map[string]any listeners map[TransientListener]bool timers map[string]*time.Timer ttlCh chan<- struct{} @@ -51,7 +51,7 @@ func (t *TransientData) sendMessageToListener(listener TransientListener, messag listener.SendMessage(message) } -func (t *TransientData) notifySet(key string, prev, value interface{}) { +func (t *TransientData) notifySet(key string, prev, value any) { msg := &ServerMessage{ Type: "transient", TransientData: &TransientDataServerMessage{ @@ -66,7 +66,7 @@ func (t *TransientData) notifySet(key string, prev, value interface{}) { } } -func (t *TransientData) notifyDeleted(key string, prev interface{}) { +func (t *TransientData) notifyDeleted(key string, prev any) { msg := &ServerMessage{ Type: "transient", TransientData: &TransientDataServerMessage{ @@ -109,7 +109,7 @@ func (t *TransientData) RemoveListener(listener TransientListener) { delete(t.listeners, listener) } -func (t *TransientData) updateTTL(key string, value interface{}, ttl time.Duration) { +func (t *TransientData) updateTTL(key string, value any, ttl time.Duration) { if ttl <= 0 { delete(t.timers, key) } else { @@ -117,7 +117,7 @@ func (t *TransientData) updateTTL(key string, value interface{}, ttl time.Durati } } -func (t *TransientData) removeAfterTTL(key string, value interface{}, ttl time.Duration) { +func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) { if ttl <= 0 { return } @@ -144,9 +144,9 @@ func (t *TransientData) removeAfterTTL(key string, value interface{}, ttl time.D t.timers[key] = timer } -func (t *TransientData) doSet(key string, value interface{}, prev interface{}, ttl time.Duration) { +func (t *TransientData) doSet(key string, value any, prev any, ttl time.Duration) { if t.data == nil { - t.data = make(map[string]interface{}) + t.data = make(map[string]any) } t.data[key] = value t.notifySet(key, prev, value) @@ -155,13 +155,13 @@ func (t *TransientData) doSet(key string, value interface{}, prev interface{}, t // Set sets a new value for the given key and notifies listeners // if the value has been changed. -func (t *TransientData) Set(key string, value interface{}) bool { +func (t *TransientData) Set(key string, value any) bool { return t.SetTTL(key, value, 0) } // SetTTL sets a new value for the given key with a time-to-live and notifies // listeners if the value has been changed. -func (t *TransientData) SetTTL(key string, value interface{}, ttl time.Duration) bool { +func (t *TransientData) SetTTL(key string, value any, ttl time.Duration) bool { if value == nil { return t.Remove(key) } @@ -181,14 +181,14 @@ func (t *TransientData) SetTTL(key string, value interface{}, ttl time.Duration) // CompareAndSet sets a new value for the given key only for a given old value // and notifies listeners if the value has been changed. -func (t *TransientData) CompareAndSet(key string, old, value interface{}) bool { +func (t *TransientData) CompareAndSet(key string, old, value any) bool { return t.CompareAndSetTTL(key, old, value, 0) } // CompareAndSetTTL sets a new value for the given key with a time-to-live, // only for a given old value and notifies listeners if the value has been // changed. -func (t *TransientData) CompareAndSetTTL(key string, old, value interface{}, ttl time.Duration) bool { +func (t *TransientData) CompareAndSetTTL(key string, old, value any, ttl time.Duration) bool { if value == nil { return t.CompareAndRemove(key, old) } @@ -207,7 +207,7 @@ func (t *TransientData) CompareAndSetTTL(key string, old, value interface{}, ttl return true } -func (t *TransientData) doRemove(key string, prev interface{}) { +func (t *TransientData) doRemove(key string, prev any) { delete(t.data, key) if old, found := t.timers[key]; found { old.Stop() @@ -233,14 +233,14 @@ func (t *TransientData) Remove(key string) bool { // CompareAndRemove deletes the value with the given key if it has a given value // and notifies listeners if the key was removed. -func (t *TransientData) CompareAndRemove(key string, old interface{}) bool { +func (t *TransientData) CompareAndRemove(key string, old any) bool { t.mu.Lock() defer t.mu.Unlock() return t.compareAndRemove(key, old) } -func (t *TransientData) compareAndRemove(key string, old interface{}) bool { +func (t *TransientData) compareAndRemove(key string, old any) bool { prev, found := t.data[key] if !found || !reflect.DeepEqual(prev, old) { return false @@ -251,11 +251,11 @@ func (t *TransientData) compareAndRemove(key string, old interface{}) bool { } // GetData returns a copy of the internal data. -func (t *TransientData) GetData() map[string]interface{} { +func (t *TransientData) GetData() map[string]any { t.mu.Lock() defer t.mu.Unlock() - result := make(map[string]interface{}) + result := make(map[string]any) for k, v := range t.data { result[k] = v } diff --git a/transient_data_test.go b/transient_data_test.go index 08b840d..228dae1 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -216,7 +216,7 @@ func Test_TransientMessages(t *testing.T) { require.ErrorIs(err, context.DeadlineExceeded) } - data := map[string]interface{}{ + data := map[string]any{ "hello": "world", } require.NoError(client1.SetTransientData("foo", data, 0)) @@ -273,7 +273,7 @@ func Test_TransientMessages(t *testing.T) { require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } - require.NoError(checkMessageTransientInitial(msg, map[string]interface{}{ + require.NoError(checkMessageTransientInitial(msg, map[string]any{ "abc": data, })) diff --git a/virtualsession.go b/virtualsession.go index 4ed1016..4794a02 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -50,7 +50,7 @@ type VirtualSession struct { flags Flags options *AddSessionOptions - parseUserData func() (map[string]interface{}, error) + parseUserData func() (map[string]any, error) } func GetVirtualSessionId(session Session, sessionId string) string { @@ -144,7 +144,7 @@ func (s *VirtualSession) UserData() json.RawMessage { return s.userData } -func (s *VirtualSession) ParsedUserData() (map[string]interface{}, error) { +func (s *VirtualSession) ParsedUserData() (map[string]any, error) { return s.parseUserData() } @@ -290,7 +290,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { message.Message.Event.Disinvite != nil && message.Message.Event.Disinvite.RoomId == room.Id() { log.Printf("Virtual session %s was disinvited from room %s, hanging up", s.PublicId(), room.Id()) - payload := map[string]interface{}{ + payload := map[string]any{ "type": "hangup", "hangup": map[string]string{ "reason": "disinvited", From 3e6428d72f5344b69bbe6d6350a6ee25ae92212e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 30 Jul 2025 10:48:31 +0200 Subject: [PATCH 156/549] Add type for string maps. --- api_backend.go | 16 ++-- api_proxy.go | 10 +-- api_signaling.go | 67 ++++++---------- backend_server.go | 6 +- backend_server_test.go | 16 ++-- capabilities.go | 14 ++-- capabilities_test.go | 6 +- clientsession.go | 16 ++-- clientsession_test.go | 4 +- federation.go | 12 +-- federation_test.go | 24 +++--- hub.go | 10 +-- hub_test.go | 90 ++++++++++----------- janus_client.go | 12 +-- mcu_common.go | 4 +- mcu_common_test.go | 2 +- mcu_janus.go | 6 +- mcu_janus_client.go | 16 ++-- mcu_janus_publisher.go | 37 ++++----- mcu_janus_remote_publisher.go | 4 +- mcu_janus_stream_selection.go | 4 +- mcu_janus_subscriber.go | 14 ++-- mcu_janus_test.go | 146 +++++++++++++++++----------------- mcu_proxy.go | 14 +++- mcu_test.go | 10 +-- proxy/proxy_server.go | 6 +- proxy/proxy_server_test.go | 6 +- proxy/proxy_session.go | 6 +- room.go | 22 ++--- roomsessions_test.go | 2 +- session.go | 8 +- stringmap.go | 53 ++++++++++++ stringmap_test.go | 56 +++++++++++++ testclient_test.go | 69 ++++++++-------- transient_data.go | 8 +- transient_data_test.go | 16 ++-- virtualsession.go | 6 +- 37 files changed, 456 insertions(+), 362 deletions(-) create mode 100644 stringmap.go create mode 100644 stringmap_test.go diff --git a/api_backend.go b/api_backend.go index 3ed14c2..49001f7 100644 --- a/api_backend.go +++ b/api_backend.go @@ -143,15 +143,15 @@ type BackendRoomDeleteRequest struct { type BackendRoomInCallRequest struct { // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. - InCall json.RawMessage `json:"incall,omitempty"` - All bool `json:"all,omitempty"` - Changed []map[string]any `json:"changed,omitempty"` - Users []map[string]any `json:"users,omitempty"` + InCall json.RawMessage `json:"incall,omitempty"` + All bool `json:"all,omitempty"` + Changed []StringMap `json:"changed,omitempty"` + Users []StringMap `json:"users,omitempty"` } type BackendRoomParticipantsRequest struct { - Changed []map[string]any `json:"changed,omitempty"` - Users []map[string]any `json:"users,omitempty"` + Changed []StringMap `json:"changed,omitempty"` + Users []StringMap `json:"users,omitempty"` } type BackendRoomMessageRequest struct { @@ -315,8 +315,8 @@ func (r *BackendClientRoomRequest) UpdateFromSession(s Session) { if s.ClientType() == HelloClientTypeFederation { // Need to send additional data for requests of federated users. if u, err := s.ParsedUserData(); err == nil && len(u) > 0 { - if actorType, found := getStringMapEntry[string](u, "actorType"); found { - if actorId, found := getStringMapEntry[string](u, "actorId"); found { + if actorType, found := GetStringMapEntry[string](u, "actorType"); found { + if actorId, found := GetStringMapEntry[string](u, "actorId"); found { r.ActorId = actorId r.ActorType = actorType } diff --git a/api_proxy.go b/api_proxy.go index d22646a..77dca04 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -277,9 +277,9 @@ type CommandProxyServerMessage struct { type PayloadProxyClientMessage struct { Type string `json:"type"` - ClientId string `json:"clientId"` - Sid string `json:"sid,omitempty"` - Payload map[string]any `json:"payload,omitempty"` + ClientId string `json:"clientId"` + Sid string `json:"sid,omitempty"` + Payload StringMap `json:"payload,omitempty"` } func (m *PayloadProxyClientMessage) CheckValid() error { @@ -308,8 +308,8 @@ func (m *PayloadProxyClientMessage) CheckValid() error { type PayloadProxyServerMessage struct { Type string `json:"type"` - ClientId string `json:"clientId"` - Payload map[string]any `json:"payload"` + ClientId string `json:"clientId"` + Payload StringMap `json:"payload"` } // Type "event" diff --git a/api_signaling.go b/api_signaling.go index 2d24941..693261c 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -63,17 +63,6 @@ func makePtr[T any](v T) *T { return &v } -func getStringMapEntry[T any](m map[string]any, 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 @@ -728,10 +717,10 @@ type MessageClientMessage struct { } type MessageClientMessageData struct { - Type string `json:"type"` - Sid string `json:"sid"` - RoomType string `json:"roomType"` - Payload map[string]any `json:"payload"` + Type string `json:"type"` + Sid string `json:"sid"` + RoomType string `json:"roomType"` + Payload StringMap `json:"payload"` // Only supported if Type == "offer" Bitrate int `json:"bitrate,omitempty"` @@ -756,7 +745,7 @@ func (m *MessageClientMessageData) String() string { func parseSDP(s string) (*sdp.SessionDescription, error) { var sdp sdp.SessionDescription if err := sdp.UnmarshalString(s); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", map[string]any{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", StringMap{ "error": err.Error(), }) } @@ -768,7 +757,7 @@ func parseSDP(s string) (*sdp.SessionDescription, error) { } if _, err := ice.UnmarshalCandidate(a.Value); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", map[string]any{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", StringMap{ "media": m.MediaName.Media, "idx": idx, "error": err.Error(), @@ -790,11 +779,7 @@ func (m *MessageClientMessageData) CheckValid() error { } switch m.Type { case "offer", "answer": - sdpValue, found := m.Payload["sdp"] - if !found { - return ErrNoSdp - } - sdpText, ok := sdpValue.(string) + sdpText, ok := GetStringMapEntry[string](m.Payload, "sdp") if !ok { return ErrInvalidSdp } @@ -815,15 +800,11 @@ func (m *MessageClientMessageData) CheckValid() error { if !found { return ErrNoCandidate } - candItem, ok := candValue.(map[string]any) + candItem, ok := ConvertStringMap(candValue) if !ok { return ErrInvalidCandidate } - candValue, ok = candItem["candidate"] - if !ok { - return ErrInvalidCandidate - } - candText, ok := candValue.(string) + candText, ok := GetStringMapEntry[string](candItem, "candidate") if !ok { return ErrInvalidCandidate } @@ -833,7 +814,7 @@ func (m *MessageClientMessageData) CheckValid() error { } else { cand, err := ice.UnmarshalCandidate(candText) if err != nil { - return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", map[string]any{ + return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", StringMap{ "error": err.Error(), }) } @@ -1142,9 +1123,9 @@ type RoomEventServerMessage struct { RoomId string `json:"roomid"` Properties json.RawMessage `json:"properties,omitempty"` // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. - InCall json.RawMessage `json:"incall,omitempty"` - Changed []map[string]any `json:"changed,omitempty"` - Users []map[string]any `json:"users,omitempty"` + InCall json.RawMessage `json:"incall,omitempty"` + Changed []StringMap `json:"changed,omitempty"` + Users []StringMap `json:"users,omitempty"` All bool `json:"all,omitempty"` } @@ -1179,7 +1160,7 @@ type RoomFlagsServerMessage struct { Flags uint32 `json:"flags"` } -type ChatComment map[string]any +type ChatComment StringMap type RoomEventMessageDataChat struct { Comment *ChatComment `json:"comment,omitempty"` @@ -1248,12 +1229,12 @@ type EventServerMessageSwitchTo struct { // MCU-related types type AnswerOfferMessage struct { - To string `json:"to"` - From string `json:"from"` - Type string `json:"type"` - RoomType string `json:"roomType"` - Payload map[string]any `json:"payload"` - Sid string `json:"sid,omitempty"` + To string `json:"to"` + From string `json:"from"` + Type string `json:"type"` + RoomType string `json:"roomType"` + Payload StringMap `json:"payload"` + Sid string `json:"sid,omitempty"` } // Type "transient" @@ -1284,8 +1265,8 @@ func (m *TransientDataClientMessage) CheckValid() error { type TransientDataServerMessage struct { Type string `json:"type"` - Key string `json:"key,omitempty"` - OldValue any `json:"oldvalue,omitempty"` - Value any `json:"value,omitempty"` - Data map[string]any `json:"data,omitempty"` + Key string `json:"key,omitempty"` + OldValue any `json:"oldvalue,omitempty"` + Value any `json:"value,omitempty"` + Data StringMap `json:"data,omitempty"` } diff --git a/backend_server.go b/backend_server.go index ee46d3c..f39eea8 100644 --- a/backend_server.go +++ b/backend_server.go @@ -427,7 +427,7 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId return sid, nil } -func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentStringStringMap, users []map[string]any) []map[string]any { +func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentStringStringMap, users []StringMap) []StringMap { if len(users) == 0 { return users } @@ -453,7 +453,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Add(1) - go func(roomSessionId string, u map[string]any) { + go func(roomSessionId string, u StringMap) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -468,7 +468,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Wait() - result := make([]map[string]any, 0, len(users)) + result := make([]StringMap, 0, len(users)) for _, user := range users { if _, found := user["sessionId"]; found { result = append(result, user) diff --git a/backend_server_test.go b/backend_server_test.go index 1692614..7dc90ed 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -829,7 +829,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]any{ + Changed: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, @@ -839,7 +839,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, @@ -911,13 +911,13 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]any{ + Changed: []StringMap{ { "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{}, }, }, - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{}, @@ -988,7 +988,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), - Changed: []map[string]any{ + Changed: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, @@ -998,7 +998,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { "inCall": 3, }, }, - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, @@ -1035,7 +1035,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), - Changed: []map[string]any{ + Changed: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, @@ -1045,7 +1045,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { "inCall": 3, }, }, - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, diff --git a/capabilities.go b/capabilities.go index c89f1fd..4cc8d57 100644 --- a/capabilities.go +++ b/capabilities.go @@ -64,7 +64,7 @@ type capabilitiesEntry struct { nextUpdate time.Time etag string mustRevalidate bool - capabilities map[string]any + capabilities StringMap } func newCapabilitiesEntry(c *Capabilities) *capabilitiesEntry { @@ -211,7 +211,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return false, nil } - var capa map[string]any + var capa StringMap if err := json.Unmarshal(capaObj, &capa); err != nil { log.Printf("Unsupported capabilities received for app spreed from %s: %+v", url, capaResponse) e.capabilities = nil @@ -223,7 +223,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return true, nil } -func (e *capabilitiesEntry) GetCapabilities() map[string]any { +func (e *capabilitiesEntry) GetCapabilities() StringMap { e.mu.RLock() defer e.mu.RUnlock() @@ -322,7 +322,7 @@ func (c *Capabilities) getKeyForUrl(u *url.URL) string { return key } -func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (map[string]any, bool, error) { +func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (StringMap, bool, error) { key := c.getKeyForUrl(u) entry, valid := c.getCapabilities(key) if valid { @@ -363,7 +363,7 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea return false } -func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (map[string]any, bool, bool) { +func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (StringMap, bool, bool) { caps, cached, err := c.loadCapabilities(ctx, u) if err != nil { log.Printf("Could not get capabilities for %s: %s", u, err) @@ -375,7 +375,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str return nil, cached, false } - config, ok := configInterface.(map[string]any) + config, ok := ConvertStringMap(configInterface) if !ok { log.Printf("Invalid config mapping received from %s: %+v", u, configInterface) return nil, cached, false @@ -386,7 +386,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str return nil, cached, false } - groupConfig, ok := groupInterface.(map[string]any) + groupConfig, ok := ConvertStringMap(groupInterface) if !ok { log.Printf("Invalid group mapping \"%s\" received from %s: %+v", group, u, groupInterface) return nil, cached, false diff --git a/capabilities_test.go b/capabilities_test.go index 69cf6fc..02e7f21 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -66,14 +66,14 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie if strings.Contains(t.Name(), "V3Api") { features = append(features, "signaling-v3") } - signaling := map[string]any{ + signaling := StringMap{ "foo": "bar", "baz": 42, } - config := map[string]any{ + config := StringMap{ "signaling": signaling, } - spreedCapa, _ := json.Marshal(map[string]any{ + spreedCapa, _ := json.Marshal(StringMap{ "features": features, "config": config, }) diff --git a/clientsession.go b/clientsession.go index 426335f..819c074 100644 --- a/clientsession.go +++ b/clientsession.go @@ -65,7 +65,7 @@ type ClientSession struct { userId string userData json.RawMessage - parseUserData func() (map[string]any, error) + parseUserData func() (StringMap, error) inCall Flags supportsPermissions bool @@ -315,7 +315,7 @@ func (s *ClientSession) UserData() json.RawMessage { return s.userData } -func (s *ClientSession) ParsedUserData() (map[string]any, error) { +func (s *ClientSession) ParsedUserData() (StringMap, error) { return s.parseUserData() } @@ -550,7 +550,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) request.Room.Action = "leave" - var response map[string]any + var response StringMap if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { log.Printf("Could not notify about room session %s left room %s: %s", sid, room.Id(), err) } else { @@ -614,7 +614,7 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { return prev } -func (s *ClientSession) sendOffer(client McuClient, sender string, streamType StreamType, offer map[string]any) { +func (s *ClientSession) sendOffer(client McuClient, sender string, streamType StreamType, offer StringMap) { offer_message := &AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -648,7 +648,7 @@ func (s *ClientSession) sendCandidate(client McuClient, sender string, streamTyp From: sender, Type: "candidate", RoomType: string(streamType), - Payload: map[string]any{ + Payload: StringMap{ "candidate": candidate, }, Sid: client.Sid(), @@ -713,7 +713,7 @@ func (s *ClientSession) SendMessages(messages []*ServerMessage) bool { return true } -func (s *ClientSession) OnUpdateOffer(client McuClient, offer map[string]any) { +func (s *ClientSession) OnUpdateOffer(client McuClient, offer StringMap) { s.mu.Lock() defer s.mu.Unlock() @@ -1108,7 +1108,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { return } - mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response map[string]any) { + mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response StringMap) { if err != nil { log.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ @@ -1171,7 +1171,7 @@ func filterDisplayNames(events []*EventServerMessageSessionEntry) []*EventServer continue } - var userdata map[string]any + var userdata StringMap if err := json.Unmarshal(event.User, &userdata); err != nil { result = append(result, event) continue diff --git a/clientsession_test.go b/clientsession_test.go index de034a8..80fdbd6 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -171,7 +171,7 @@ func TestBandwidth_Client(t *testing.T) { Sid: "54321", RoomType: "video", Bitrate: bitrate, - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -245,7 +245,7 @@ func TestBandwidth_Backend(t *testing.T) { Sid: "54321", RoomType: string(streamType), Bitrate: bitrate, - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) diff --git a/federation.go b/federation.go index c1527cd..8df95a3 100644 --- a/federation.go +++ b/federation.go @@ -603,14 +603,14 @@ func (c *FederationClient) joinRoom() error { }) } -func (c *FederationClient) updateEventUsers(users []map[string]any, localSessionId string, remoteSessionId string) { +func (c *FederationClient) updateEventUsers(users []StringMap, 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 { + 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) { @@ -626,10 +626,10 @@ func (c *FederationClient) updateEventUsers(users []map[string]any, localSession if checkSessionId { key := "sessionId" - sid, found := getStringMapEntry[string](u, key) + sid, found := GetStringMapEntry[string](u, key) if !found { key := "sessionid" - sid, found = getStringMapEntry[string](u, key) + sid, found = GetStringMapEntry[string](u, key) } if found && sid == remoteSessionId { u[key] = localSessionId @@ -668,7 +668,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { 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]any + var data StringMap 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 { diff --git a/federation_test.go b/federation_test.go index 51d4c26..a7bf98f 100644 --- a/federation_test.go +++ b/federation_test.go @@ -111,7 +111,7 @@ func Test_Federation(t *testing.T) { require.NotNil(room) now := time.Now() - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -311,7 +311,7 @@ func Test_Federation(t *testing.T) { } // Special handling for the "forceMute" control event. - forceMute := map[string]any{ + forceMute := StringMap{ "action": "forceMute", "peerId": remoteSessionId, } @@ -319,7 +319,7 @@ func Test_Federation(t *testing.T) { Type: "session", SessionId: remoteSessionId, }, forceMute)) { - var payload map[string]any + var payload StringMap 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 @@ -359,7 +359,7 @@ func Test_Federation(t *testing.T) { } // Simulate request from the backend that a federated user joined the call. - users := []map[string]any{ + users := []StringMap{ { "sessionId": remoteSessionId, "inCall": 1, @@ -383,7 +383,7 @@ func Test_Federation(t *testing.T) { assert.Equal(federatedRoomId, event.Update.RoomId) // Simulate request from the backend that a local user joined the call. - users = []map[string]any{ + users = []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -435,7 +435,7 @@ func Test_Federation(t *testing.T) { hello4, err := client4.RunUntilHello(ctx) require.NoError(err) - userdata = map[string]any{ + userdata = StringMap{ "displayname": "Federated user 2", "actorType": "federated_users", "actorId": "the-other-federated-user-id", @@ -532,7 +532,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -643,7 +643,7 @@ func Test_FederationChangeRoom(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -770,7 +770,7 @@ func Test_FederationMedia(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -821,7 +821,7 @@ func Test_FederationMedia(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "screen", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -867,7 +867,7 @@ func Test_FederationResume(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -1000,7 +1000,7 @@ func Test_FederationResumeNewSession(t *testing.T) { assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) now := time.Now() - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", diff --git a/hub.go b/hub.go index b446a23..fcd227e 100644 --- a/hub.go +++ b/hub.go @@ -2155,7 +2155,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { return } - mc.SendMessage(session.Context(), msg, clientData, func(err error, response map[string]any) { + mc.SendMessage(session.Context(), msg, clientData, func(err error, response StringMap) { if err != nil { log.Printf("Could not send MCU message %+v for session %s to %s: %s", clientData, session.PublicId(), recipient.PublicId(), err) sendMcuProcessingFailed(session, message) @@ -2749,7 +2749,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe return } - mc.SendMessage(session.Context(), message, data, func(err error, response map[string]any) { + mc.SendMessage(session.Context(), message, data, func(err error, response StringMap) { if err != nil { if !errors.Is(err, ErrCandidateFiltered) { log.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) @@ -2765,7 +2765,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe }) } -func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response map[string]any) { +func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response StringMap) { var response_message *ServerMessage switch response["type"] { case "answer": @@ -2880,8 +2880,8 @@ func (h *Hub) processRoomParticipants(message *BackendServerRoomRequest) { room.PublishUsersChanged(message.Participants.Changed, message.Participants.Users) } -func (h *Hub) GetStats() map[string]any { - result := make(map[string]any) +func (h *Hub) GetStats() StringMap { + result := make(StringMap) h.ru.RLock() result["rooms"] = len(h.rooms) h.ru.RUnlock() diff --git a/hub_test.go b/hub_test.go index 0353fd7..3e79561 100644 --- a/hub_test.go +++ b/hub_test.go @@ -698,11 +698,11 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "Federation") { features = append(features, "federation-v2") } - signaling := map[string]any{ + signaling := StringMap{ "foo": "bar", "baz": 42, } - config := map[string]any{ + config := StringMap{ "signaling": signaling, } if strings.Contains(t.Name(), "MultiRoom") { @@ -734,7 +734,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { signaling[ConfigKeyHelloV2TokenKey] = string(public) } } - spreedCapa, _ := json.Marshal(map[string]any{ + spreedCapa, _ := json.Marshal(StringMap{ "features": features, "config": config, }) @@ -1885,7 +1885,7 @@ func TestClientHelloResumeProxy(t *testing.T) { room2 := hub2.getRoom(roomId) require.Nil(room2, "Should not have gotten room %s", roomId) - users := []map[string]any{ + users := []StringMap{ { "sessionId": "the-session-id", "inCall": 1, @@ -2137,7 +2137,7 @@ func TestClientMessageToSessionId(t *testing.T) { SessionId: hello2.Hello.SessionId, } - data1 := map[string]any{ + data1 := StringMap{ "type": "test", "message": "from-1-to-2", } @@ -2149,7 +2149,7 @@ func TestClientMessageToSessionId(t *testing.T) { if err := checkReceiveClientMessage(ctx, client1, "session", hello2.Hello, &payload1); assert.NoError(err) { assert.Equal(data2, payload1) } - var payload2 map[string]any + var payload2 StringMap if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload2); assert.NoError(err) { assert.Equal(data1, payload2) } @@ -2653,7 +2653,7 @@ func TestClientMessageToCall(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []map[string]any{ + users := []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2690,7 +2690,7 @@ func TestClientMessageToCall(t *testing.T) { } // Simulate request from the backend that somebody joined the call. - users = []map[string]any{ + users = []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2774,7 +2774,7 @@ func TestClientControlToCall(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []map[string]any{ + users := []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2811,7 +2811,7 @@ func TestClientControlToCall(t *testing.T) { } // Simulate request from the backend that somebody joined the call. - users = []map[string]any{ + users = []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -3583,7 +3583,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { // The two chat messages should get combined into one when receiving pending messages. chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" - var data1 map[string]any + var data1 StringMap require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint client1.SendMessage(recipient2, data1) // nolint @@ -3600,7 +3600,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { assert.Equal(hello2.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) } - var payload map[string]any + var payload StringMap if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { assert.Equal(data1, payload) } @@ -3655,7 +3655,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []map[string]any{ + users := []StringMap{ { "sessionId": "the-session-id", "inCall": 1, @@ -3680,7 +3680,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { } chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" - var data1 map[string]any + var data1 StringMap require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint @@ -3697,7 +3697,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { // TODO(jojo): Check contents of message and try with multiple users. assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) - var payload map[string]any + var payload StringMap if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { assert.Equal(data1, payload) } @@ -3907,7 +3907,7 @@ func TestClientSendOfferPermissions(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "screen", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3985,7 +3985,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4002,7 +4002,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioOnly, }, })) @@ -4056,7 +4056,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4067,13 +4067,13 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]any{ + Changed: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, @@ -4162,7 +4162,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4173,13 +4173,13 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []map[string]any{ + Changed: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, @@ -4286,7 +4286,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "screen", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4330,7 +4330,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.NoError(checkMessageError(msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. - users1 := []map[string]any{ + users1 := []StringMap{ { "sessionId": hello2.Hello.SessionId, "inCall": 1, @@ -4357,7 +4357,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.NoError(checkMessageError(msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. - users2 := []map[string]any{ + users2 := []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -4388,7 +4388,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { Type: "answer", Sid: "12345", RoomType: "screen", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpAnswerAudioAndVideo, }, })) @@ -4746,7 +4746,7 @@ func TestClientSendOffer(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4820,7 +4820,7 @@ func TestClientUnshareScreen(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "screen", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioOnly, }, })) @@ -5273,7 +5273,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: []byte("0"), - Users: []map[string]any{ + Users: []StringMap{ { "sessionId": virtualSession.PublicId(), "participantPermissions": 246, @@ -5387,7 +5387,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } -func DoTestSwitchToOne(t *testing.T, details map[string]any) { +func DoTestSwitchToOne(t *testing.T, details StringMap) { CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { @@ -5440,7 +5440,7 @@ func DoTestSwitchToOne(t *testing.T, details map[string]any) { roomId2 := "test-room-2" var sessions json.RawMessage if details != nil { - sessions, err = json.Marshal(map[string]any{ + sessions, err = json.Marshal(StringMap{ roomSessionId1: details, }) require.NoError(err) @@ -5491,7 +5491,7 @@ func DoTestSwitchToOne(t *testing.T, details map[string]any) { } func TestSwitchToOneMap(t *testing.T) { - DoTestSwitchToOne(t, map[string]any{ + DoTestSwitchToOne(t, StringMap{ "foo": "bar", }) } @@ -5500,7 +5500,7 @@ func TestSwitchToOneList(t *testing.T) { DoTestSwitchToOne(t, nil) } -func DoTestSwitchToMultiple(t *testing.T, details1 map[string]any, details2 map[string]any) { +func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap) { CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { @@ -5553,7 +5553,7 @@ func DoTestSwitchToMultiple(t *testing.T, details1 map[string]any, details2 map[ roomId2 := "test-room-2" var sessions json.RawMessage if details1 != nil || details2 != nil { - sessions, err = json.Marshal(map[string]any{ + sessions, err = json.Marshal(StringMap{ roomSessionId1: details1, roomSessionId2: details2, }) @@ -5603,9 +5603,9 @@ func DoTestSwitchToMultiple(t *testing.T, details1 map[string]any, details2 map[ } func TestSwitchToMultipleMap(t *testing.T) { - DoTestSwitchToMultiple(t, map[string]any{ + DoTestSwitchToMultiple(t, StringMap{ "foo": "bar", - }, map[string]any{ + }, StringMap{ "bar": "baz", }) } @@ -5615,7 +5615,7 @@ func TestSwitchToMultipleList(t *testing.T) { } func TestSwitchToMultipleMixed(t *testing.T) { - DoTestSwitchToMultiple(t, map[string]any{ + DoTestSwitchToMultiple(t, StringMap{ "foo": "bar", }, nil) } @@ -5746,7 +5746,7 @@ func TestDialoutStatus(t *testing.T) { key := "callstatus_" + callId if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(msg, key, map[string]any{ + assert.NoError(checkMessageTransientSet(t, msg, key, StringMap{ "callid": callId, "status": "accepted", }, nil)) @@ -5762,10 +5762,10 @@ func TestDialoutStatus(t *testing.T) { })) if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(msg, key, map[string]any{ + assert.NoError(checkMessageTransientSet(t, msg, key, StringMap{ "callid": callId, "status": "ringing", - }, map[string]any{ + }, StringMap{ "callid": callId, "status": "accepted", })) @@ -5789,11 +5789,11 @@ func TestDialoutStatus(t *testing.T) { })) if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(msg, key, map[string]any{ + assert.NoError(checkMessageTransientSet(t, msg, key, StringMap{ "callid": callId, "status": "cleared", "cause": clearedCause, - }, map[string]any{ + }, StringMap{ "callid": callId, "status": "ringing", })) @@ -5803,7 +5803,7 @@ func TestDialoutStatus(t *testing.T) { defer cancel() if msg, err := client.RunUntilMessage(ctx2); assert.NoError(err) { - assert.NoError(checkMessageTransientRemove(msg, key, map[string]any{ + assert.NoError(checkMessageTransientRemove(t, msg, key, StringMap{ "callid": callId, "status": "cleared", "cause": clearedCause, diff --git a/janus_client.go b/janus_client.go index 10d48c5..fbc568a 100644 --- a/janus_client.go +++ b/janus_client.go @@ -204,8 +204,8 @@ func newTransaction() *transaction { return t } -func newRequest(method string) (map[string]any, *transaction) { - req := make(map[string]any, 8) +func newRequest(method string) (StringMap, *transaction) { + req := make(StringMap, 8) req["janus"] = method return req, newTransaction() } @@ -225,7 +225,7 @@ type JanusGatewayInterface interface { Create(context.Context) (*JanusSession, error) Close() error - send(map[string]any, *transaction) (uint64, error) + send(StringMap, *transaction) (uint64, error) removeTransaction(uint64) removeSession(*JanusSession) @@ -338,7 +338,7 @@ func (gateway *JanusGateway) removeTransaction(id uint64) { } } -func (gateway *JanusGateway) send(msg map[string]any, t *transaction) (uint64, error) { +func (gateway *JanusGateway) send(msg StringMap, t *transaction) (uint64, error) { id := gateway.nextTransaction.Add(1) msg["transaction"] = strconv.FormatUint(id, 10) data, err := json.Marshal(msg) @@ -599,7 +599,7 @@ type JanusSession struct { gateway JanusGatewayInterface } -func (session *JanusSession) send(msg map[string]any, t *transaction) (uint64, error) { +func (session *JanusSession) send(msg StringMap, t *transaction) (uint64, error) { msg["session_id"] = session.Id return session.gateway.send(msg, t) } @@ -711,7 +711,7 @@ type JanusHandle struct { session *JanusSession } -func (handle *JanusHandle) send(msg map[string]any, t *transaction) (uint64, error) { +func (handle *JanusHandle) send(msg StringMap, t *transaction) (uint64, error) { msg["handle_id"] = handle.Id return handle.session.send(msg, t) } diff --git a/mcu_common.go b/mcu_common.go index 65cdfef..9c1e7f5 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -56,7 +56,7 @@ const ( type McuListener interface { PublicId() string - OnUpdateOffer(client McuClient, offer map[string]any) + OnUpdateOffer(client McuClient, offer StringMap) OnIceCandidate(client McuClient, candidate any) OnIceCompleted(client McuClient) @@ -205,7 +205,7 @@ type McuClient interface { Close(ctx context.Context) - SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) + SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) } type McuPublisher interface { diff --git a/mcu_common_test.go b/mcu_common_test.go index 285dd94..c1facd2 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -37,7 +37,7 @@ func (m *MockMcuListener) PublicId() string { return m.publicId } -func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer map[string]any) { +func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer StringMap) { } diff --git a/mcu_janus.go b/mcu_janus.go index 5a49314..1383fcd 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -568,7 +568,7 @@ func (m *mcuJanus) SubscriberDisconnected(id string, publisher string, streamTyp } func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id string, streamType StreamType, settings NewPublisherSettings) (uint64, int, error) { - create_msg := map[string]any{ + create_msg := StringMap{ "request": "create", "description": getStreamId(id, streamType), // We publish every stream in its own Janus room. @@ -642,7 +642,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id string, st return nil, 0, 0, 0, err } - msg := map[string]any{ + msg := StringMap{ "request": "join", "ptype": "publisher", "room": roomId, @@ -834,7 +834,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re return nil, err } - response, err := handle.Request(ctx, map[string]any{ + response, err := handle.Request(ctx, StringMap{ "request": "add_remote_publisher", "room": roomId, "id": streamTypeUserIds[streamType], diff --git a/mcu_janus_client.go b/mcu_janus_client.go index cceb046..8d7ae8f 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -75,7 +75,7 @@ func (c *mcuJanusClient) MaxBitrate() int { func (c *mcuJanusClient) Close(ctx context.Context) { } -func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { } func (c *mcuJanusClient) closeClient(ctx context.Context) bool { @@ -124,14 +124,14 @@ loop: } } -func (c *mcuJanusClient) sendOffer(ctx context.Context, offer map[string]any, callback func(error, map[string]any)) { +func (c *mcuJanusClient) sendOffer(ctx context.Context, offer StringMap, callback func(error, StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) return } - configure_msg := map[string]any{ + configure_msg := StringMap{ "request": "configure", "audio": true, "video": true, @@ -146,14 +146,14 @@ func (c *mcuJanusClient) sendOffer(ctx context.Context, offer map[string]any, ca callback(nil, answer_msg.Jsep) } -func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer map[string]any, callback func(error, map[string]any)) { +func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer StringMap, callback func(error, StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) return } - start_msg := map[string]any{ + start_msg := StringMap{ "request": "start", "room": c.roomId, } @@ -166,7 +166,7 @@ func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer map[string]any, callback(nil, nil) } -func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, map[string]any)) { +func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) @@ -188,7 +188,7 @@ func (c *mcuJanusClient) handleTrickle(event *TrickleMsg) { } } -func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, map[string]any)) { +func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) @@ -200,7 +200,7 @@ func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelecti return } - configure_msg := map[string]any{ + configure_msg := StringMap{ "request": "configure", } if stream != nil { diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 1e330f6..283d274 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -139,7 +139,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { notify := false p.mu.Lock() if handle := p.handle; handle != nil && p.roomId != 0 { - destroy_msg := map[string]any{ + destroy_msg := StringMap{ "request": "destroy", "room": p.roomId, } @@ -167,7 +167,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { @@ -201,30 +201,25 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli msgctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() - p.sendOffer(msgctx, jsep_msg, func(err error, jsep map[string]any) { + p.sendOffer(msgctx, jsep_msg, func(err error, jsep StringMap) { if err != nil { callback(err, jsep) return } - sdpData, found := jsep["sdp"] + sdpString, found := GetStringMapEntry[string](jsep, "sdp") if !found { - log.Printf("No sdp found in answer %+v", jsep) + log.Printf("No/invalid sdp found in answer %+v", jsep) + } else if answerSdp, err := parseSDP(sdpString); err != nil { + log.Printf("Error parsing answer sdp %+v: %s", sdpString, err) + p.answerSdp.Store(nil) + p.sdpFlags.Remove(sdpHasAnswer) } else { - sdpString, ok := sdpData.(string) - if !ok { - log.Printf("Invalid sdp found in answer %+v", jsep) - } else if answerSdp, err := parseSDP(sdpString); err != nil { - log.Printf("Error parsing answer sdp %+v: %s", sdpString, err) - p.answerSdp.Store(nil) - p.sdpFlags.Remove(sdpHasAnswer) - } else { - // Note: we don't need to filter the SDP received from Janus. - p.answerSdp.Store(answerSdp) - p.sdpFlags.Add(sdpHasAnswer) - if p.sdpFlags.Get() == sdpHasAnswer|sdpHasOffer { - p.sdpReady.Close() - } + // Note: we don't need to filter the SDP received from Janus. + p.answerSdp.Store(answerSdp) + p.sdpFlags.Add(sdpHasAnswer) + if p.sdpFlags.Get() == sdpHasAnswer|sdpHasOffer { + p.sdpReady.Close() } } @@ -403,7 +398,7 @@ func getPublisherRemoteId(id string, remoteId string, hostname string, port int, } func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { - msg := map[string]any{ + msg := StringMap{ "request": "publish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], @@ -440,7 +435,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId string, } func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { - msg := map[string]any{ + msg := StringMap{ "request": "unpublish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], diff --git a/mcu_janus_remote_publisher.go b/mcu_janus_remote_publisher.go index f622efa..8c0b9e1 100644 --- a/mcu_janus_remote_publisher.go +++ b/mcu_janus_remote_publisher.go @@ -124,7 +124,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { p.mu.Lock() if handle := p.handle; handle != nil { - response, err := p.handle.Request(ctx, map[string]any{ + response, err := p.handle.Request(ctx, StringMap{ "request": "remove_remote_publisher", "room": p.roomId, "id": streamTypeUserIds[p.streamType], @@ -135,7 +135,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { log.Printf("Removed remote publisher: %+v", response) } if p.roomId != 0 { - destroy_msg := map[string]any{ + destroy_msg := StringMap{ "request": "destroy", "room": p.roomId, } diff --git a/mcu_janus_stream_selection.go b/mcu_janus_stream_selection.go index 3d31bb4..2787d85 100644 --- a/mcu_janus_stream_selection.go +++ b/mcu_janus_stream_selection.go @@ -37,7 +37,7 @@ func (s *streamSelection) HasValues() bool { return s.substream.Valid || s.temporal.Valid || s.audio.Valid || s.video.Valid } -func (s *streamSelection) AddToMessage(message map[string]any) { +func (s *streamSelection) AddToMessage(message StringMap) { if s.substream.Valid { message["substream"] = s.substream.Int16 } @@ -52,7 +52,7 @@ func (s *streamSelection) AddToMessage(message map[string]any) { } } -func parseStreamSelection(payload map[string]any) (*streamSelection, error) { +func parseStreamSelection(payload StringMap) (*streamSelection, error) { var stream streamSelection if value, found := payload["substream"]; found { switch value := value.(type) { diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 6c75788..4afe0dd 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -55,7 +55,7 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { } for _, stream := range streams { - if stream, ok := stream.(map[string]any); ok { + if stream, ok := ConvertStringMap(stream); ok { if (stream["type"] == "audio" || stream["type"] == "video") && stream["active"] != false { return } @@ -149,7 +149,7 @@ func (p *mcuJanusSubscriber) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, map[string]any)) { +func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, StringMap)) { handle := p.handle if handle == nil { callback(ErrNotConnected, nil) @@ -161,13 +161,13 @@ func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelecti loggedNotPublishingYet := false retry: - join_msg := map[string]any{ + join_msg := StringMap{ "request": "join", "ptype": "subscriber", "room": p.roomId, } if p.mcu.isMultistream() { - join_msg["streams"] = []map[string]any{ + join_msg["streams"] = []StringMap{ { "feed": streamTypeUserIds[p.streamType], }, @@ -255,14 +255,14 @@ retry: callback(nil, join_response.Jsep) } -func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, map[string]any)) { +func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, StringMap)) { handle := p.handle if handle == nil { callback(ErrNotConnected, nil) return } - configure_msg := map[string]any{ + configure_msg := StringMap{ "request": "configure", "update": true, } @@ -278,7 +278,7 @@ func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection callback(nil, configure_response.Jsep) } -func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 8d25f64..83c569b 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -48,7 +48,7 @@ type TestJanusRoom struct { publisher atomic.Pointer[TestJanusHandle] } -type TestJanusHandler func(room *TestJanusRoom, body map[string]any, jsep map[string]any) (any, *janus.ErrorMsg) +type TestJanusHandler func(room *TestJanusRoom, body StringMap, jsep StringMap) (any, *janus.ErrorMsg) type TestJanusGateway struct { t *testing.T @@ -141,7 +141,7 @@ func (g *TestJanusGateway) Close() error { return nil } -func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body map[string]any, jsep map[string]any) any { +func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body StringMap, jsep StringMap) any { request := body["request"].(string) switch request { case "create": @@ -153,7 +153,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "room": room.id, }, }, @@ -181,7 +181,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "error_code": error_code, }, }, @@ -192,7 +192,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, }, }, @@ -216,7 +216,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan Handle: handle.id, Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "room": room.id, }, }, @@ -227,7 +227,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED, }, }, @@ -236,7 +236,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan sdp := publisher.sdp.Load() return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "type": "offer", "sdp": sdp.(string), }, @@ -265,7 +265,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{}, + Data: StringMap{}, }, } default: @@ -331,7 +331,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return nil } -func (g *TestJanusGateway) processRequest(msg map[string]any) any { +func (g *TestJanusGateway) processRequest(msg StringMap) any { method, found := msg["janus"] if !found { return nil @@ -408,9 +408,12 @@ func (g *TestJanusGateway) processRequest(msg map[string]any) any { var result any switch method { case "message": - body := msg["body"].(map[string]any) - if jsep, found := msg["jsep"]; found { - result = g.processMessage(session, handle, body, jsep.(map[string]any)) + body, ok := ConvertStringMap(msg["body"]) + assert.True(g.t, ok, "not a string map: %+v", msg["body"]) + if jsepOb, found := msg["jsep"]; found { + if jsep, ok := ConvertStringMap(jsepOb); assert.True(g.t, ok, "not a string map: %+v", jsepOb) { + result = g.processMessage(session, handle, body, jsep) + } } else { result = g.processMessage(session, handle, body, nil) } @@ -449,7 +452,7 @@ func (g *TestJanusGateway) processRequest(msg map[string]any) any { return nil } -func (g *TestJanusGateway) send(msg map[string]any, t *transaction) (uint64, error) { +func (g *TestJanusGateway) send(msg StringMap, t *transaction) (uint64, error) { tid := g.tid.Add(1) data, err := json.Marshal(msg) @@ -521,7 +524,7 @@ func (t *TestMcuListener) PublicId() string { return t.id } -func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer map[string]any) { +func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer StringMap) { } @@ -591,7 +594,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { // The SDP received by Janus will be filtered from blocked candidates. @@ -604,12 +607,12 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "sdp": MockSdpAnswerAudioOnly, }, }, nil }, - "trickle": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "trickle": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.AckMsg{}, nil }, @@ -635,7 +638,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { // Send offer containing candidates that will be blocked / filtered. data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioOnly, }, } @@ -643,7 +646,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer wg.Done() if assert.NoError(err) { @@ -659,15 +662,15 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]any{ - "candidate": map[string]any{ + Payload: StringMap{ + "candidate": StringMap{ "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -677,15 +680,15 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]any{ - "candidate": map[string]any{ + Payload: StringMap{ + "candidate": StringMap{ "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer wg.Done() assert.NoError(err) @@ -702,7 +705,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "start": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "start": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { // The SDP received by Janus will be filtered from blocked candidates. @@ -717,7 +720,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "room": room.id, "started": true, "videoroom": "event", @@ -725,7 +728,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { }, }, nil }, - "trickle": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "trickle": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.AckMsg{}, nil }, @@ -762,7 +765,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { // Send answer containing candidates that will be blocked / filtered. data := &MessageClientMessageData{ Type: "answer", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpAnswerAudioOnly, }, } @@ -770,7 +773,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer wg.Done() if assert.NoError(err) { @@ -781,15 +784,15 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]any{ - "candidate": map[string]any{ + Payload: StringMap{ + "candidate": StringMap{ "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -799,15 +802,15 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: map[string]any{ - "candidate": map[string]any{ + Payload: StringMap{ + "candidate": StringMap{ "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer wg.Done() assert.NoError(err) @@ -824,7 +827,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { if sdpValue, found := jsep["sdp"]; assert.True(found) { @@ -836,7 +839,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "sdp": MockSdpAnswerAudioOnly, }, }, nil @@ -862,14 +865,14 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioOnly, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer close(done) if assert.NoError(err) { @@ -906,7 +909,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { _, found := jsep["sdp"] @@ -914,7 +917,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "sdp": MockSdpAnswerAudioAndVideo, }, }, nil @@ -940,7 +943,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, } @@ -949,7 +952,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { // Defer sending of offer / answer so "GetStreams" will wait. go func() { done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer close(done) if assert.NoError(err) { @@ -1083,7 +1086,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { if sdp, found := jsep["sdp"]; assert.True(found) { @@ -1092,7 +1095,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { } return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "sdp": MockSdpAnswerAudioAndVideo, }, }, nil @@ -1130,14 +1133,14 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { go func() { data := &MessageClientMessageData{ Type: "offer", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer close(done) if assert.NoError(err) { @@ -1158,7 +1161,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { require.NoError(data.CheckValid()) done := make(chan struct{}) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m map[string]any) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { defer close(done) if assert.NoError(err) { @@ -1186,21 +1189,22 @@ func Test_JanusRemotePublisher(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "add_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "add_remote_publisher": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) assert.Nil(jsep) if streams := body["streams"].([]any); assert.Len(streams, 1) { - stream := streams[0].(map[string]any) - assert.Equal("0", stream["mid"]) - assert.EqualValues(0, stream["mindex"]) - assert.Equal("audio", stream["type"]) - assert.Equal("opus", stream["codec"]) + if stream, ok := ConvertStringMap(streams[0]); assert.True(ok, "not a string map: %+v", streams[0]) { + assert.Equal("0", stream["mid"]) + assert.EqualValues(0, stream["mindex"]) + assert.Equal("audio", stream["type"]) + assert.Equal("opus", stream["codec"]) + } } added.Add(1) return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{ + Data: StringMap{ "id": 12345, "port": 10000, "rtcp_port": 10001, @@ -1208,14 +1212,14 @@ func Test_JanusRemotePublisher(t *testing.T) { }, }, nil }, - "remove_remote_publisher": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "remove_remote_publisher": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) assert.Nil(jsep) removed.Add(1) return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: map[string]any{}, + Data: StringMap{}, }, }, nil }, @@ -1269,10 +1273,10 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1317,7 +1321,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []map[string]any{ + users1 := []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1339,7 +1343,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -1378,10 +1382,10 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1426,7 +1430,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []map[string]any{ + users1 := []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1448,7 +1452,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -1497,10 +1501,10 @@ func Test_JanusSubscriberTimeout(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep map[string]any) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: map[string]any{ + Jsep: StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1545,7 +1549,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []map[string]any{ + users1 := []StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1567,7 +1571,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: map[string]any{ + Payload: StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) diff --git a/mcu_proxy.go b/mcu_proxy.go index f1b94f7..aea111c 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -101,7 +101,7 @@ func (c *mcuProxyPubSubCommon) MaxBitrate() int { return c.maxBitrate } -func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, map[string]any)) { +func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, StringMap)) { c.conn.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { if err != nil { callback(err, nil) @@ -124,7 +124,13 @@ func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClie func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadProxyServerMessage) { switch msg.Type { case "offer": - c.listener.OnUpdateOffer(client, msg.Payload["offer"].(map[string]any)) + offer, ok := ConvertStringMap(msg.Payload["offer"]) + if !ok { + log.Printf("Unsupported payload from %s: %+v", c.conn, msg) + return + } + + c.listener.OnUpdateOffer(client, offer) case "candidate": c.listener.OnIceCandidate(client, msg.Payload["candidate"]) default: @@ -195,7 +201,7 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { log.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) } -func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ @@ -307,7 +313,7 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { } } -func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ diff --git a/mcu_test.go b/mcu_test.go index a149ac5..2dcf9ef 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -196,7 +196,7 @@ func (p *TestMCUPublisher) SetMedia(mt MediaType) { p.settings.MediaTypes = mt } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { go func() { if p.isClosed() { callback(fmt.Errorf("Already closed"), nil) @@ -210,13 +210,13 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClie p.sdp = sdp switch sdp { case MockSdpOfferAudioOnly: - callback(nil, map[string]any{ + callback(nil, StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioOnly, }) return case MockSdpOfferAudioAndVideo: - callback(nil, map[string]any{ + callback(nil, StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }) @@ -252,7 +252,7 @@ func (s *TestMCUSubscriber) Publisher() string { return s.publisher.id } -func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, map[string]any)) { +func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { go func() { if s.isClosed() { callback(fmt.Errorf("Already closed"), nil) @@ -269,7 +269,7 @@ func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageCli return } - callback(nil, map[string]any{ + callback(nil, StringMap{ "type": "offer", "sdp": sdp, }) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 9feca63..17a8bae 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -1344,7 +1344,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s ctx2, cancel := context.WithTimeout(ctx, s.mcuTimeout) defer cancel() - mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response map[string]any) { + mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response signaling.StringMap) { var responseMsg *signaling.ProxyServerMessage if errors.Is(err, signaling.ErrCandidateFiltered) { // Silently ignore filtered candidates. @@ -1586,8 +1586,8 @@ func (s *ProxyServer) GetClientId(client signaling.McuClient) string { return s.clientIds[client.Id()] } -func (s *ProxyServer) getStats() map[string]any { - result := map[string]any{ +func (s *ProxyServer) getStats() signaling.StringMap { + result := signaling.StringMap{ "sessions": s.GetSessionsCount(), "load": s.load.Load(), "mcu": s.mcu.GetStats(), diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 17b3b69..95dba73 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -420,7 +420,7 @@ func (p *TestMCUPublisher) MaxBitrate() int { func (p *TestMCUPublisher) Close(ctx context.Context) { } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]any)) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, signaling.StringMap)) { callback(errors.New("not implemented"), nil) } @@ -673,7 +673,7 @@ func (p *TestRemotePublisher) Close(ctx context.Context) { } } -func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]any)) { +func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, signaling.StringMap)) { callback(errors.New("not implemented"), nil) } @@ -729,7 +729,7 @@ func (s *TestRemoteSubscriber) Close(ctx context.Context) { s.closeFunc() } -func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, map[string]any)) { +func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, signaling.StringMap)) { callback(errors.New("not implemented"), nil) } diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 0c2e77b..30d9fab 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -158,7 +158,7 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient { return prev } -func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[string]any) { +func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer signaling.StringMap) { id := s.proxy.GetClientId(client) if id == "" { log.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client) @@ -170,7 +170,7 @@ func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer map[strin Payload: &signaling.PayloadProxyServerMessage{ Type: "offer", ClientId: id, - Payload: map[string]any{ + Payload: signaling.StringMap{ "offer": offer, }, }, @@ -190,7 +190,7 @@ func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate any) Payload: &signaling.PayloadProxyServerMessage{ Type: "candidate", ClientId: id, - Payload: map[string]any{ + Payload: signaling.StringMap{ "candidate": candidate, }, }, diff --git a/room.go b/room.go index e12df91..4687aec 100644 --- a/room.go +++ b/room.go @@ -80,7 +80,7 @@ type Room struct { statsRoomSessionsCurrent *prometheus.GaugeVec // Users currently in the room - users []map[string]any + users []StringMap // Timestamps of last backend requests for the different types. lastRoomRequests map[string]int64 @@ -458,7 +458,7 @@ func (r *Room) RemoveSession(session Session) bool { if virtualSession, ok := session.(*VirtualSession); ok { delete(r.virtualSessions, virtualSession) // Handle case where virtual session was also sent by Nextcloud. - users := make([]map[string]any, 0, len(r.users)) + users := make([]StringMap, 0, len(r.users)) for _, u := range r.users { if u["sessionId"] != sid { users = append(users, u) @@ -631,7 +631,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[string]*Inter return } -func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { +func (r *Room) addInternalSessions(users []StringMap) []StringMap { now := time.Now().Unix() r.mu.RLock() defer r.mu.RUnlock() @@ -675,7 +675,7 @@ func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { } } for session := range r.internalSessions { - u := map[string]any{ + u := StringMap{ "inCall": session.GetInCall(), "sessionId": session.PublicId(), "lastPing": now, @@ -687,7 +687,7 @@ func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { users = append(users, u) } for _, session := range clusteredInternalSessions { - u := map[string]any{ + u := StringMap{ "inCall": session.GetInCall(), "sessionId": session.GetSessionId(), "lastPing": now, @@ -704,7 +704,7 @@ func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { continue } skipSession[sid] = true - users = append(users, map[string]any{ + users = append(users, StringMap{ "inCall": session.GetInCall(), "sessionId": sid, "lastPing": now, @@ -716,7 +716,7 @@ func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { continue } - users = append(users, map[string]any{ + users = append(users, StringMap{ "inCall": session.GetInCall(), "sessionId": sid, "lastPing": now, @@ -726,7 +726,7 @@ func (r *Room) addInternalSessions(users []map[string]any) []map[string]any { return users } -func (r *Room) filterPermissions(users []map[string]any) []map[string]any { +func (r *Room) filterPermissions(users []StringMap) []StringMap { for _, user := range users { delete(user, "permissions") } @@ -753,7 +753,7 @@ func IsInCall(value any) (bool, bool) { } } -func (r *Room) PublishUsersInCallChanged(changed []map[string]any, users []map[string]any) { +func (r *Room) PublishUsersInCallChanged(changed []StringMap, users []StringMap) { r.users = users for _, user := range changed { inCallInterface, found := user["inCall"] @@ -908,7 +908,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { } } -func (r *Room) PublishUsersChanged(changed []map[string]any, users []map[string]any) { +func (r *Room) PublishUsersChanged(changed []StringMap, users []StringMap) { changed = r.filterPermissions(changed) users = r.filterPermissions(users) @@ -929,7 +929,7 @@ func (r *Room) PublishUsersChanged(changed []map[string]any, users []map[string] } } -func (r *Room) getParticipantsUpdateMessage(users []map[string]any) *ServerMessage { +func (r *Room) getParticipantsUpdateMessage(users []StringMap) *ServerMessage { users = r.filterPermissions(users) message := &ServerMessage{ diff --git a/roomsessions_test.go b/roomsessions_test.go index 575713b..94417fc 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -63,7 +63,7 @@ func (s *DummySession) UserData() json.RawMessage { return nil } -func (s *DummySession) ParsedUserData() (map[string]any, error) { +func (s *DummySession) ParsedUserData() (StringMap, error) { return nil, nil } diff --git a/session.go b/session.go index 5bf83f1..c2cc8d2 100644 --- a/session.go +++ b/session.go @@ -56,7 +56,7 @@ type Session interface { UserId() string UserData() json.RawMessage - ParsedUserData() (map[string]any, error) + ParsedUserData() (StringMap, error) Backend() *Backend BackendUrl() string @@ -74,13 +74,13 @@ type Session interface { SendMessage(message *ServerMessage) bool } -func parseUserData(data json.RawMessage) func() (map[string]any, error) { - return sync.OnceValues(func() (map[string]any, error) { +func parseUserData(data json.RawMessage) func() (StringMap, error) { + return sync.OnceValues(func() (StringMap, error) { if len(data) == 0 { return nil, nil } - var m map[string]any + var m StringMap if err := json.Unmarshal(data, &m); err != nil { return nil, err } diff --git a/stringmap.go b/stringmap.go new file mode 100644 index 0000000..fd6c437 --- /dev/null +++ b/stringmap.go @@ -0,0 +1,53 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 + +// StringMap maps string keys to arbitrary values. +type StringMap map[string]any + +func ConvertStringMap(ob any) (StringMap, bool) { + if ob == nil { + return nil, true + } + + if m, ok := ob.(map[string]any); ok { + return StringMap(m), true + } + + if m, ok := ob.(StringMap); ok { + return m, true + } + + return nil, false +} + +// GetStringMapEntry returns an entry from a string map in a given type. +func GetStringMapEntry[T any](m StringMap, key string) (s T, ok bool) { + var defaultValue T + v, found := m[key] + if !found { + return defaultValue, false + } + + s, ok = v.(T) + return +} diff --git a/stringmap_test.go b/stringmap_test.go new file mode 100644 index 0000000..7b24491 --- /dev/null +++ b/stringmap_test.go @@ -0,0 +1,56 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConvertStringMap(t *testing.T) { + assert := assert.New(t) + d := map[string]any{ + "foo": "bar", + "bar": 2, + } + + m, ok := ConvertStringMap(d) + if assert.True(ok) { + assert.EqualValues(d, m) + } + + if m, ok := ConvertStringMap(nil); assert.True(ok) { + assert.Nil(m) + } + + _, ok = ConvertStringMap("foo") + assert.False(ok) + + _, ok = ConvertStringMap(1) + assert.False(ok) + + _, ok = ConvertStringMap(map[int]any{ + 1: "foo", + }) + assert.False(ok) +} diff --git a/testclient_test.go b/testclient_test.go index 77bf12a..4a68a64 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -31,7 +31,6 @@ import ( "fmt" "net" "net/http/httptest" - "reflect" "strconv" "strings" "sync" @@ -401,7 +400,7 @@ func (c *TestClient) SendHelloV2WithFeatures(userid string, features []string) e return c.SendHelloV2WithTimesAndFeatures(userid, now, now.Add(time.Minute), features) } -func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time.Time, expiresAt time.Time, userdata map[string]any) (string, error) { +func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time.Time, expiresAt time.Time, userdata StringMap) (string, error) { data, err := json.Marshal(userdata) if err != nil { return "", err @@ -434,7 +433,7 @@ func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time } func (c *TestClient) CreateHelloV2Token(userid string, issuedAt time.Time, expiresAt time.Time) (string, error) { - userdata := map[string]any{ + userdata := StringMap{ "displayname": "Displayname " + userid, } @@ -998,21 +997,24 @@ func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) error { return err } - var data map[string]any + var data StringMap if err := json.Unmarshal(message.Message.Data, &data); err != nil { return err } - if data["type"].(string) != "offer" { + if dt, ok := GetStringMapEntry[string](data, "type"); !ok || dt != "offer" { return fmt.Errorf("expected data type offer, got %+v", data) } - payload := data["payload"].(map[string]any) - if payload["type"].(string) != "offer" { + payload, ok := ConvertStringMap(data["payload"]) + if !ok { + return fmt.Errorf("expected string map, got %+v", data["payload"]) + } + if pt, ok := GetStringMapEntry[string](payload, "type"); !ok || pt != "offer" { return fmt.Errorf("expected payload type offer, got %+v", payload) } - if payload["sdp"].(string) != offer { - return fmt.Errorf("expected payload answer %s, got %+v", offer, payload) + if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !ok || sdp != offer { + return fmt.Errorf("expected payload offer %s, got %+v", offer, payload) } return nil @@ -1042,65 +1044,62 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string } } - var data map[string]any + var data StringMap if err := json.Unmarshal(message.Message.Data, &data); err != nil { return err } - if data["type"].(string) != "answer" { + if dt, ok := GetStringMapEntry[string](data, "type"); !ok || dt != "answer" { return fmt.Errorf("expected data type answer, got %+v", data) } - payload := data["payload"].(map[string]any) - if payload["type"].(string) != "answer" { + payload, ok := ConvertStringMap(data["payload"]) + if !ok { + return fmt.Errorf("expected string map, got %+v", payload) + } + if pt, ok := GetStringMapEntry[string](payload, "type"); !ok || pt != "answer" { return fmt.Errorf("expected payload type answer, got %+v", payload) } - if payload["sdp"].(string) != answer { + if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !ok || sdp != answer { return fmt.Errorf("expected payload answer %s, got %+v", answer, payload) } return nil } -func checkMessageTransientSet(message *ServerMessage, key string, value any, oldValue any) error { +func checkMessageTransientSet(t *testing.T, message *ServerMessage, key string, value any, oldValue any) error { if err := checkMessageType(message, "transient"); err != nil { return err - } else if message.TransientData.Type != "set" { - return fmt.Errorf("Expected transient set, got %+v", message.TransientData) - } else if message.TransientData.Key != key { - return fmt.Errorf("Expected transient set key %s, got %+v", key, message.TransientData) - } else if !reflect.DeepEqual(message.TransientData.Value, value) { - return fmt.Errorf("Expected transient set value %+v, got %+v", value, message.TransientData.Value) - } else if !reflect.DeepEqual(message.TransientData.OldValue, oldValue) { - return fmt.Errorf("Expected transient set old value %+v, got %+v", oldValue, message.TransientData.OldValue) } + assert := assert.New(t) + assert.Equal("set", message.TransientData.Type, "invalid message type") + assert.Equal(key, message.TransientData.Key, "invalid key") + assert.EqualValues(value, message.TransientData.Value, "invalid value") + assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value") return nil } -func checkMessageTransientRemove(message *ServerMessage, key string, oldValue any) error { +func checkMessageTransientRemove(t *testing.T, message *ServerMessage, key string, oldValue any) error { if err := checkMessageType(message, "transient"); err != nil { return err - } else if message.TransientData.Type != "remove" { - return fmt.Errorf("Expected transient remove, got %+v", message.TransientData) - } else if message.TransientData.Key != key { - return fmt.Errorf("Expected transient remove key %s, got %+v", key, message.TransientData) - } else if !reflect.DeepEqual(message.TransientData.OldValue, oldValue) { - return fmt.Errorf("Expected transient remove old value %+v, got %+v", oldValue, message.TransientData.OldValue) } + assert := assert.New(t) + assert.Equal("remove", message.TransientData.Type, "invalid message type") + assert.Equal(key, message.TransientData.Key, "invalid key") + assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value") return nil } -func checkMessageTransientInitial(message *ServerMessage, data map[string]any) error { +func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data StringMap) error { if err := checkMessageType(message, "transient"); err != nil { return err - } else if message.TransientData.Type != "initial" { - return fmt.Errorf("Expected transient initial, got %+v", message.TransientData) - } else if !reflect.DeepEqual(message.TransientData.Data, data) { - return fmt.Errorf("Expected transient initial data %+v, got %+v", data, message.TransientData.Data) } + assert := assert.New(t) + assert.Equal("initial", message.TransientData.Type, "invalid message type") + assert.EqualValues(data, message.TransientData.Data, "invalid initial data") return nil } diff --git a/transient_data.go b/transient_data.go index d0969bc..59bfd97 100644 --- a/transient_data.go +++ b/transient_data.go @@ -33,7 +33,7 @@ type TransientListener interface { type TransientData struct { mu sync.Mutex - data map[string]any + data StringMap listeners map[TransientListener]bool timers map[string]*time.Timer ttlCh chan<- struct{} @@ -146,7 +146,7 @@ func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) func (t *TransientData) doSet(key string, value any, prev any, ttl time.Duration) { if t.data == nil { - t.data = make(map[string]any) + t.data = make(StringMap) } t.data[key] = value t.notifySet(key, prev, value) @@ -251,11 +251,11 @@ func (t *TransientData) compareAndRemove(key string, old any) bool { } // GetData returns a copy of the internal data. -func (t *TransientData) GetData() map[string]any { +func (t *TransientData) GetData() StringMap { t.mu.Lock() defer t.mu.Unlock() - result := make(map[string]any) + result := make(StringMap) for k, v := range t.data { result[k] = v } diff --git a/transient_data_test.go b/transient_data_test.go index 228dae1..321a2ab 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -194,10 +194,10 @@ func Test_TransientMessages(t *testing.T) { require.NoError(client1.SetTransientData("foo", "bar", 0)) if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(msg, "foo", "bar", nil)) + require.NoError(checkMessageTransientSet(t, msg, "foo", "bar", nil)) } if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(msg, "foo", "bar", nil)) + require.NoError(checkMessageTransientSet(t, msg, "foo", "bar", nil)) } require.NoError(client2.RemoveTransientData("foo")) @@ -222,19 +222,19 @@ func Test_TransientMessages(t *testing.T) { require.NoError(client1.SetTransientData("foo", data, 0)) if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(msg, "foo", data, "bar")) + require.NoError(checkMessageTransientSet(t, msg, "foo", data, "bar")) } if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(msg, "foo", data, "bar")) + require.NoError(checkMessageTransientSet(t, msg, "foo", data, "bar")) } require.NoError(client1.RemoveTransientData("foo")) if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientRemove(msg, "foo", data)) + require.NoError(checkMessageTransientRemove(t, msg, "foo", data)) } if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientRemove(msg, "foo", data)) + require.NoError(checkMessageTransientRemove(t, msg, "foo", data)) } // Removing a non-existing key is ignored by the server. @@ -273,12 +273,12 @@ func Test_TransientMessages(t *testing.T) { require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } - require.NoError(checkMessageTransientInitial(msg, map[string]any{ + require.NoError(checkMessageTransientInitial(t, msg, StringMap{ "abc": data, })) time.Sleep(10 * time.Millisecond) if msg, err = client3.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientRemove(msg, "abc", data)) + require.NoError(checkMessageTransientRemove(t, msg, "abc", data)) } } diff --git a/virtualsession.go b/virtualsession.go index 4794a02..094774c 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -50,7 +50,7 @@ type VirtualSession struct { flags Flags options *AddSessionOptions - parseUserData func() (map[string]any, error) + parseUserData func() (StringMap, error) } func GetVirtualSessionId(session Session, sessionId string) string { @@ -144,7 +144,7 @@ func (s *VirtualSession) UserData() json.RawMessage { return s.userData } -func (s *VirtualSession) ParsedUserData() (map[string]any, error) { +func (s *VirtualSession) ParsedUserData() (StringMap, error) { return s.parseUserData() } @@ -290,7 +290,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { message.Message.Event.Disinvite != nil && message.Message.Event.Disinvite.RoomId == room.Id() { log.Printf("Virtual session %s was disinvited from room %s, hanging up", s.PublicId(), room.Id()) - payload := map[string]any{ + payload := StringMap{ "type": "hangup", "hangup": map[string]string{ "reason": "disinvited", From e36c21d04606a663d650c42356147001d2f814bf Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 30 Jul 2025 10:48:40 +0200 Subject: [PATCH 157/549] Update generated files. --- api_backend_easyjson.go | 32 ++++++++++++++++---------------- api_proxy_easyjson.go | 4 ++-- api_signaling_easyjson.go | 38 +++++++++++++++++++------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 1905387..c5b853b 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -2396,21 +2396,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]map[string]interface{}, 0, 8) + out.Changed = make([]StringMap, 0, 8) } else { - out.Changed = []map[string]interface{}{} + out.Changed = []StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v36 map[string]interface{} + var v36 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v36 = make(map[string]interface{}) + v36 = make(StringMap) } else { v36 = nil } @@ -2443,21 +2443,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]map[string]interface{}, 0, 8) + out.Users = make([]StringMap, 0, 8) } else { - out.Users = []map[string]interface{}{} + out.Users = []StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v38 map[string]interface{} + var v38 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v38 = make(map[string]interface{}) + v38 = make(StringMap) } else { v38 = nil } @@ -2855,21 +2855,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]map[string]interface{}, 0, 8) + out.Changed = make([]StringMap, 0, 8) } else { - out.Changed = []map[string]interface{}{} + out.Changed = []StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v52 map[string]interface{} + var v52 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v52 = make(map[string]interface{}) + v52 = make(StringMap) } else { v52 = nil } @@ -2902,21 +2902,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]map[string]interface{}, 0, 8) + out.Users = make([]StringMap, 0, 8) } else { - out.Users = []map[string]interface{}{} + out.Users = []StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v54 map[string]interface{} + var v54 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v54 = make(map[string]interface{}) + v54 = make(StringMap) } else { v54 = nil } diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index e41b81c..93f4f12 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -591,7 +591,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex in.Skip() } else { in.Delim('{') - out.Payload = make(map[string]interface{}) + out.Payload = make(StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() @@ -716,7 +716,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex } else { in.Delim('{') if !in.IsDelim('}') { - out.Payload = make(map[string]interface{}) + out.Payload = make(StringMap) } else { out.Payload = nil } diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 86ee338..ae6ef45 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -288,7 +288,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex } else { in.Delim('{') if !in.IsDelim('}') { - out.Data = make(map[string]interface{}) + out.Data = make(StringMap) } else { out.Data = nil } @@ -1017,21 +1017,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]map[string]interface{}, 0, 8) + out.Changed = make([]StringMap, 0, 8) } else { - out.Changed = []map[string]interface{}{} + out.Changed = []StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v6 map[string]interface{} + var v6 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v6 = make(map[string]interface{}) + v6 = make(StringMap) } else { v6 = nil } @@ -1064,21 +1064,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]map[string]interface{}, 0, 8) + out.Users = make([]StringMap, 0, 8) } else { - out.Users = []map[string]interface{}{} + out.Users = []StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v8 map[string]interface{} + var v8 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v8 = make(map[string]interface{}) + v8 = make(StringMap) } else { v8 = nil } @@ -1631,21 +1631,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]map[string]interface{}, 0, 8) + out.Changed = make([]StringMap, 0, 8) } else { - out.Changed = []map[string]interface{}{} + out.Changed = []StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v18 map[string]interface{} + var v18 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v18 = make(map[string]interface{}) + v18 = make(StringMap) } else { v18 = nil } @@ -1678,21 +1678,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]map[string]interface{}, 0, 8) + out.Users = make([]StringMap, 0, 8) } else { - out.Users = []map[string]interface{}{} + out.Users = []StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v20 map[string]interface{} + var v20 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v20 = make(map[string]interface{}) + v20 = make(StringMap) } else { v20 = nil } @@ -2469,7 +2469,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Skip() } else { in.Delim('{') - out.Payload = make(map[string]interface{}) + out.Payload = make(StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() @@ -5346,7 +5346,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Skip() } else { in.Delim('{') - out.Payload = make(map[string]interface{}) + out.Payload = make(StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() From fcf912bd15080e24491f47b9dff251355299a581 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:34:09 +0000 Subject: [PATCH 158/549] Bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0 Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.3...v5.3.0) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-version: 5.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0b2db89..97a514f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 github.com/fsnotify/fsnotify v1.9.0 - github.com/golang-jwt/jwt/v5 v5.2.3 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/securecookie v1.1.2 diff --git a/go.sum b/go.sum index b85725e..9b66e3c 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= -github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= From 4b2bc57e25ffa84c0a4bf523c1d2994a25bfe6fe Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 31 Jul 2025 08:24:56 +0200 Subject: [PATCH 159/549] CI: Migrate to coveralls. --- .github/workflows/test.yml | 30 ++++-------------------------- README.md | 2 +- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a59b47f..0020d6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,31 +50,9 @@ jobs: - name: Generate coverage report run: | make cover TIMEOUT=120s - echo "GOROOT=$(go env GOROOT)" >> $GITHUB_ENV - - name: Convert coverage to lcov - uses: jandelgado/gcov2lcov-action@v1.1.1 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 with: - infile: cover.out - outfile: cover.lcov - - - name: Coveralls Parallel - uses: coverallsapp/github-action@v2.3.6 - env: - COVERALLS_FLAG_NAME: run-${{ matrix.go-version }} - with: - path-to-lcov: cover.lcov - github-token: ${{ secrets.github_token }} - parallel: true - - finish: - permissions: - contents: none - needs: go - runs-on: ubuntu-latest - steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@v2.3.6 - with: - github-token: ${{ secrets.github_token }} - parallel-finished: true + token: ${{ secrets.CODECOV_TOKEN }} + files: ./cover.out diff --git a/README.md b/README.md index f9eef3c..8a75043 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Spreed standalone signaling server ![Build Status](https://github.com/strukturag/nextcloud-spreed-signaling/actions/workflows/test.yml/badge.svg) -[![Coverage Status](https://coveralls.io/repos/github/strukturag/nextcloud-spreed-signaling/badge.svg?branch=master)](https://coveralls.io/github/strukturag/nextcloud-spreed-signaling?branch=master) +[![Coverage Status](https://codecov.io/gh/strukturag/nextcloud-spreed-signaling/graph/badge.svg?token=IMXMIRNAJ8)](https://codecov.io/gh/strukturag/nextcloud-spreed-signaling) [![Documentation Status](https://readthedocs.org/projects/nextcloud-spreed-signaling/badge/?version=latest)](https://nextcloud-spreed-signaling.readthedocs.io/en/latest/?badge=latest) [![Go Report](https://goreportcard.com/badge/github.com/strukturag/nextcloud-spreed-signaling)](https://goreportcard.com/report/github.com/strukturag/nextcloud-spreed-signaling) From ac69fa3a0cd5aaea870fad352342c37452e8577d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 31 Jul 2025 08:55:45 +0200 Subject: [PATCH 160/549] CI: Add codecov configuration. --- .codecov.yml | 12 ++++++++++++ Makefile | 5 +---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..fcffd13 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,12 @@ +coverage: + status: + project: + default: + threshold: 2% + +comment: + after_n_builds: 2 + +ignore: + - "*_easyjson.go" + - "*.pb.go" diff --git a/Makefile b/Makefile index 99a7549..cce5251 100644 --- a/Makefile +++ b/Makefile @@ -111,10 +111,7 @@ test: vet cover: vet rm -f cover.out && \ - $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) && \ - sed -i "/_easyjson/d" cover.out && \ - sed -i "/\.pb\.go/d" cover.out && \ - $(GO) tool cover -func=cover.out + $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) coverhtml: vet rm -f cover.out && \ From 542c764d259b1efd3cbda870b014b28f520a642f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 31 Jul 2025 08:57:05 +0200 Subject: [PATCH 161/549] CI: Run tests if codecov configuration has changed. --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0020d6a..43a1291 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,7 @@ on: branches: [ master ] paths: - '.github/workflows/test.yml' + - '.codecov.yml' - '**.go' - 'go.*' - 'Makefile' @@ -12,6 +13,7 @@ on: branches: [ master ] paths: - '.github/workflows/test.yml' + - '.codecov.yml' - '**.go' - 'go.*' - 'Makefile' From 595628dcf4b65082bf1016c506c159c02e30c34b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 31 Jul 2025 09:05:15 +0200 Subject: [PATCH 162/549] Update codecov comment layout. --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index fcffd13..b1e33c6 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -5,6 +5,7 @@ coverage: threshold: 2% comment: + layout: "header, diff, flags, files" after_n_builds: 2 ignore: From 7d571ed73a245a16a0515dad2ceb05a4d895ea7d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 31 Jul 2025 09:16:50 +0200 Subject: [PATCH 163/549] Set flags for codecov uploads. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43a1291..195c99f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,3 +58,4 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: ./cover.out + flags: go-${{ matrix.go-version }} From d3798a3174f5acaa86a5183d0dc9125b1cd6c5c8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 30 Jul 2025 11:40:10 +0200 Subject: [PATCH 164/549] Use testify assertions to check expected fields / values internally. --- backend_server_test.go | 305 +++----- clientsession_test.go | 25 +- federation_test.go | 302 ++++---- hub_test.go | 1675 ++++++++++++---------------------------- mcu_janus_test.go | 111 +-- room_test.go | 145 ++-- testclient_test.go | 783 ++++++++++--------- testutils_test.go | 36 + transient_data_test.go | 92 +-- virtualsession_test.go | 228 ++---- 10 files changed, 1367 insertions(+), 2335 deletions(-) diff --git a/backend_server_test.go b/backend_server_test.go index 7dc90ed..5c138c6 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -28,7 +28,6 @@ import ( "crypto/sha1" "encoding/base64" "encoding/json" - "fmt" "io" "net" "net/http" @@ -43,7 +42,6 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" - "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -229,25 +227,29 @@ func performBackendRequest(requestUrl string, body []byte) (*http.Response, erro return client.Do(request) } -func expectRoomlistEvent(ch chan *AsyncMessage, msgType string) (*EventServerMessage, error) { +func expectRoomlistEvent(t *testing.T, ch chan *AsyncMessage, msgType string) (*EventServerMessage, bool) { + assert := assert.New(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() select { case message := <-ch: - if message.Type != "message" || message.Message == nil { - return nil, fmt.Errorf("Expected message type message, got %+v", message) + if !assert.Equal("message", message.Type, "invalid message type, got %+v", message) || + !assert.NotNil(message.Message, "message missing, got %+v", message) { + return nil, false } msg := message.Message - if msg.Type != "event" || msg.Event == nil { - return nil, fmt.Errorf("Expected message type event, got %+v", msg) + if !assert.Equal("event", msg.Type, "invalid message type, got %+v", msg) || + !assert.NotNil(msg.Event, "event missing, got %+v", msg) || + !assert.Equal("roomlist", msg.Event.Target, "invalid event target, got %+v", msg.Event) || + !assert.Equal(msgType, msg.Event.Type, "invalid event type, got %+v", msg.Event) { + return nil, false } - if msg.Event.Target != "roomlist" || msg.Event.Type != msgType { - return nil, fmt.Errorf("Expected roomlist %s event, got %+v", msgType, msg.Event) - } - return msg.Event, nil + + return msg.Event, true case <-ctx.Done(): - return nil, ctx.Err() + assert.NoError(ctx.Err()) + return nil, false } } @@ -439,11 +441,9 @@ func RunTestBackendServer_RoomInvite(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s: %s", res.Status, string(body)) - if event, err := expectRoomlistEvent(eventsChan, "invite"); assert.NoError(err) { - if assert.NotNil(event.Invite) { - assert.Equal(roomId, event.Invite.RoomId) - assert.Equal(string(roomProperties), string(event.Invite.Properties)) - } + if event, ok := expectRoomlistEvent(t, eventsChan, "invite"); ok && assert.NotNil(event.Invite) { + assert.Equal(roomId, event.Invite.RoomId) + assert.Equal(string(roomProperties), string(event.Invite.Properties)) } } @@ -467,19 +467,14 @@ func RunTestBackendServer_RoomDisinvite(t *testing.T) { backend := hub.backend.GetBackend(u) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - if room, err := client.JoinRoom(ctx, roomId); assert.NoError(err) { + if room, ok := client.JoinRoom(ctx, roomId); assert.True(ok) { assert.Equal(roomId, room.Room.RoomId) } @@ -518,23 +513,17 @@ func RunTestBackendServer_RoomDisinvite(t *testing.T) { require.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s: %s", res.Status, string(body)) - if event, err := expectRoomlistEvent(eventsChan, "disinvite"); assert.NoError(err) { - if assert.NotNil(event.Disinvite) { - assert.Equal(roomId, event.Disinvite.RoomId) - assert.Equal("disinvited", event.Disinvite.Reason) - } + if event, ok := expectRoomlistEvent(t, eventsChan, "disinvite"); ok && assert.NotNil(event.Disinvite) { + assert.Equal(roomId, event.Disinvite.RoomId) + assert.Equal("disinvited", event.Disinvite.Reason) assert.Empty(string(event.Disinvite.Properties)) } - if message, err := client.RunUntilRoomlistDisinvite(ctx); assert.NoError(err) { + if message, ok := client.RunUntilRoomlistDisinvite(ctx); ok { assert.Equal(roomId, message.RoomId) } - if message, err := client.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { - assert.NoError(err, "Received unexpected error") - } else if err == nil { - assert.Fail("Server should have closed the connection", "received %+v", *message) - } + client.RunUntilClosed(ctx) } func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { @@ -544,30 +533,19 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId)) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId)) - 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) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId1 := "test-room1" - _, err = client1.JoinRoom(ctx, roomId1) - require.NoError(err) - require.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + MustSucceed2(t, client1.JoinRoom, ctx, roomId1) + require.True(client1.RunUntilJoined(ctx, hello1.Hello)) roomId2 := "test-room2" - _, err = client2.JoinRoom(ctx, roomId2) - require.NoError(err) - require.NoError(client2.RunUntilJoined(ctx, hello2.Hello)) + MustSucceed2(t, client2.JoinRoom, ctx, roomId2) + require.True(client2.RunUntilJoined(ctx, hello2.Hello)) msg := &BackendServerRoomRequest{ Type: "disinvite", @@ -591,17 +569,13 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - if message, err := client1.RunUntilRoomlistDisinvite(ctx); assert.NoError(err) { + if message, ok := client1.RunUntilRoomlistDisinvite(ctx); ok { assert.Equal(roomId1, message.RoomId) } - if message, err := client1.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { - assert.NoError(err) - } else if err == nil { - assert.Fail("Server should have closed the connection", "received %+v", *message) - } + client1.RunUntilClosed(ctx) - if message, err := client2.RunUntilRoomlistDisinvite(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilRoomlistDisinvite(ctx); ok { assert.Equal(roomId1, message.RoomId) } @@ -624,7 +598,7 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - if message, err := client2.RunUntilRoomlistUpdate(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilRoomlistUpdate(ctx); ok { assert.Equal(roomId2, message.RoomId) } } @@ -684,11 +658,9 @@ func RunTestBackendServer_RoomUpdate(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - if event, err := expectRoomlistEvent(eventsChan, "update"); assert.NoError(err) { - if assert.NotNil(event.Update) { - assert.Equal(roomId, event.Update.RoomId) - assert.Equal(string(roomProperties), string(event.Update.Properties)) - } + if event, ok := expectRoomlistEvent(t, eventsChan, "update"); ok && assert.NotNil(event.Update) { + assert.Equal(roomId, event.Update.RoomId) + assert.Equal(string(roomProperties), string(event.Update.Properties)) } // TODO: Use event to wait for asynchronous messages. @@ -751,12 +723,10 @@ func RunTestBackendServer_RoomDelete(t *testing.T) { assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) // A deleted room is signalled as a "disinvite" event. - if event, err := expectRoomlistEvent(eventsChan, "disinvite"); assert.NoError(err) { - if assert.NotNil(event.Disinvite) { - assert.Equal(roomId, event.Disinvite.RoomId) - assert.Empty(event.Disinvite.Properties) - assert.Equal("deleted", event.Disinvite.Reason) - } + if event, ok := expectRoomlistEvent(t, eventsChan, "disinvite"); ok && assert.NotNil(event.Disinvite) { + assert.Equal(roomId, event.Disinvite.RoomId) + assert.Empty(event.Disinvite.Properties) + assert.Equal("deleted", event.Disinvite.Reason) } // TODO: Use event to wait for asynchronous messages. @@ -787,20 +757,11 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { _, _, hub1, hub2, server1, server2 = CreateBackendServerWithClusteringForTest(t) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId) require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) @@ -815,11 +776,9 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. @@ -880,15 +839,10 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) session := hub.GetSessionByPublicId(hello.Hello.SessionId) assert.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) @@ -899,8 +853,7 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. @@ -949,32 +902,21 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -1077,26 +1019,24 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { return } - msg1_a, err := client1.RunUntilMessage(ctx) - assert.NoError(err) - if in_call_1, err := checkMessageParticipantsInCall(msg1_a); assert.NoError(err) { - if len(in_call_1.Users) != 2 { - msg1_b, err := client1.RunUntilMessage(ctx) - assert.NoError(err) - if in_call_2, err := checkMessageParticipantsInCall(msg1_b); assert.NoError(err) { - assert.Len(in_call_2.Users, 2) + if msg1_a, ok := client1.RunUntilMessage(ctx); ok { + if in_call_1, ok := checkMessageParticipantsInCall(t, msg1_a); ok { + if len(in_call_1.Users) != 2 { + msg1_b, _ := client1.RunUntilMessage(ctx) + if in_call_2, ok := checkMessageParticipantsInCall(t, msg1_b); ok { + assert.Len(in_call_2.Users, 2) + } } } } - msg2_a, err := client2.RunUntilMessage(ctx) - assert.NoError(err) - if in_call_1, err := checkMessageParticipantsInCall(msg2_a); assert.NoError(err) { - if len(in_call_1.Users) != 2 { - msg2_b, err := client2.RunUntilMessage(ctx) - assert.NoError(err) - if in_call_2, err := checkMessageParticipantsInCall(msg2_b); assert.NoError(err) { - assert.Len(in_call_2.Users, 2) + if msg2_a, ok := client2.RunUntilMessage(ctx); ok { + if in_call_1, ok := checkMessageParticipantsInCall(t, msg2_a); ok { + if len(in_call_1.Users) != 2 { + msg2_b, _ := client2.RunUntilMessage(ctx) + if in_call_2, ok := checkMessageParticipantsInCall(t, msg2_b); ok { + assert.Len(in_call_2.Users, 2) + } } } } @@ -1104,15 +1044,12 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if msg1_c, err := client1.RunUntilMessage(ctx2); !assert.ErrorIs(err, context.DeadlineExceeded) { - assert.Fail("should have timeout out", "received %+v", msg1_c) - } + client1.RunUntilErrorIs(ctx2, context.DeadlineExceeded) ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() - if msg2_c, err := client2.RunUntilMessage(ctx3); !assert.ErrorIs(err, context.DeadlineExceeded) { - assert.Fail("should have timeout out", "received %+v", msg2_c) - } + + client2.RunUntilErrorIs(ctx3, context.DeadlineExceeded) } func TestBackendServer_InCallAll(t *testing.T) { @@ -1136,20 +1073,11 @@ func TestBackendServer_InCallAll(t *testing.T) { _, _, hub1, hub2, server1, server2 = CreateBackendServerWithClusteringForTest(t) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId) require.NotNil(session1, "Could not find session %s", hello1.Hello.SessionId) @@ -1158,15 +1086,13 @@ func TestBackendServer_InCallAll(t *testing.T) { // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -1210,15 +1136,15 @@ func TestBackendServer_InCallAll(t *testing.T) { return } - if msg1_a, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if in_call_1, err := checkMessageParticipantsInCall(msg1_a); assert.NoError(err) { + if msg1_a, ok := client1.RunUntilMessage(ctx); ok { + if in_call_1, ok := checkMessageParticipantsInCall(t, msg1_a); ok { assert.True(in_call_1.All, "All flag not set in message %+v", in_call_1) assert.Equal("7", string(in_call_1.InCall)) } } - if msg2_a, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if in_call_1, err := checkMessageParticipantsInCall(msg2_a); assert.NoError(err) { + if msg2_a, ok := client2.RunUntilMessage(ctx); ok { + if in_call_1, ok := checkMessageParticipantsInCall(t, msg2_a); ok { assert.True(in_call_1.All, "All flag not set in message %+v", in_call_1) assert.Equal("7", string(in_call_1.InCall)) } @@ -1230,20 +1156,12 @@ func TestBackendServer_InCallAll(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if message, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() - if message, err := client2.RunUntilMessage(ctx3); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx3, ErrNoMessageReceived, context.DeadlineExceeded) wg.Add(1) go func() { @@ -1275,15 +1193,15 @@ func TestBackendServer_InCallAll(t *testing.T) { return } - if msg1_a, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if in_call_1, err := checkMessageParticipantsInCall(msg1_a); assert.NoError(err) { + if msg1_a, ok := client1.RunUntilMessage(ctx); ok { + if in_call_1, ok := checkMessageParticipantsInCall(t, msg1_a); ok { assert.True(in_call_1.All, "All flag not set in message %+v", in_call_1) assert.Equal("0", string(in_call_1.InCall)) } } - if msg2_a, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if in_call_1, err := checkMessageParticipantsInCall(msg2_a); assert.NoError(err) { + if msg2_a, ok := client2.RunUntilMessage(ctx); ok { + if in_call_1, ok := checkMessageParticipantsInCall(t, msg2_a); ok { assert.True(in_call_1.All, "All flag not set in message %+v", in_call_1) assert.Equal("0", string(in_call_1.InCall)) } @@ -1295,20 +1213,12 @@ func TestBackendServer_InCallAll(t *testing.T) { ctx4, cancel4 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel4() - if message, err := client1.RunUntilMessage(ctx4); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client1.RunUntilErrorIs(ctx4, ErrNoMessageReceived, context.DeadlineExceeded) ctx5, cancel5 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel5() - if message, err := client2.RunUntilMessage(ctx5); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx5, ErrNoMessageReceived, context.DeadlineExceeded) }) } } @@ -1320,20 +1230,14 @@ func TestBackendServer_RoomMessage(t *testing.T) { assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId + "1")) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client.RunUntilHello(ctx) - require.NoError(err) + client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. @@ -1356,7 +1260,7 @@ func TestBackendServer_RoomMessage(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - if message, err := client.RunUntilRoomMessage(ctx); assert.NoError(err) { + if message, ok := client.RunUntilRoomMessage(ctx); ok { assert.Equal(roomId, message.RoomId) assert.Equal(string(messageData), string(message.Data)) } @@ -1504,8 +1408,7 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, client.RunUntilHello, ctx) roomId := "12345" msg := &BackendServerRoomRequest{ @@ -1548,8 +1451,7 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, client.RunUntilHello, ctx) roomId := "12345" callId := "call-123" @@ -1558,8 +1460,8 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { go func() { defer close(stopped) - msg, err := client.RunUntilMessage(ctx) - if !assert.NoError(err) { + msg, ok := client.RunUntilMessage(ctx) + if !ok { return } @@ -1635,8 +1537,7 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, client.RunUntilHello, ctx) roomId := "12345" callId := "call-123" @@ -1645,8 +1546,8 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { go func() { defer close(stopped) - msg, err := client.RunUntilMessage(ctx) - if !assert.NoError(err) { + msg, ok := client.RunUntilMessage(ctx) + if !ok { return } @@ -1722,8 +1623,7 @@ func TestBackendServer_DialoutRejected(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, client.RunUntilHello, ctx) roomId := "12345" errorCode := "error-code" @@ -1733,8 +1633,8 @@ func TestBackendServer_DialoutRejected(t *testing.T) { go func() { defer close(stopped) - msg, err := client.RunUntilMessage(ctx) - if !assert.NoError(err) { + msg, ok := client.RunUntilMessage(ctx) + if !ok { return } @@ -1811,11 +1711,8 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client1.RunUntilHello(ctx) - require.NoError(err) - - _, err = client2.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, client1.RunUntilHello, ctx) + MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "12345" callId := "call-123" @@ -1826,8 +1723,8 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { runClient := func(client *TestClient) { defer wg.Done() - msg, err := client.RunUntilMessage(ctx) - if !assert.NoError(err) { + msg, ok := client.RunUntilMessage(ctx) + if !ok { return } diff --git a/clientsession_test.go b/clientsession_test.go index 80fdbd6..81c3a06 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -144,22 +144,15 @@ func TestBandwidth_Client(t *testing.T) { hub.SetMcu(mcu) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) // Client may not send an offer with audio and video. bitrate := 10000 @@ -176,7 +169,7 @@ func TestBandwidth_Client(t *testing.T) { }, })) - require.NoError(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) pub := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(pub) @@ -223,17 +216,15 @@ func TestBandwidth_Backend(t *testing.T) { } require.NoError(client.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params)) - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + hello := MustSucceed1(t, client.RunUntilHello, ctx) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - require.NoError(client.RunUntilJoined(ctx, hello.Hello)) + require.True(client.RunUntilJoined(ctx, hello.Hello)) // Client may not send an offer with audio and video. bitrate := 10000 @@ -250,7 +241,7 @@ func TestBandwidth_Backend(t *testing.T) { }, })) - require.NoError(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) pub := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(pub, "Could not find publisher") diff --git a/federation_test.go b/federation_test.go index a7bf98f..732e609 100644 --- a/federation_test.go +++ b/federation_test.go @@ -47,8 +47,7 @@ func Test_FederationInvalidToken(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := client.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, client.RunUntilHello, ctx) msg := &ClientMessage{ Id: "join-room-fed", @@ -65,7 +64,7 @@ func Test_FederationInvalidToken(t *testing.T) { } require.NoError(client.WriteJSON(msg)) - if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("error", message.Type) require.Equal("invalid_token", message.Error.Code) @@ -93,19 +92,15 @@ func Test_Federation(t *testing.T) { 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) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" federatedRoomId := roomId + "@federated" - room1, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) room := hub1.getRoom(roomId) require.NotNil(room) @@ -135,7 +130,7 @@ func Test_Federation(t *testing.T) { } require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) @@ -143,8 +138,8 @@ func Test_Federation(t *testing.T) { // 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -154,7 +149,7 @@ func Test_Federation(t *testing.T) { } // The client2 will see its own session id, not the one from the remote server. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) tmpRoom1 := hub2.getRoom(roomId) assert.Nil(tmpRoom1) @@ -211,31 +206,31 @@ func Test_Federation(t *testing.T) { } // Leaving and re-joining a room as "direct" session will trigger correct events. - if room, err := client1.JoinRoom(ctx, ""); assert.NoError(err) { + if room, ok := client1.JoinRoom(ctx, ""); ok { assert.Equal("", room.Room.RoomId) } - assert.NoError(client2.RunUntilLeft(ctx, hello1.Hello)) + client2.RunUntilLeft(ctx, hello1.Hello) - if room, err := client1.JoinRoom(ctx, roomId); assert.NoError(err) { + if room, ok := client1.JoinRoom(ctx, roomId); ok { assert.Equal(roomId, room.Room.RoomId) } - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + client1.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, - })) - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello)) + }) + 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) { + if room, ok := client2.JoinRoom(ctx, ""); ok { assert.Equal("", room.Room.RoomId) } - assert.NoError(client1.RunUntilLeft(ctx, &HelloServerMessage{ + client1.RunUntilLeft(ctx, &HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, - })) + }) // The federated session has left the room, so no more pings. count3, wg3 := hub2.publishFederatedSessions() @@ -243,15 +238,15 @@ func Test_Federation(t *testing.T) { assert.Equal(0, count3) require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -259,7 +254,7 @@ func Test_Federation(t *testing.T) { assert.True(evt.Federated) assert.Equal(features2, evt.Features) } - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) // Test sending messages between sessions. data1 := "from-1-to-2" @@ -269,7 +264,7 @@ func Test_Federation(t *testing.T) { SessionId: remoteSessionId, }, data1)) { var payload string - if assert.NoError(checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload)) { + if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } } @@ -279,7 +274,7 @@ func Test_Federation(t *testing.T) { SessionId: remoteSessionId, }, data1)) { var payload string - if assert.NoError(checkReceiveClientControl(ctx, client2, "session", hello1.Hello, &payload)) { + if checkReceiveClientControl(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } } @@ -289,10 +284,10 @@ func Test_Federation(t *testing.T) { SessionId: hello1.Hello.SessionId, }, data2)) { var payload string - if assert.NoError(checkReceiveClientMessage(ctx, client1, "session", &HelloServerMessage{ + if checkReceiveClientMessage(ctx, t, client1, "session", &HelloServerMessage{ SessionId: remoteSessionId, UserId: testDefaultUserId + "2", - }, &payload)) { + }, &payload) { assert.Equal(data2, payload) } } @@ -302,10 +297,10 @@ func Test_Federation(t *testing.T) { SessionId: hello1.Hello.SessionId, }, data2)) { var payload string - if assert.NoError(checkReceiveClientControl(ctx, client1, "session", &HelloServerMessage{ + if checkReceiveClientControl(ctx, t, client1, "session", &HelloServerMessage{ SessionId: remoteSessionId, UserId: testDefaultUserId + "2", - }, &payload)) { + }, &payload) { assert.Equal(data2, payload) } } @@ -320,7 +315,7 @@ func Test_Federation(t *testing.T) { SessionId: remoteSessionId, }, forceMute)) { var payload StringMap - if assert.NoError(checkReceiveClientControl(ctx, client2, "session", hello1.Hello, &payload)) { + if checkReceiveClientControl(ctx, t, 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) @@ -336,11 +331,7 @@ func Test_Federation(t *testing.T) { ctx2, cancel2 := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) } // Clients can't send to their own (remote) session id. @@ -351,11 +342,7 @@ func Test_Federation(t *testing.T) { ctx2, cancel2 := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) } // Simulate request from the backend that a federated user joined the call. @@ -370,17 +357,19 @@ func Test_Federation(t *testing.T) { 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) + if checkReceiveClientEvent(ctx, t, 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) + if checkReceiveClientEvent(ctx, t, 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 = []StringMap{ @@ -393,17 +382,19 @@ func Test_Federation(t *testing.T) { } 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) + if checkReceiveClientEvent(ctx, t, 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) + if checkReceiveClientEvent(ctx, t, 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. @@ -411,20 +402,19 @@ func Test_Federation(t *testing.T) { defer client3.CloseWithBye() require.NoError(client3.SendHelloV2(testDefaultUserId + "3")) - hello3, err := client3.RunUntilHello(ctx) - require.NoError(err) + hello3 := MustSucceed1(t, client3.RunUntilHello, ctx) - if room, err := client3.JoinRoom(ctx, roomId); assert.NoError(err) { + if room, ok := client3.JoinRoom(ctx, roomId); ok { require.Equal(roomId, room.Room.RoomId) } - assert.NoError(client1.RunUntilJoined(ctx, hello3.Hello)) - assert.NoError(client2.RunUntilJoined(ctx, hello3.Hello)) + client1.RunUntilJoined(ctx, hello3.Hello) + client2.RunUntilJoined(ctx, hello3.Hello) - assert.NoError(client3.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + client3.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, - }, hello3.Hello)) + }, hello3.Hello) // Joining another "federated" session will trigger correct events. @@ -432,8 +422,7 @@ func Test_Federation(t *testing.T) { defer client4.CloseWithBye() require.NoError(client4.SendHelloV2WithFeatures(testDefaultUserId+"4", features2)) - hello4, err := client4.RunUntilHello(ctx) - require.NoError(err) + hello4 := MustSucceed1(t, client4.RunUntilHello, ctx) userdata = StringMap{ "displayname": "Federated user 2", @@ -459,7 +448,7 @@ func Test_Federation(t *testing.T) { } require.NoError(client4.WriteJSON(msg)) - if message, err := client4.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client4.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) @@ -467,8 +456,8 @@ func Test_Federation(t *testing.T) { // The client1 will see the remote session id for client4. var remoteSessionId4 string - if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkSingleMessageJoined(message)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId4 = evt.SessionId assert.NotEqual(hello4.Hello.SessionId, remoteSessionId) @@ -477,23 +466,22 @@ func Test_Federation(t *testing.T) { assert.Equal(features2, evt.Features) } - assert.NoError(client2.RunUntilJoined(ctx, &HelloServerMessage{ + client2.RunUntilJoined(ctx, &HelloServerMessage{ SessionId: remoteSessionId4, UserId: hello4.Hello.UserId, - })) + }) - assert.NoError(client3.RunUntilJoined(ctx, &HelloServerMessage{ + client3.RunUntilJoined(ctx, &HelloServerMessage{ SessionId: remoteSessionId4, UserId: hello4.Hello.UserId, - })) + }) - assert.NoError(client4.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + client4.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, - }, hello3.Hello, hello4.Hello)) + }, hello3.Hello, hello4.Hello) - room3, err := client2.JoinRoom(ctx, "") - if assert.NoError(err) { + if room3, ok := client2.JoinRoom(ctx, ""); ok { assert.Equal("", room3.Room.RoomId) } } @@ -517,19 +505,15 @@ func Test_FederationJoinRoomTwice(t *testing.T) { 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) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" federatedRoomId := roomId + "@federated" - room1, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() userdata := StringMap{ @@ -556,7 +540,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) @@ -564,8 +548,8 @@ func Test_FederationJoinRoomTwice(t *testing.T) { // 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -574,7 +558,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } // The client2 will see its own session id, not the one from the remote server. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) msg2 := &ClientMessage{ Id: "join-room-fed-2", @@ -592,7 +576,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } require.NoError(client2.WriteJSON(msg2)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg2.Id, message.Id) if assert.Equal("error", message.Type) { assert.Equal("already_joined", message.Error.Code) @@ -628,19 +612,15 @@ func Test_FederationChangeRoom(t *testing.T) { 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) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" federatedRoomId := roomId + "@federated" - room1, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() userdata := StringMap{ @@ -667,7 +647,7 @@ func Test_FederationChangeRoom(t *testing.T) { } require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) @@ -680,8 +660,8 @@ func Test_FederationChangeRoom(t *testing.T) { // 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -690,7 +670,7 @@ func Test_FederationChangeRoom(t *testing.T) { } // The client2 will see its own session id, not the one from the remote server. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) roomId2 := roomId + "-2" federatedRoomId2 := roomId2 + "@federated" @@ -710,7 +690,7 @@ func Test_FederationChangeRoom(t *testing.T) { } require.NoError(client2.WriteJSON(msg2)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg2.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId2, message.Room.RoomId) @@ -755,19 +735,15 @@ func Test_FederationMedia(t *testing.T) { 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) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" federatedRooId := roomId + "@federated" - room1, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() userdata := StringMap{ @@ -794,7 +770,7 @@ func Test_FederationMedia(t *testing.T) { } require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRooId, message.Room.RoomId) @@ -802,8 +778,8 @@ func Test_FederationMedia(t *testing.T) { // 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -812,7 +788,7 @@ func Test_FederationMedia(t *testing.T) { } // The client2 will see its own session id, not the one from the remote server. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) require.NoError(client2.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -826,11 +802,11 @@ func Test_FederationMedia(t *testing.T) { }, })) - require.NoError(client2.RunUntilAnswerFromSender(ctx, MockSdpAnswerAudioAndVideo, &MessageServerMessageSender{ + client2.RunUntilAnswerFromSender(ctx, MockSdpAnswerAudioAndVideo, &MessageServerMessageSender{ Type: "session", SessionId: hello2.Hello.SessionId, UserId: hello2.Hello.UserId, - })) + }) } func Test_FederationResume(t *testing.T) { @@ -852,19 +828,15 @@ func Test_FederationResume(t *testing.T) { 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) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" federatedRoomId := roomId + "@federated" - room1, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() userdata := StringMap{ @@ -891,7 +863,7 @@ func Test_FederationResume(t *testing.T) { } require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) @@ -899,8 +871,8 @@ func Test_FederationResume(t *testing.T) { // 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -909,7 +881,7 @@ func Test_FederationResume(t *testing.T) { } // The client2 will see its own session id, not the one from the remote server. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) fed2 := session2.GetFederationClient() @@ -925,13 +897,13 @@ func Test_FederationResume(t *testing.T) { fed2.mu.Unlock() assert.NoError(err) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { 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) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal("event", message.Type) assert.Equal("room", message.Event.Target) assert.Equal("federation_resumed", message.Event.Type) @@ -943,27 +915,19 @@ func Test_FederationResume(t *testing.T) { defer cancel1() var payload string - if assert.NoError(checkReceiveClientMessage(ctx, client1, "session", &HelloServerMessage{ + if checkReceiveClientMessage(ctx, t, client1, "session", &HelloServerMessage{ SessionId: remoteSessionId, UserId: testDefaultUserId + "2", - }, &payload)) { + }, &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) - } + client1.RunUntilErrorIs(ctx1, ErrNoMessageReceived, context.DeadlineExceeded) 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) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) } func Test_FederationResumeNewSession(t *testing.T) { @@ -985,19 +949,15 @@ func Test_FederationResumeNewSession(t *testing.T) { 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) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" federatedRoomId := roomId + "@federated" - room1, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() userdata := StringMap{ @@ -1024,7 +984,7 @@ func Test_FederationResumeNewSession(t *testing.T) { } require.NoError(client2.WriteJSON(msg)) - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) @@ -1032,8 +992,8 @@ func Test_FederationResumeNewSession(t *testing.T) { // 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)) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] remoteSessionId = evt.SessionId assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -1042,7 +1002,7 @@ func Test_FederationResumeNewSession(t *testing.T) { } // The client2 will see its own session id, not the one from the remote server. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) remoteSession2 := hub1.GetSessionByPublicId(remoteSessionId).(*ClientSession) // Simulate disconnected federated client with an expired session. @@ -1052,13 +1012,13 @@ func Test_FederationResumeNewSession(t *testing.T) { } remoteSession2.Close() - if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if message, ok := client2.RunUntilMessage(ctx); ok { 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) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal("event", message.Type) assert.Equal("room", message.Event.Target) assert.Equal("federation_resumed", message.Event.Type) @@ -1068,12 +1028,12 @@ func Test_FederationResumeNewSession(t *testing.T) { // Client1 will get a "leave" for the expired session and a "join" with the // new remote session id. - assert.NoError(client1.RunUntilLeft(ctx, &HelloServerMessage{ + client1.RunUntilLeft(ctx, &HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, - })) - if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkSingleMessageJoined(message)) + }) + if message, ok := client1.RunUntilMessage(ctx); ok { + client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] assert.NotEqual(remoteSessionId, evt.SessionId) assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) @@ -1086,10 +1046,10 @@ func Test_FederationResumeNewSession(t *testing.T) { // 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) { + if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal("", message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) } - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) } diff --git a/hub_test.go b/hub_test.go index 3e79561..c279ba0 100644 --- a/hub_test.go +++ b/hub_test.go @@ -822,7 +822,6 @@ func TestWebsocketFeatures(t *testing.T) { func TestInitialWelcome(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -832,20 +831,18 @@ func TestInitialWelcome(t *testing.T) { client := NewTestClientContext(ctx, t, server, hub) defer client.CloseWithBye() - msg, err := client.RunUntilMessage(ctx) - require.NoError(err) - - assert.Equal("welcome", msg.Type, "%+v", msg) - if assert.NotNil(msg.Welcome, "%+v", msg) { - assert.NotEmpty(msg.Welcome.Version, "%+v", msg) - assert.NotEmpty(msg.Welcome.Features, "%+v", msg) + if msg, ok := client.RunUntilMessage(ctx); ok { + assert.Equal("welcome", msg.Type, "%+v", msg) + if assert.NotNil(msg.Welcome, "%+v", msg) { + assert.NotEmpty(msg.Welcome.Version, "%+v", msg) + assert.NotEmpty(msg.Welcome.Features, "%+v", msg) + } } } func TestExpectClientHello(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -860,18 +857,14 @@ func TestExpectClientHello(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - message, err := client.RunUntilMessage(ctx) - require.NoError(checkUnexpectedClose(err)) - message2, err := client.RunUntilMessage(ctx) - if message2 != nil { - require.Fail("Received multiple messages", "already have %+v, also got %+v", message, message2) + if message, ok := client.RunUntilMessage(ctx); ok { + if checkMessageType(t, message, "bye") { + assert.Equal("hello_timeout", message.Bye.Reason, "%+v", message.Bye) + } } - require.NoError(checkUnexpectedClose(err)) - if err := checkMessageType(message, "bye"); assert.NoError(err) { - assert.Equal("hello_timeout", message.Bye.Reason, "%+v", message.Bye) - } + client.RunUntilClosed(ctx) } func TestExpectClientHelloUnsupportedVersion(t *testing.T) { @@ -892,33 +885,26 @@ func TestExpectClientHelloUnsupportedVersion(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - message, err := client.RunUntilMessage(ctx) - require.NoError(checkUnexpectedClose(err)) - - if err := checkMessageType(message, "error"); assert.NoError(err) { - assert.Equal("invalid_hello_version", message.Error.Code) + if message, ok := client.RunUntilMessage(ctx); ok { + if checkMessageType(t, message, "error") { + assert.Equal("invalid_hello_version", message.Error.Code) + } } } func TestClientHelloV1(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) - } + _, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) } func TestClientHelloV2(t *testing.T) { @@ -937,23 +923,23 @@ func TestClientHelloV2(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + if hello, ok := client.RunUntilHello(ctx); ok { + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - data := hub.decodePublicSessionId(hello.Hello.SessionId) - require.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) + data := hub.decodePublicSessionId(hello.Hello.SessionId) + require.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) - hub.mu.RLock() - session := hub.sessions[data.Sid] - hub.mu.RUnlock() - require.NotNil(session, "Could not get session for id %+v", data) + hub.mu.RLock() + session := hub.sessions[data.Sid] + hub.mu.RUnlock() + require.NotNil(session, "Could not get session for id %+v", data) - var userdata map[string]string - require.NoError(json.Unmarshal(session.UserData(), &userdata)) + var userdata map[string]string + require.NoError(json.Unmarshal(session.UserData(), &userdata)) - assert.Equal("Displayname "+testDefaultUserId, userdata["displayname"]) + assert.Equal("Displayname "+testDefaultUserId, userdata["displayname"]) + } }) } } @@ -976,10 +962,10 @@ func TestClientHelloV2_IssuedInFuture(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + if hello, ok := client.RunUntilHello(ctx); ok { + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + } }) } } @@ -989,7 +975,6 @@ func TestClientHelloV2_IssuedFarInFuture(t *testing.T) { for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) client := NewTestClient(t, server, hub) @@ -1002,12 +987,7 @@ func TestClientHelloV2_IssuedFarInFuture(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - message, err := client.RunUntilMessage(ctx) - require.NoError(checkUnexpectedClose(err)) - - if err := checkMessageType(message, "error"); assert.NoError(err) { - assert.Equal("token_not_valid_yet", message.Error.Code, "%+v", message) - } + client.RunUntilError(ctx, "token_not_valid_yet") // nolint }) } } @@ -1017,7 +997,6 @@ func TestClientHelloV2_Expired(t *testing.T) { for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) client := NewTestClient(t, server, hub) @@ -1030,12 +1009,7 @@ func TestClientHelloV2_Expired(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - message, err := client.RunUntilMessage(ctx) - require.NoError(checkUnexpectedClose(err)) - - if err := checkMessageType(message, "error"); assert.NoError(err) { - assert.Equal("token_expired", message.Error.Code, "%+v", message) - } + client.RunUntilError(ctx, "token_expired") // nolint }) } } @@ -1045,7 +1019,6 @@ func TestClientHelloV2_IssuedAtMissing(t *testing.T) { for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) client := NewTestClient(t, server, hub) @@ -1058,12 +1031,7 @@ func TestClientHelloV2_IssuedAtMissing(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - message, err := client.RunUntilMessage(ctx) - require.NoError(checkUnexpectedClose(err)) - - if err := checkMessageType(message, "error"); assert.NoError(err) { - assert.Equal("token_not_valid_yet", message.Error.Code, "%+v", message) - } + client.RunUntilError(ctx, "token_not_valid_yet") // nolint }) } } @@ -1073,7 +1041,6 @@ func TestClientHelloV2_ExpiresAtMissing(t *testing.T) { for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) client := NewTestClient(t, server, hub) @@ -1086,12 +1053,7 @@ func TestClientHelloV2_ExpiresAtMissing(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - message, err := client.RunUntilMessage(ctx) - require.NoError(checkUnexpectedClose(err)) - - if err := checkMessageType(message, "error"); assert.NoError(err) { - assert.Equal("token_expired", message.Error.Code, "%+v", message) - } + client.RunUntilError(ctx, "token_expired") // nolint }) } } @@ -1115,8 +1077,7 @@ func TestClientHelloV2_CachedCapabilities(t *testing.T) { require.NoError(client1.SendHelloV1(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) assert.Equal(testDefaultUserId+"1", hello1.Hello.UserId, "%+v", hello1.Hello) assert.NotEmpty(hello1.Hello.SessionId, "%+v", hello1.Hello) @@ -1128,8 +1089,7 @@ func TestClientHelloV2_CachedCapabilities(t *testing.T) { require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) assert.Equal(testDefaultUserId+"2", hello2.Hello.UserId, "%+v", hello2.Hello) assert.NotEmpty(hello2.Hello.SessionId, "%+v", hello2.Hello) }) @@ -1139,29 +1099,22 @@ func TestClientHelloV2_CachedCapabilities(t *testing.T) { func TestClientHelloWithSpaces(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - userId := "test user with spaces" - require.NoError(client.SendHello(userId)) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { - assert.Equal(userId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - } + _, hello := NewTestClientWithHello(ctx, t, server, hub, userId) + assert.Equal(userId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) } func TestClientHelloAllowAll(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTestWithConfig(t, func(server *httptest.Server) (*goconf.ConfigFile, error) { config, err := getTestConfig(server) @@ -1174,18 +1127,12 @@ func TestClientHelloAllowAll(t *testing.T) { return config, nil }) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - } + _, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) } func TestClientHelloSessionLimit(t *testing.T) { @@ -1273,7 +1220,7 @@ func TestClientHelloSessionLimit(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := client.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) } @@ -1287,17 +1234,12 @@ func TestClientHelloSessionLimit(t *testing.T) { } require.NoError(client2.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params2)) - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("session_limit_exceeded", msg.Error.Code, "%+v", msg) - } - } + client2.RunUntilError(ctx, "session_limit_exceeded") //nolint // The client can connect to a different backend. require.NoError(client2.SendHelloParams(server1.URL+"/two", HelloVersionV1, "client", nil, params2)) - if hello, err := client2.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := client2.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId+"2", hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) } @@ -1314,7 +1256,7 @@ func TestClientHelloSessionLimit(t *testing.T) { } require.NoError(client3.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params3)) - if hello, err := client3.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := client3.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId+"3", hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) } @@ -1336,34 +1278,29 @@ func TestSessionIdsUnordered(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + _, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - data := hub.decodePublicSessionId(hello.Hello.SessionId) - if !assert.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) { - return - } - - hub.mu.RLock() - session := hub.sessions[data.Sid] - hub.mu.RUnlock() - if !assert.NotNil(session, "Could not get session for id %+v", data) { - return - } - - mu.Lock() - publicSessionIds = append(publicSessionIds, session.PublicId()) - mu.Unlock() + data := hub.decodePublicSessionId(hello.Hello.SessionId) + if !assert.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) { + return } + + hub.mu.RLock() + session := hub.sessions[data.Sid] + hub.mu.RUnlock() + if !assert.NotNil(session, "Could not get session for id %+v", data) { + return + } + + mu.Lock() + publicSessionIds = append(publicSessionIds, session.PublicId()) + mu.Unlock() }() } @@ -1401,16 +1338,10 @@ func TestClientHelloResume(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) require.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1422,7 +1353,7 @@ func TestClientHelloResume(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) - if hello2, err := client.RunUntilHello(ctx); assert.NoError(err) { + if hello2, ok := client.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) @@ -1454,20 +1385,9 @@ func TestClientHelloResumeThrottle(t *testing.T) { timing.expectedSleep = 100 * time.Millisecond require.NoError(client.SendHelloResume("this-is-invalid")) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } - } + client.RunUntilError(ctx, "no_such_session") //nolint - client = NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId) assert.NotEmpty(hello.Hello.SessionId) assert.NotEmpty(hello.Hello.ResumeId) @@ -1485,12 +1405,7 @@ func TestClientHelloResumeThrottle(t *testing.T) { // Valid but expired resume ids will not be throttled. timing.expectedSleep = 0 * time.Millisecond require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } - } + client.RunUntilError(ctx, "no_such_session") //nolint client = NewTestClient(t, server, hub) defer client.CloseWithBye() @@ -1498,31 +1413,19 @@ func TestClientHelloResumeThrottle(t *testing.T) { timing.expectedSleep = 200 * time.Millisecond require.NoError(client.SendHelloResume("this-is-invalid")) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } - } + client.RunUntilError(ctx, "no_such_session") //nolint } func TestClientHelloResumeExpired(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1537,12 +1440,8 @@ func TestClientHelloResumeExpired(t *testing.T) { client = NewTestClient(t, server, hub) defer client.CloseWithBye() - require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } + if assert.NoError(client.SendHelloResume(hello.Hello.ResumeId)) { + client.RunUntilError(ctx, "no_such_session") //nolint } } @@ -1553,16 +1452,10 @@ func TestClientHelloResumeTakeover(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) require.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1571,25 +1464,20 @@ func TestClientHelloResumeTakeover(t *testing.T) { defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello.Hello.ResumeId)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) // The first client got disconnected with a reason in a "Bye" message. - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { assert.Equal("bye", msg.Type, "%+v", msg) if assert.NotNil(msg.Bye, "%+v", msg) { assert.Equal("session_resumed", msg.Bye.Reason, "%+v", msg) } } - if msg, err := client1.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error", "received %+v", msg) - } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error", "received %+v", err) - } + client1.RunUntilClosed(ctx) } func TestClientHelloResumeOtherHub(t *testing.T) { @@ -1599,16 +1487,10 @@ func TestClientHelloResumeOtherHub(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1633,13 +1515,7 @@ func TestClientHelloResumeOtherHub(t *testing.T) { assert.Equal(0, count, "Should have removed all sessions") // The new client will get the same (internal) sid for his session. - newClient := NewTestClient(t, server, hub) - defer newClient.CloseWithBye() - - require.NoError(newClient.SendHello(testDefaultUserId)) - - hello2, err := newClient.RunUntilHello(ctx) - require.NoError(err) + _, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.NotEmpty(hello2.Hello.SessionId, "%+v", hello2.Hello) assert.NotEmpty(hello2.Hello.ResumeId, "%+v", hello2.Hello) @@ -1648,12 +1524,7 @@ func TestClientHelloResumeOtherHub(t *testing.T) { client = NewTestClient(t, server, hub) defer client.CloseWithBye() require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } - } + client.RunUntilError(ctx, "no_such_session") //nolint // Expire old sessions hub.performHousekeeping(time.Now().Add(2 * sessionExpireDuration)) @@ -1667,21 +1538,11 @@ func TestClientHelloResumePublicId(t *testing.T) { // Test that a client can't resume a "public" session of another user. hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) recipient2 := MessageClientMessageRecipient{ @@ -1694,7 +1555,7 @@ func TestClientHelloResumePublicId(t *testing.T) { var payload string var sender *MessageServerMessageSender - if err := checkReceiveClientMessageWithSender(ctx, client2, "session", hello1.Hello, &payload, &sender); assert.NoError(err) { + if checkReceiveClientMessageWithSender(ctx, t, client2, "session", hello1.Hello, &payload, &sender) { assert.Equal(data, payload) } @@ -1706,12 +1567,7 @@ func TestClientHelloResumePublicId(t *testing.T) { // Can't resume a session with the id received from messages of a client. require.NoError(client1.SendHelloResume(sender.SessionId)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } - } + client1.RunUntilError(ctx, "no_such_session") // nolint // Expire old sessions hub.performHousekeeping(time.Now().Add(2 * sessionExpireDuration)) @@ -1724,23 +1580,17 @@ func TestClientHelloByeResume(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) require.NoError(client.SendBye()) - if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageType(message, "bye")) + if message, ok := client.RunUntilMessage(ctx); ok { + checkMessageType(t, message, "bye") } client.Close() @@ -1751,12 +1601,7 @@ func TestClientHelloByeResume(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.Equal("error", msg.Type, "%+v", msg) - if assert.NotNil(msg.Error, "%+v", msg) { - assert.Equal("no_such_session", msg.Error.Code, "%+v", msg) - } - } + client.RunUntilError(ctx, "no_such_session") //nolint } func TestClientHelloResumeAndJoin(t *testing.T) { @@ -1766,16 +1611,10 @@ func TestClientHelloResumeAndJoin(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1787,17 +1626,17 @@ func TestClientHelloResumeAndJoin(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) - hello2, err := client.RunUntilHello(ctx) - require.NoError(err) - assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) - assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) - assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) + if hello2, ok := client.RunUntilHello(ctx); ok && hello != nil { + assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) + assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) + assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) + } // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) - require.Equal(roomId, roomMsg.Room.RoomId) + if roomMsg, ok := client.JoinRoom(ctx, roomId); ok { + assert.Equal(roomId, roomMsg.Room.RoomId) + } } func runGrpcProxyTest(t *testing.T, f func(hub1, hub2 *Hub, server1, server2 *httptest.Server)) { @@ -1844,16 +1683,10 @@ func TestClientHelloResumeProxy(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) assert := assert.New(t) - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1865,20 +1698,18 @@ func TestClientHelloResumeProxy(t *testing.T) { defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello.Hello.ResumeId)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) // Join room by id. roomId := "test-room" - roomMsg, err := client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client2.RunUntilJoined(ctx, hello.Hello)) + client2.RunUntilJoined(ctx, hello.Hello) room := hub1.getRoom(roomId) require.NotNil(room, "Could not find room %s", roomId) @@ -1892,7 +1723,7 @@ func TestClientHelloResumeProxy(t *testing.T) { }, } room.PublishUsersInCallChanged(users, users) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client2, "update", nil) }) }) } @@ -1903,16 +1734,10 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) assert := assert.New(t) - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) require.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1921,49 +1746,39 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello.Hello.ResumeId)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) // The first client got disconnected with a reason in a "Bye" message. - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { assert.Equal("bye", msg.Type, "%+v", msg) if assert.NotNil(msg.Bye, "%+v", msg) { assert.Equal("session_resumed", msg.Bye.Reason, "%+v", msg) } } - if msg, err := client1.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error", "received %+v", msg) - } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error", "received %+v", err) - } + client1.RunUntilClosed(ctx) client3 := NewTestClient(t, server1, hub1) defer client3.CloseWithBye() require.NoError(client3.SendHelloResume(hello.Hello.ResumeId)) - hello3, err := client3.RunUntilHello(ctx) - require.NoError(err) + hello3 := MustSucceed1(t, client3.RunUntilHello, ctx) assert.Equal(testDefaultUserId, hello3.Hello.UserId, "%+v", hello3.Hello) assert.Equal(hello.Hello.SessionId, hello3.Hello.SessionId, "%+v", hello3.Hello) assert.Equal(hello.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) // The second client got disconnected with a reason in a "Bye" message. - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { assert.Equal("bye", msg.Type, "%+v", msg) if assert.NotNil(msg.Bye, "%+v", msg) { assert.Equal("session_resumed", msg.Bye.Reason, "%+v", msg) } } - if msg, err := client2.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error", "received %+v", msg) - } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error", "received %+v", err) - } + client2.RunUntilClosed(ctx) }) }) } @@ -1974,16 +1789,10 @@ func TestClientHelloResumeProxy_Disconnect(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) assert := assert.New(t) - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId) assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -1995,8 +1804,7 @@ func TestClientHelloResumeProxy_Disconnect(t *testing.T) { defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello.Hello.ResumeId)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) @@ -2012,23 +1820,16 @@ func TestClientHelloResumeProxy_Disconnect(t *testing.T) { func TestClientHelloClient(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHelloClient(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { - assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) - } + _, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) } func TestClientHelloClient_V3Api(t *testing.T) { @@ -2051,7 +1852,7 @@ func TestClientHelloClient_V3Api(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := client.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -2073,7 +1874,7 @@ func TestClientHelloInternal(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := client.RunUntilHello(ctx); ok { assert.Empty(hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) @@ -2111,21 +1912,11 @@ func TestClientMessageToSessionId(t *testing.T) { hub2.SetMcu(mcu2) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) recipient1 := MessageClientMessageRecipient{ @@ -2146,11 +1937,11 @@ func TestClientMessageToSessionId(t *testing.T) { client2.SendMessage(recipient1, data2) // nolint var payload1 string - if err := checkReceiveClientMessage(ctx, client1, "session", hello2.Hello, &payload1); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "session", hello2.Hello, &payload1) { assert.Equal(data2, payload1) } var payload2 StringMap - if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload2); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload2) { assert.Equal(data1, payload2) } }) @@ -2178,21 +1969,11 @@ func TestClientControlToSessionId(t *testing.T) { hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) recipient1 := MessageClientMessageRecipient{ @@ -2210,10 +1991,10 @@ func TestClientControlToSessionId(t *testing.T) { client2.SendControl(recipient1, data2) // nolint var payload string - if err := checkReceiveClientControl(ctx, client1, "session", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client1, "session", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientControl(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } }) @@ -2227,21 +2008,11 @@ func TestClientControlMissingPermissions(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) @@ -2276,18 +2047,14 @@ func TestClientControlMissingPermissions(t *testing.T) { client2.SendControl(recipient1, data2) // nolint var payload string - if err := checkReceiveClientControl(ctx, client1, "session", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client1, "session", hello2.Hello, &payload) { assert.Equal(data2, payload) } ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if err := checkReceiveClientMessage(ctx2, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + client2.RunUntilErrorIs(ctx2, context.DeadlineExceeded) } func TestClientMessageToUserId(t *testing.T) { @@ -2297,21 +2064,11 @@ func TestClientMessageToUserId(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) @@ -2330,11 +2087,11 @@ func TestClientMessageToUserId(t *testing.T) { client2.SendMessage(recipient1, data2) // nolint var payload string - if err := checkReceiveClientMessage(ctx, client1, "user", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "user", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientMessage(ctx, client2, "user", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "user", hello1.Hello, &payload) { assert.Equal(data1, payload) } } @@ -2346,21 +2103,11 @@ func TestClientControlToUserId(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) @@ -2379,11 +2126,11 @@ func TestClientControlToUserId(t *testing.T) { client2.SendControl(recipient1, data2) // nolint var payload string - if err := checkReceiveClientControl(ctx, client1, "user", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client1, "user", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientControl(ctx, client2, "user", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client2, "user", hello1.Hello, &payload) { assert.Equal(data1, payload) } } @@ -2395,25 +2142,12 @@ func TestClientMessageToUserIdMultipleSessions(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2a := NewTestClient(t, server, hub) - defer client2a.CloseWithBye() - require.NoError(client2a.SendHello(testDefaultUserId + "2")) - client2b := NewTestClient(t, server, hub) - defer client2b.CloseWithBye() - require.NoError(client2b.SendHello(testDefaultUserId + "2")) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - hello2a, err := client2a.RunUntilHello(ctx) - require.NoError(err) - hello2b, err := client2b.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2a, hello2a := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + client2b, hello2b := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2a.Hello.SessionId) require.NotEqual(hello1.Hello.SessionId, hello2b.Hello.SessionId) @@ -2433,21 +2167,14 @@ func TestClientMessageToUserIdMultipleSessions(t *testing.T) { // Both clients will receive the message as it was sent to the user. var payload string - if err := checkReceiveClientMessage(ctx, client2a, "user", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2a, "user", hello1.Hello, &payload) { assert.Equal(data1, payload) } - if err := checkReceiveClientMessage(ctx, client2b, "user", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2b, "user", hello1.Hello, &payload) { assert.Equal(data1, payload) } } -func WaitForUsersJoined(ctx context.Context, t *testing.T, client1 *TestClient, hello1 *ServerMessage, client2 *TestClient, hello2 *ServerMessage) { - // We will receive "joined" events for all clients. The ordering is not - // defined as messages are processed and sent by asynchronous event handlers. - assert.NoError(t, client1.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) - assert.NoError(t, client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) -} - func TestClientMessageToRoom(t *testing.T) { CatchLogForTest(t) for _, subtest := range clusteredTests { @@ -2472,32 +2199,20 @@ func TestClientMessageToRoom(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -2512,11 +2227,11 @@ func TestClientMessageToRoom(t *testing.T) { client2.SendMessage(recipient, data2) // nolint var payload string - if err := checkReceiveClientMessage(ctx, client1, "room", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "room", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientMessage(ctx, client2, "room", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "room", hello1.Hello, &payload) { assert.Equal(data1, payload) } }) @@ -2547,32 +2262,20 @@ func TestClientControlToRoom(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -2587,11 +2290,11 @@ func TestClientControlToRoom(t *testing.T) { client2.SendControl(recipient, data2) // nolint var payload string - if err := checkReceiveClientControl(ctx, client1, "room", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client1, "room", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientControl(ctx, client2, "room", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client2, "room", hello1.Hello, &payload) { assert.Equal(data1, payload) } }) @@ -2622,32 +2325,20 @@ func TestClientMessageToCall(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -2662,8 +2353,8 @@ func TestClientMessageToCall(t *testing.T) { room1 := hub1.getRoom(roomId) require.NotNil(room1, "Could not find room %s", roomId) room1.PublishUsersInCallChanged(users, users) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) recipient := MessageClientMessageRecipient{ Type: "call", @@ -2675,7 +2366,7 @@ func TestClientMessageToCall(t *testing.T) { client2.SendMessage(recipient, data2) // nolint var payload string - if err := checkReceiveClientMessage(ctx, client1, "call", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "call", hello2.Hello, &payload) { assert.Equal(data2, payload) } @@ -2683,11 +2374,7 @@ func TestClientMessageToCall(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // Simulate request from the backend that somebody joined the call. users = []StringMap{ @@ -2703,16 +2390,16 @@ func TestClientMessageToCall(t *testing.T) { room2 := hub2.getRoom(roomId) require.NotNil(room2, "Could not find room %s", roomId) room2.PublishUsersInCallChanged(users, users) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) client1.SendMessage(recipient, data1) // nolint client2.SendMessage(recipient, data2) // nolint - if err := checkReceiveClientMessage(ctx, client1, "call", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "call", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientMessage(ctx, client2, "call", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "call", hello1.Hello, &payload) { assert.Equal(data1, payload) } }) @@ -2743,32 +2430,20 @@ func TestClientControlToCall(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -2783,8 +2458,8 @@ func TestClientControlToCall(t *testing.T) { room1 := hub1.getRoom(roomId) require.NotNil(room1, "Could not find room %s", roomId) room1.PublishUsersInCallChanged(users, users) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) recipient := MessageClientMessageRecipient{ Type: "call", @@ -2796,7 +2471,7 @@ func TestClientControlToCall(t *testing.T) { client2.SendControl(recipient, data2) // nolint var payload string - if err := checkReceiveClientControl(ctx, client1, "call", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client1, "call", hello2.Hello, &payload) { assert.Equal(data2, payload) } @@ -2804,11 +2479,7 @@ func TestClientControlToCall(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // Simulate request from the backend that somebody joined the call. users = []StringMap{ @@ -2824,16 +2495,16 @@ func TestClientControlToCall(t *testing.T) { room2 := hub2.getRoom(roomId) require.NotNil(room2, "Could not find room %s", roomId) room2.PublishUsersInCallChanged(users, users) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) client1.SendControl(recipient, data1) // nolint client2.SendControl(recipient, data2) // nolint - if err := checkReceiveClientControl(ctx, client1, "call", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client1, "call", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientControl(ctx, client2, "call", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientControl(ctx, t, client2, "call", hello1.Hello, &payload) { assert.Equal(data1, payload) } }) @@ -2844,32 +2515,23 @@ func TestJoinRoom(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) // Leave room. - roomMsg, err = client.JoinRoom(ctx, "") - require.NoError(err) + roomMsg = MustSucceed2(t, client.JoinRoom, ctx, "") require.Equal("", roomMsg.Room.RoomId) } @@ -2880,16 +2542,10 @@ func TestJoinInvalidRoom(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-invalid-room" @@ -2903,13 +2559,11 @@ func TestJoinInvalidRoom(t *testing.T) { } require.NoError(client.WriteJSON(msg)) - message, err := client.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkUnexpectedClose(err)) - - assert.Equal(msg.Id, message.Id) - if assert.NoError(checkMessageType(message, "error")) { - assert.Equal("no_such_room", message.Error.Code) + if message, ok := client.RunUntilMessage(ctx); ok { + assert.Equal(msg.Id, message.Id) + if checkMessageType(t, message, "error") { + assert.Equal("no_such_room", message.Error.Code) + } } } @@ -2920,26 +2574,19 @@ func TestJoinRoomTwice(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) require.Equal(string(testRoomProperties), string(roomMsg.Room.Properties)) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) msg := &ClientMessage{ Id: "ABCD", @@ -2951,21 +2598,19 @@ func TestJoinRoomTwice(t *testing.T) { } require.NoError(client.WriteJSON(msg)) - message, err := client.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkUnexpectedClose(err)) - - assert.Equal(msg.Id, message.Id) - if assert.NoError(checkMessageType(message, "error")) { - assert.Equal("already_joined", message.Error.Code) - assert.NotNil(message.Error.Details) - } - - var roomDetails RoomErrorDetails - if err := json.Unmarshal(message.Error.Details, &roomDetails); assert.NoError(err) { - if assert.NotNil(roomDetails.Room, "%+v", message) { - assert.Equal(roomId, roomDetails.Room.RoomId) - assert.Equal(string(testRoomProperties), string(roomDetails.Room.Properties)) + if message, ok := client.RunUntilMessage(ctx); ok { + assert.Equal(msg.Id, message.Id) + if checkMessageType(t, message, "error") { + assert.Equal("already_joined", message.Error.Code) + if assert.NotEmpty(message.Error.Details) { + var roomDetails RoomErrorDetails + if err := json.Unmarshal(message.Error.Details, &roomDetails); assert.NoError(err) { + if assert.NotNil(roomDetails.Room, "%+v", message) { + assert.Equal(roomId, roomDetails.Room.RoomId) + assert.Equal(string(testRoomProperties), string(roomDetails.Room.Properties)) + } + } + } } } } @@ -2973,31 +2618,23 @@ func TestJoinRoomTwice(t *testing.T) { func TestExpectAnonymousJoinRoom(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(authAnonymousUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - if assert.NoError(err) { - assert.Empty(hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) - } + client, hello := NewTestClientWithHello(ctx, t, server, hub, authAnonymousUserId) + assert.Empty(hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) // Perform housekeeping in the future, this will cause the connection to // be terminated because the anonymous client didn't join a room. performHousekeeping(hub, time.Now().Add(anonmyousJoinRoomTimeout+time.Second)) - if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { - if assert.NoError(checkMessageType(message, "bye")) { + if message, ok := client.RunUntilMessage(ctx); ok { + if checkMessageType(t, message, "bye") { assert.Equal("room_join_timeout", message.Bye.Reason, "%+v", message.Bye) } } @@ -3014,29 +2651,21 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(authAnonymousUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - if assert.NoError(err) { - assert.Empty(hello.Hello.UserId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) - assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) - } + client, hello := NewTestClientWithHello(ctx, t, server, hub, authAnonymousUserId) + assert.Empty(hello.Hello.UserId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) + assert.NotEmpty(hello.Hello.ResumeId, "%+v", hello.Hello) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) // Perform housekeeping in the future, this will keep the connection as the // session joined a room. @@ -3046,23 +2675,18 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel2() - if message, err := client.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // Leave room - roomMsg, err = client.JoinRoom(ctx, "") - require.NoError(err) + roomMsg = MustSucceed2(t, client.JoinRoom, ctx, "") require.Equal("", roomMsg.Room.RoomId) // Perform housekeeping in the future, this will cause the connection to // be terminated because the anonymous client didn't join a room. performHousekeeping(hub, time.Now().Add(anonmyousJoinRoomTimeout+time.Second)) - if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { - if assert.NoError(checkMessageType(message, "bye")) { + if message, ok := client.RunUntilMessage(ctx); ok { + if checkMessageType(t, message, "bye") { assert.Equal("room_join_timeout", message.Bye.Reason, "%+v", message.Bye) } } @@ -3076,41 +2700,31 @@ func TestJoinRoomChange(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) // Change room. roomId = "other-test-room" - roomMsg, err = client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) // Leave room. - roomMsg, err = client.JoinRoom(ctx, "") - require.NoError(err) + roomMsg = MustSucceed2(t, client.JoinRoom, ctx, "") require.Equal("", roomMsg.Room.RoomId) } @@ -3118,55 +2732,40 @@ func TestJoinMultiple(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) // Join room by id (first client). roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) // Join room by id (second client). - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event for the first and the second client. - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) // The first client will also receive a "joined" event from the second client. - assert.NoError(client1.RunUntilJoined(ctx, hello2.Hello)) + client1.RunUntilJoined(ctx, hello2.Hello) // Leave room. - roomMsg, err = client1.JoinRoom(ctx, "") - require.NoError(err) + roomMsg = MustSucceed2(t, client1.JoinRoom, ctx, "") require.Equal("", roomMsg.Room.RoomId) // The second client will now receive a "left" event - assert.NoError(client2.RunUntilLeft(ctx, hello1.Hello)) + client2.RunUntilLeft(ctx, hello1.Hello) - roomMsg, err = client2.JoinRoom(ctx, "") - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, "") require.Equal("", roomMsg.Room.RoomId) } @@ -3177,20 +2776,11 @@ func TestJoinDisplaynamesPermission(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") session2 := hub.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) @@ -3200,20 +2790,18 @@ func TestJoinDisplaynamesPermission(t *testing.T) { // Join room by id (first client). roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) // Join room by id (second client). - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event for the first and the second client. - if events, unexpected, err := client2.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello); assert.NoError(err) { + if events, unexpected, ok := client2.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello); ok { assert.Empty(unexpected) if assert.Len(events, 2) { assert.Nil(events[0].User) @@ -3221,7 +2809,7 @@ func TestJoinDisplaynamesPermission(t *testing.T) { } } // The first client will also receive a "joined" event from the second client. - if events, unexpected, err := client1.RunUntilJoinedAndReturn(ctx, hello2.Hello); assert.NoError(err) { + if events, unexpected, ok := client1.RunUntilJoinedAndReturn(ctx, hello2.Hello); ok { assert.Empty(unexpected) if assert.Len(events, 1) { assert.NotNil(events[0].User) @@ -3239,21 +2827,14 @@ func TestInitialRoomPermissions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId + "1")) - - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room-initial-permissions" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + client.RunUntilJoined(ctx, hello.Hello) session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) @@ -3269,16 +2850,10 @@ func TestJoinRoomSwitchClient(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room-slow" @@ -3302,24 +2877,22 @@ func TestJoinRoomSwitchClient(t *testing.T) { client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello.Hello.ResumeId)) - if hello2, err := client2.RunUntilHello(ctx); assert.NoError(err) { + if hello2, ok := client2.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId, hello2.Hello.UserId, "%+v", hello2.Hello) assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId, "%+v", hello2.Hello) assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) } - roomMsg, err := client2.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkUnexpectedClose(err)) - require.NoError(checkMessageType(roomMsg, "room")) - require.Equal(roomId, roomMsg.Room.RoomId) + roomMsg := MustSucceed1(t, client2.RunUntilMessage, ctx) + if checkMessageType(t, roomMsg, "room") { + assert.Equal(roomId, roomMsg.Room.RoomId) + } // We will receive a "joined" event. - assert.NoError(client2.RunUntilJoined(ctx, hello.Hello)) + client2.RunUntilJoined(ctx, hello.Hello) // Leave room. - roomMsg, err = client2.JoinRoom(ctx, "") - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, "") require.Equal("", roomMsg.Room.RoomId) } @@ -3556,21 +3129,11 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) client2.Close() @@ -3594,25 +3157,21 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { client2 = NewTestClient(t, server, hub) defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello2.Hello.ResumeId)) - if hello3, err := client2.RunUntilHello(ctx); assert.NoError(err) { + if hello3, ok := client2.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId+"2", hello3.Hello.UserId, "%+v", hello3.Hello) assert.Equal(hello2.Hello.SessionId, hello3.Hello.SessionId, "%+v", hello3.Hello) assert.Equal(hello2.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) } var payload StringMap - if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if err := checkReceiveClientMessage(ctx2, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) } func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { @@ -3622,34 +3181,22 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -3664,7 +3211,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { room := hub.getRoom(roomId) require.NotNil(room, "Could not find room %s", roomId) room.PublishUsersInCallChanged(users, users) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client2, "update", nil) client2.Close() assert.NoError(client2.WaitForClientRemoved(ctx)) @@ -3687,7 +3234,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { client2 = NewTestClient(t, server, hub) defer client2.CloseWithBye() require.NoError(client2.SendHelloResume(hello2.Hello.ResumeId)) - if hello3, err := client2.RunUntilHello(ctx); assert.NoError(err) { + if hello3, ok := client2.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId+"2", hello3.Hello.UserId, "%+v", hello3.Hello) assert.Equal(hello2.Hello.SessionId, hello3.Hello.SessionId, "%+v", hello3.Hello) assert.Equal(hello2.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) @@ -3695,21 +3242,17 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { // The participants list update event is triggered again after the session resume. // TODO(jojo): Check contents of message and try with multiple users. - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client2, "update", nil) var payload StringMap - if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if err := checkReceiveClientMessage(ctx2, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) } func TestClientTakeoverRoomSession(t *testing.T) { @@ -3738,22 +3281,15 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") // Join room by id. roomId := "test-room-takeover-room-session" roomSessionid := "room-session-id" - roomMsg, err := client1.JoinRoomWithRoomSession(ctx, roomId, roomSessionid) - require.NoError(err) + roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId, roomSessionid) require.Equal(roomId, roomMsg.Room.RoomId) hubRoom := hub1.getRoom(roomId) @@ -3762,46 +3298,28 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId) require.NotNil(session1, "There should be a session %s", hello1.Hello.SessionId) - client3 := NewTestClient(t, server2, hub2) - defer client3.CloseWithBye() + client3, hello3 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"3") - require.NoError(client3.SendHello(testDefaultUserId + "3")) - - hello3, err := client3.RunUntilHello(ctx) - require.NoError(err) - - roomMsg, err = client3.JoinRoomWithRoomSession(ctx, roomId, roomSessionid+"other") - require.NoError(err) + roomMsg = MustSucceed3(t, client3.JoinRoomWithRoomSession, ctx, roomId, roomSessionid+"other") require.Equal(roomId, roomMsg.Room.RoomId) // Wait until both users have joined. WaitForUsersJoined(ctx, t, client1, hello1, client3, hello3) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - - roomMsg, err = client2.JoinRoomWithRoomSession(ctx, roomId, roomSessionid) - require.NoError(err) + roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId, roomSessionid) require.Equal(roomId, roomMsg.Room.RoomId) // The first client got disconnected with a reason in a "Bye" message. - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { assert.Equal("bye", msg.Type, "%+v", msg) if assert.NotNil(msg.Bye, "%+v", msg) { assert.Equal("room_session_reconnected", msg.Bye.Reason, "%+v", msg) } } - if msg, err := client1.RunUntilMessage(ctx); err == nil { - assert.Fail("Expected error", "received %+v", msg) - } else if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - assert.Fail("Expected close error", "received %+v", err) - } + client1.RunUntilClosed(ctx) // The first session has been closed session1 = hub1.GetSessionByPublicId(hello1.Hello.SessionId) @@ -3809,30 +3327,25 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { // The new client will receive "joined" events for the existing client3 and // himself. - assert.NoError(client2.RunUntilJoined(ctx, hello3.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello3.Hello, hello2.Hello) // No message about the closing is sent to the new connection. ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // The permanently connected client will receive a "left" event from the // overridden session and a "joined" for the new session. In that order as // both were on the same server. - assert.NoError(client3.RunUntilLeft(ctx, hello1.Hello)) - assert.NoError(client3.RunUntilJoined(ctx, hello2.Hello)) + client3.RunUntilLeft(ctx, hello1.Hello) + client3.RunUntilJoined(ctx, hello2.Hello) } func TestClientSendOfferPermissions(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -3845,33 +3358,18 @@ func TestClientSendOfferPermissions(t *testing.T) { hub.SetMcu(mcu) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -3896,9 +3394,8 @@ func TestClientSendOfferPermissions(t *testing.T) { RoomType: "screen", })) - msg, err := client2.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageError(msg, "not_allowed")) + msg := MustSucceed1(t, client2.RunUntilMessage, ctx) + require.True(checkMessageError(t, msg, "not_allowed")) require.NoError(client1.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -3912,7 +3409,7 @@ func TestClientSendOfferPermissions(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) // Client 1 may send an offer. require.NoError(client1.SendMessage(MessageClientMessageRecipient{ @@ -3928,21 +3425,16 @@ func TestClientSendOfferPermissions(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel2() - if message, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // ...but the other peer will get an offer. - require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -3955,32 +3447,25 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { hub.SetMcu(mcu) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client.RunUntilJoined(ctx, hello.Hello) - session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) - require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) + session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) + require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client is allowed to send audio only. - session1.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO}) + session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO}) // Client may not send an offer with audio and video. - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", - SessionId: hello1.Hello.SessionId, + SessionId: hello.Hello.SessionId, }, MessageClientMessageData{ Type: "offer", Sid: "54321", @@ -3990,14 +3475,13 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { }, })) - msg, err := client1.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageError(msg, "not_allowed")) + msg := MustSucceed1(t, client.RunUntilMessage, ctx) + require.True(checkMessageError(t, msg, "not_allowed")) // Client may send an offer (audio only). - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", - SessionId: hello1.Hello.SessionId, + SessionId: hello.Hello.SessionId, }, MessageClientMessageData{ Type: "offer", Sid: "54321", @@ -4007,7 +3491,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioOnly)) + client.RunUntilAnswer(ctx, MockSdpAnswerAudioOnly) } func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { @@ -4027,31 +3511,24 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { hub.SetMcu(mcu) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client.RunUntilJoined(ctx, hello.Hello) - session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) - require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) + session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) + require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client is allowed to send audio and video. - session1.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO}) + session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO}) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", - SessionId: hello1.Hello.SessionId, + SessionId: hello.Hello.SessionId, }, MessageClientMessageData{ Type: "offer", Sid: "54321", @@ -4061,7 +3538,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) // Client is no longer allowed to send video, this will stop the publisher. msg := &BackendServerRoomRequest{ @@ -4069,13 +3546,13 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { Participants: &BackendRoomParticipantsRequest{ Changed: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, @@ -4132,32 +3609,25 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { hub.SetMcu(mcu) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client.RunUntilJoined(ctx, hello.Hello) - session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) - require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) + session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) + require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client is allowed to send audio and video. - session1.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_MEDIA}) + session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_MEDIA}) // Client may send an offer (audio and video). - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", - SessionId: hello1.Hello.SessionId, + SessionId: hello.Hello.SessionId, }, MessageClientMessageData{ Type: "offer", Sid: "54321", @@ -4167,7 +3637,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) // Client is no longer allowed to send video, this will stop the publisher. msg := &BackendServerRoomRequest{ @@ -4175,13 +3645,13 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { Participants: &BackendRoomParticipantsRequest{ Changed: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, @@ -4229,7 +3699,6 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { t.Run(subtest, func(t *testing.T) { t.Parallel() require := require.New(t) - assert := assert.New(t) var hub1 *Hub var hub2 *Hub var server1 *httptest.Server @@ -4254,30 +3723,16 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { hub1.SetMcu(mcu) hub2.SetMcu(mcu) - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoomWithRoomSession(ctx, roomId, "roomsession1") - require.NoError(err) + roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId, "roomsession1") require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) require.NoError(client1.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -4291,7 +3746,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) // Client 2 may not request an offer (he is not in the room yet). require.NoError(client2.SendMessage(MessageClientMessageRecipient{ @@ -4303,17 +3758,15 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { RoomType: "screen", })) - msg, err := client2.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageError(msg, "not_allowed")) + msg := MustSucceed1(t, client2.RunUntilMessage, ctx) + require.True(checkMessageError(t, msg, "not_allowed")) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - require.NoError(client1.RunUntilJoined(ctx, hello2.Hello)) - require.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + require.True(client1.RunUntilJoined(ctx, hello2.Hello)) + require.True(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) // Client 2 may not request an offer (he is not in the call yet). require.NoError(client2.SendMessage(MessageClientMessageRecipient{ @@ -4325,9 +3778,8 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { RoomType: "screen", })) - msg, err = client2.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageError(msg, "not_allowed")) + msg = MustSucceed1(t, client2.RunUntilMessage, ctx) + require.True(checkMessageError(t, msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. users1 := []StringMap{ @@ -4339,8 +3791,8 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { room2 := hub2.getRoom(roomId) require.NotNil(room2, "Could not find room %s", roomId) room2.PublishUsersInCallChanged(users1, users1) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) // Client 2 may not request an offer (recipient is not in the call yet). require.NoError(client2.SendMessage(MessageClientMessageRecipient{ @@ -4352,9 +3804,8 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { RoomType: "screen", })) - msg, err = client2.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageError(msg, "not_allowed")) + msg = MustSucceed1(t, client2.RunUntilMessage, ctx) + require.True(checkMessageError(t, msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. users2 := []StringMap{ @@ -4366,8 +3817,8 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { room1 := hub1.getRoom(roomId) require.NotNil(room1, "Could not find room %s", roomId) room1.PublishUsersInCallChanged(users2, users2) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) // Client 2 may request an offer now (both are in the same room and call). require.NoError(client2.SendMessage(MessageClientMessageRecipient{ @@ -4379,7 +3830,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { RoomType: "screen", })) - require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + require.True(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) require.NoError(client2.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -4397,11 +3848,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) }) } } @@ -4410,7 +3857,6 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) // Clients can't send messages to sessions connected from other backends. hub, _, _, server := CreateHubWithMultipleBackendsForTest(t) @@ -4424,8 +3870,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { UserId: "user1", } require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() @@ -4434,8 +3879,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { UserId: "user2", } require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) recipient1 := MessageClientMessageRecipient{ Type: "session", @@ -4451,22 +3895,15 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { data2 := "from-2-to-1" client2.SendMessage(recipient1, data2) // nolint - var payload string ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if err := checkReceiveClientMessage(ctx2, client1, "session", hello2.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + + client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() - if err := checkReceiveClientMessage(ctx3, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + + client2.RunUntilErrorIs(ctx3, ErrNoMessageReceived, context.DeadlineExceeded) } func TestSendBetweenDifferentUrls(t *testing.T) { @@ -4486,8 +3923,7 @@ func TestSendBetweenDifferentUrls(t *testing.T) { UserId: "user1", } require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() @@ -4496,8 +3932,7 @@ func TestSendBetweenDifferentUrls(t *testing.T) { UserId: "user2", } require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) recipient1 := MessageClientMessageRecipient{ Type: "session", @@ -4514,10 +3949,10 @@ func TestSendBetweenDifferentUrls(t *testing.T) { client2.SendMessage(recipient1, data2) // nolint var payload string - if err := checkReceiveClientMessage(ctx, client1, "session", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "session", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } } @@ -4539,8 +3974,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { UserId: "user1", } require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() @@ -4549,23 +3983,20 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { UserId: "user2", } require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - if msg1, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkMessageJoined(msg1, hello1.Hello)) + if msg1, ok := client1.RunUntilMessage(ctx); ok { + client1.checkMessageJoined(msg1, hello1.Hello) } - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - if msg2, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client2.checkMessageJoined(msg2, hello2.Hello)) + if msg2, ok := client2.RunUntilMessage(ctx); ok { + client2.checkMessageJoined(msg2, hello2.Hello) } hub.ru.RLock() @@ -4589,22 +4020,15 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { data2 := "from-2-to-1" client2.SendMessage(recipient, data2) // nolint - var payload string ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if err := checkReceiveClientMessage(ctx2, client1, "session", hello2.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + + client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() - if err := checkReceiveClientMessage(ctx3, client2, "session", hello1.Hello, &payload); err == nil { - assert.Fail("Expected no payload", "received %+v", payload) - } else { - assert.ErrorIs(err, ErrNoMessageReceived) - } + + client2.RunUntilErrorIs(ctx3, ErrNoMessageReceived, context.DeadlineExceeded) } func TestSameRoomOnDifferentUrls(t *testing.T) { @@ -4624,8 +4048,7 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { UserId: "user1", } require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() @@ -4634,17 +4057,14 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { UserId: "user2", } require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -4669,10 +4089,10 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { client2.SendMessage(recipient, data2) // nolint var payload string - if err := checkReceiveClientMessage(ctx, client1, "room", hello2.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client1, "room", hello2.Hello, &payload) { assert.Equal(data2, payload) } - if err := checkReceiveClientMessage(ctx, client2, "room", hello1.Hello, &payload); assert.NoError(err) { + if checkReceiveClientMessage(ctx, t, client2, "room", hello1.Hello, &payload) { assert.Equal(data1, payload) } } @@ -4683,7 +4103,6 @@ func TestClientSendOffer(t *testing.T) { t.Run(subtest, func(t *testing.T) { t.Parallel() require := require.New(t) - assert := assert.New(t) var hub1 *Hub var hub2 *Hub var server1 *httptest.Server @@ -4708,33 +4127,18 @@ func TestClientSendOffer(t *testing.T) { hub1.SetMcu(mcu) hub2.SetMcu(mcu) - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoomWithRoomSession(ctx, roomId, "roomsession1") - require.NoError(err) + roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId, "roomsession1") require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -4751,7 +4155,7 @@ func TestClientSendOffer(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) require.NoError(client1.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -4765,14 +4169,10 @@ func TestClientSendOffer(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel2() - if message, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // ...but the other peer will get an offer. - require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) }) } } @@ -4781,7 +4181,6 @@ func TestClientUnshareScreen(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -4794,28 +4193,21 @@ func TestClientUnshareScreen(t *testing.T) { hub.SetMcu(mcu) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client.RunUntilJoined(ctx, hello.Hello) - session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) - require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) + session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) + require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", - SessionId: hello1.Hello.SessionId, + SessionId: hello.Hello.SessionId, }, MessageClientMessageData{ Type: "offer", Sid: "54321", @@ -4825,11 +4217,11 @@ func TestClientUnshareScreen(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioOnly)) + client.RunUntilAnswer(ctx, MockSdpAnswerAudioOnly) - publisher := mcu.GetPublisher(hello1.Hello.SessionId) - require.NotNil(publisher, "No publisher for %s found", hello1.Hello.SessionId) - require.False(publisher.isClosed(), "Publisher %s should not be closed", hello1.Hello.SessionId) + publisher := mcu.GetPublisher(hello.Hello.SessionId) + require.NotNil(publisher, "No publisher for %s found", hello.Hello.SessionId) + require.False(publisher.isClosed(), "Publisher %s should not be closed", hello.Hello.SessionId) old := cleanupScreenPublisherDelay cleanupScreenPublisherDelay = time.Millisecond @@ -4837,9 +4229,9 @@ func TestClientUnshareScreen(t *testing.T) { cleanupScreenPublisherDelay = old }() - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", - SessionId: hello1.Hello.SessionId, + SessionId: hello.Hello.SessionId, }, MessageClientMessageData{ Type: "unshareScreen", Sid: "54321", @@ -4848,7 +4240,7 @@ func TestClientUnshareScreen(t *testing.T) { time.Sleep(10 * time.Millisecond) - require.True(publisher.isClosed(), "Publisher %s should be closed", hello1.Hello.SessionId) + require.True(publisher.isClosed(), "Publisher %s should be closed", hello.Hello.SessionId) } func TestVirtualClientSessions(t *testing.T) { @@ -4874,37 +4266,27 @@ func TestVirtualClientSessions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId)) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId) roomId := "test-room" - _, err = client1.JoinRoom(ctx, roomId) - require.NoError(err) + MustSucceed2(t, client1.JoinRoom, ctx, roomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) client2 := NewTestClient(t, server2, hub2) defer client2.CloseWithBye() require.NoError(client2.SendHelloInternal()) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) - _, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + MustSucceed2(t, client2.JoinRoom, ctx, roomId) - assert.NoError(client1.RunUntilJoined(ctx, hello2.Hello)) + client1.RunUntilJoined(ctx, hello2.Hello) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) @@ -4913,17 +4295,16 @@ func TestVirtualClientSessions(t *testing.T) { } } - _, unexpected, err := client2.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello) - assert.NoError(err) + _, unexpected, _ := client2.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello) if len(unexpected) == 0 { - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { unexpected = append(unexpected, msg) } } require.Len(unexpected, 1) - if msg, err := checkMessageParticipantsInCall(unexpected[0]); assert.NoError(err) { + if msg, ok := checkMessageParticipantsInCall(t, unexpected[0]); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"]) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"]) @@ -4966,12 +4347,12 @@ func TestVirtualClientSessions(t *testing.T) { virtualSession := virtualSessions[0] - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + client1.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId) } - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) @@ -4984,20 +4365,20 @@ func TestVirtualClientSessions(t *testing.T) { } } - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if flags, err := checkMessageParticipantFlags(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if flags, ok := checkMessageParticipantFlags(t, msg); ok { assert.Equal(roomId, flags.RoomId) assert.Equal(virtualSession.PublicId(), flags.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flags.Flags) } } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client2.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + client2.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) @@ -5010,8 +4391,8 @@ func TestVirtualClientSessions(t *testing.T) { } } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if flags, err := checkMessageParticipantFlags(msg); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { + if flags, ok := checkMessageParticipantFlags(t, msg); ok { assert.Equal(roomId, flags.RoomId) assert.Equal(virtualSession.PublicId(), flags.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flags.Flags) @@ -5028,16 +4409,16 @@ func TestVirtualClientSessions(t *testing.T) { Flags: &updatedFlags, })) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if flags, err := checkMessageParticipantFlags(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if flags, ok := checkMessageParticipantFlags(t, msg); ok { assert.Equal(roomId, flags.RoomId) assert.Equal(virtualSession.PublicId(), flags.SessionId) assert.EqualValues(0, flags.Flags) } } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if flags, err := checkMessageParticipantFlags(msg); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { + if flags, ok := checkMessageParticipantFlags(t, msg); ok { assert.Equal(roomId, flags.RoomId) assert.Equal(virtualSession.PublicId(), flags.SessionId) assert.EqualValues(0, flags.Flags) @@ -5066,7 +4447,7 @@ func TestVirtualClientSessions(t *testing.T) { var payload string var sender *MessageServerMessageSender var recipient *MessageClientMessageRecipient - if err := checkReceiveClientMessageWithSenderAndRecipient(ctx, client2, "session", hello1.Hello, &payload, &sender, &recipient); assert.NoError(err) { + if checkReceiveClientMessageWithSenderAndRecipient(ctx, t, client2, "session", hello1.Hello, &payload, &sender, &recipient) { assert.Equal(virtualSessionId, recipient.SessionId, "%+v", recipient) assert.Equal(data, payload) } @@ -5074,7 +4455,7 @@ func TestVirtualClientSessions(t *testing.T) { data = "control-to-virtual" client1.SendControl(virtualRecipient, data) // nolint - if err := checkReceiveClientControlWithSenderAndRecipient(ctx, client2, "session", hello1.Hello, &payload, &sender, &recipient); assert.NoError(err) { + if checkReceiveClientControlWithSenderAndRecipient(ctx, t, client2, "session", hello1.Hello, &payload, &sender, &recipient) { assert.Equal(virtualSessionId, recipient.SessionId, "%+v", recipient) assert.Equal(data, payload) } @@ -5092,12 +4473,12 @@ func TestVirtualClientSessions(t *testing.T) { require.NoError(err) } - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkMessageRoomLeaveSession(msg, virtualSession.PublicId())) + if msg, ok := client1.RunUntilMessage(ctx); ok { + client1.checkMessageRoomLeaveSession(msg, virtualSession.PublicId()) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkMessageRoomLeaveSession(msg, virtualSession.PublicId())) + if msg, ok := client2.RunUntilMessage(ctx); ok { + client1.checkMessageRoomLeaveSession(msg, virtualSession.PublicId()) } }) } @@ -5126,37 +4507,28 @@ func TestDuplicateVirtualSessions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId)) - - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId) roomId := "test-room" - _, err = client1.JoinRoom(ctx, roomId) - require.NoError(err) + MustSucceed2(t, client1.JoinRoom, ctx, roomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) client2 := NewTestClient(t, server2, hub2) defer client2.CloseWithBye() require.NoError(client2.SendHelloInternal()) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) - _, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + MustSucceed2(t, client2.JoinRoom, ctx, roomId) - assert.NoError(client1.RunUntilJoined(ctx, hello2.Hello)) + client1.RunUntilJoined(ctx, hello2.Hello) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) @@ -5165,17 +4537,16 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } - _, unexpected, err := client2.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello) - assert.NoError(err) + _, unexpected, _ := client2.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello) if len(unexpected) == 0 { - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { unexpected = append(unexpected, msg) } } require.Len(unexpected, 1) - if msg, err := checkMessageParticipantsInCall(unexpected[0]); assert.NoError(err) { + if msg, ok := checkMessageParticipantsInCall(t, unexpected[0]); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"]) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"]) @@ -5217,12 +4588,12 @@ func TestDuplicateVirtualSessions(t *testing.T) { } virtualSession := virtualSessions[0] - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client1.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + client1.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId) } - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) @@ -5235,20 +4606,20 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if flags, err := checkMessageParticipantFlags(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if flags, ok := checkMessageParticipantFlags(t, msg); ok { assert.Equal(roomId, flags.RoomId) assert.Equal(virtualSession.PublicId(), flags.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flags.Flags) } } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client2.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + client2.checkMessageJoinedSession(msg, virtualSession.PublicId(), virtualUserId) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) @@ -5261,8 +4632,8 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if flags, err := checkMessageParticipantFlags(msg); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { + if flags, ok := checkMessageParticipantFlags(t, msg); ok { assert.Equal(roomId, flags.RoomId) assert.Equal(virtualSession.PublicId(), flags.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flags.Flags) @@ -5300,8 +4671,8 @@ func TestDuplicateVirtualSessions(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client1.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 3) { assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg) assert.Equal(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) @@ -5321,8 +4692,8 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client2.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 3) { assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg) assert.Equal(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) @@ -5349,14 +4720,14 @@ func TestDuplicateVirtualSessions(t *testing.T) { defer client3.CloseWithBye() require.NoError(client3.SendHelloResume(hello1.Hello.ResumeId)) - if hello3, err := client3.RunUntilHello(ctx); assert.NoError(err) { + if hello3, ok := client3.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId, hello3.Hello.UserId, "%+v", hello3.Hello) assert.Equal(hello1.Hello.SessionId, hello3.Hello.SessionId, "%+v", hello3.Hello) assert.Equal(hello1.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) } - if msg, err := client3.RunUntilMessage(ctx); assert.NoError(err) { - if msg, err := checkMessageParticipantsInCall(msg); assert.NoError(err) { + if msg, ok := client3.RunUntilMessage(ctx); ok { + if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 3) { assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg) assert.Equal(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) @@ -5408,37 +4779,26 @@ func DoTestSwitchToOne(t *testing.T, details StringMap) { hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") roomSessionId1 := "roomsession1" roomId1 := "test-room" - roomMsg, err := client1.JoinRoomWithRoomSession(ctx, roomId1, roomSessionId1) - require.NoError(err) + roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) roomSessionId2 := "roomsession2" - roomMsg, err = client2.JoinRoomWithRoomSession(ctx, roomId1, roomSessionId2) - require.NoError(err) + roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) roomId2 := "test-room-2" var sessions json.RawMessage + var err error if details != nil { sessions, err = json.Marshal(StringMap{ roomSessionId1: details, @@ -5474,18 +4834,13 @@ func DoTestSwitchToOne(t *testing.T, details StringMap) { detailsData, err = json.Marshal(details) require.NoError(err) } - _, err = client1.RunUntilSwitchTo(ctx, roomId2, detailsData) - assert.NoError(err) + client1.RunUntilSwitchTo(ctx, roomId2, detailsData) // The other client will not receive a message. ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel2() - if message, err := client2.RunUntilMessage(ctx2); err == nil { - assert.Fail("Expected no message", "received %+v", message) - } else if err != ErrNoMessageReceived && err != context.DeadlineExceeded { - assert.NoError(err) - } + client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) }) } } @@ -5521,37 +4876,26 @@ func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) } - client1 := NewTestClient(t, server1, hub1) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - client2 := NewTestClient(t, server2, hub2) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(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) + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") roomSessionId1 := "roomsession1" roomId1 := "test-room" - roomMsg, err := client1.JoinRoomWithRoomSession(ctx, roomId1, roomSessionId1) - require.NoError(err) + roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) roomSessionId2 := "roomsession2" - roomMsg, err = client2.JoinRoomWithRoomSession(ctx, roomId1, roomSessionId2) - require.NoError(err) + roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) roomId2 := "test-room-2" var sessions json.RawMessage + var err error if details1 != nil || details2 != nil { sessions, err = json.Marshal(StringMap{ roomSessionId1: details1, @@ -5588,16 +4932,14 @@ func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap detailsData1, err = json.Marshal(details1) require.NoError(err) } - _, err = client1.RunUntilSwitchTo(ctx, roomId2, detailsData1) - assert.NoError(err) + client1.RunUntilSwitchTo(ctx, roomId2, detailsData1) var detailsData2 json.RawMessage if details2 != nil { detailsData2, err = json.Marshal(details2) require.NoError(err) } - _, err = client2.RunUntilSwitchTo(ctx, roomId2, detailsData2) - assert.NoError(err) + client2.RunUntilSwitchTo(ctx, roomId2, detailsData2) }) } } @@ -5660,22 +5002,13 @@ func TestDialoutStatus(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, err := internalClient.RunUntilHello(ctx) - require.NoError(err) + MustSucceed1(t, internalClient.RunUntilHello, ctx) roomId := "12345" - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) - require.NoError(client.SendHello(testDefaultUserId)) - - hello, err := client.RunUntilHello(ctx) - require.NoError(err) - - _, err = client.JoinRoom(ctx, roomId) - require.NoError(err) - - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + MustSucceed2(t, client.JoinRoom, ctx, roomId) + client.RunUntilJoined(ctx, hello.Hello) callId := "call-123" @@ -5683,8 +5016,8 @@ func TestDialoutStatus(t *testing.T) { go func(client *TestClient) { defer close(stopped) - msg, err := client.RunUntilMessage(ctx) - if !assert.NoError(err) { + msg, ok := client.RunUntilMessage(ctx) + if !ok { return } @@ -5745,11 +5078,11 @@ func TestDialoutStatus(t *testing.T) { } key := "callstatus_" + callId - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(t, msg, key, StringMap{ + if msg, ok := client.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, key, StringMap{ "callid": callId, "status": "accepted", - }, nil)) + }, nil) } require.NoError(internalClient.SendInternalDialout(&DialoutInternalClientMessage{ @@ -5761,14 +5094,14 @@ func TestDialoutStatus(t *testing.T) { }, })) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(t, msg, key, StringMap{ + if msg, ok := client.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, key, StringMap{ "callid": callId, "status": "ringing", }, StringMap{ "callid": callId, "status": "accepted", - })) + }) } old := removeCallStatusTTL @@ -5788,26 +5121,26 @@ func TestDialoutStatus(t *testing.T) { }, })) - if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageTransientSet(t, msg, key, StringMap{ + if msg, ok := client.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, key, StringMap{ "callid": callId, "status": "cleared", "cause": clearedCause, }, StringMap{ "callid": callId, "status": "ringing", - })) + }) } ctx2, cancel := context.WithTimeout(ctx, removeCallStatusTTL*2) defer cancel() - if msg, err := client.RunUntilMessage(ctx2); assert.NoError(err) { - assert.NoError(checkMessageTransientRemove(t, msg, key, StringMap{ + if msg, ok := client.RunUntilMessage(ctx2); ok { + checkMessageTransientRemove(t, msg, key, StringMap{ "callid": callId, "status": "cleared", "cause": clearedCause, - })) + }) } } @@ -5823,20 +5156,13 @@ func TestGracefulShutdownInitial(t *testing.T) { func TestGracefulShutdownOnBye(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - - _, err := client.RunUntilHello(ctx) - require.NoError(err) + client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) hub.ScheduleShutdown() select { @@ -5857,20 +5183,13 @@ func TestGracefulShutdownOnBye(t *testing.T) { func TestGracefulShutdownOnExpiration(t *testing.T) { t.Parallel() CatchLogForTest(t) - require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - - _, err := client.RunUntilHello(ctx) - require.NoError(err) + client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) hub.ScheduleShutdown() select { diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 83c569b..e0f923d 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -1265,7 +1265,11 @@ func Test_JanusRemotePublisher(t *testing.T) { func Test_JanusSubscriberNoSuchRoom(t *testing.T) { ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - defer checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + t.Cleanup(func() { + if !t.Failed() { + checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + } + }) CatchLogForTest(t) require := require.New(t) @@ -1290,32 +1294,20 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -1334,8 +1326,8 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { room := hub.getRoom(roomId) require.NotNil(room, "Could not find room %s", roomId) room.PublishUsersInCallChanged(users1, users1) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) require.NoError(client1.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1348,7 +1340,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) require.NoError(client2.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1358,8 +1350,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { RoomType: "video", })) - _, err = client2.RunUntilError(ctx, "processing_failed") - require.NoError(err) + MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint require.NoError(client2.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1369,12 +1360,16 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { RoomType: "video", })) - require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } func test_JanusSubscriberAlreadyJoined(t *testing.T) { ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - defer checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + t.Cleanup(func() { + if !t.Failed() { + checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + } + }) CatchLogForTest(t) require := require.New(t) @@ -1399,32 +1394,20 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -1443,8 +1426,8 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { room := hub.getRoom(roomId) require.NotNil(room, "Could not find room %s", roomId) room.PublishUsersInCallChanged(users1, users1) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) require.NoError(client1.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1457,7 +1440,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) require.NoError(client2.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1468,8 +1451,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { })) if strings.Contains(t.Name(), "AttachError") { - _, err = client2.RunUntilError(ctx, "processing_failed") - require.NoError(err) + MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint require.NoError(client2.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1480,7 +1462,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { })) } - require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } func Test_JanusSubscriberAlreadyJoined(t *testing.T) { @@ -1493,7 +1475,11 @@ func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { func Test_JanusSubscriberTimeout(t *testing.T) { ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - defer checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + t.Cleanup(func() { + if !t.Failed() { + checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + } + }) CatchLogForTest(t) require := require.New(t) @@ -1518,32 +1504,20 @@ func Test_JanusSubscriberTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) - + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -1562,8 +1536,8 @@ func Test_JanusSubscriberTimeout(t *testing.T) { room := hub.getRoom(roomId) require.NotNil(room, "Could not find room %s", roomId) room.PublishUsersInCallChanged(users1, users1) - assert.NoError(checkReceiveClientEvent(ctx, client1, "update", nil)) - assert.NoError(checkReceiveClientEvent(ctx, client2, "update", nil)) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) require.NoError(client1.SendMessage(MessageClientMessageRecipient{ Type: "session", @@ -1576,7 +1550,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { }, })) - require.NoError(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) oldTimeout := mcu.settings.timeout.Swap(100 * int64(time.Millisecond)) @@ -1588,8 +1562,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { RoomType: "video", })) - _, err = client2.RunUntilError(ctx, "processing_failed") - require.NoError(err) + MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint mcu.settings.timeout.Store(oldTimeout) @@ -1601,5 +1574,5 @@ func Test_JanusSubscriberTimeout(t *testing.T) { RoomType: "video", })) - require.NoError(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } diff --git a/room_test.go b/room_test.go index b5b9c7d..02bc169 100644 --- a/room_test.go +++ b/room_test.go @@ -32,7 +32,6 @@ import ( "testing" "time" - "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -88,25 +87,18 @@ func TestRoom_Update(t *testing.T) { require.NoError(err) require.NoError(b.Start(router)) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + assert.True(client.RunUntilJoined(ctx, hello.Hello)) // Simulate backend request from Nextcloud to update the room. roomProperties := json.RawMessage("{\"foo\":\"bar\"}") @@ -132,21 +124,19 @@ func TestRoom_Update(t *testing.T) { // The client receives a roomlist update and a changed room event. The // ordering is not defined because messages are sent by asynchronous event // handlers. - message1, err := client.RunUntilMessage(ctx) - assert.NoError(err) - message2, err := client.RunUntilMessage(ctx) - assert.NoError(err) + message1, _ := client.RunUntilMessage(ctx) + message2, _ := client.RunUntilMessage(ctx) - if msg, err := checkMessageRoomlistUpdate(message1); err != nil { - assert.NoError(checkMessageRoomId(message1, roomId)) - if msg, err := checkMessageRoomlistUpdate(message2); assert.NoError(err) { + if message1.Type != "event" { + checkMessageRoomId(t, message1, roomId) + if msg, ok := checkMessageRoomlistUpdate(t, message2); ok { assert.Equal(roomId, msg.RoomId) assert.Equal(string(roomProperties), string(msg.Properties)) } - } else { + } else if msg, ok := checkMessageRoomlistUpdate(t, message1); ok { assert.Equal(roomId, msg.RoomId) assert.Equal(string(roomProperties), string(msg.Properties)) - assert.NoError(checkMessageRoomId(message2, roomId)) + checkMessageRoomId(t, message2, roomId) } // Allow up to 100 milliseconds for asynchronous event processing. @@ -191,25 +181,18 @@ func TestRoom_Delete(t *testing.T) { require.NoError(err) require.NoError(b.Start(router)) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event. - assert.NoError(client.RunUntilJoined(ctx, hello.Hello)) + assert.True(client.RunUntilJoined(ctx, hello.Hello)) // Simulate backend request from Nextcloud to update the room. msg := &BackendServerRoomRequest{ @@ -233,31 +216,20 @@ func TestRoom_Delete(t *testing.T) { // The client is no longer invited to the room and leaves it. The ordering // of messages is not defined as they get published through events and handled // by asynchronous channels. - message1, err := client.RunUntilMessage(ctx) - assert.NoError(err) - - if err := checkMessageType(message1, "event"); err != nil { + if message1, ok := client.RunUntilMessage(ctx); ok && message1.Type != "event" { // Ordering should be "leave room", "disinvited". - assert.NoError(checkMessageRoomId(message1, "")) - if message2, err := client.RunUntilMessage(ctx); assert.NoError(err) { - _, err := checkMessageRoomlistDisinvite(message2) - assert.NoError(err) + checkMessageRoomId(t, message1, "") + if message2, ok := client.RunUntilMessage(ctx); ok { + checkMessageRoomlistDisinvite(t, message2) } } else { // Ordering should be "disinvited", "leave room". - _, err := checkMessageRoomlistDisinvite(message1) - assert.NoError(err) - message2, err := client.RunUntilMessage(ctx) - if err != nil { - // The connection should get closed after the "disinvited". - if websocket.IsUnexpectedCloseError(err, - websocket.CloseNormalClosure, - websocket.CloseGoingAway, - websocket.CloseNoStatusReceived) { - assert.NoError(err) - } - } else { - assert.NoError(checkMessageRoomId(message2, "")) + checkMessageRoomlistDisinvite(t, message1) + // The connection should get closed after the "disinvited". + // However due to the asynchronous processing, the "leave room" message might be received before. + if message2, ok := client.RunUntilMessageOrClosed(ctx); ok && message2 != nil { + checkMessageRoomId(t, message2, "") + client.RunUntilClosed(ctx) } } @@ -269,6 +241,7 @@ loop: for { select { case <-ctx2.Done(): + err = ctx2.Err() break loop default: // The internal room has been updated with the new properties. @@ -314,17 +287,15 @@ func TestRoom_RoomJoinFeatures(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + hello := MustSucceed1(t, client.RunUntilHello, ctx) // Join room by id. roomId := "test-room" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { - if assert.NoError(client.checkMessageJoinedSession(message, hello.Hello.SessionId, testDefaultUserId)) { + if message, ok := client.RunUntilMessage(ctx); ok { + if client.checkMessageJoinedSession(message, hello.Hello.SessionId, testDefaultUserId) { assert.Equal(roomId+"-"+hello.Hello.SessionId, message.Event.Join[0].RoomSessionId) assert.Equal(features, message.Event.Join[0].Features) } @@ -344,27 +315,20 @@ func TestRoom_RoomSessionData(t *testing.T) { require.NoError(err) require.NoError(b.Start(router)) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - - require.NoError(client.SendHello(authAnonymousUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello, err := client.RunUntilHello(ctx) - require.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, authAnonymousUserId) // Join room by id. roomId := "test-room-with-sessiondata" - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // We will receive a "joined" event with the userid from the room session data. expected := "userid-from-sessiondata" - if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { - if assert.NoError(client.checkMessageJoinedSession(message, hello.Hello.SessionId, expected)) { + if message, ok := client.RunUntilMessage(ctx); ok { + if client.checkMessageJoinedSession(message, hello.Hello.SessionId, expected) { assert.Equal(roomId+"-"+hello.Hello.SessionId, message.Event.Join[0].RoomSessionId) } } @@ -394,40 +358,25 @@ func TestRoom_InCallAll(t *testing.T) { require.NoError(err) require.NoError(b.Start(router)) - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - - require.NoError(client1.SendHello(testDefaultUserId + "1")) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) - - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + client1.RunUntilJoined(ctx, hello1.Hello) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) - assert.NoError(client1.RunUntilJoined(ctx, hello2.Hello)) + client1.RunUntilJoined(ctx, hello2.Hello) // Simulate backend request from Nextcloud to update the "inCall" flag of all participants. msg1 := &BackendServerRoomRequest{ @@ -447,12 +396,12 @@ func TestRoom_InCallAll(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res1.StatusCode, "Expected successful request, got %s", string(body1)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageInCallAll(msg, roomId, FlagInCall)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageInCallAll(t, msg, roomId, FlagInCall) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageInCallAll(msg, roomId, FlagInCall)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageInCallAll(t, msg, roomId, FlagInCall) } // Simulate backend request from Nextcloud to update the "inCall" flag of all participants. @@ -473,11 +422,11 @@ func TestRoom_InCallAll(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res2.StatusCode, "Expected successful request, got %s", string(body2)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageInCallAll(msg, roomId, 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageInCallAll(t, msg, roomId, 0) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(checkMessageInCallAll(msg, roomId, 0)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageInCallAll(t, msg, roomId, 0) } } diff --git a/testclient_test.go b/testclient_test.go index 4a68a64..d5bcf5a 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -22,12 +22,12 @@ package signaling import ( - "bytes" "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "net" "net/http/httptest" @@ -76,146 +76,129 @@ func getPubliceSessionIdData(h *Hub, publicId string) *SessionIdData { return decodedPublic } -func checkUnexpectedClose(err error) error { - if err != nil && websocket.IsUnexpectedCloseError(err, - websocket.CloseNormalClosure, - websocket.CloseGoingAway, - websocket.CloseNoStatusReceived) { - return fmt.Errorf("Connection was closed with unexpected error: %s", err) +func checkMessageType(t *testing.T, message *ServerMessage, expectedType string) bool { + assert := assert.New(t) + if !assert.NotNil(message, "no message received") { + return false } - return nil -} + failed := !assert.Equal(expectedType, message.Type, "invalid message type") -func checkMessageType(message *ServerMessage, expectedType string) error { - if message == nil { - return ErrNoMessageReceived - } - - if message.Type != expectedType { - return fmt.Errorf("Expected \"%s\" message, got %+v", expectedType, message) - } switch message.Type { case "hello": - if message.Hello == nil { - return fmt.Errorf("Expected \"%s\" message, got %+v", expectedType, message) + if !assert.NotNil(message.Hello, "hello missing in %+v", message) { + failed = true } case "message": - if message.Message == nil { - return fmt.Errorf("Expected \"%s\" message, got %+v", expectedType, message) - } else if len(message.Message.Data) == 0 { - return fmt.Errorf("Received message without data") + if assert.NotNil(message.Message, "message missing in %+v", message) { + if !assert.NotEmpty(message.Message.Data, "message %+v has no data", message) { + failed = true + } + } else { + failed = true } case "room": - if message.Room == nil { - return fmt.Errorf("Expected \"%s\" message, got %+v", expectedType, message) + if !assert.NotNil(message.Room, "room missing in %+v", message) { + failed = true } case "event": - if message.Event == nil { - return fmt.Errorf("Expected \"%s\" message, got %+v", expectedType, message) + if !assert.NotNil(message.Event, "event missing in %+v", message) { + failed = true } case "transient": - if message.TransientData == nil { - return fmt.Errorf("Expected \"%s\" message, got %+v", expectedType, message) + if !assert.NotNil(message.TransientData, "transient data missing in %+v", message) { + failed = true } } - - return nil + return !failed } -func checkMessageSender(hub *Hub, sender *MessageServerMessageSender, senderType string, hello *HelloServerMessage) error { - if sender.Type != senderType { - return fmt.Errorf("Expected sender type %s, got %s", senderType, sender.Type) - } else if sender.SessionId != hello.SessionId { - return fmt.Errorf("Expected session id %+v, got %+v", - getPubliceSessionIdData(hub, hello.SessionId), getPubliceSessionIdData(hub, sender.SessionId)) - } else if sender.UserId != hello.UserId { - return fmt.Errorf("Expected user id %s, got %s", hello.UserId, sender.UserId) - } - - return nil +func checkMessageSender(t *testing.T, hub *Hub, sender *MessageServerMessageSender, senderType string, hello *HelloServerMessage) bool { + assert := assert.New(t) + return assert.Equal(senderType, sender.Type, "invalid sender type in %+v", sender) && + assert.Equal(hello.SessionId, sender.SessionId, "invalid session id, expectd %+v, got %+v in %+v", + getPubliceSessionIdData(hub, hello.SessionId), + getPubliceSessionIdData(hub, sender.SessionId), + sender, + ) && + assert.Equal(hello.UserId, sender.UserId, "invalid userid in %+v", sender) } -func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) error { - message, err := client.RunUntilMessage(ctx) - if err := checkUnexpectedClose(err); err != nil { - return err - } else if err := checkMessageType(message, "message"); err != nil { - return err - } else if err := checkMessageSender(client.hub, message.Message.Sender, senderType, hello); err != nil { - return err - } else { - if err := json.Unmarshal(message.Message.Data, payload); err != nil { - return err - } +func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) bool { + assert := assert.New(t) + message, ok := client.RunUntilMessage(ctx) + if !ok || + !checkMessageType(t, message, "message") || + !checkMessageSender(t, client.hub, message.Message.Sender, senderType, hello) || + !assert.NoError(json.Unmarshal(message.Message.Data, payload)) { + return false } + if sender != nil { *sender = message.Message.Sender } if recipient != nil { *recipient = message.Message.Recipient } - return nil + return true } -func checkReceiveClientMessageWithSender(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) error { - return checkReceiveClientMessageWithSenderAndRecipient(ctx, client, senderType, hello, payload, sender, nil) +func checkReceiveClientMessageWithSender(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) bool { + return checkReceiveClientMessageWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, sender, nil) } -func checkReceiveClientMessage(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any) error { - return checkReceiveClientMessageWithSenderAndRecipient(ctx, client, senderType, hello, payload, nil, nil) +func checkReceiveClientMessage(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any) bool { + return checkReceiveClientMessageWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, nil, nil) } -func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) error { - message, err := client.RunUntilMessage(ctx) - if err := checkUnexpectedClose(err); err != nil { - return err - } else if err := checkMessageType(message, "control"); err != nil { - return err - } else if err := checkMessageSender(client.hub, message.Control.Sender, senderType, hello); err != nil { - return err - } else { - if err := json.Unmarshal(message.Control.Data, payload); err != nil { - return err - } +func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) bool { + assert := assert.New(t) + message, ok := client.RunUntilMessage(ctx) + if !ok || + !checkMessageType(t, message, "control") || + !checkMessageSender(t, client.hub, message.Control.Sender, senderType, hello) || + !assert.NoError(json.Unmarshal(message.Control.Data, payload)) { + return false } + if sender != nil { *sender = message.Control.Sender } if recipient != nil { *recipient = message.Control.Recipient } - return nil + return true } -func checkReceiveClientControlWithSender(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) error { // nolint - return checkReceiveClientControlWithSenderAndRecipient(ctx, client, senderType, hello, payload, sender, nil) +func checkReceiveClientControlWithSender(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) bool { // nolint + return checkReceiveClientControlWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, sender, nil) } -func checkReceiveClientControl(ctx context.Context, client *TestClient, senderType string, hello *HelloServerMessage, payload any) error { - return checkReceiveClientControlWithSenderAndRecipient(ctx, client, senderType, hello, payload, nil, nil) +func checkReceiveClientControl(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any) bool { + return checkReceiveClientControlWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, nil, nil) } -func checkReceiveClientEvent(ctx context.Context, client *TestClient, eventType string, msg **EventServerMessage) error { - message, err := client.RunUntilMessage(ctx) - if err := checkUnexpectedClose(err); err != nil { - return err - } else if err := checkMessageType(message, "event"); err != nil { - return err - } else if message.Event.Type != eventType { - return fmt.Errorf("Expected \"%s\" event type, got \"%s\"", eventType, message.Event.Type) - } else { - if msg != nil { - *msg = message.Event - } +func checkReceiveClientEvent(ctx context.Context, t *testing.T, client *TestClient, eventType string, msg **EventServerMessage) bool { + assert := assert.New(t) + message, ok := client.RunUntilMessage(ctx) + if !ok || + !checkMessageType(t, message, "event") || + !assert.Equal(eventType, message.Event.Type, "invalid event type in %+v", message) { + return false } - return nil + + if msg != nil { + *msg = message.Event + } + return true } type TestClient struct { - t *testing.T - hub *Hub - server *httptest.Server + t *testing.T + assert *assert.Assertions + require *require.Assertions + hub *Hub + server *httptest.Server mu sync.Mutex conn *websocket.Conn @@ -250,9 +233,11 @@ func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Se }() return &TestClient{ - t: t, - hub: hub, - server: server, + t: t, + assert: assert.New(t), + require: require.New(t), + hub: hub, + server: server, conn: conn, localAddr: conn.LocalAddr(), @@ -267,12 +252,23 @@ func NewTestClient(t *testing.T, server *httptest.Server, hub *Hub) *TestClient defer cancel() client := NewTestClientContext(ctx, t, server, hub) - msg, err := client.RunUntilMessage(ctx) - require.NoError(t, err) - assert.Equal(t, "welcome", msg.Type) + if msg, ok := client.RunUntilMessage(ctx); ok { + assert.Equal(t, "welcome", msg.Type, "invalid initial message type in %+v", msg) + } return client } +func NewTestClientWithHello(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub, userId string) (*TestClient, *ServerMessage) { + client := NewTestClient(t, server, hub) + t.Cleanup(func() { + client.CloseWithBye() + }) + + require.NoError(t, client.SendHello(userId)) + hello := MustSucceed1(t, client.RunUntilHello, ctx) + return client, hello +} + func (c *TestClient) CloseWithBye() { c.SendBye() // nolint c.Close() @@ -675,56 +671,115 @@ func (c *TestClient) GetPendingMessages(ctx context.Context) ([]*ServerMessage, return result, nil } -func (c *TestClient) RunUntilMessage(ctx context.Context) (message *ServerMessage, err error) { +func (c *TestClient) RunUntilClosed(ctx context.Context) bool { + select { + case err := <-c.readErrorChan: + if c.assert.Error(err) && websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { + return true + } + + c.assert.NoError(err, "Received unexpected error") + case msg := <-c.messageChan: + var m ServerMessage + if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { + c.assert.Fail("Server should have closed the connection", "received %+v", m) + } + case <-ctx.Done(): + c.assert.NoError(ctx.Err(), "error while waiting for closed connection") + } + return false +} + +func (c *TestClient) RunUntilErrorIs(ctx context.Context, targets ...error) bool { + var err error select { case err = <-c.readErrorChan: case msg := <-c.messageChan: var m ServerMessage - if err = json.Unmarshal(msg, &m); err == nil { - message = &m + if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { + c.assert.Fail("received message", "expected one of errors %+v, got message %+v", targets, m) } + return false case <-ctx.Done(): err = ctx.Err() } - return + + if c.assert.Error(err, "expected one of errors %+v", targets) { + for _, t := range targets { + if errors.Is(err, t) { + return true + } + } + + c.assert.Fail("invalid error", "expected one of errors %+v, got %s", targets, err) + } + + return false } -func (c *TestClient) RunUntilError(ctx context.Context, code string) (*Error, error) { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return nil, err +func (c *TestClient) RunUntilMessage(ctx context.Context) (*ServerMessage, bool) { + select { + case err := <-c.readErrorChan: + c.assert.NoError(err, "error reading while waiting for message") + return nil, false + case msg := <-c.messageChan: + var m ServerMessage + if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { + return &m, true + } + case <-ctx.Done(): + c.assert.NoError(ctx.Err(), "error while waiting for message") } - if err := checkUnexpectedClose(err); err != nil { - return nil, err - } - if err := checkMessageType(message, "error"); err != nil { - return nil, err - } - if message.Error.Code != code { - return nil, fmt.Errorf("expected error %s, got %s", code, message.Error.Code) - } - return message.Error, nil + return nil, false } -func (c *TestClient) RunUntilHello(ctx context.Context) (message *ServerMessage, err error) { - if message, err = c.RunUntilMessage(ctx); err != nil { - return nil, err +func (c *TestClient) RunUntilMessageOrClosed(ctx context.Context) (*ServerMessage, bool) { + select { + case err := <-c.readErrorChan: + if c.assert.Error(err) && websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { + return nil, true + } + + c.assert.NoError(err, "Received unexpected error") + return nil, false + case msg := <-c.messageChan: + var m ServerMessage + if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { + return &m, true + } + case <-ctx.Done(): + c.assert.NoError(ctx.Err(), "error while waiting for message") } - if err := checkUnexpectedClose(err); err != nil { - return nil, err + return nil, false +} + +func (c *TestClient) RunUntilError(ctx context.Context, code string) (*Error, bool) { + message, ok := c.RunUntilMessage(ctx) + if !ok || + !checkMessageType(c.t, message, "error") || + !c.assert.Equal(code, message.Error.Code, "invalid error code in %+v", message) { + return nil, false } - if err := checkMessageType(message, "hello"); err != nil { - return nil, err + + return message.Error, true +} + +func (c *TestClient) RunUntilHello(ctx context.Context) (*ServerMessage, bool) { + message, ok := c.RunUntilMessage(ctx) + if !ok || + !checkMessageType(c.t, message, "hello") { + return nil, false } + c.publicId = message.Hello.SessionId - return message, nil + return message, true } -func (c *TestClient) JoinRoom(ctx context.Context, roomId string) (message *ServerMessage, err error) { +func (c *TestClient) JoinRoom(ctx context.Context, roomId string) (*ServerMessage, bool) { return c.JoinRoomWithRoomSession(ctx, roomId, roomId+"-"+c.publicId) } -func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, roomSessionId string) (message *ServerMessage, err error) { +func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, roomSessionId string) (message *ServerMessage, ok bool) { msg := &ClientMessage{ Id: "ABCD", Type: "room", @@ -733,80 +788,63 @@ func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, SessionId: roomSessionId, }, } - if err := c.WriteJSON(msg); err != nil { - return nil, err + if err := c.WriteJSON(msg); !c.assert.NoError(err) { + return nil, false } - if message, err = c.RunUntilMessage(ctx); err != nil { - return nil, err + if message, ok = c.RunUntilMessage(ctx); !ok || + !checkMessageType(c.t, message, "room") || + !c.assert.Equal(msg.Id, message.Id, "invalid message id in %+v", message) { + return nil, false } - if err := checkUnexpectedClose(err); err != nil { - return nil, err - } - 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 + + return message, true } -func checkMessageRoomId(message *ServerMessage, roomId string) error { - if err := checkMessageType(message, "room"); err != nil { - return err - } - if message.Room.RoomId != roomId { - return fmt.Errorf("Expected room id %s, got %+v", roomId, message.Room) - } - return nil +func checkMessageRoomId(t *testing.T, message *ServerMessage, roomId string) bool { + return checkMessageType(t, message, "room") && + assert.Equal(t, roomId, message.Room.RoomId, "invalid room id in %+v", message) } -func (c *TestClient) RunUntilRoom(ctx context.Context, roomId string) error { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return err - } - if err := checkUnexpectedClose(err); err != nil { - return err - } - return checkMessageRoomId(message, roomId) +func (c *TestClient) RunUntilRoom(ctx context.Context, roomId string) bool { + message, ok := c.RunUntilMessage(ctx) + return ok && checkMessageRoomId(c.t, message, roomId) } -func (c *TestClient) checkMessageJoined(message *ServerMessage, hello *HelloServerMessage) error { +func (c *TestClient) checkMessageJoined(message *ServerMessage, hello *HelloServerMessage) bool { return c.checkMessageJoinedSession(message, hello.SessionId, hello.UserId) } -func (c *TestClient) checkSingleMessageJoined(message *ServerMessage) error { - if err := checkMessageType(message, "event"); err != nil { - return err - } else if message.Event.Target != "room" { - return fmt.Errorf("Expected event target room, got %+v", message.Event) - } else if message.Event.Type != "join" { - return fmt.Errorf("Expected event type join, got %+v", message.Event) - } else if len(message.Event.Join) != 1 { - return fmt.Errorf("Expected one join event entry, got %+v", message.Event) - } - return nil +func (c *TestClient) checkSingleMessageJoined(message *ServerMessage) bool { + return checkMessageType(c.t, message, "event") && + c.assert.Equal("room", message.Event.Target, "invalid event target in %+v", message) && + c.assert.Equal("join", message.Event.Type, "invalid event type in %+v", message) && + c.assert.Len(message.Event.Join, 1, "invalid number of join event entries in %+v", message) } -func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId string, userId string) error { - if err := c.checkSingleMessageJoined(message); err != nil { - return err +func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId string, userId string) bool { + if !c.checkSingleMessageJoined(message) { + return false } + failed := false evt := message.Event.Join[0] - if sessionId != "" && evt.SessionId != sessionId { - return fmt.Errorf("Expected join session id %+v, got %+v", - getPubliceSessionIdData(c.hub, sessionId), getPubliceSessionIdData(c.hub, evt.SessionId)) + if sessionId != "" { + if !c.assert.Equal(sessionId, evt.SessionId, "invalid join session id: expected %+v, got %+v in %+v", + getPubliceSessionIdData(c.hub, sessionId), + getPubliceSessionIdData(c.hub, evt.SessionId), + message, + ) { + failed = true + } } - if evt.UserId != userId { - return fmt.Errorf("Expected join user id %s, got %+v", userId, evt) + if !c.assert.Equal(userId, evt.UserId, "invalid user id in %+v", evt) { + failed = true } - return nil + return !failed } -func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*HelloServerMessage) ([]*EventServerMessageSessionEntry, []*ServerMessage, error) { +func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*HelloServerMessage) ([]*EventServerMessageSessionEntry, []*ServerMessage, bool) { received := make([]*EventServerMessageSessionEntry, len(hello)) var ignored []*ServerMessage hellos := make(map[*HelloServerMessage]int, len(hello)) @@ -814,12 +852,12 @@ func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*Hell hellos[h] = idx } for len(hellos) > 0 { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return nil, nil, fmt.Errorf("got error while waiting for %+v: %w", hellos, err) + message, ok := c.RunUntilMessage(ctx) + if !ok { + return nil, nil, false } - if err := checkMessageType(message, "event"); err != nil { + if message.Type != "event" || message.Event == nil { ignored = append(ignored, message) continue } else if message.Event.Target != "room" || message.Event.Type != "join" { @@ -827,6 +865,10 @@ func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*Hell continue } + if !checkMessageType(c.t, message, "event") { + continue + } + for len(message.Event.Join) > 0 { found := false loop: @@ -841,310 +883,265 @@ func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*Hell } } } - if !found { - return nil, nil, fmt.Errorf("expected one of the passed hello sessions, got %+v", message.Event.Join[0]) - } + c.assert.True(found, "expected one of the passed hello sessions, got %+v", message.Event.Join) } } - return received, ignored, nil + return received, ignored, true } -func (c *TestClient) RunUntilJoined(ctx context.Context, hello ...*HelloServerMessage) error { - _, unexpected, err := c.RunUntilJoinedAndReturn(ctx, hello...) - if err != nil { - return err - } - if len(unexpected) > 0 { - return fmt.Errorf("Received unexpected messages: %+v", unexpected) - } - return nil +func (c *TestClient) RunUntilJoined(ctx context.Context, hello ...*HelloServerMessage) bool { + _, unexpected, ok := c.RunUntilJoinedAndReturn(ctx, hello...) + return ok && c.assert.Empty(unexpected, "Received unexpected messages: %+v", unexpected) } -func (c *TestClient) checkMessageRoomLeave(message *ServerMessage, hello *HelloServerMessage) error { +func (c *TestClient) checkMessageRoomLeave(message *ServerMessage, hello *HelloServerMessage) bool { return c.checkMessageRoomLeaveSession(message, hello.SessionId) } -func (c *TestClient) checkMessageRoomLeaveSession(message *ServerMessage, sessionId string) error { - if err := checkMessageType(message, "event"); err != nil { - return err - } else if message.Event.Target != "room" { - return fmt.Errorf("Expected event target room, got %+v", message.Event) - } else if message.Event.Type != "leave" { - return fmt.Errorf("Expected event type leave, got %+v", message.Event) - } else if len(message.Event.Leave) != 1 { - return fmt.Errorf("Expected one leave event entry, got %+v", message.Event) - } else if message.Event.Leave[0] != sessionId { - return fmt.Errorf("Expected leave session id %+v, got %+v", - getPubliceSessionIdData(c.hub, sessionId), getPubliceSessionIdData(c.hub, message.Event.Leave[0])) - } - return nil +func (c *TestClient) checkMessageRoomLeaveSession(message *ServerMessage, sessionId string) bool { + return checkMessageType(c.t, message, "event") && + c.assert.Equal("room", message.Event.Target, "invalid target in %+v", message) && + c.assert.Equal("leave", message.Event.Type, "invalid event type in %+v", message) && + c.assert.Len(message.Event.Leave, 1, "invalid number of leave event entries: %+v", message.Event) && + c.assert.Equal(sessionId, message.Event.Leave[0], "invalid leave session: expected %+v, got %+v in %+v", + getPubliceSessionIdData(c.hub, sessionId), + getPubliceSessionIdData(c.hub, message.Event.Leave[0]), + message, + ) } -func (c *TestClient) RunUntilLeft(ctx context.Context, hello *HelloServerMessage) error { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return err - } - - return c.checkMessageRoomLeave(message, hello) +func (c *TestClient) RunUntilLeft(ctx context.Context, hello *HelloServerMessage) bool { + message, ok := c.RunUntilMessage(ctx) + return ok && c.checkMessageRoomLeave(message, hello) } -func checkMessageRoomlistUpdate(message *ServerMessage) (*RoomEventServerMessage, error) { - if err := checkMessageType(message, "event"); err != nil { - return nil, err - } else if message.Event.Target != "roomlist" { - return nil, fmt.Errorf("Expected event target room, got %+v", message.Event) - } else if message.Event.Type != "update" || message.Event.Update == nil { - return nil, fmt.Errorf("Expected event type update, got %+v", message.Event) - } else { - return message.Event.Update, nil +func checkMessageRoomlistUpdate(t *testing.T, message *ServerMessage) (*RoomEventServerMessage, bool) { + assert := assert.New(t) + if !checkMessageType(t, message, "event") || + !assert.Equal("roomlist", message.Event.Target, "invalid event target in %+v", message) || + !assert.Equal("update", message.Event.Type, "invalid event type in %+v", message) || + !assert.NotNil(message.Event.Update, "update missing in %+v", message) { + return nil, false } + + return message.Event.Update, true } -func (c *TestClient) RunUntilRoomlistUpdate(ctx context.Context) (*RoomEventServerMessage, error) { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return nil, err +func (c *TestClient) RunUntilRoomlistUpdate(ctx context.Context) (*RoomEventServerMessage, bool) { + message, ok := c.RunUntilMessage(ctx) + if !ok { + return nil, false } - return checkMessageRoomlistUpdate(message) + return checkMessageRoomlistUpdate(c.t, message) } -func checkMessageRoomlistDisinvite(message *ServerMessage) (*RoomDisinviteEventServerMessage, error) { - if err := checkMessageType(message, "event"); err != nil { - return nil, err - } else if message.Event.Target != "roomlist" { - return nil, fmt.Errorf("Expected event target room, got %+v", message.Event) - } else if message.Event.Type != "disinvite" || message.Event.Disinvite == nil { - return nil, fmt.Errorf("Expected event type disinvite, got %+v", message.Event) +func checkMessageRoomlistDisinvite(t *testing.T, message *ServerMessage) (*RoomDisinviteEventServerMessage, bool) { + assert := assert.New(t) + if !checkMessageType(t, message, "event") || + !assert.Equal("roomlist", message.Event.Target, "invalid event target in %+v", message) || + !assert.Equal("disinvite", message.Event.Type, "invalid event type in %+v", message) || + !assert.NotNil(message.Event.Disinvite, "disinvite missing in %+v", message) { + return nil, false } - return message.Event.Disinvite, nil + return message.Event.Disinvite, true } -func (c *TestClient) RunUntilRoomlistDisinvite(ctx context.Context) (*RoomDisinviteEventServerMessage, error) { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return nil, err +func (c *TestClient) RunUntilRoomlistDisinvite(ctx context.Context) (*RoomDisinviteEventServerMessage, bool) { + message, ok := c.RunUntilMessage(ctx) + if !ok { + return nil, false } - return checkMessageRoomlistDisinvite(message) + return checkMessageRoomlistDisinvite(c.t, message) } -func checkMessageParticipantsInCall(message *ServerMessage) (*RoomEventServerMessage, error) { - if err := checkMessageType(message, "event"); err != nil { - return nil, err - } else if message.Event.Target != "participants" { - return nil, fmt.Errorf("Expected event target participants, got %+v", message.Event) - } else if message.Event.Type != "update" || message.Event.Update == nil { - return nil, fmt.Errorf("Expected event type update, got %+v", message.Event) +func checkMessageParticipantsInCall(t *testing.T, message *ServerMessage) (*RoomEventServerMessage, bool) { + assert := assert.New(t) + if !checkMessageType(t, message, "event") || + !assert.Equal("participants", message.Event.Target, "invalid event target in %+v", message) || + !assert.Equal("update", message.Event.Type, "invalid event type in %+v", message) || + !assert.NotNil(message.Event.Update, "update missing in %+v", message) { + return nil, false } - return message.Event.Update, nil + return message.Event.Update, true } -func checkMessageParticipantFlags(message *ServerMessage) (*RoomFlagsServerMessage, error) { - if err := checkMessageType(message, "event"); err != nil { - return nil, err - } else if message.Event.Target != "participants" { - return nil, fmt.Errorf("Expected event target room, got %+v", message.Event) - } else if message.Event.Type != "flags" || message.Event.Flags == nil { - return nil, fmt.Errorf("Expected event type flags, got %+v", message.Event) +func checkMessageParticipantFlags(t *testing.T, message *ServerMessage) (*RoomFlagsServerMessage, bool) { + assert := assert.New(t) + if !checkMessageType(t, message, "event") || + !assert.Equal("participants", message.Event.Target, "invalid event target in %+v", message) || + !assert.Equal("flags", message.Event.Type, "invalid event type in %+v", message) || + !assert.NotNil(message.Event.Flags, "flags missing in %+v", message) { + return nil, false } - return message.Event.Flags, nil + return message.Event.Flags, true } -func checkMessageRoomMessage(message *ServerMessage) (*RoomEventMessage, error) { - if err := checkMessageType(message, "event"); err != nil { - return nil, err - } else if message.Event.Target != "room" { - return nil, fmt.Errorf("Expected event target room, got %+v", message.Event) - } else if message.Event.Type != "message" || message.Event.Message == nil { - return nil, fmt.Errorf("Expected event type message, got %+v", message.Event) +func checkMessageRoomMessage(t *testing.T, message *ServerMessage) (*RoomEventMessage, bool) { + assert := assert.New(t) + if !checkMessageType(t, message, "event") || + !assert.Equal("room", message.Event.Target, "invalid event target in %+v", message) || + !assert.Equal("message", message.Event.Type, "invalid event type in %+v", message) || + !assert.NotNil(message.Event.Message, "message missing in %+v", message) { + return nil, false } - return message.Event.Message, nil + return message.Event.Message, true } -func (c *TestClient) RunUntilRoomMessage(ctx context.Context) (*RoomEventMessage, error) { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return nil, err +func (c *TestClient) RunUntilRoomMessage(ctx context.Context) (*RoomEventMessage, bool) { + message, ok := c.RunUntilMessage(ctx) + if !ok { + return nil, false } - return checkMessageRoomMessage(message) + return checkMessageRoomMessage(c.t, message) } -func checkMessageError(message *ServerMessage, msgid string) error { - if err := checkMessageType(message, "error"); err != nil { - return err - } else if message.Error.Code != msgid { - return fmt.Errorf("Expected error \"%s\", got \"%s\" (%+v)", msgid, message.Error.Code, message.Error) - } - - return nil +func checkMessageError(t *testing.T, message *ServerMessage, msgid string) bool { + return checkMessageType(t, message, "error") && + assert.Equal(t, msgid, message.Error.Code, "invalid error code in %+v", message) } -func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) error { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return err - } - if err := checkUnexpectedClose(err); err != nil { - return err - } else if err := checkMessageType(message, "message"); err != nil { - return err +func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) bool { + message, ok := c.RunUntilMessage(ctx) + if !ok || !checkMessageType(c.t, message, "message") { + return false } var data StringMap - if err := json.Unmarshal(message.Message.Data, &data); err != nil { - return err + if err := json.Unmarshal(message.Message.Data, &data); !c.assert.NoError(err) { + return false } - if dt, ok := GetStringMapEntry[string](data, "type"); !ok || dt != "offer" { - return fmt.Errorf("expected data type offer, got %+v", data) + if dt, ok := GetStringMapEntry[string](data, "type"); !c.assert.True(ok, "no/invalid type in %+v", data) || + !c.assert.Equal("offer", dt, "invalid data type in %+v", data) { + return false } - payload, ok := ConvertStringMap(data["payload"]) - if !ok { - return fmt.Errorf("expected string map, got %+v", data["payload"]) - } - if pt, ok := GetStringMapEntry[string](payload, "type"); !ok || pt != "offer" { - return fmt.Errorf("expected payload type offer, got %+v", payload) - } - if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !ok || sdp != offer { - return fmt.Errorf("expected payload offer %s, got %+v", offer, payload) + if payload, ok := ConvertStringMap(data["payload"]); !c.assert.True(ok, "not a string map, got %+v", data["payload"]) { + return false + } else { + if pt, ok := GetStringMapEntry[string](payload, "type"); !c.assert.True(ok, "no/invalid type in payload %+v", payload) || + !c.assert.Equal("offer", pt, "invalid payload type in %+v", payload) { + return false + } + if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !c.assert.True(ok, "no/invalid sdp in payload %+v", payload) || + !c.assert.Equal(offer, sdp, "invalid payload offer") { + return false + } } - return nil + return true } -func (c *TestClient) RunUntilAnswer(ctx context.Context, answer string) error { +func (c *TestClient) RunUntilAnswer(ctx context.Context, answer string) bool { 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 - } - if err := checkUnexpectedClose(err); err != nil { - return err - } else if err := checkMessageType(message, "message"); err != nil { - return err +func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string, sender *MessageServerMessageSender) bool { + message, ok := c.RunUntilMessage(ctx) + if !ok || !checkMessageType(c.t, message, "message") { + return false } if sender != nil { - if err := checkMessageSender(c.hub, message.Message.Sender, sender.Type, &HelloServerMessage{ + if !checkMessageSender(c.t, c.hub, message.Message.Sender, sender.Type, &HelloServerMessage{ SessionId: sender.SessionId, UserId: sender.UserId, - }); err != nil { - return err + }) { + return false } } var data StringMap - if err := json.Unmarshal(message.Message.Data, &data); err != nil { - return err + if err := json.Unmarshal(message.Message.Data, &data); !c.assert.NoError(err) { + return false } - if dt, ok := GetStringMapEntry[string](data, "type"); !ok || dt != "answer" { - return fmt.Errorf("expected data type answer, got %+v", data) + if dt, ok := GetStringMapEntry[string](data, "type"); !c.assert.True(ok, "no/invalid type in %+v", data) || + !c.assert.Equal("answer", dt, "invalid data type in %+v", data) { + return false } - payload, ok := ConvertStringMap(data["payload"]) - if !ok { - return fmt.Errorf("expected string map, got %+v", payload) - } - if pt, ok := GetStringMapEntry[string](payload, "type"); !ok || pt != "answer" { - return fmt.Errorf("expected payload type answer, got %+v", payload) - } - if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !ok || sdp != answer { - return fmt.Errorf("expected payload answer %s, got %+v", answer, payload) + if payload, ok := ConvertStringMap(data["payload"]); !c.assert.True(ok, "not a string map, got %+v", data["payload"]) { + return false + } else { + if pt, ok := GetStringMapEntry[string](payload, "type"); !c.assert.True(ok, "no/invalid type in payload %+v", payload) || + !c.assert.Equal("answer", pt, "invalid payload type in %+v", payload) { + return false + } + if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !c.assert.True(ok, "no/invalid sdp in payload %+v", payload) || + !c.assert.Equal(answer, sdp, "invalid payload answer") { + return false + } } - return nil + return true } -func checkMessageTransientSet(t *testing.T, message *ServerMessage, key string, value any, oldValue any) error { - if err := checkMessageType(message, "transient"); err != nil { - return err - } - +func checkMessageTransientSet(t *testing.T, message *ServerMessage, key string, value any, oldValue any) bool { assert := assert.New(t) - assert.Equal("set", message.TransientData.Type, "invalid message type") - assert.Equal(key, message.TransientData.Key, "invalid key") - assert.EqualValues(value, message.TransientData.Value, "invalid value") - assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value") - return nil + return checkMessageType(t, message, "transient") && + assert.Equal("set", message.TransientData.Type, "invalid message type in %+v", message) && + assert.Equal(key, message.TransientData.Key, "invalid key in %+v", message) && + assert.EqualValues(value, message.TransientData.Value, "invalid value in %+v", message) && + assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value in %+v", message) } -func checkMessageTransientRemove(t *testing.T, message *ServerMessage, key string, oldValue any) error { - if err := checkMessageType(message, "transient"); err != nil { - return err - } - +func checkMessageTransientRemove(t *testing.T, message *ServerMessage, key string, oldValue any) bool { assert := assert.New(t) - assert.Equal("remove", message.TransientData.Type, "invalid message type") - assert.Equal(key, message.TransientData.Key, "invalid key") - assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value") - return nil + return checkMessageType(t, message, "transient") && + assert.Equal("remove", message.TransientData.Type, "invalid message type in %+v", message) && + assert.Equal(key, message.TransientData.Key, "invalid key in %+v", message) && + assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value in %+v", message) } -func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data StringMap) error { - if err := checkMessageType(message, "transient"); err != nil { - return err - } - +func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data StringMap) bool { assert := assert.New(t) - assert.Equal("initial", message.TransientData.Type, "invalid message type") - assert.EqualValues(data, message.TransientData.Data, "invalid initial data") - return nil + return checkMessageType(t, message, "transient") && + assert.Equal("initial", message.TransientData.Type, "invalid message type in %+v", message) && + assert.EqualValues(data, message.TransientData.Data, "invalid initial data in %+v", message) } -func checkMessageInCallAll(message *ServerMessage, roomId string, inCall int) error { - if err := checkMessageType(message, "event"); err != nil { - return err - } else if message.Event.Type != "update" { - return fmt.Errorf("Expected update event, got %+v", message.Event) - } else if message.Event.Target != "participants" { - return fmt.Errorf("Expected participants update event, got %+v", message.Event) - } else if message.Event.Update.RoomId != roomId { - return fmt.Errorf("Expected participants update event for room %s, got %+v", roomId, message.Event.Update) - } else if !message.Event.Update.All { - return fmt.Errorf("Expected participants update event for all, got %+v", message.Event.Update) - } else if !bytes.Equal(message.Event.Update.InCall, []byte(strconv.FormatInt(int64(inCall), 10))) { - return fmt.Errorf("Expected incall flags %d, got %+v", inCall, message.Event.Update) - } - return nil +func checkMessageInCallAll(t *testing.T, message *ServerMessage, roomId string, inCall int) bool { + assert := assert.New(t) + return checkMessageType(t, message, "event") && + assert.Equal("update", message.Event.Type, "invalid event type, got %+v", message.Event) && + assert.Equal("participants", message.Event.Target, "invalid event target, got %+v", message.Event) && + assert.Equal(roomId, message.Event.Update.RoomId, "invalid event update room id, got %+v", message.Event) && + assert.True(message.Event.Update.All, "expected participants update event for all, got %+v", message.Event) && + assert.EqualValues(strconv.FormatInt(int64(inCall), 10), message.Event.Update.InCall, "expected incall flags %d, got %+v", inCall, message.Event.Update) } -func checkMessageSwitchTo(message *ServerMessage, roomId string, details json.RawMessage) (*EventServerMessageSwitchTo, error) { - if err := checkMessageType(message, "event"); err != nil { - return nil, err - } else if message.Event.Type != "switchto" { - return nil, fmt.Errorf("Expected switchto event, got %+v", message.Event) - } else if message.Event.Target != "room" { - return nil, fmt.Errorf("Expected room switchto event, got %+v", message.Event) - } else if message.Event.SwitchTo.RoomId != roomId { - return nil, fmt.Errorf("Expected room switchto event for room %s, got %+v", roomId, message.Event) +func checkMessageSwitchTo(t *testing.T, message *ServerMessage, roomId string, details json.RawMessage) (*EventServerMessageSwitchTo, bool) { + assert := assert.New(t) + if !checkMessageType(t, message, "event") || + !assert.Equal("switchto", message.Event.Type, "invalid event type, got %+v", message.Event) || + !assert.Equal("room", message.Event.Target, "invalid event target, got %+v", message.Event) || + !assert.Equal(roomId, message.Event.SwitchTo.RoomId, "invalid event switchto room id, got %+v", message.Event) { + return nil, false } if details != nil { - if message.Event.SwitchTo.Details == nil || !bytes.Equal(details, message.Event.SwitchTo.Details) { - return nil, fmt.Errorf("Expected details %s, got %+v", string(details), message.Event) + if !assert.NotEmpty(message.Event.SwitchTo.Details, "details missing in %+v", message) || + !assert.Equal(details, message.Event.SwitchTo.Details, "invalid details, got %+v", message.Event) { + return nil, false } - } else if message.Event.SwitchTo.Details != nil { - return nil, fmt.Errorf("Expected no details, got %+v", message.Event) + } else if assert.Empty(message.Event.SwitchTo.Details, "expected no details in %+v", message) { + return nil, false } - return message.Event.SwitchTo, nil + return message.Event.SwitchTo, true } -func (c *TestClient) RunUntilSwitchTo(ctx context.Context, roomId string, details json.RawMessage) (*EventServerMessageSwitchTo, error) { - message, err := c.RunUntilMessage(ctx) - if err != nil { - return nil, err +func (c *TestClient) RunUntilSwitchTo(ctx context.Context, roomId string, details json.RawMessage) (*EventServerMessageSwitchTo, bool) { + message, ok := c.RunUntilMessage(ctx) + if !ok { + return nil, false } - return checkMessageSwitchTo(message, roomId, details) + return checkMessageSwitchTo(c.t, message, roomId, details) } diff --git a/testutils_test.go b/testutils_test.go index 8aaa076..dbf91e3 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -23,6 +23,7 @@ package signaling import ( "bytes" + "context" "io" "os" "os/signal" @@ -96,3 +97,38 @@ func dumpGoroutines(prefix string, w io.Writer) { profile := pprof.Lookup("goroutine") profile.WriteTo(w, 2) // nolint } + +func WaitForUsersJoined(ctx context.Context, t *testing.T, client1 *TestClient, hello1 *ServerMessage, client2 *TestClient, hello2 *ServerMessage) { + t.Helper() + // We will receive "joined" events for all clients. The ordering is not + // defined as messages are processed and sent by asynchronous event handlers. + client1.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) +} + +func MustSucceed1[T any, A1 any](t *testing.T, f func(a1 A1) (T, bool), a1 A1) T { + t.Helper() + result, ok := f(a1) + if !ok { + t.FailNow() + } + return result +} + +func MustSucceed2[T any, A1 any, A2 any](t *testing.T, f func(a1 A1, a2 A2) (T, bool), a1 A1, a2 A2) T { + t.Helper() + result, ok := f(a1, a2) + if !ok { + t.FailNow() + } + return result +} + +func MustSucceed3[T any, A1 any, A2 any, A3 any](t *testing.T, f func(a1 A1, a2 A2, a3 A3) (T, bool), a1 A1, a2 A2, a3 A3) T { + t.Helper() + result, ok := f(a1, a2, a3) + if !ok { + t.FailNow() + } + return result +} diff --git a/transient_data_test.go b/transient_data_test.go index 321a2ab..b60aecb 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -138,40 +138,29 @@ func Test_TransientMessages(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) - assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client1 := NewTestClient(t, server, hub) - defer client1.CloseWithBye() - require.NoError(client1.SendHello(testDefaultUserId + "1")) - hello1, err := client1.RunUntilHello(ctx) - require.NoError(err) + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") require.NoError(client1.SetTransientData("foo", "bar", 0)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageError(msg, "not_in_room")) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_in_room") } - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - hello2, err := client2.RunUntilHello(ctx) - require.NoError(err) + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") // Join room by id. roomId := "test-room" - roomMsg, err := client1.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Give message processing some time. time.Sleep(10 * time.Millisecond) - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) @@ -187,22 +176,22 @@ func Test_TransientMessages(t *testing.T) { session2.SetPermissions([]Permission{}) require.NoError(client2.SetTransientData("foo", "bar", 0)) - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageError(msg, "not_allowed")) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") } require.NoError(client1.SetTransientData("foo", "bar", 0)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(t, msg, "foo", "bar", nil)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", "bar", nil) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(t, msg, "foo", "bar", nil)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", "bar", nil) } require.NoError(client2.RemoveTransientData("foo")) - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageError(msg, "not_allowed")) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") } // Setting the same value is ignored by the server. @@ -210,31 +199,27 @@ func Test_TransientMessages(t *testing.T) { ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - if msg, err := client1.RunUntilMessage(ctx2); err == nil { - assert.Nil(msg, "Expected no payload") - } else { - require.ErrorIs(err, context.DeadlineExceeded) - } + client1.RunUntilErrorIs(ctx2, context.DeadlineExceeded) data := map[string]any{ "hello": "world", } require.NoError(client1.SetTransientData("foo", data, 0)) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(t, msg, "foo", data, "bar")) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", data, "bar") } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientSet(t, msg, "foo", data, "bar")) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", data, "bar") } require.NoError(client1.RemoveTransientData("foo")) - if msg, err := client1.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientRemove(t, msg, "foo", data)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "foo", data) } - if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientRemove(t, msg, "foo", data)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "foo", data) } // Removing a non-existing key is ignored by the server. @@ -242,43 +227,32 @@ func Test_TransientMessages(t *testing.T) { ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel3() - if msg, err := client1.RunUntilMessage(ctx3); err == nil { - assert.Nil(msg, "Expected no payload") - } else { - require.ErrorIs(err, context.DeadlineExceeded) - } + client1.RunUntilErrorIs(ctx3, context.DeadlineExceeded) require.NoError(client1.SetTransientData("abc", data, 10*time.Millisecond)) - client3 := NewTestClient(t, server, hub) - defer client3.CloseWithBye() - require.NoError(client3.SendHello(testDefaultUserId + "3")) - hello3, err := client3.RunUntilHello(ctx) - require.NoError(err) - - roomMsg, err = client3.JoinRoom(ctx, roomId) - require.NoError(err) + client3, hello3 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"3") + roomMsg = MustSucceed2(t, client3.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - _, ignored, err := client3.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello, hello3.Hello) - require.NoError(err) + _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello, hello3.Hello) + require.True(ok) var msg *ServerMessage if len(ignored) == 0 { - msg, err = client3.RunUntilMessage(ctx) - require.NoError(err) + msg = MustSucceed1(t, client3.RunUntilMessage, ctx) } else if len(ignored) == 1 { msg = ignored[0] } else { require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } - require.NoError(checkMessageTransientInitial(t, msg, StringMap{ + checkMessageTransientInitial(t, msg, StringMap{ "abc": data, - })) + }) time.Sleep(10 * time.Millisecond) - if msg, err = client3.RunUntilMessage(ctx); assert.NoError(err) { - require.NoError(checkMessageTransientRemove(t, msg, "abc", data)) + if msg, ok = client3.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "abc", data) } } diff --git a/virtualsession_test.go b/virtualsession_test.go index f9fe06b..3341cc7 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -52,23 +51,17 @@ func TestVirtualSession(t *testing.T) { defer clientInternal.CloseWithBye() require.NoError(clientInternal.SendHelloInternal()) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := clientInternal.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := clientInternal.RunUntilHello(ctx); ok { assert.Empty(hello.Hello.UserId) assert.NotEmpty(hello.Hello.SessionId) assert.NotEmpty(hello.Hello.ResumeId) } - hello, err := client.RunUntilHello(ctx) - assert.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. @@ -92,10 +85,9 @@ func TestVirtualSession(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgAdd)) - msg1, err := client.RunUntilMessage(ctx) - require.NoError(err) + msg1 := MustSucceed1(t, client.RunUntilMessage, ctx) // The public session id will be generated by the server, so don't check for it. - require.NoError(client.checkMessageJoinedSession(msg1, "", userId)) + require.True(client.checkMessageJoinedSession(msg1, "", userId)) sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session, "Could not get virtual session %s", sessionId) { @@ -105,9 +97,8 @@ func TestVirtualSession(t *testing.T) { } // Also a participants update event will be triggered for the virtual user. - msg2, err := client.RunUntilMessage(ctx) - require.NoError(err) - if updateMsg, err := checkMessageParticipantsInCall(msg2); assert.NoError(err) { + msg2 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) @@ -116,10 +107,8 @@ func TestVirtualSession(t *testing.T) { } } - msg3, err := client.RunUntilMessage(ctx) - require.NoError(err) - - if flagsMsg, err := checkMessageParticipantFlags(msg3); assert.NoError(err) { + msg3 := MustSucceed1(t, client.RunUntilMessage, ctx) + if flagsMsg, ok := checkMessageParticipantFlags(t, msg3); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flagsMsg.Flags) @@ -141,25 +130,16 @@ func TestVirtualSession(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgFlags)) - msg4, err := client.RunUntilMessage(ctx) - require.NoError(err) - - if flagsMsg, err := checkMessageParticipantFlags(msg4); assert.NoError(err) { + msg4 := MustSucceed1(t, client.RunUntilMessage, ctx) + if flagsMsg, ok := checkMessageParticipantFlags(t, msg4); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) assert.EqualValues(newFlags, flagsMsg.Flags) } // A new client will receive the initial flags of the virtual session. - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - _, err = client2.RunUntilHello(ctx) - require.NoError(err) - - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + client2, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) gotFlags := false @@ -202,10 +182,9 @@ func TestVirtualSession(t *testing.T) { data := "from-client-to-virtual" require.NoError(client.SendMessage(recipient, data)) - msg2, err = clientInternal.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageType(msg2, "message")) - require.NoError(checkMessageSender(hub, msg2.Message.Sender, "session", hello.Hello)) + msg2 = MustSucceed1(t, clientInternal.RunUntilMessage, ctx) + require.True(checkMessageType(t, msg2, "message")) + require.True(checkMessageSender(t, hub, msg2.Message.Sender, "session", hello.Hello)) if assert.NotNil(msg2.Message.Recipient) { assert.Equal("session", msg2.Message.Recipient.Type) @@ -231,8 +210,8 @@ func TestVirtualSession(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgRemove)) - if msg5, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client.checkMessageRoomLeaveSession(msg5, sessionId)) + if msg5, ok := client.RunUntilMessage(ctx); ok { + client.checkMessageRoomLeaveSession(msg5, sessionId) } } @@ -256,23 +235,17 @@ func TestVirtualSessionActorInformation(t *testing.T) { defer clientInternal.CloseWithBye() require.NoError(clientInternal.SendHelloInternal()) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := clientInternal.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := clientInternal.RunUntilHello(ctx); ok { assert.Empty(hello.Hello.UserId) assert.NotEmpty(hello.Hello.SessionId) assert.NotEmpty(hello.Hello.ResumeId) } - hello, err := client.RunUntilHello(ctx) - assert.NoError(err) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. @@ -300,10 +273,9 @@ func TestVirtualSessionActorInformation(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgAdd)) - msg1, err := client.RunUntilMessage(ctx) - require.NoError(err) + msg1 := MustSucceed1(t, client.RunUntilMessage, ctx) // The public session id will be generated by the server, so don't check for it. - require.NoError(client.checkMessageJoinedSession(msg1, "", userId)) + require.True(client.checkMessageJoinedSession(msg1, "", userId)) sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session, "Could not get virtual session %s", sessionId) { @@ -313,9 +285,8 @@ func TestVirtualSessionActorInformation(t *testing.T) { } // Also a participants update event will be triggered for the virtual user. - msg2, err := client.RunUntilMessage(ctx) - require.NoError(err) - if updateMsg, err := checkMessageParticipantsInCall(msg2); assert.NoError(err) { + msg2 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) @@ -324,10 +295,8 @@ func TestVirtualSessionActorInformation(t *testing.T) { } } - msg3, err := client.RunUntilMessage(ctx) - require.NoError(err) - - if flagsMsg, err := checkMessageParticipantFlags(msg3); assert.NoError(err) { + msg3 := MustSucceed1(t, client.RunUntilMessage, ctx) + if flagsMsg, ok := checkMessageParticipantFlags(t, msg3); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flagsMsg.Flags) @@ -349,25 +318,16 @@ func TestVirtualSessionActorInformation(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgFlags)) - msg4, err := client.RunUntilMessage(ctx) - require.NoError(err) - - if flagsMsg, err := checkMessageParticipantFlags(msg4); assert.NoError(err) { + msg4 := MustSucceed1(t, client.RunUntilMessage, ctx) + if flagsMsg, ok := checkMessageParticipantFlags(t, msg4); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) assert.EqualValues(newFlags, flagsMsg.Flags) } // A new client will receive the initial flags of the virtual session. - client2 := NewTestClient(t, server, hub) - defer client2.CloseWithBye() - require.NoError(client2.SendHello(testDefaultUserId + "2")) - - _, err = client2.RunUntilHello(ctx) - require.NoError(err) - - roomMsg, err = client2.JoinRoom(ctx, roomId) - require.NoError(err) + client2, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) gotFlags := false @@ -410,10 +370,9 @@ func TestVirtualSessionActorInformation(t *testing.T) { data := "from-client-to-virtual" require.NoError(client.SendMessage(recipient, data)) - msg2, err = clientInternal.RunUntilMessage(ctx) - require.NoError(err) - require.NoError(checkMessageType(msg2, "message")) - require.NoError(checkMessageSender(hub, msg2.Message.Sender, "session", hello.Hello)) + msg2 = MustSucceed1(t, clientInternal.RunUntilMessage, ctx) + require.True(checkMessageType(t, msg2, "message")) + require.True(checkMessageSender(t, hub, msg2.Message.Sender, "session", hello.Hello)) if assert.NotNil(msg2.Message.Recipient) { assert.Equal("session", msg2.Message.Recipient.Type) @@ -439,32 +398,31 @@ func TestVirtualSessionActorInformation(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgRemove)) - if msg5, err := client.RunUntilMessage(ctx); assert.NoError(err) { - assert.NoError(client.checkMessageRoomLeaveSession(msg5, sessionId)) + if msg5, ok := client.RunUntilMessage(ctx); ok { + client.checkMessageRoomLeaveSession(msg5, sessionId) } } -func checkHasEntryWithInCall(message *RoomEventServerMessage, sessionId string, entryType string, inCall int) error { +func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sessionId string, entryType string, inCall int) bool { + assert := assert.New(t) found := false for _, entry := range message.Users { - if sid, ok := entry["sessionId"].(string); ok && sid == sessionId { - if value, ok := entry[entryType].(bool); !ok || !value { - return fmt.Errorf("Expected %s user, got %+v", entryType, entry) + if sid, ok := GetStringMapEntry[string](entry, "sessionId"); ok && sid == sessionId { + if value, found := GetStringMapEntry[bool](entry, entryType); !assert.True(found, "entry %s not found or invalid in %+v", entryType, entry) || + !assert.True(value, "entry %s invalid in %+v", entryType, entry) { + return false } - if value, ok := entry["inCall"].(float64); !ok || int(value) != inCall { - return fmt.Errorf("Expected in call %d, got %+v", inCall, entry) + if value, found := GetStringMapEntry[float64](entry, "inCall"); !assert.True(found, "inCall not found or invalid in %+v", entry) || + !assert.EqualValues(value, inCall, "invalid inCall") { + return false } found = true break } } - if !found { - return fmt.Errorf("No user with session id %s found, got %+v", sessionId, message) - } - - return nil + return assert.True(found, "no user with session id %s found, got %+v", sessionId, message) } func TestVirtualSessionCustomInCall(t *testing.T) { @@ -490,30 +448,24 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } require.NoError(clientInternal.SendHelloInternalWithFeatures(features)) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - helloInternal, err := clientInternal.RunUntilHello(ctx) - if assert.NoError(err) { + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + helloInternal, ok := clientInternal.RunUntilHello(ctx) + if ok { assert.Empty(helloInternal.Hello.UserId) assert.NotEmpty(helloInternal.Hello.SessionId) assert.NotEmpty(helloInternal.Hello.ResumeId) } - roomMsg, err := clientInternal.JoinRoomWithRoomSession(ctx, roomId, "") - require.NoError(err) + roomMsg := MustSucceed3(t, clientInternal.JoinRoomWithRoomSession, ctx, roomId, "") require.Equal(roomId, roomMsg.Room.RoomId) - hello, err := client.RunUntilHello(ctx) - assert.NoError(err) - roomMsg, err = client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg = MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - if _, additional, err := clientInternal.RunUntilJoinedAndReturn(ctx, helloInternal.Hello, hello.Hello); assert.NoError(err) { + if _, additional, ok := clientInternal.RunUntilJoinedAndReturn(ctx, helloInternal.Hello, hello.Hello); ok { if assert.Len(additional, 1) && assert.Equal("event", additional[0].Type) { assert.Equal("participants", additional[0].Event.Target) assert.Equal("update", additional[0].Event.Type) @@ -521,7 +473,7 @@ func TestVirtualSessionCustomInCall(t *testing.T) { assert.EqualValues(0, additional[0].Event.Update.Users[0]["inCall"]) } } - assert.NoError(client.RunUntilJoined(ctx, helloInternal.Hello, hello.Hello)) + client.RunUntilJoined(ctx, helloInternal.Hello, hello.Hello) internalSessionId := "session1" userId := "user1" @@ -541,10 +493,9 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgAdd)) - msg1, err := client.RunUntilMessage(ctx) - require.NoError(err) + msg1 := MustSucceed1(t, client.RunUntilMessage, ctx) // The public session id will be generated by the server, so don't check for it. - require.NoError(client.checkMessageJoinedSession(msg1, "", userId)) + require.True(client.checkMessageJoinedSession(msg1, "", userId)) sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session) { @@ -554,20 +505,17 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } // Also a participants update event will be triggered for the virtual user. - msg2, err := client.RunUntilMessage(ctx) - require.NoError(err) - if updateMsg, err := checkMessageParticipantsInCall(msg2); assert.NoError(err) { + msg2 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) assert.Len(updateMsg.Users, 2) - assert.NoError(checkHasEntryWithInCall(updateMsg, sessionId, "virtual", 0)) - assert.NoError(checkHasEntryWithInCall(updateMsg, helloInternal.Hello.SessionId, "internal", 0)) + checkHasEntryWithInCall(t, updateMsg, sessionId, "virtual", 0) + checkHasEntryWithInCall(t, updateMsg, helloInternal.Hello.SessionId, "internal", 0) } - msg3, err := client.RunUntilMessage(ctx) - require.NoError(err) - - if flagsMsg, err := checkMessageParticipantFlags(msg3); assert.NoError(err) { + msg3 := MustSucceed1(t, client.RunUntilMessage, ctx) + if flagsMsg, ok := checkMessageParticipantFlags(t, msg3); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flagsMsg.Flags) @@ -585,13 +533,12 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgInCall)) - msg4, err := client.RunUntilMessage(ctx) - require.NoError(err) - if updateMsg, err := checkMessageParticipantsInCall(msg4); assert.NoError(err) { + msg4 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg4); ok { assert.Equal(roomId, updateMsg.RoomId) assert.Len(updateMsg.Users, 2) - assert.NoError(checkHasEntryWithInCall(updateMsg, sessionId, "virtual", 0)) - assert.NoError(checkHasEntryWithInCall(updateMsg, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio)) + checkHasEntryWithInCall(t, updateMsg, sessionId, "virtual", 0) + checkHasEntryWithInCall(t, updateMsg, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio) } // The internal session can change the "inCall" flags of a virtual session @@ -611,13 +558,12 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgInCall2)) - msg5, err := client.RunUntilMessage(ctx) - require.NoError(err) - if updateMsg, err := checkMessageParticipantsInCall(msg5); assert.NoError(err) { + msg5 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg5); ok { assert.Equal(roomId, updateMsg.RoomId) assert.Len(updateMsg.Users, 2) - assert.NoError(checkHasEntryWithInCall(updateMsg, sessionId, "virtual", newInCall)) - assert.NoError(checkHasEntryWithInCall(updateMsg, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio)) + checkHasEntryWithInCall(t, updateMsg, sessionId, "virtual", newInCall) + checkHasEntryWithInCall(t, updateMsg, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio) } } @@ -641,23 +587,17 @@ func TestVirtualSessionCleanup(t *testing.T) { defer clientInternal.CloseWithBye() require.NoError(clientInternal.SendHelloInternal()) - client := NewTestClient(t, server, hub) - defer client.CloseWithBye() - require.NoError(client.SendHello(testDefaultUserId)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - if hello, err := clientInternal.RunUntilHello(ctx); assert.NoError(err) { + if hello, ok := clientInternal.RunUntilHello(ctx); ok { assert.Empty(hello.Hello.UserId) assert.NotEmpty(hello.Hello.SessionId) assert.NotEmpty(hello.Hello.ResumeId) } - _, err = client.RunUntilHello(ctx) - assert.NoError(err) + client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) - roomMsg, err := client.JoinRoom(ctx, roomId) - require.NoError(err) + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. @@ -681,10 +621,9 @@ func TestVirtualSessionCleanup(t *testing.T) { } require.NoError(clientInternal.WriteJSON(msgAdd)) - msg1, err := client.RunUntilMessage(ctx) - require.NoError(err) + msg1 := MustSucceed1(t, client.RunUntilMessage, ctx) // The public session id will be generated by the server, so don't check for it. - require.NoError(client.checkMessageJoinedSession(msg1, "", userId)) + require.True(client.checkMessageJoinedSession(msg1, "", userId)) sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session) { @@ -694,9 +633,8 @@ func TestVirtualSessionCleanup(t *testing.T) { } // Also a participants update event will be triggered for the virtual user. - msg2, err := client.RunUntilMessage(ctx) - require.NoError(err) - if updateMsg, err := checkMessageParticipantsInCall(msg2); assert.NoError(err) { + msg2 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) @@ -705,10 +643,8 @@ func TestVirtualSessionCleanup(t *testing.T) { } } - msg3, err := client.RunUntilMessage(ctx) - require.NoError(err) - - if flagsMsg, err := checkMessageParticipantFlags(msg3); assert.NoError(err) { + msg3 := MustSucceed1(t, client.RunUntilMessage, ctx) + if flagsMsg, ok := checkMessageParticipantFlags(t, msg3); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) assert.EqualValues(FLAG_MUTED_SPEAKING, flagsMsg.Flags) @@ -717,7 +653,7 @@ func TestVirtualSessionCleanup(t *testing.T) { // The virtual sessions are closed when the parent session is deleted. clientInternal.CloseWithBye() - msg2, err = client.RunUntilMessage(ctx) - require.NoError(err) - assert.NoError(client.checkMessageRoomLeaveSession(msg2, sessionId)) + if msg2, ok := client.RunUntilMessage(ctx); ok { + client.checkMessageRoomLeaveSession(msg2, sessionId) + } } From 81b0e1a8dd1ca2bc2518ed655ae4e59bfe290d95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:17:49 +0000 Subject: [PATCH 165/549] Bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.22.0 to 1.23.0. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/v1.23.0/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.0) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-version: 1.23.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 97a514f..ce474b3 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/pion/ice/v4 v4.0.10 github.com/pion/sdp/v3 v3.0.15 github.com/pquerna/cachecontrol v0.2.0 - github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_golang v1.23.0 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.6.4 go.etcd.io/etcd/client/pkg/v3 v3.6.4 @@ -65,9 +65,9 @@ require ( github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/turn/v4 v4.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index 9b66e3c..f89024e 100644 --- a/go.sum +++ b/go.sum @@ -112,14 +112,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= From c444a740ba72b1c787e33664ed486901ca68b7bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:40:16 +0000 Subject: [PATCH 166/549] Bump github.com/nats-io/nats-server/v2 from 2.11.6 to 2.11.7 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.6 to 2.11.7. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.6...v2.11.7) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index ce474b3..5a68954 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.6 + github.com/nats-io/nats-server/v2 v2.11.7 github.com/nats-io/nats.go v1.44.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -87,10 +87,10 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect diff --git a/go.sum b/go.sum index f89024e..efa3908 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.6 h1:4VXRjbTUFKEB+7UoaKL3F5Y83xC7MxPoIONOnGgpkHw= -github.com/nats-io/nats-server/v2 v2.11.6/go.mod h1:2xoztlcb4lDL5Blh1/BiukkKELXvKQ5Vy29FPVRBUYs= +github.com/nats-io/nats-server/v2 v2.11.7 h1:lINWQ/Hb3cnaoHmWTjj/7WppZnaSh9C/1cD//nHCbms= +github.com/nats-io/nats-server/v2 v2.11.7/go.mod h1:DchDPVzAsAPqhqm7VLedX0L7hjnV/SYtlmsl9F8U53s= github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M= github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -184,8 +184,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -193,24 +193,24 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 8d4fac7181db99fd1934575d719179d72a413e56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:09:26 +0000 Subject: [PATCH 167/549] Bump google.golang.org/protobuf from 1.36.6 to 1.36.7 Bumps google.golang.org/protobuf from 1.36.6 to 1.36.7. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5a68954..495c8b9 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.74.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.7 ) require ( diff --git a/go.sum b/go.sum index efa3908..42166cc 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,8 @@ google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 42408e5b34d531afe4d442556c5feb00006b30c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:33:03 +0000 Subject: [PATCH 168/549] Bump actions/download-artifact from 4 to 5 in the artifacts group Bumps the artifacts group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 4 to 5 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts ... Signed-off-by: dependabot[bot] --- .github/workflows/tarball.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index d7aa549..6e578b8 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -58,7 +58,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Download tarball - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: tarball-${{ matrix.go-version }} From 1300d8d970f7ac78ef33d4a772811a0f8824d230 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 06:54:34 +0000 Subject: [PATCH 169/549] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-continentmap.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/command-rebase.yml | 2 +- .github/workflows/deploydocker.yml | 4 ++-- .github/workflows/docker-compose.yml | 4 ++-- .github/workflows/docker-janus.yml | 2 +- .github/workflows/docker.yml | 4 ++-- .github/workflows/generated.yml | 4 ++-- .github/workflows/govuln.yml | 2 +- .github/workflows/licensecheck.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/shellcheck.yml | 2 +- .github/workflows/tarball.yml | 2 +- .github/workflows/test.yml | 2 +- 14 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/check-continentmap.yml b/.github/workflows/check-continentmap.yml index eff3c10..6f76ff0 100644 --- a/.github/workflows/check-continentmap.yml +++ b/.github/workflows/check-continentmap.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Check continentmap run: make check-continentmap diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 045b013..f1b2c4d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/command-rebase.yml b/.github/workflows/command-rebase.yml index 7fd90cd..4e58a56 100644 --- a/.github/workflows/command-rebase.yml +++ b/.github/workflows/command-rebase.yml @@ -31,7 +31,7 @@ jobs: reaction-type: "+1" - name: Checkout the latest code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.COMMAND_BOT_PAT }} diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 2471061..0ff24dc 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Check Out Repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -92,7 +92,7 @@ jobs: steps: - name: Check Out Repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/docker-compose.yml b/.github/workflows/docker-compose.yml index 5f6ae64..826f86f 100644 --- a/.github/workflows/docker-compose.yml +++ b/.github/workflows/docker-compose.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Update docker-compose run: | @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Update docker-compose run: | diff --git a/.github/workflows/docker-janus.yml b/.github/workflows/docker-janus.yml index d96888e..ba27074 100644 --- a/.github/workflows/docker-janus.yml +++ b/.github/workflows/docker-janus.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9b75474..fd050de 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/generated.yml b/.github/workflows/generated.yml index 8a19e1b..824b5d3 100644 --- a/.github/workflows/generated.yml +++ b/.github/workflows/generated.yml @@ -53,7 +53,7 @@ jobs: contents: write continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: token: ${{ secrets.CODE_GENERATOR_PAT }} ref: ${{ github.event.pull_request.head.ref }} @@ -97,7 +97,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: "stable" diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index 6b3e4aa..3630d74 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -27,7 +27,7 @@ jobs: - "1.23" - "1.24" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/licensecheck.yml b/.github/workflows/licensecheck.yml index eec2df3..cb3f2ad 100644 --- a/.github/workflows/licensecheck.yml +++ b/.github/workflows/licensecheck.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install licensecheck run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0275269..ea419da 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: "1.23" @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: "stable" diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 7cadc02..42d0e87 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -20,7 +20,7 @@ jobs: name: shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: shellcheck run: | diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index d7aa549..f373a32 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -28,7 +28,7 @@ jobs: - "1.24" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 195c99f..df2b5a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: - "1.24" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} From 90d69a727a76650fb0e132a580121fabb89f285b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:47:27 +0000 Subject: [PATCH 170/549] Bump golang from 1.24-alpine to 1.25-alpine in /docker/proxy Bumps golang from 1.24-alpine to 1.25-alpine. --- updated-dependencies: - dependency-name: golang dependency-version: 1.25-alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker/proxy/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile index 048dbd7..02d2a9f 100644 --- a/docker/proxy/Dockerfile +++ b/docker/proxy/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM} golang:1.24-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.25-alpine AS builder ARG TARGETARCH ARG TARGETOS From 1a12fca8dde3aa5389b966af013fa7460fa2d010 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 21:00:19 +0000 Subject: [PATCH 171/549] Bump golang from 1.24-alpine to 1.25-alpine in /docker/server Bumps golang from 1.24-alpine to 1.25-alpine. --- updated-dependencies: - dependency-name: golang dependency-version: 1.25-alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker/server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 454794b..27686d6 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM} golang:1.24-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.25-alpine AS builder ARG TARGETARCH ARG TARGETOS From 67b52f4f18d9a7bb244ca09d402ee0c2991fa8db Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 08:05:28 +0200 Subject: [PATCH 172/549] CI: Test with Golang 1.25 --- .github/workflows/govuln.yml | 1 + .github/workflows/tarball.yml | 2 ++ .github/workflows/test.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index 3630d74..051117a 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -26,6 +26,7 @@ jobs: go-version: - "1.23" - "1.24" + - "1.25" steps: - uses: actions/checkout@v5 - uses: actions/setup-go@v5 diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 72a5218..f7f8679 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -26,6 +26,7 @@ jobs: go-version: - "1.23" - "1.24" + - "1.25" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -50,6 +51,7 @@ jobs: go-version: - "1.23" - "1.24" + - "1.25" runs-on: ubuntu-latest needs: [create] steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df2b5a1..3b013ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,7 @@ jobs: go-version: - "1.23" - "1.24" + - "1.25" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 From 9769d4fddafae58b3e0740fa6535ee6a9e388f33 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 08:17:09 +0200 Subject: [PATCH 173/549] Drop support for Go 1.23 --- .github/workflows/govuln.yml | 1 - .github/workflows/lint.yml | 4 ++-- .github/workflows/tarball.yml | 2 -- .github/workflows/test.yml | 1 - README.md | 2 +- go.mod | 2 +- 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index 051117a..cb050c0 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -24,7 +24,6 @@ jobs: strategy: matrix: go-version: - - "1.23" - "1.24" - "1.25" steps: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ea419da..cec1d86 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: - go-version: "1.23" + go-version: "1.24" - name: lint uses: golangci/golangci-lint-action@v8.0.0 @@ -49,7 +49,7 @@ jobs: - name: Check minimum supported version of Go run: | - go mod tidy -go=1.23.0 -compat=1.23.0 + go mod tidy -go=1.24.0 -compat=1.24.0 - name: Check go.mod / go.sum run: | diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index f7f8679..e3fee07 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -24,7 +24,6 @@ jobs: strategy: matrix: go-version: - - "1.23" - "1.24" - "1.25" runs-on: ubuntu-latest @@ -49,7 +48,6 @@ jobs: strategy: matrix: go-version: - - "1.23" - "1.24" - "1.25" runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b013ce..386b794 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,6 @@ jobs: strategy: matrix: go-version: - - "1.23" - "1.24" - "1.25" runs-on: ubuntu-latest diff --git a/README.md b/README.md index 8a75043..22508e5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ information on the API of the signaling server. The following tools are required for building the signaling server. - git -- go >= 1.23 +- go >= 1.24 - make Usually the last two versions of Go are supported. This follows the release diff --git a/go.mod b/go.mod index 495c8b9..d0bec7a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/strukturag/nextcloud-spreed-signaling -go 1.23.0 +go 1.24.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 From 639588f550d359f520fb9bf1df369337886135cc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 09:23:25 +0200 Subject: [PATCH 174/549] Modernize Go code and check from CI. --- .github/workflows/lint.yml | 14 +++++++++++++ allowed_ips.go | 2 +- api_signaling.go | 21 ++++++------------- api_signaling_test.go | 10 ++++----- backend_configuration_test.go | 14 ++++--------- backend_server.go | 2 +- backend_server_test.go | 2 -- backend_storage_static.go | 4 ++-- backoff.go | 6 +----- capabilities_test.go | 2 +- client/main.go | 2 +- clientsession.go | 17 +++++----------- clientsession_test.go | 1 - closer_test.go | 2 +- concurrentmap_test.go | 2 +- deferred_executor_test.go | 4 ++-- etcd_client.go | 2 +- flags_test.go | 2 +- geoip_test.go | 4 ---- grpc_client.go | 11 +++------- hub.go | 2 +- hub_test.go | 4 ++-- lru_test.go | 14 ++++++------- mcu_janus.go | 5 +---- mcu_janus_publisher.go | 3 +-- mcu_janus_test.go | 5 ++--- mcu_proxy.go | 38 ++++++++--------------------------- mcu_proxy_test.go | 4 ---- mcu_test.go | 6 ++---- natsclient_test.go | 2 +- notifier_test.go | 2 +- proxy/main.go | 2 +- proxy/proxy_remote.go | 5 +---- proxy/proxy_server_test.go | 2 +- proxy/proxy_tokens_static.go | 4 ++-- proxy_config_static.go | 8 +++----- room.go | 9 +++------ room_ping.go | 5 +---- server/main.go | 9 +++------ single_notifier_test.go | 2 +- testclient_test.go | 4 ++-- throttle.go | 5 +---- throttle_test.go | 4 ++-- transient_data.go | 5 ++--- 44 files changed, 99 insertions(+), 174 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cec1d86..4ea00e8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -37,6 +37,20 @@ jobs: args: --timeout=2m0s skip-cache: true + modernize: + name: golang + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: moderize + run: | + go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... + dependencies: name: dependencies runs-on: ubuntu-latest diff --git a/allowed_ips.go b/allowed_ips.go index f401ec6..669255e 100644 --- a/allowed_ips.go +++ b/allowed_ips.go @@ -83,7 +83,7 @@ func parseIPNet(s string) (*net.IPNet, error) { func ParseAllowedIps(allowed string) (*AllowedIps, error) { var allowedIps []*net.IPNet - for _, ip := range strings.Split(allowed, ",") { + for ip := range strings.SplitSeq(allowed, ",") { ip = strings.TrimSpace(ip) if ip != "" { i, err := parseIPNet(ip) diff --git a/api_signaling.go b/api_signaling.go index 693261c..16a6361 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -29,7 +29,6 @@ import ( "net" "net/url" "slices" - "sort" "strings" "time" @@ -290,27 +289,19 @@ func NewWelcomeServerMessage(version string, feature ...string) *WelcomeServerMe Features: feature, } if len(feature) > 0 { - sort.Strings(message.Features) + slices.Sort(message.Features) } return message } func (m *WelcomeServerMessage) AddFeature(feature ...string) { - newFeatures := make([]string, len(m.Features)) - copy(newFeatures, m.Features) + newFeatures := slices.Clone(m.Features) for _, feat := range feature { - found := false - for _, f := range newFeatures { - if f == feat { - found = true - break - } - } - if !found { + if !slices.Contains(newFeatures, feat) { newFeatures = append(newFeatures, feat) } } - sort.Strings(newFeatures) + slices.Sort(newFeatures) m.Features = newFeatures } @@ -318,8 +309,8 @@ func (m *WelcomeServerMessage) RemoveFeature(feature ...string) { newFeatures := make([]string, len(m.Features)) copy(newFeatures, m.Features) for _, feat := range feature { - idx := sort.SearchStrings(newFeatures, feat) - if idx < len(newFeatures) && newFeatures[idx] == feat { + idx, found := slices.BinarySearch(newFeatures, feat) + if found { newFeatures = append(newFeatures[:idx], newFeatures[idx+1:]...) } } diff --git a/api_signaling_test.go b/api_signaling_test.go index 3b08fc0..b384a7a 100644 --- a/api_signaling_test.go +++ b/api_signaling_test.go @@ -24,7 +24,7 @@ package signaling import ( "encoding/json" "fmt" - "sort" + "slices" "strings" "testing" @@ -399,12 +399,12 @@ func assertEqualStrings(t *testing.T, expected, result []string) { if expected == nil { expected = make([]string, 0) } else { - sort.Strings(expected) + slices.Sort(expected) } if result == nil { result = make([]string, 0) } else { - sort.Strings(result) + slices.Sort(result) } assert.Equal(t, expected, result) @@ -418,11 +418,11 @@ func Test_Welcome_AddRemoveFeature(t *testing.T) { msg.AddFeature("one", "two", "one") assertEqualStrings(t, []string{"one", "two"}, msg.Features) - assert.True(sort.StringsAreSorted(msg.Features), "features should be sorted, got %+v", msg.Features) + assert.True(slices.IsSorted(msg.Features), "features should be sorted, got %+v", msg.Features) msg.AddFeature("three") assertEqualStrings(t, []string{"one", "two", "three"}, msg.Features) - assert.True(sort.StringsAreSorted(msg.Features), "features should be sorted, got %+v", msg.Features) + assert.True(slices.IsSorted(msg.Features), "features should be sorted, got %+v", msg.Features) msg.RemoveFeature("three", "one") assertEqualStrings(t, []string{"two"}, msg.Features) diff --git a/backend_configuration_test.go b/backend_configuration_test.go index d51ba2f..f882acd 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -25,7 +25,7 @@ import ( "context" "net/url" "reflect" - "sort" + "slices" "strings" "testing" @@ -36,7 +36,6 @@ import ( func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) { for _, u := range valid_urls { - u := u t.Run(u, func(t *testing.T) { assert := assert.New(t) parsed, err := url.ParseRequestURI(u) @@ -49,7 +48,6 @@ func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, i }) } for _, u := range invalid_urls { - u := u t.Run(u, func(t *testing.T) { assert := assert.New(t) parsed, _ := url.ParseRequestURI(u) @@ -60,7 +58,6 @@ func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, i func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]string, invalid_urls []string) { for _, entry := range valid_urls { - entry := entry t.Run(entry[0], func(t *testing.T) { assert := assert.New(t) u := entry[0] @@ -75,7 +72,6 @@ func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]str }) } for _, u := range invalid_urls { - u := u t.Run(u, func(t *testing.T) { assert := assert.New(t) parsed, _ := url.ParseRequestURI(u) @@ -448,11 +444,9 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { } func sortBackends(backends []*Backend) []*Backend { - result := make([]*Backend, len(backends)) - copy(result, backends) - - sort.Slice(result, func(i, j int) bool { - return result[i].Id() < result[j].Id() + result := slices.Clone(backends) + slices.SortFunc(result, func(a, b *Backend) int { + return strings.Compare(a.Id(), b.Id()) }) return result } diff --git a/backend_server.go b/backend_server.go index f39eea8..25c3211 100644 --- a/backend_server.go +++ b/backend_server.go @@ -84,7 +84,7 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac turnvalid := 24 * time.Hour var turnserverslist []string - for _, s := range strings.Split(turnservers, ",") { + for s := range strings.SplitSeq(turnservers, ",") { s = strings.TrimSpace(s) if s != "" { turnserverslist = append(turnserverslist, s) diff --git a/backend_server_test.go b/backend_server_test.go index 5c138c6..a90b2d2 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -1317,7 +1317,6 @@ func TestBackendServer_StatsAllowedIps(t *testing.T) { } for _, addr := range allowed { - addr := addr t.Run(addr, func(t *testing.T) { t.Parallel() assert := assert.New(t) @@ -1357,7 +1356,6 @@ func TestBackendServer_StatsAllowedIps(t *testing.T) { } for _, addr := range notAllowed { - addr := addr t.Run(addr, func(t *testing.T) { t.Parallel() r := &http.Request{ diff --git a/backend_storage_static.go b/backend_storage_static.go index 1ca6751..fb88ef9 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -88,7 +88,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { // Old-style configuration, only hosts are configured and are using a common secret. allowMap := make(map[string]bool) - for _, u := range strings.Split(allowedUrls, ",") { + for u := range strings.SplitSeq(allowedUrls, ",") { u = strings.TrimSpace(u) if idx := strings.IndexByte(u, '/'); idx != -1 { log.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u) @@ -265,7 +265,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen func getConfiguredBackendIDs(backendIds string) (ids []string) { seen := make(map[string]bool) - for _, id := range strings.Split(backendIds, ",") { + for id := range strings.SplitSeq(backendIds, ",") { id = strings.TrimSpace(id) if id == "" { continue diff --git a/backoff.go b/backoff.go index 27af0f9..0a37075 100644 --- a/backoff.go +++ b/backoff.go @@ -71,10 +71,6 @@ func (b *exponentialBackoff) Wait(ctx context.Context) { waiter, cancel := b.getContextWithTimeout(ctx, b.nextWait) defer cancel() - b.nextWait = b.nextWait * 2 - if b.nextWait > b.maxWait { - b.nextWait = b.maxWait - } - + b.nextWait = min(b.nextWait*2, b.maxWait) <-waiter.Done() } diff --git a/capabilities_test.go b/capabilities_test.go index 02e7f21..e74e732 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -554,7 +554,7 @@ func TestConcurrentExpired(t *testing.T) { var numCached atomic.Uint32 var numFetched atomic.Uint32 var finished sync.WaitGroup - for i := 0; i < count; i++ { + for range count { finished.Add(1) go func() { defer finished.Done() diff --git a/client/main.go b/client/main.go index ef803fe..701ebf8 100644 --- a/client/main.go +++ b/client/main.go @@ -542,7 +542,7 @@ func main() { urls := make([]url.URL, 0) urlstrings := make([]string, 0) - for _, host := range strings.Split(*addr, ",") { + for host := range strings.SplitSeq(*addr, ",") { u := url.URL{ Scheme: "ws", Host: host, diff --git a/clientsession.go b/clientsession.go index 819c074..38188a6 100644 --- a/clientsession.go +++ b/clientsession.go @@ -27,6 +27,7 @@ import ( "fmt" "log" "net/url" + "slices" "strings" "sync" "sync/atomic" @@ -183,12 +184,7 @@ func (s *ClientSession) GetFeatures() []string { } func (s *ClientSession) HasFeature(feature string) bool { - for _, f := range s.features { - if f == feature { - return true - } - } - return false + return slices.Contains(s.features, feature) } // HasPermission checks if the session has the passed permissions. @@ -216,12 +212,9 @@ func (s *ClientSession) hasAnyPermissionLocked(permission ...Permission) bool { return false } - for _, p := range permission { - if s.hasPermissionLocked(p) { - return true - } - } - return false + return slices.ContainsFunc(permission, func(p Permission) bool { + return s.hasPermissionLocked(p) + }) } func (s *ClientSession) hasPermissionLocked(permission Permission) bool { diff --git a/clientsession_test.go b/clientsession_test.go index 81c3a06..c668bff 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -118,7 +118,6 @@ func Test_permissionsEqual(t *testing.T) { }, } for idx, test := range tests { - test := test t.Run(strconv.Itoa(idx), func(t *testing.T) { t.Parallel() equal := permissionsEqual(test.a, test.b) diff --git a/closer_test.go b/closer_test.go index ab23621..f51f3f3 100644 --- a/closer_test.go +++ b/closer_test.go @@ -33,7 +33,7 @@ func TestCloserMulti(t *testing.T) { var wg sync.WaitGroup count := 10 - for i := 0; i < count; i++ { + for range count { wg.Add(1) go func() { defer wg.Done() diff --git a/concurrentmap_test.go b/concurrentmap_test.go index 990a520..a723db5 100644 --- a/concurrentmap_test.go +++ b/concurrentmap_test.go @@ -76,7 +76,7 @@ func TestConcurrentStringStringMap(t *testing.T) { var wg sync.WaitGroup concurrency := 100 count := 1000 - for x := 0; x < concurrency; x++ { + for x := range concurrency { wg.Add(1) go func(x int) { defer wg.Done() diff --git a/deferred_executor_test.go b/deferred_executor_test.go index 8fa96ce..0e5f04b 100644 --- a/deferred_executor_test.go +++ b/deferred_executor_test.go @@ -71,7 +71,7 @@ func TestDeferredExecutor_Order(t *testing.T) { } done := make(chan struct{}) - for x := 0; x < 10; x++ { + for x := range 10 { e.Execute(getFunc(x)) } @@ -80,7 +80,7 @@ func TestDeferredExecutor_Order(t *testing.T) { }) <-done - for x := 0; x < 10; x++ { + for x := range 10 { assert.Equal(t, entries[x], x, "Unexpected at position %d", x) } } diff --git a/etcd_client.go b/etcd_client.go index e232fce..806f7b3 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -103,7 +103,7 @@ func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, opti func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { var endpoints []string if endpointsString := c.getConfigStringWithFallback(config, "endpoints"); endpointsString != "" { - for _, ep := range strings.Split(endpointsString, ",") { + for ep := range strings.SplitSeq(endpointsString, ",") { ep := strings.TrimSpace(ep) if ep != "" { endpoints = append(endpoints, ep) diff --git a/flags_test.go b/flags_test.go index 1665167..336e39c 100644 --- a/flags_test.go +++ b/flags_test.go @@ -54,7 +54,7 @@ func runConcurrentFlags(t *testing.T, count int, f func()) { start.Add(1) var ready sync.WaitGroup var done sync.WaitGroup - for i := 0; i < count; i++ { + for range count { done.Add(1) ready.Add(1) go func() { diff --git a/geoip_test.go b/geoip_test.go index 4d1a1e1..0aa1aa7 100644 --- a/geoip_test.go +++ b/geoip_test.go @@ -46,8 +46,6 @@ func testGeoLookupReader(t *testing.T, reader *GeoLookup) { } for ip, expected := range tests { - ip := ip - expected := expected t.Run(ip, func(t *testing.T) { country, err := reader.LookupCountry(net.ParseIP(ip)) if !assert.NoError(t, err, "Could not lookup %s", ip) { @@ -113,8 +111,6 @@ func TestGeoLookupContinent(t *testing.T) { } for country, expected := range tests { - country := country - expected := expected t.Run(country, func(t *testing.T) { continents := LookupContinents(country) if !assert.Equal(t, len(expected), len(continents), "Continents didn't match for %s: got %s, expected %s", country, continents, expected) { diff --git a/grpc_client.go b/grpc_client.go index fac9d0c..a27f1b0 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -29,6 +29,7 @@ import ( "io" "log" "net" + "slices" "strings" "sync" "sync/atomic" @@ -531,13 +532,7 @@ func (c *GrpcClients) isClientAvailable(target string, client *GrpcClient) bool return false } - for _, entry := range entries.clients { - if entry == client { - return true - } - } - - return false + return slices.Contains(entries.clients, client) } func (c *GrpcClients) getServerIdWithTimeout(ctx context.Context, client *GrpcClient) (string, string, error) { @@ -617,7 +612,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo } targets, _ := config.GetString("grpc", "targets") - for _, target := range strings.Split(targets, ",") { + for target := range strings.SplitSeq(targets, ",") { target = strings.TrimSpace(target) if target == "" { continue diff --git a/hub.go b/hub.go index fcd227e..080dbe6 100644 --- a/hub.go +++ b/hub.go @@ -284,7 +284,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer } decodeCaches := make([]*LruCache, 0, numDecodeCaches) - for i := 0; i < numDecodeCaches; i++ { + for range numDecodeCaches { decodeCaches = append(decodeCaches, NewLruCache(decodeCacheSize)) } diff --git a/hub_test.go b/hub_test.go index c279ba0..f3c2b44 100644 --- a/hub_test.go +++ b/hub_test.go @@ -802,7 +802,7 @@ func TestWebsocketFeatures(t *testing.T) { assert.True(strings.HasPrefix(serverHeader, "nextcloud-spreed-signaling/"), "expected valid server header, got \"%s\"", serverHeader) features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) - for _, f := range strings.Split(features, ",") { + for f := range strings.SplitSeq(features, ",") { f = strings.TrimSpace(f) if f != "" { _, found := featuresList[f] @@ -1274,7 +1274,7 @@ func TestSessionIdsUnordered(t *testing.T) { var mu sync.Mutex publicSessionIds := make([]string, 0) var wg sync.WaitGroup - for i := 0; i < 20; i++ { + for range 20 { wg.Add(1) go func() { defer wg.Done() diff --git a/lru_test.go b/lru_test.go index 98fbb66..93445ac 100644 --- a/lru_test.go +++ b/lru_test.go @@ -32,12 +32,12 @@ func TestLruUnbound(t *testing.T) { assert := assert.New(t) lru := NewLruCache(0) count := 10 - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) lru.Set(key, i) } assert.Equal(count, lru.Len()) - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) if value := lru.Get(key); assert.NotNil(value, "No value found for %s", key) { assert.EqualValues(i, value) @@ -46,7 +46,7 @@ func TestLruUnbound(t *testing.T) { // The first key ("0") is now the oldest. lru.RemoveOldest() assert.Equal(count-1, lru.Len()) - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i == 0 { @@ -76,7 +76,7 @@ func TestLruUnbound(t *testing.T) { // The last key ("9") is now the oldest. lru.RemoveOldest() assert.Equal(count-2, lru.Len()) - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i == 0 || i == count-1 { @@ -91,7 +91,7 @@ func TestLruUnbound(t *testing.T) { key := fmt.Sprintf("%d", count/2) lru.Remove(key) assert.Equal(count-3, lru.Len()) - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i == 0 || i == count-1 || i == count/2 { @@ -108,13 +108,13 @@ func TestLruBound(t *testing.T) { size := 2 lru := NewLruCache(size) count := 10 - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) lru.Set(key, i) } assert.Equal(size, lru.Len()) // Only the last "size" entries have been stored. - for i := 0; i < count; i++ { + for i := range count { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i < count-size { diff --git a/mcu_janus.go b/mcu_janus.go index 1383fcd..a9f095b 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -339,10 +339,7 @@ func (m *mcuJanus) scheduleReconnect(err error) { log.Printf("Reconnect to Janus gateway failed (%s), reconnecting in %s", err, m.reconnectInterval) } - m.reconnectInterval = m.reconnectInterval * 2 - if m.reconnectInterval > maxReconnectInterval { - m.reconnectInterval = maxReconnectInterval - } + m.reconnectInterval = min(m.reconnectInterval*2, maxReconnectInterval) } func (m *mcuJanus) ConnectionInterrupted() { diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 283d274..11253b7 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -250,8 +250,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli } func getFmtpValue(fmtp string, key string) (string, bool) { - parts := strings.Split(fmtp, ";") - for _, part := range parts { + for part := range strings.SplitSeq(fmtp, ";") { kv := strings.SplitN(part, "=", 2) if len(kv) != 2 { continue diff --git a/mcu_janus_test.go b/mcu_janus_test.go index e0f923d..1c08884 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "maps" "strings" "sync" "sync/atomic" @@ -101,9 +102,7 @@ func NewTestJanusGateway(t *testing.T) *TestJanusGateway { func (g *TestJanusGateway) registerHandlers(handlers map[string]TestJanusHandler) { g.mu.Lock() defer g.mu.Unlock() - for name, handler := range handlers { - g.handlers[name] = handler - } + maps.Copy(g.handlers, handlers) } func (g *TestJanusGateway) Info(ctx context.Context) (*InfoMsg, error) { diff --git a/mcu_proxy.go b/mcu_proxy.go index aea111c..556b034 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -35,7 +35,6 @@ import ( "net/url" "os" "slices" - "sort" "strconv" "strings" "sync" @@ -766,10 +765,7 @@ func (c *mcuProxyConnection) scheduleReconnect() { jitter := rand.Int64N(interval) - (interval / 2) c.reconnectTimer.Reset(time.Duration(interval + jitter)) - interval = interval * 2 - if interval > int64(maxReconnectInterval) { - interval = int64(maxReconnectInterval) - } + interval = min(interval*2, int64(maxReconnectInterval)) c.reconnectInterval.Store(interval) } @@ -1569,7 +1565,7 @@ func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { } var values []string - for _, v := range strings.Split(value, ",") { + for v := range strings.SplitSeq(value, ",") { v = strings.ToUpper(strings.TrimSpace(v)) if !IsValidContinent(v) { log.Printf("Ignore unknown continent %s for override %s", v, option) @@ -1872,32 +1868,14 @@ func (m *mcuProxy) setContinentsMap(continentsMap map[string][]string) { type mcuProxyConnectionsList []*mcuProxyConnection -func (l mcuProxyConnectionsList) Len() int { - return len(l) -} - -func (l mcuProxyConnectionsList) Less(i, j int) bool { - return l[i].Load() < l[j].Load() -} - -func (l mcuProxyConnectionsList) Swap(i, j int) { - l[i], l[j] = l[j], l[i] -} - -func (l mcuProxyConnectionsList) Sort() { - sort.Sort(l) -} - func ContinentsOverlap(a, b []string) bool { if len(a) == 0 || len(b) == 0 { return false } for _, checkA := range a { - for _, checkB := range b { - if checkA == checkB { - return true - } + if slices.Contains(b, checkA) { + return true } } return false @@ -1958,10 +1936,10 @@ func (m *mcuProxy) getSortedConnections(initiator McuInitiator) []*mcuProxyConne if m.connRequests.Add(1)%connectionSortRequests == 0 || m.nextSort.Load() <= now { m.nextSort.Store(now + int64(connectionSortInterval)) - sorted := make(mcuProxyConnectionsList, len(connections)) - copy(sorted, connections) - - sorted.Sort() + sorted := slices.Clone(connections) + slices.SortFunc(sorted, func(a, b *mcuProxyConnection) int { + return int(a.Load() - b.Load()) + }) m.connectionsMu.Lock() m.connections = sorted diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 18571c8..6ddde24 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -106,8 +106,6 @@ func Test_sortConnectionsForCountry(t *testing.T) { } for country, test := range testcases { - country := country - test := test t.Run(country, func(t *testing.T) { sorted := sortConnectionsForCountry(test[0], country, nil) for idx, conn := range sorted { @@ -178,8 +176,6 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { "OC": {"AS", "NA"}, } for country, test := range testcases { - country := country - test := test t.Run(country, func(t *testing.T) { sorted := sortConnectionsForCountry(test[0], country, continentMap) for idx, conn := range sorted { diff --git a/mcu_test.go b/mcu_test.go index 2dcf9ef..defebda 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -26,6 +26,7 @@ import ( "errors" "fmt" "log" + "maps" "sync" "sync/atomic" @@ -107,10 +108,7 @@ func (m *TestMCU) GetPublishers() map[string]*TestMCUPublisher { m.mu.Lock() defer m.mu.Unlock() - result := make(map[string]*TestMCUPublisher, len(m.publishers)) - for id, pub := range m.publishers { - result[id] = pub - } + result := maps.Clone(m.publishers) return result } diff --git a/natsclient_test.go b/natsclient_test.go index bc92bfb..473a543 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -94,7 +94,7 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { } }() <-ready - for i := int32(0); i < maxPublish; i++ { + for range maxPublish { assert.NoError(client.Publish("foo", []byte("hello"))) // Allow NATS goroutines to process messages. diff --git a/notifier_test.go b/notifier_test.go index c40d7f0..5313275 100644 --- a/notifier_test.go +++ b/notifier_test.go @@ -115,7 +115,7 @@ func TestNotifierDuplicate(t *testing.T) { var wgStart sync.WaitGroup var wgEnd sync.WaitGroup - for i := 0; i < 2; i++ { + for range 2 { wgStart.Add(1) wgEnd.Add(1) diff --git a/proxy/main.go b/proxy/main.go index 706f220..260805b 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -100,7 +100,7 @@ func main() { writeTimeout = defaultWriteTimeout } - for _, address := range strings.Split(addr, " ") { + for address := range strings.SplitSeq(addr, " ") { go func(address string) { log.Println("Listening on", address) listener, err := net.Listen("tcp", address) diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index b16ade2..150403b 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -176,10 +176,7 @@ func (c *RemoteConnection) scheduleReconnect() { jitter := rand.Int64N(interval) - (interval / 2) c.reconnectTimer.Reset(time.Duration(interval + jitter)) - interval = interval * 2 - if interval > int64(maxReconnectInterval) { - interval = int64(maxReconnectInterval) - } + interval = min(interval*2, int64(maxReconnectInterval)) c.reconnectInterval.Store(interval) } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 95dba73..43d4139 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -316,7 +316,7 @@ func TestWebsocketFeatures(t *testing.T) { } features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) - for _, f := range strings.Split(features, ",") { + for f := range strings.SplitSeq(features, ",") { f = strings.TrimSpace(f) if f != "" { if _, found := featuresList[f]; found { diff --git a/proxy/proxy_tokens_static.go b/proxy/proxy_tokens_static.go index 9ad7917..37c23e4 100644 --- a/proxy/proxy_tokens_static.go +++ b/proxy/proxy_tokens_static.go @@ -25,7 +25,7 @@ import ( "fmt" "log" "os" - "sort" + "slices" "sync/atomic" "github.com/dlintw/goconf" @@ -110,7 +110,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error for k := range tokenKeys { keyIds = append(keyIds, k) } - sort.Strings(keyIds) + slices.Sort(keyIds) log.Printf("Enabled token keys: %v", keyIds) } t.setTokenKeys(tokenKeys) diff --git a/proxy_config_static.go b/proxy_config_static.go index bea279f..3731ddb 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -24,6 +24,7 @@ package signaling import ( "errors" "log" + "maps" "net" "net/url" "strings" @@ -81,13 +82,10 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool p.dnsDiscovery = dnsDiscovery } - remove := make(map[string]*ipList) - for u, ips := range p.connectionsMap { - remove[u] = ips - } + remove := maps.Clone(p.connectionsMap) mcuUrl, _ := GetStringOptionWithEnv(config, "mcu", "url") - for _, u := range strings.Split(mcuUrl, " ") { + for u := range strings.SplitSeq(mcuUrl, " ") { u = strings.TrimSpace(u) if u == "" { continue diff --git a/room.go b/room.go index 4687aec..e1790b9 100644 --- a/room.go +++ b/room.go @@ -27,6 +27,7 @@ import ( "encoding/json" "fmt" "log" + "maps" "net/url" "strconv" "strings" @@ -615,15 +616,11 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[string]*Inter if internal == nil { internal = make(map[string]*InternalSessionData, len(clientInternal)) } - for sid, s := range clientInternal { - internal[sid] = s - } + maps.Copy(internal, clientInternal) if virtual == nil { virtual = make(map[string]*VirtualSessionData, len(clientVirtual)) } - for sid, s := range clientVirtual { - virtual[sid] = s - } + maps.Copy(virtual, clientVirtual) }(client) } wg.Wait() diff --git a/room_ping.go b/room_ping.go index 5902068..46a7355 100644 --- a/room_ping.go +++ b/room_ping.go @@ -167,10 +167,7 @@ func (p *RoomPing) sendPingsDirect(ctx context.Context, roomId string, url *url. func (p *RoomPing) sendPingsCombined(url *url.URL, entries []BackendPingEntry, limit int, timeout time.Duration) { total := len(entries) for idx := 0; idx < total; idx += limit { - end := idx + limit - if end > total { - end = total - } + end := min(idx+limit, total) tosend := entries[idx:end] ctx, cancel := context.WithTimeout(context.Background(), timeout) diff --git a/server/main.go b/server/main.go index 97becd6..c8b2a66 100644 --- a/server/main.go +++ b/server/main.go @@ -289,10 +289,7 @@ func main() { } case <-mcuRetryTimer.C: // Retry connection - mcuRetry = mcuRetry * 2 - if mcuRetry > maxMcuRetry { - mcuRetry = maxMcuRetry - } + mcuRetry = min(mcuRetry*2, maxMcuRetry) } } if mcu != nil { @@ -344,7 +341,7 @@ func main() { if writeTimeout <= 0 { writeTimeout = defaultWriteTimeout } - for _, address := range strings.Split(saddr, " ") { + for address := range strings.SplitSeq(saddr, " ") { go func(address string) { log.Println("Listening on", address) listener, err := createTLSListener(address, cert, key) @@ -377,7 +374,7 @@ func main() { writeTimeout = defaultWriteTimeout } - for _, address := range strings.Split(addr, " ") { + for address := range strings.SplitSeq(addr, " ") { go func(address string) { log.Println("Listening on", address) listener, err := createListener(address) diff --git a/single_notifier_test.go b/single_notifier_test.go index 7b95beb..a26d03a 100644 --- a/single_notifier_test.go +++ b/single_notifier_test.go @@ -115,7 +115,7 @@ func TestSingleNotifierDuplicate(t *testing.T) { var wgStart sync.WaitGroup var wgEnd sync.WaitGroup - for i := 0; i < 2; i++ { + for range 2 { wgStart.Add(1) wgEnd.Add(1) diff --git a/testclient_test.go b/testclient_test.go index d5bcf5a..4165265 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -636,7 +636,7 @@ func (c *TestClient) DrainMessages(ctx context.Context) error { return err case <-c.messageChan: n := len(c.messageChan) - for i := 0; i < n; i++ { + for range n { <-c.messageChan } case <-ctx.Done(): @@ -657,7 +657,7 @@ func (c *TestClient) GetPendingMessages(ctx context.Context) ([]*ServerMessage, } result = append(result, &m) n := len(c.messageChan) - for i := 0; i < n; i++ { + for range n { var m ServerMessage msg = <-c.messageChan if err := json.Unmarshal(msg, &m); err != nil { diff --git a/throttle.go b/throttle.go index dcd5332..8c96eac 100644 --- a/throttle.go +++ b/throttle.go @@ -257,10 +257,7 @@ func (t *memoryThrottler) getDelay(count int) time.Duration { return maxThrottleDelay } - delay := time.Duration(100*intPow(2, count)) * time.Millisecond - if delay > maxThrottleDelay { - delay = maxThrottleDelay - } + delay := min(time.Duration(100*intPow(2, count))*time.Millisecond, maxThrottleDelay) return delay } diff --git a/throttle_test.go b/throttle_test.go index a95102a..20a3599 100644 --- a/throttle_test.go +++ b/throttle_test.go @@ -145,7 +145,7 @@ func TestThrottler_Bruteforce(t *testing.T) { ctx := context.Background() - for i := 0; i < maxBruteforceAttempts; i++ { + for i := range maxBruteforceAttempts { timing.now = timing.now.Add(time.Millisecond) throttle, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -292,7 +292,7 @@ func TestThrottler_Negative(t *testing.T) { ctx := context.Background() - for i := 0; i < maxBruteforceAttempts*10; i++ { + for i := range maxBruteforceAttempts * 10 { timing.now = timing.now.Add(time.Millisecond) throttle, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") if err != nil { diff --git a/transient_data.go b/transient_data.go index 59bfd97..ac3ffb9 100644 --- a/transient_data.go +++ b/transient_data.go @@ -22,6 +22,7 @@ package signaling import ( + "maps" "reflect" "sync" "time" @@ -256,8 +257,6 @@ func (t *TransientData) GetData() StringMap { defer t.mu.Unlock() result := make(StringMap) - for k, v := range t.data { - result[k] = v - } + maps.Copy(result, t.data) return result } From 0b4fc5f7c7b1c44b9131310c3fef4e22901e15b3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 09:30:51 +0200 Subject: [PATCH 175/549] Fix name of modernize CI job. --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4ea00e8..78079d7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -38,7 +38,7 @@ jobs: skip-cache: true modernize: - name: golang + name: modernize runs-on: ubuntu-latest continue-on-error: true steps: From c8444b4ecd6913af8b2a5277b1e5b0413bec4933 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 09:41:57 +0200 Subject: [PATCH 176/549] Test "HasAnyPermission" method. --- session_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/session_test.go b/session_test.go index b157c80..677fd1d 100644 --- a/session_test.go +++ b/session_test.go @@ -30,9 +30,15 @@ import ( func assertSessionHasPermission(t *testing.T, session Session, permission Permission) { t.Helper() assert.True(t, session.HasPermission(permission), "Session %s doesn't have permission %s", session.PublicId(), permission) + if cs, ok := session.(*ClientSession); ok { + assert.True(t, cs.HasAnyPermission(permission), "Session %s doesn't have permission %s", session.PublicId(), permission) + } } func assertSessionHasNotPermission(t *testing.T, session Session, permission Permission) { t.Helper() assert.False(t, session.HasPermission(permission), "Session %s has permission %s but shouldn't", session.PublicId(), permission) + if cs, ok := session.(*ClientSession); ok { + assert.False(t, cs.HasAnyPermission(permission), "Session %s has permission %s but shouldn't", session.PublicId(), permission) + } } From d1a57b34af59144d90d42d24919d776795b645bc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 14:36:09 +0200 Subject: [PATCH 177/549] Use "maps.Equal" instead of custom implementation. --- clientsession.go | 23 +---------- clientsession_test.go | 96 ------------------------------------------- 2 files changed, 2 insertions(+), 117 deletions(-) diff --git a/clientsession.go b/clientsession.go index 38188a6..1e4966b 100644 --- a/clientsession.go +++ b/clientsession.go @@ -26,6 +26,7 @@ import ( "encoding/json" "fmt" "log" + "maps" "net/url" "slices" "strings" @@ -232,26 +233,6 @@ func (s *ClientSession) hasPermissionLocked(permission Permission) bool { return false } -func permissionsEqual(a, b map[Permission]bool) bool { - if a == nil && b == nil { - return true - } else if a != nil && b == nil { - return false - } else if a == nil && b != nil { - return false - } - if len(a) != len(b) { - return false - } - - for k, v1 := range a { - if v2, found := b[k]; !found || v1 != v2 { - return false - } - } - return true -} - func (s *ClientSession) SetPermissions(permissions []Permission) { var p map[Permission]bool for _, permission := range permissions { @@ -263,7 +244,7 @@ func (s *ClientSession) SetPermissions(permissions []Permission) { s.mu.Lock() defer s.mu.Unlock() - if s.supportsPermissions && permissionsEqual(s.permissions, p) { + if s.supportsPermissions && maps.Equal(s.permissions, p) { return } diff --git a/clientsession_test.go b/clientsession_test.go index c668bff..daf98cc 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -24,108 +24,12 @@ package signaling import ( "context" "net/url" - "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var ( - equalStrings = map[bool]string{ - true: "equal", - false: "not equal", - } -) - -type EqualTestData struct { - a map[Permission]bool - b map[Permission]bool - equal bool -} - -func Test_permissionsEqual(t *testing.T) { - tests := []EqualTestData{ - { - a: nil, - b: nil, - equal: true, - }, - { - a: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - }, - b: nil, - equal: false, - }, - { - a: nil, - b: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - }, - equal: false, - }, - { - a: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - }, - b: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - }, - equal: true, - }, - { - a: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - PERMISSION_MAY_PUBLISH_SCREEN: true, - }, - b: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - }, - equal: false, - }, - { - a: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - }, - b: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - PERMISSION_MAY_PUBLISH_SCREEN: true, - }, - equal: false, - }, - { - a: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - PERMISSION_MAY_PUBLISH_SCREEN: true, - }, - b: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - PERMISSION_MAY_PUBLISH_SCREEN: true, - }, - equal: true, - }, - { - a: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - PERMISSION_MAY_PUBLISH_SCREEN: true, - }, - b: map[Permission]bool{ - PERMISSION_MAY_PUBLISH_MEDIA: true, - PERMISSION_MAY_PUBLISH_SCREEN: false, - }, - equal: false, - }, - } - for idx, test := range tests { - t.Run(strconv.Itoa(idx), func(t *testing.T) { - t.Parallel() - equal := permissionsEqual(test.a, test.b) - assert.Equal(t, test.equal, equal, "Expected %+v to be %s to %+v but was %s", test.a, equalStrings[test.equal], test.b, equalStrings[equal]) - }) - } -} - func TestBandwidth_Client(t *testing.T) { t.Parallel() CatchLogForTest(t) From 3d0db426fa76f7ecede0c941aba8251cab1909b0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 14:40:48 +0200 Subject: [PATCH 178/549] Use methods on atomic class instead of custom implementation. --- flags.go | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/flags.go b/flags.go index 3f67283..e089e1e 100644 --- a/flags.go +++ b/flags.go @@ -30,46 +30,17 @@ type Flags struct { } func (f *Flags) Add(flags uint32) bool { - for { - old := f.flags.Load() - if old&flags == flags { - // Flags already set. - return false - } - newFlags := old | flags - if f.flags.CompareAndSwap(old, newFlags) { - return true - } - // Another thread updated the flags while we were checking, retry. - } + old := f.flags.Or(flags) + return old&flags != flags } func (f *Flags) Remove(flags uint32) bool { - for { - old := f.flags.Load() - if old&flags == 0 { - // Flags not set. - return false - } - newFlags := old & ^flags - if f.flags.CompareAndSwap(old, newFlags) { - return true - } - // Another thread updated the flags while we were checking, retry. - } + old := f.flags.And(^flags) + return old&flags != 0 } func (f *Flags) Set(flags uint32) bool { - for { - old := f.flags.Load() - if old == flags { - return false - } - - if f.flags.CompareAndSwap(old, flags) { - return true - } - } + return f.flags.Swap(flags) != flags } func (f *Flags) Get() uint32 { From 210aec62db53f814041ea84296cc4d70db288508 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 15:22:40 +0200 Subject: [PATCH 179/549] Simlify splitting string into non-empty entries. --- allowed_ips.go | 13 +++++-------- backend_server.go | 10 ++-------- backend_storage_static.go | 37 +++++++++---------------------------- client/main.go | 3 +-- config.go | 16 ++++++++++++++++ etcd_client.go | 9 ++------- grpc_client.go | 8 +------- hub_test.go | 11 ++++------- mcu_janus_publisher.go | 2 +- mcu_proxy.go | 4 ++-- proxy/main.go | 3 +-- proxy/proxy_server_test.go | 11 ++++------- proxy_config_static.go | 8 +------- server/main.go | 5 ++--- 14 files changed, 51 insertions(+), 89 deletions(-) diff --git a/allowed_ips.go b/allowed_ips.go index 669255e..6693680 100644 --- a/allowed_ips.go +++ b/allowed_ips.go @@ -83,15 +83,12 @@ func parseIPNet(s string) (*net.IPNet, error) { func ParseAllowedIps(allowed string) (*AllowedIps, error) { var allowedIps []*net.IPNet - for ip := range strings.SplitSeq(allowed, ",") { - ip = strings.TrimSpace(ip) - if ip != "" { - i, err := parseIPNet(ip) - if err != nil { - return nil, err - } - allowedIps = append(allowedIps, i) + for ip := range SplitEntries(allowed, ",") { + i, err := parseIPNet(ip) + if err != nil { + return nil, err } + allowedIps = append(allowedIps, i) } result := &AllowedIps{ diff --git a/backend_server.go b/backend_server.go index 25c3211..9384e8b 100644 --- a/backend_server.go +++ b/backend_server.go @@ -37,6 +37,7 @@ import ( "net/url" "reflect" "regexp" + "slices" "strings" "sync" "sync/atomic" @@ -83,14 +84,7 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac // TODO(jojo): Make the validity for TURN credentials configurable. turnvalid := 24 * time.Hour - var turnserverslist []string - for s := range strings.SplitSeq(turnservers, ",") { - s = strings.TrimSpace(s) - if s != "" { - turnserverslist = append(turnserverslist, s) - } - } - + turnserverslist := slices.Collect(SplitEntries(turnservers, ",")) if len(turnserverslist) != 0 { if turnapikey == "" { return nil, fmt.Errorf("need a TURN API key if TURN servers are configured") diff --git a/backend_storage_static.go b/backend_storage_static.go index fb88ef9..6f9d4e4 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -88,15 +88,15 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { // Old-style configuration, only hosts are configured and are using a common secret. allowMap := make(map[string]bool) - for u := range strings.SplitSeq(allowedUrls, ",") { - u = strings.TrimSpace(u) + for u := range SplitEntries(allowedUrls, ",") { if idx := strings.IndexByte(u, '/'); idx != -1 { log.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u) - u = u[:idx] - } - if u != "" { - allowMap[strings.ToLower(u)] = true + if u = u[:idx]; u == "" { + continue + } } + + allowMap[strings.ToLower(u)] = true } if len(allowMap) == 0 { @@ -265,15 +265,11 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen func getConfiguredBackendIDs(backendIds string) (ids []string) { seen := make(map[string]bool) - for id := range strings.SplitSeq(backendIds, ",") { - id = strings.TrimSpace(id) - if id == "" { - continue - } - + for id := range SplitEntries(backendIds, ",") { if seen[id] { continue } + ids = append(ids, id) seen[id] = true } @@ -281,16 +277,6 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } -func MapIf[T any](s []T, f func(T) (T, bool)) []T { - result := make([]T, 0, len(s)) - for _, v := range s { - if v, ok := f(v); ok { - result = append(result, v) - } - } - return result -} - func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { hosts = make(map[string][]*Backend) seenUrls := make(map[string]string) @@ -324,12 +310,7 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr var urls []string if u, _ := GetStringOptionWithEnv(config, id, "urls"); u != "" { - urls = strings.Split(u, ",") - urls = MapIf(urls, func(s string) (string, bool) { - s = strings.TrimSpace(s) - return s, len(s) > 0 - }) - slices.Sort(urls) + urls = slices.Sorted(SplitEntries(u, ",")) urls = slices.Compact(urls) } else if u, _ := GetStringOptionWithEnv(config, id, "url"); u != "" { if u = strings.TrimSpace(u); u != "" { diff --git a/client/main.go b/client/main.go index 701ebf8..6a244d9 100644 --- a/client/main.go +++ b/client/main.go @@ -35,7 +35,6 @@ import ( "os" "os/signal" "runtime" - "strings" "sync" "sync/atomic" "time" @@ -542,7 +541,7 @@ func main() { urls := make([]url.URL, 0) urlstrings := make([]string, 0) - for host := range strings.SplitSeq(*addr, ",") { + for host := range signaling.SplitEntries(*addr, ",") { u := url.URL{ Scheme: "ws", Host: host, diff --git a/config.go b/config.go index c3a006e..0ed4deb 100644 --- a/config.go +++ b/config.go @@ -23,8 +23,10 @@ package signaling import ( "errors" + "iter" "os" "regexp" + "strings" "github.com/dlintw/goconf" ) @@ -85,3 +87,17 @@ func GetStringOptions(config *goconf.ConfigFile, section string, ignoreErrors bo return result, nil } + +// SplitEntries returns an iterator over all non-empty substrings of s separated +// by sep. +func SplitEntries(s string, sep string) iter.Seq[string] { + return func(yield func(entry string) bool) { + for entry := range strings.SplitSeq(s, sep) { + if entry = strings.TrimSpace(entry); entry != "" { + if !yield(entry) { + return + } + } + } + } +} diff --git a/etcd_client.go b/etcd_client.go index 806f7b3..d83a926 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -26,7 +26,7 @@ import ( "errors" "fmt" "log" - "strings" + "slices" "sync" "sync/atomic" "time" @@ -103,12 +103,7 @@ func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, opti func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { var endpoints []string if endpointsString := c.getConfigStringWithFallback(config, "endpoints"); endpointsString != "" { - for ep := range strings.SplitSeq(endpointsString, ",") { - ep := strings.TrimSpace(ep) - if ep != "" { - endpoints = append(endpoints, ep) - } - } + endpoints = slices.Collect(SplitEntries(endpointsString, ",")) } else if discoverySrv := c.getConfigStringWithFallback(config, "discoverysrv"); discoverySrv != "" { discoveryService := c.getConfigStringWithFallback(config, "discoveryservice") clients, err := srv.GetClient("etcd-client", discoverySrv, discoveryService) diff --git a/grpc_client.go b/grpc_client.go index a27f1b0..f8e5b95 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -30,7 +30,6 @@ import ( "log" "net" "slices" - "strings" "sync" "sync/atomic" "time" @@ -612,12 +611,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo } targets, _ := config.GetString("grpc", "targets") - for target := range strings.SplitSeq(targets, ",") { - target = strings.TrimSpace(target) - if target == "" { - continue - } - + for target := range SplitEntries(targets, ",") { if entries, found := clientsMap[target]; found { clients = append(clients, entries.clients...) if dnsDiscovery && entries.entry == nil { diff --git a/hub_test.go b/hub_test.go index f3c2b44..94266e0 100644 --- a/hub_test.go +++ b/hub_test.go @@ -802,13 +802,10 @@ func TestWebsocketFeatures(t *testing.T) { assert.True(strings.HasPrefix(serverHeader, "nextcloud-spreed-signaling/"), "expected valid server header, got \"%s\"", serverHeader) features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) - for f := range strings.SplitSeq(features, ",") { - f = strings.TrimSpace(f) - if f != "" { - _, found := featuresList[f] - assert.False(found, "duplicate feature id \"%s\" in \"%s\"", f, features) - featuresList[f] = true - } + for f := range SplitEntries(features, ",") { + _, found := featuresList[f] + assert.False(found, "duplicate feature id \"%s\" in \"%s\"", f, features) + featuresList[f] = true } if len(featuresList) <= 1 { assert.Fail("expected valid features header", "received \"%s\"", features) diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 11253b7..a033fa6 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -250,7 +250,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli } func getFmtpValue(fmtp string, key string) (string, bool) { - for part := range strings.SplitSeq(fmtp, ";") { + for part := range SplitEntries(fmtp, ";") { kv := strings.SplitN(part, "=", 2) if len(kv) != 2 { continue diff --git a/mcu_proxy.go b/mcu_proxy.go index 556b034..e7960df 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1565,8 +1565,8 @@ func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { } var values []string - for v := range strings.SplitSeq(value, ",") { - v = strings.ToUpper(strings.TrimSpace(v)) + for v := range SplitEntries(value, ",") { + v = strings.ToUpper(v) if !IsValidContinent(v) { log.Printf("Ignore unknown continent %s for override %s", v, option) continue diff --git a/proxy/main.go b/proxy/main.go index 260805b..f768eb5 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -30,7 +30,6 @@ import ( "os" "os/signal" "runtime" - "strings" "syscall" "time" @@ -100,7 +99,7 @@ func main() { writeTimeout = defaultWriteTimeout } - for address := range strings.SplitSeq(addr, " ") { + for address := range signaling.SplitEntries(addr, " ") { go func(address string) { log.Println("Listening on", address) listener, err := net.Listen("tcp", address) diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 43d4139..1d90da4 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -316,14 +316,11 @@ func TestWebsocketFeatures(t *testing.T) { } features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) - for f := range strings.SplitSeq(features, ",") { - f = strings.TrimSpace(f) - if f != "" { - if _, found := featuresList[f]; found { - assert.Fail("duplicate feature", "id \"%s\" in \"%s\"", f, features) - } - featuresList[f] = true + for f := range signaling.SplitEntries(features, ",") { + if _, found := featuresList[f]; found { + assert.Fail("duplicate feature", "id \"%s\" in \"%s\"", f, features) } + featuresList[f] = true } assert.NotEmpty(featuresList, "expected valid features header, got \"%s\"", features) if _, found := featuresList["remote-streams"]; !found { diff --git a/proxy_config_static.go b/proxy_config_static.go index 3731ddb..b3b2573 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -27,7 +27,6 @@ import ( "maps" "net" "net/url" - "strings" "sync" "github.com/dlintw/goconf" @@ -85,12 +84,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool remove := maps.Clone(p.connectionsMap) mcuUrl, _ := GetStringOptionWithEnv(config, "mcu", "url") - for u := range strings.SplitSeq(mcuUrl, " ") { - u = strings.TrimSpace(u) - if u == "" { - continue - } - + for u := range SplitEntries(mcuUrl, " ") { if existing, found := remove[u]; found { // Proxy connection still exists in new configuration delete(remove, u) diff --git a/server/main.go b/server/main.go index c8b2a66..eedf316 100644 --- a/server/main.go +++ b/server/main.go @@ -35,7 +35,6 @@ import ( "os/signal" "runtime" runtimepprof "runtime/pprof" - "strings" "sync" "syscall" "time" @@ -341,7 +340,7 @@ func main() { if writeTimeout <= 0 { writeTimeout = defaultWriteTimeout } - for address := range strings.SplitSeq(saddr, " ") { + for address := range signaling.SplitEntries(saddr, " ") { go func(address string) { log.Println("Listening on", address) listener, err := createTLSListener(address, cert, key) @@ -374,7 +373,7 @@ func main() { writeTimeout = defaultWriteTimeout } - for address := range strings.SplitSeq(addr, " ") { + for address := range signaling.SplitEntries(addr, " ") { go func(address string) { log.Println("Listening on", address) listener, err := createListener(address) From 73434ac0cf93a12d2d013eb168ed782a6dfdac76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 20:23:41 +0000 Subject: [PATCH 180/549] Bump github.com/nats-io/nats-server/v2 from 2.11.7 to 2.11.8 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.7 to 2.11.8. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.7...v2.11.8) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index d0bec7a..a8878f8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.7 + github.com/nats-io/nats-server/v2 v2.11.8 github.com/nats-io/nats.go v1.44.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -87,10 +87,10 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect diff --git a/go.sum b/go.sum index 42166cc..3f7f11b 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.7 h1:lINWQ/Hb3cnaoHmWTjj/7WppZnaSh9C/1cD//nHCbms= -github.com/nats-io/nats-server/v2 v2.11.7/go.mod h1:DchDPVzAsAPqhqm7VLedX0L7hjnV/SYtlmsl9F8U53s= +github.com/nats-io/nats-server/v2 v2.11.8 h1:7T1wwwd/SKTDWW47KGguENE7Wa8CpHxLD1imet1iW7c= +github.com/nats-io/nats-server/v2 v2.11.8/go.mod h1:C2zlzMA8PpiMMxeXSz7FkU3V+J+H15kiqrkvgtn2kS8= github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M= github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -184,8 +184,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -193,8 +193,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -205,12 +205,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From efb9771f471b590116caf45dcfbe1b1ff92a464c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 14 Aug 2025 16:44:50 +0200 Subject: [PATCH 181/549] Use "slices" / "iter" module where possible. --- api_signaling.go | 2 +- backend_server.go | 12 ++---- backend_storage_static.go | 20 +++------- dns_monitor.go | 3 +- federation.go | 5 +-- hub.go | 3 +- mcu_proxy.go | 80 ++++++++++++++++++-------------------- proxy/proxy_server_test.go | 8 ++-- room_ping.go | 7 +--- testclient_test.go | 8 ++-- 10 files changed, 61 insertions(+), 87 deletions(-) diff --git a/api_signaling.go b/api_signaling.go index 16a6361..9d8ff3a 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -311,7 +311,7 @@ func (m *WelcomeServerMessage) RemoveFeature(feature ...string) { for _, feat := range feature { idx, found := slices.BinarySearch(newFeatures, feat) if found { - newFeatures = append(newFeatures[:idx], newFeatures[idx+1:]...) + newFeatures = slices.Delete(newFeatures, idx, idx+1) } } m.Features = newFeatures diff --git a/backend_server.go b/backend_server.go index 9384e8b..e57b36b 100644 --- a/backend_server.go +++ b/backend_server.go @@ -701,15 +701,9 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie } if urls := backend.Urls(); len(urls) > 0 { // Check if client-provided URL is registered for backend and use that. - found := false - for _, u := range urls { - if strings.HasPrefix(url, u) { - found = true - break - } - } - - if !found { + if !slices.ContainsFunc(urls, func(u string) bool { + return strings.HasPrefix(url, u) + }) { url = urls[0] } } diff --git a/backend_storage_static.go b/backend_storage_static.go index 6f9d4e4..6855ebb 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -159,7 +159,7 @@ func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[strin } seen[backend.Id()] = seenDeleted - urls := filter(backend.urls, func(s string) bool { + urls := slices.DeleteFunc(backend.urls, func(s string) bool { return !strings.Contains(s, "://"+host) }) log.Printf("Backend %s removed for %s", backend.id, strings.Join(urls, ", ")) @@ -175,16 +175,6 @@ func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[strin delete(s.backends, host) } -func filter[T any](s []T, del func(T) bool) []T { - result := make([]T, 0, len(s)) - for _, e := range s { - if !del(e) { - result = append(result, e) - } - } - return result -} - type seenState int const ( @@ -201,12 +191,12 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen for _, newBackend := range backends { if existingBackend.Equal(newBackend) { found = true - backends = append(backends[:index], backends[index+1:]...) + backends = slices.Delete(backends, index, index+1) break } else if newBackend.id == existingBackend.id { found = true s.backends[host][existingIndex] = newBackend - backends = append(backends[:index], backends[index+1:]...) + backends = slices.Delete(backends, index, index+1) if seen[newBackend.id] != seenUpdated { seen[newBackend.id] = seenUpdated log.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) @@ -220,10 +210,10 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen } if !found { removed := s.backends[host][existingIndex] - s.backends[host] = append(s.backends[host][:existingIndex], s.backends[host][existingIndex+1:]...) + s.backends[host] = slices.Delete(s.backends[host], existingIndex, existingIndex+1) if seen[removed.id] != seenDeleted { seen[removed.id] = seenDeleted - urls := filter(removed.urls, func(s string) bool { + urls := slices.DeleteFunc(removed.urls, func(s string) bool { return !strings.Contains(s, "://"+host) }) log.Printf("Backend %s removed for %s", removed.id, strings.Join(urls, ", ")) diff --git a/dns_monitor.go b/dns_monitor.go index 4121bbf..d324f04 100644 --- a/dns_monitor.go +++ b/dns_monitor.go @@ -26,6 +26,7 @@ import ( "log" "net" "net/url" + "slices" "strings" "sync" "sync/atomic" @@ -94,7 +95,7 @@ func (e *dnsMonitorEntry) setIPs(ips []net.IP, fromIP bool) { found := false for idx, newIP := range ips { if oldIP.Equal(newIP) { - ips = append(ips[:idx], ips[idx+1:]...) + ips = slices.Delete(ips, idx, idx+1) found = true keepIPs = append(keepIPs, oldIP) newIPs = append(newIPs, oldIP) diff --git a/federation.go b/federation.go index 8df95a3..542d194 100644 --- a/federation.go +++ b/federation.go @@ -58,9 +58,8 @@ func isClosedError(err error) bool { } func getCloudUrl(s string) string { - if strings.HasPrefix(s, "https://") { - s = s[8:] - } else { + var found bool + if s, found = strings.CutPrefix(s, "https://"); !found { s = strings.TrimPrefix(s, "http://") } if pos := strings.Index(s, "/ocs/v"); pos != -1 { diff --git a/hub.go b/hub.go index 080dbe6..73fea89 100644 --- a/hub.go +++ b/hub.go @@ -310,8 +310,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer var geoip *GeoLookup if geoipUrl != "" { - if strings.HasPrefix(geoipUrl, "file://") { - geoipUrl = geoipUrl[7:] + if geoipUrl, found := strings.CutPrefix(geoipUrl, "file://"); found { log.Printf("Using GeoIP database from %s", geoipUrl) geoip, err = NewGeoLookupFromFile(geoipUrl) } else { diff --git a/mcu_proxy.go b/mcu_proxy.go index e7960df..43b4920 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -28,6 +28,7 @@ import ( "encoding/json" "errors" "fmt" + "iter" "log" "math/rand/v2" "net" @@ -1623,12 +1624,10 @@ func (m *mcuProxy) createToken(subject string) (string, error) { func (m *mcuProxy) hasConnections() bool { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() - for _, conn := range m.connections { - if conn.IsConnected() { - return true - } - } - return false + + return slices.ContainsFunc(m.connections, func(conn *mcuProxyConnection) bool { + return conn.IsConnected() + }) } func (m *mcuProxy) WaitForConnections(ctx context.Context) error { @@ -1694,53 +1693,48 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e return nil } -func containsIP(ips []net.IP, ip net.IP) bool { - for _, i := range ips { - if i.Equal(ip) { - return true +func (m *mcuProxy) iterateConnections(url string, ips []net.IP) iter.Seq[*mcuProxyConnection] { + return func(yield func(*mcuProxyConnection) bool) { + m.connectionsMu.Lock() + defer m.connectionsMu.Unlock() + + conns, found := m.connectionsMap[url] + if !found { + return } - } - return false -} - -func (m *mcuProxy) iterateConnections(url string, ips []net.IP, f func(conn *mcuProxyConnection)) { - m.connectionsMu.Lock() - defer m.connectionsMu.Unlock() - - conns, found := m.connectionsMap[url] - if !found { - return - } - - var toRemove []*mcuProxyConnection - if len(ips) == 0 { - toRemove = conns - } else { - for _, conn := range conns { - if containsIP(ips, conn.ip) { - toRemove = append(toRemove, conn) + if len(ips) == 0 { + for _, conn := range conns { + if !yield(conn) { + return + } + } + } else { + for _, conn := range conns { + if slices.ContainsFunc(ips, func(i net.IP) bool { + return i.Equal(conn.ip) + }) { + if !yield(conn) { + return + } + } } } } - - for _, conn := range toRemove { - f(conn) - } } func (m *mcuProxy) RemoveConnection(url string, ips ...net.IP) { - m.iterateConnections(url, ips, func(conn *mcuProxyConnection) { + for conn := range m.iterateConnections(url, ips) { log.Printf("Removing connection to %s", conn) conn.closeIfEmpty() - }) + } } func (m *mcuProxy) KeepConnection(url string, ips ...net.IP) { - m.iterateConnections(url, ips, func(conn *mcuProxyConnection) { + for conn := range m.iterateConnections(url, ips) { conn.stopCloseIfEmpty() conn.clearTemporary() - }) + } } func (m *mcuProxy) Reload(config *goconf.ConfigFile) { @@ -1764,12 +1758,12 @@ func (m *mcuProxy) removeConnection(c *mcuProxyConnection) { defer m.connectionsMu.Unlock() if conns, found := m.connectionsMap[c.rawUrl]; found { - for idx, conn := range conns { - if conn == c { - conns = append(conns[:idx], conns[idx+1:]...) - break - } + idx := slices.Index(conns, c) + if idx == -1 { + return } + + conns = slices.Delete(conns, idx, idx+1) if len(conns) == 0 { delete(m.connectionsMap, c.rawUrl) m.connections = nil diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 1d90da4..f441d03 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -54,10 +54,10 @@ const ( ) func getWebsocketUrl(url string) string { - if strings.HasPrefix(url, "http://") { - return "ws://" + url[7:] + "/proxy" - } else if strings.HasPrefix(url, "https://") { - return "wss://" + url[8:] + "/proxy" + if url, found := strings.CutPrefix(url, "http://"); found { + return "ws://" + url + "/proxy" + } else if url, found := strings.CutPrefix(url, "https://"); found { + return "wss://" + url + "/proxy" } else { panic("Unsupported URL: " + url) } diff --git a/room_ping.go b/room_ping.go index 46a7355..8370c7c 100644 --- a/room_ping.go +++ b/room_ping.go @@ -25,6 +25,7 @@ import ( "context" "log" "net/url" + "slices" "sync" "time" ) @@ -165,11 +166,7 @@ func (p *RoomPing) sendPingsDirect(ctx context.Context, roomId string, url *url. } func (p *RoomPing) sendPingsCombined(url *url.URL, entries []BackendPingEntry, limit int, timeout time.Duration) { - total := len(entries) - for idx := 0; idx < total; idx += limit { - end := min(idx+limit, total) - tosend := entries[idx:end] - + for tosend := range slices.Chunk(entries, limit) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() diff --git a/testclient_test.go b/testclient_test.go index 4165265..fe6d08e 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -59,10 +59,10 @@ type TestBackendClientAuthParams struct { } func getWebsocketUrl(url string) string { - if strings.HasPrefix(url, "http://") { - return "ws://" + url[7:] + "/spreed" - } else if strings.HasPrefix(url, "https://") { - return "wss://" + url[8:] + "/spreed" + if url, found := strings.CutPrefix(url, "http://"); found { + return "ws://" + url + "/spreed" + } else if url, found := strings.CutPrefix(url, "https://"); found { + return "wss://" + url + "/spreed" } else { panic("Unsupported URL: " + url) } From dd45d0e0d8714daa81e79f9a78a5b21f111854e4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 18 Aug 2025 08:52:14 +0200 Subject: [PATCH 182/549] Add testcase for adding/removing proxy connections. --- mcu_proxy_test.go | 113 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 6ddde24..f931067 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -33,6 +33,7 @@ import ( "net/http/httptest" "net/url" "path" + "slices" "strings" "sync" "sync/atomic" @@ -736,7 +737,7 @@ type proxyTestOptions struct { servers []*TestProxyServerHandler } -func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int) *mcuProxy { +func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int) (*mcuProxy, *goconf.ConfigFile) { t.Helper() require := require.New(t) if options.etcd == nil { @@ -816,15 +817,16 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i time.Sleep(time.Millisecond) } - return proxy + return proxy, cfg } func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int) *mcuProxy { t.Helper() - return newMcuProxyForTestWithOptions(t, proxyTestOptions{ + proxy, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: servers, }, idx) + return proxy } func newMcuProxyForTest(t *testing.T, idx int) *mcuProxy { @@ -834,6 +836,81 @@ func newMcuProxyForTest(t *testing.T, idx int) *mcuProxy { return newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{server}, idx) } +func Test_ProxyAddRemoveConnections(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + assert := assert.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + server1 := NewProxyServerForTest(t, "DE") + mcu, config := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{ + server1, + }, + }, 0) + + server2 := NewProxyServerForTest(t, "DE") + server1.servers = append(server1.servers, server2) + server2.servers = server1.servers + server2.tokens = server1.tokens + urls1 := []string{ + server1.URL, + server2.URL, + } + config.AddOption("mcu", "url", strings.Join(urls1, " ")) + mcu.Reload(config) + + mcu.connectionsMu.RLock() + assert.Len(mcu.connections, 2) + mcu.connectionsMu.RUnlock() + + // Wait until connection is established. + waitCtx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + notAllConnected := slices.ContainsFunc(mcu.connections, func(conn *mcuProxyConnection) bool { + return !conn.IsConnected() + }) + mcu.connectionsMu.RUnlock() + if notAllConnected { + time.Sleep(time.Millisecond) + continue + } + + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be established") + + urls2 := []string{ + server2.URL, + } + config.AddOption("mcu", "url", strings.Join(urls2, " ")) + mcu.Reload(config) + + // Removing the connections takes a short while (asynchronously, closed when unused). + waitCtx, cancel = context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + if len(mcu.connections) != 1 { + mcu.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + continue + } + + assert.Len(mcu.connections, 1) + assert.Equal(server2.URL, mcu.connections[0].rawUrl) + mcu.connectionsMu.RUnlock() + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") +} + func Test_ProxyPublisherSubscriber(t *testing.T) { CatchLogForTest(t) t.Parallel() @@ -1525,7 +1602,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") - mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1533,7 +1610,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { }, }, 1) hub1.proxy.Store(mcu1) - mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1610,7 +1687,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2 := NewProxyServerForTest(t, "US") server3 := NewProxyServerForTest(t, "US") - mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1619,7 +1696,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { }, }, 1) hub1.proxy.Store(mcu1) - mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1628,7 +1705,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { }, }, 2) hub2.proxy.Store(mcu2) - mcu3 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu3, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1712,7 +1789,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") - mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1720,7 +1797,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { }, }, 1) hub1.proxy.Store(mcu1) - mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, @@ -1808,14 +1885,14 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") - mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, }, }, 1) hub1.proxy.Store(mcu1) - mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, @@ -1920,14 +1997,14 @@ func Test_ProxyConnectToken(t *testing.T) { // Signaling server instances are in a cluster but don't share their proxies, // i.e. they are only known to their local proxy, not the one of the other // signaling server - so the connection token must be passed between them. - mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, }, }, 1) hub1.proxy.Store(mcu1) - mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, @@ -2003,14 +2080,14 @@ func Test_ProxyPublisherToken(t *testing.T) { // signaling server - so the connection token must be passed between them. // Also the subscriber is connecting from a different country, so a remote // stream will be created that needs a valid token from the remote proxy. - mcu1 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server1, }, }, 1) hub1.proxy.Store(mcu1) - mcu2 := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, @@ -2070,7 +2147,7 @@ func Test_ProxyPublisherTimeout(t *testing.T) { require := require.New(t) assert := assert.New(t) server := NewProxyServerForTest(t, "DE") - mcu := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, }, 0) @@ -2111,7 +2188,7 @@ func Test_ProxySubscriberTimeout(t *testing.T) { require := require.New(t) assert := assert.New(t) server := NewProxyServerForTest(t, "DE") - mcu := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, }, 0) From 11dd532502cda552efcaae3a4e605fe55ec1f604 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 18 Aug 2025 09:52:48 +0200 Subject: [PATCH 183/549] Not allowed to run tests with mock dns lookup in parallel. --- dns_monitor_test.go | 2 ++ grpc_client_test.go | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dns_monitor_test.go b/dns_monitor_test.go index 809e833..fbb7762 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -42,6 +42,8 @@ type mockDnsLookup struct { } func newMockDnsLookupForTest(t *testing.T) *mockDnsLookup { + t.Helper() + t.Setenv("PARALLEL_CHECK", "1") mock := &mockDnsLookup{ ips: make(map[string][]net.IP), } diff --git a/grpc_client_test.go b/grpc_client_test.go index 3c75506..329cd37 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -268,7 +268,6 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { } func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { - t.Parallel() CatchLogForTest(t) assert := assert.New(t) lookup := newMockDnsLookupForTest(t) From b7fdde761c528134ac030af07c17a971398e64dd Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 18 Aug 2025 09:53:01 +0200 Subject: [PATCH 184/549] Add test for DNS discovery with proxy connections. With that, fix issue where the connections list was not updated if a connection with an IP was removed while others remain for the same host. --- mcu_proxy.go | 9 ++-- mcu_proxy_test.go | 126 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 5 deletions(-) diff --git a/mcu_proxy.go b/mcu_proxy.go index 43b4920..cec20cc 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1766,14 +1766,15 @@ func (m *mcuProxy) removeConnection(c *mcuProxyConnection) { conns = slices.Delete(conns, idx, idx+1) if len(conns) == 0 { delete(m.connectionsMap, c.rawUrl) - m.connections = nil - for _, conns := range m.connectionsMap { - m.connections = append(m.connections, conns...) - } } else { m.connectionsMap[c.rawUrl] = conns } + m.connections = nil + for _, conns := range m.connectionsMap { + m.connections = append(m.connections, conns...) + } + m.nextSort.Store(0) } } diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index f931067..2ec0697 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -29,6 +29,7 @@ import ( "errors" "fmt" "io" + "net" "net/http" "net/http/httptest" "net/url" @@ -717,7 +718,10 @@ func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler subscribers: make(map[string]*testProxyServerSubscriber), wakeupChan: make(chan struct{}), } - server := httptest.NewServer(proxyHandler) + server := httptest.NewUnstartedServer(proxyHandler) + if !strings.Contains(t.Name(), "DnsDiscovery") { + server.Start() + } proxyHandler.server = server proxyHandler.URL = server.URL t.Cleanup(func() { @@ -755,6 +759,9 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "urltype", "static") + if strings.Contains(t.Name(), "DnsDiscovery") { + cfg.AddOption("mcu", "dnsdiscovery", "true") + } cfg.AddOption("mcu", "proxytimeout", fmt.Sprintf("%d", int(testTimeout.Seconds()))) var urls []string waitingMap := make(map[string]bool) @@ -911,6 +918,123 @@ func Test_ProxyAddRemoveConnections(t *testing.T) { assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") } +func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { + CatchLogForTest(t) + assert := assert.New(t) + require := require.New(t) + + lookup := newMockDnsLookupForTest(t) + + server1 := NewProxyServerForTest(t, "DE") + server1.server.Start() + server1.URL = server1.server.URL + h, port, err := net.SplitHostPort(server1.server.Listener.Addr().String()) + require.NoError(err) + ip1 := net.ParseIP(h) + require.NotNil(ip1, "failed for %s", h) + + require.Contains(server1.URL, ip1.String()) + server1.URL = strings.ReplaceAll(server1.URL, ip1.String(), "proxydomain.invalid") + u1, err := url.Parse(server1.URL) + require.NoError(err) + lookup.Set(u1.Hostname(), []net.IP{ + ip1, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{ + server1, + }, + }, 0) + + if assert.NotNil(mcu.connections[0].ip) { + assert.True(ip1.Equal(mcu.connections[0].ip), "ip addresses differ: expected %s, got %s", ip1.String(), mcu.connections[0].ip.String()) + } + + dnsMonitor := mcu.config.(*proxyConfigStatic).dnsMonitor + require.NotNil(dnsMonitor) + + server2 := NewProxyServerForTest(t, "DE") + l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.2:%s", port)) + require.NoError(err) + assert.NoError(server2.server.Listener.Close()) + server2.server.Listener = l + server2.server.Start() + + server2.URL = server2.server.URL + h, _, err = net.SplitHostPort(server2.server.Listener.Addr().String()) + require.NoError(err) + ip2 := net.ParseIP(h) + require.NotNil(ip2, "failed for %s", h) + require.Contains(server2.URL, ip2.String()) + server2.URL = strings.ReplaceAll(server2.URL, ip2.String(), "proxydomain.invalid") + + server1.servers = append(server1.servers, server2) + server2.servers = server1.servers + server2.tokens = server1.tokens + + lookup.Set(u1.Hostname(), []net.IP{ + ip1, + ip2, + }) + dnsMonitor.checkHostnames() + + // Wait until connection is established. + waitCtx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + if len(mcu.connections) != 2 { + mcu.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + continue + } + + notAllConnected := slices.ContainsFunc(mcu.connections, func(conn *mcuProxyConnection) bool { + return !conn.IsConnected() + }) + mcu.connectionsMu.RUnlock() + if notAllConnected { + time.Sleep(time.Millisecond) + continue + } + + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be established") + + lookup.Set(u1.Hostname(), []net.IP{ + ip2, + }) + dnsMonitor.checkHostnames() + + // Removing the connections takes a short while (asynchronously, closed when unused). + waitCtx, cancel = context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + if len(mcu.connections) != 1 { + mcu.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + continue + } + + assert.Len(mcu.connections, 1) + assert.Equal(server1.URL, mcu.connections[0].rawUrl) + if assert.NotNil(mcu.connections[0].ip) { + assert.True(ip2.Equal(mcu.connections[0].ip), "ip addresses differ: expected %s, got %s", ip2.String(), mcu.connections[0].ip.String()) + } + mcu.connectionsMu.RUnlock() + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") +} + func Test_ProxyPublisherSubscriber(t *testing.T) { CatchLogForTest(t) t.Parallel() From 46ae7de9a6cb49bb7295f07e6181f8843e2bca14 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 18 Aug 2025 10:58:05 +0200 Subject: [PATCH 185/549] Update changelog for 2.0.4 --- CHANGELOG.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6889d73..25d0a08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,124 @@ All notable changes to this project will be documented in this file. +## 2.0.4 - 2025-08-18 + +### Added +- Comment / document possible error responses. + [#1004](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1004) +- Support multiple sessions for dialout. + [#1005](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1005) +- Support filtering candidates received by clients. + [#1000](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1000) +- Describe how to pass caller information for outgoing calls. + [#1019](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1019) +- Support multiple urls per backend + [#770](https://github.com/strukturag/nextcloud-spreed-signaling/pull/770) +- Return connection / publisher tokens for remote publishers. + [#1025](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1025) + +### Changed +- Drop support for Go 1.23 + [#1049](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1049) +- Only forward actor id / -type in "addsession" request if both are given. + [#1009](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1009) +- Use backend id in backend client stats to match other stats. + [#1020](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1020) +- Remove debug output. + [#1022](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1022) +- Only forward actor details in leave virtual sessions request if both are given. + [#1026](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1026) +- Delete (unused) proxy publisher/subscriber created after local timeout. + [#1032](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1032) +- modernize: Replace "interface{}" with "any". + [#1033](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1033) +- Add type for string maps. + [#1034](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1034) +- CI: Migrate to codecov. + [#1037](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1037) + [#1038](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1038) +- Use testify assertions to check expected fields / values internally. + [#1035](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1035) +- CI: Test with Golang 1.25 + [#1048](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1048) +- Modernize Go code and check from CI. + [#1050](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1050) +- Test "HasAnyPermission" method. + [#1051](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1051) +- Use standard library where possible. + [#1052](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1052) + +### Fixed +- Fix deadlock when setting transient data while removing listener. + [#992](https://github.com/strukturag/nextcloud-spreed-signaling/pull/992) +- Fixes for file watcher special cases + [#1017](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1017) +- Fix updating metric "signaling_mcu_subscribers" in various error cases. + [#1027](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1027) + +### Dependencies +- Bump google.golang.org/grpc from 1.72.0 to 1.72.1 + [#989](https://github.com/strukturag/nextcloud-spreed-signaling/pull/989) +- Bump github.com/pion/sdp/v3 from 3.0.11 to 3.0.12 + [#991](https://github.com/strukturag/nextcloud-spreed-signaling/pull/991) +- Bump the etcd group with 4 updates + [#990](https://github.com/strukturag/nextcloud-spreed-signaling/pull/990) +- Bump github.com/nats-io/nats-server/v2 from 2.11.3 to 2.11.4 + [#993](https://github.com/strukturag/nextcloud-spreed-signaling/pull/993) +- Bump github.com/pion/sdp/v3 from 3.0.12 to 3.0.13 + [#994](https://github.com/strukturag/nextcloud-spreed-signaling/pull/994) +- Bump google.golang.org/grpc from 1.72.1 to 1.72.2 + [#995](https://github.com/strukturag/nextcloud-spreed-signaling/pull/995) +- Bump github.com/nats-io/nats.go from 1.42.0 to 1.43.0 + [#999](https://github.com/strukturag/nextcloud-spreed-signaling/pull/999) +- Bump google.golang.org/grpc from 1.72.2 to 1.73.0 + [#1001](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1001) +- Bump the etcd group with 4 updates + [#1002](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1002) +- Bump markdown from 3.8 to 3.8.2 in /docs + [#1008](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1008) +- Bump github.com/pion/sdp/v3 from 3.0.13 to 3.0.14 + [#1006](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1006) +- Bump github.com/nats-io/nats-server/v2 from 2.11.4 to 2.11.5 + [#1010](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1010) +- Bump github.com/nats-io/nats-server/v2 from 2.11.5 to 2.11.6 + [#1011](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1011) +- Bump the etcd group with 4 updates + [#1015](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1015) +- Bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3 + [#1016](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1016) +- Bump google.golang.org/grpc from 1.73.0 to 1.74.0 + [#1018](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1018) +- Bump github.com/pion/sdp/v3 from 3.0.14 to 3.0.15 + [#1021](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1021) +- Bump the etcd group with 4 updates + [#1023](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1023) +- Bump google.golang.org/grpc from 1.74.0 to 1.74.2 + [#1024](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1024) +- Bump the etcd group with 4 updates + [#1028](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1028) +- Bump github.com/nats-io/nats.go from 1.43.0 to 1.44.0 + [#1031](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1031) +- Bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0 + [#1036](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1036) +- Bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0 + [#1039](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1039) +- Bump github.com/nats-io/nats-server/v2 from 2.11.6 to 2.11.7 + [#1040](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1040) +- Bump google.golang.org/protobuf from 1.36.6 to 1.36.7 + [#1043](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1043) +- Bump actions/checkout from 4 to 5 + [#1044](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1044) +- Bump actions/download-artifact from 4 to 5 in the artifacts group + [#1042](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1042) +- Bump golang from 1.24-alpine to 1.25-alpine in /docker/server + [#1047](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1047) +- Bump golang from 1.24-alpine to 1.25-alpine in /docker/proxy + [#1046](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1046) +- Bump github.com/nats-io/nats-server/v2 from 2.11.7 to 2.11.8 + [#1053](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1053) + + ## 2.0.3 - 2025-05-07 ### Added From d97832a5f87aedd289decdfa47faf74b4406438d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:02:10 +0000 Subject: [PATCH 186/549] Bump google.golang.org/grpc from 1.74.2 to 1.75.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.74.2 to 1.75.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.74.2...v1.75.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.75.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 34 ++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index a8878f8..3d2a1e2 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.4 go.etcd.io/etcd/server/v3 v3.6.4 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.74.2 + google.golang.org/grpc v1.75.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.7 ) @@ -79,12 +79,12 @@ require ( go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.41.0 // indirect @@ -92,8 +92,8 @@ require ( golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index 3f7f11b..563872b 100644 --- a/go.sum +++ b/go.sum @@ -159,20 +159,20 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -221,12 +221,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= From d084ea0e56a8732a1d35f272a2d12412e354d2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:53:50 +0000 Subject: [PATCH 187/549] Bump github.com/nats-io/nats.go from 1.44.0 to 1.45.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.44.0 to 1.45.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.44.0...v1.45.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.45.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8878f8..4698264 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 github.com/nats-io/nats-server/v2 v2.11.8 - github.com/nats-io/nats.go v1.44.0 + github.com/nats-io/nats.go v1.45.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 diff --git a/go.sum b/go.sum index 3f7f11b..9ff21b6 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.11.8 h1:7T1wwwd/SKTDWW47KGguENE7Wa8CpHxLD1imet1iW7c= github.com/nats-io/nats-server/v2 v2.11.8/go.mod h1:C2zlzMA8PpiMMxeXSz7FkU3V+J+H15kiqrkvgtn2kS8= -github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M= -github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.45.0 h1:/wGPbnYXDM0pLKFjZTX+2JOw9TQPoIgTFrUaH97giwA= +github.com/nats-io/nats.go v1.45.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From f20f0a3ccb53b9123994eb628b63a85336f73fa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 05:00:51 +0000 Subject: [PATCH 188/549] Bump google.golang.org/protobuf from 1.36.7 to 1.36.8 Bumps google.golang.org/protobuf from 1.36.7 to 1.36.8. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7196226..35358b1 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.75.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.7 + google.golang.org/protobuf v1.36.8 ) require ( diff --git a/go.sum b/go.sum index 1dbd262..7f7c435 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From bb996a75713b1b5f350d844bef14569e8f9ab6d8 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 21 Aug 2025 19:08:20 +0800 Subject: [PATCH 189/549] dockerfile: create system user (uid <= 999) instead of normal user (uid >= 1000), avoid home directory currently the signaling server is run as uid=1000, which may be the occupied by the first non-root normal user on most unix setups, despite not causing permission or privilege issues, (uid is just an icon in docker, privileges are determined by linux CAPs) a user whose uid=1000 could terminate the process of signaling server running in docker this patch ensures that user `spreedbackend` in the container has a uid <= 999 by specifying `adduser -S` to address the issue mentioned above this patch also prevent creating of home directory, which is not necessary, for user `spreedbackend` with `adduser -H` Signed-off-by: Leo --- docker/proxy/Dockerfile | 2 +- docker/server/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile index 02d2a9f..87eaa23 100644 --- a/docker/proxy/Dockerfile +++ b/docker/proxy/Dockerfile @@ -12,7 +12,7 @@ RUN touch /.dockerenv && \ FROM alpine:3 ENV CONFIG=/config/proxy.conf -RUN adduser -D spreedbackend && \ +RUN adduser -D -S -H spreedbackend && \ apk add --no-cache bash tzdata ca-certificates su-exec COPY --from=builder /workdir/bin/proxy /usr/bin/nextcloud-spreed-signaling-proxy diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 27686d6..71e30fb 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -12,7 +12,7 @@ RUN touch /.dockerenv && \ FROM alpine:3 ENV CONFIG=/config/server.conf -RUN adduser -D spreedbackend && \ +RUN adduser -D -S -H spreedbackend && \ apk add --no-cache bash tzdata ca-certificates su-exec COPY --from=builder /workdir/bin/signaling /usr/bin/nextcloud-spreed-signaling From 259ebd877a12a04e34fd834c391ff440df3efc34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:24:48 +0000 Subject: [PATCH 190/549] Bump github.com/stretchr/testify from 1.10.0 to 1.11.0 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 35358b1..d8200c0 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/pion/sdp/v3 v3.0.15 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.0 go.etcd.io/etcd/api/v3 v3.6.4 go.etcd.io/etcd/client/pkg/v3 v3.6.4 go.etcd.io/etcd/client/v3 v3.6.4 diff --git a/go.sum b/go.sum index 7f7c435..37d61ab 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,8 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= From 560b795d58104dc125d3814f491abe1cd21ddd59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 00:41:52 +0000 Subject: [PATCH 191/549] Bump github.com/stretchr/testify from 1.11.0 to 1.11.1 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.11.0 to 1.11.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d8200c0..d9cee0f 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/pion/sdp/v3 v3.0.15 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.0 - github.com/stretchr/testify v1.11.0 + github.com/stretchr/testify v1.11.1 go.etcd.io/etcd/api/v3 v3.6.4 go.etcd.io/etcd/client/pkg/v3 v3.6.4 go.etcd.io/etcd/client/v3 v3.6.4 diff --git a/go.sum b/go.sum index 37d61ab..81c9988 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,8 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= From 23513e91590210982afa466773384bf7f5b07826 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:08:04 +0000 Subject: [PATCH 192/549] Bump github.com/pion/sdp/v3 from 3.0.15 to 3.0.16 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.15 to 3.0.16. - [Release notes](https://github.com/pion/sdp/releases) - [Changelog](https://github.com/pion/sdp/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/sdp/compare/v3.0.15...v3.0.16) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.16 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d9cee0f..833a2c1 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 - github.com/pion/sdp/v3 v3.0.15 + github.com/pion/sdp/v3 v3.0.16 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.0 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 81c9988..37ad42d 100644 --- a/go.sum +++ b/go.sum @@ -100,8 +100,8 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk= -github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= +github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= From 2c21aeed3a08361647253602a18d2987e9db5d64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 20:12:31 +0000 Subject: [PATCH 193/549] Bump actions/setup-go from 5 to 6 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/generated.yml | 4 ++-- .github/workflows/govuln.yml | 2 +- .github/workflows/lint.yml | 6 +++--- .github/workflows/tarball.yml | 4 ++-- .github/workflows/test.yml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/generated.yml b/.github/workflows/generated.yml index 824b5d3..3fba84f 100644 --- a/.github/workflows/generated.yml +++ b/.github/workflows/generated.yml @@ -58,7 +58,7 @@ jobs: token: ${{ secrets.CODE_GENERATOR_PAT }} ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: "stable" @@ -98,7 +98,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: "stable" diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index cb050c0..ca286c5 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -28,7 +28,7 @@ jobs: - "1.25" steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 78079d7..d0285ef 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,7 +26,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: "1.24" @@ -43,7 +43,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: "1.24" @@ -57,7 +57,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: "stable" diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index e3fee07..c5e2f54 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest needs: [create] steps: - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 386b794..ffdd8fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} From 11340aa436bbd9d3f47fa67e0ec31b83f714c118 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:01:32 +0000 Subject: [PATCH 194/549] Bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.0 to 1.23.2. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.2) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-version: 1.23.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 7 ++++--- go.sum | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d9cee0f..e3d7a24 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/pion/ice/v4 v4.0.10 github.com/pion/sdp/v3 v3.0.15 github.com/pquerna/cachecontrol v0.2.0 - github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 go.etcd.io/etcd/api/v3 v3.6.4 go.etcd.io/etcd/client/pkg/v3 v3.6.4 @@ -66,7 +66,7 @@ require ( github.com/pion/turn/v4 v4.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect @@ -87,8 +87,9 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.41.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.12.0 // indirect diff --git a/go.sum b/go.sum index 81c9988..abd2077 100644 --- a/go.sum +++ b/go.sum @@ -112,12 +112,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -181,6 +181,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -193,8 +195,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 521e5c0ff8ed385ce751d3e7418a81ad3621d301 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:03:20 +0000 Subject: [PATCH 195/549] Bump markdown from 3.8.2 to 3.9 in /docs Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.8.2 to 3.9. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.8.2...3.9.0) --- updated-dependencies: - dependency-name: markdown dependency-version: '3.9' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 6d9e4ec..d11b0ff 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ jinja2==3.1.6 -markdown==3.8.2 +markdown==3.9 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==8.2.3 From 8ee19e3bcf0bcecc3b53a60a97967bf4f5dcc305 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Sep 2025 11:12:13 +0200 Subject: [PATCH 196/549] Use "testing/synctest" to simplify timing-dependent tests. --- .github/workflows/lint.yml | 4 +- Makefile | 8 +- backoff.go | 6 +- backoff_test.go | 62 +++--- deferred_executor_test.go | 32 ++-- hub_test.go | 4 +- notifier_test.go | 42 ++--- single_notifier_test.go | 43 ++--- synctest24_test.go | 42 +++++ synctest25_test.go | 32 ++++ throttle_test.go | 377 ++++++++++++++++++------------------- 11 files changed, 348 insertions(+), 304 deletions(-) create mode 100644 synctest24_test.go create mode 100644 synctest25_test.go diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d0285ef..fbce48d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,6 +36,8 @@ jobs: version: latest args: --timeout=2m0s skip-cache: true + env: + GOEXPERIMENT: synctest modernize: name: modernize @@ -49,7 +51,7 @@ jobs: - name: moderize run: | - go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... + GOEXPERIMENT=synctest go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... dependencies: name: dependencies diff --git a/Makefile b/Makefile index cce5251..80ddc34 100644 --- a/Makefile +++ b/Makefile @@ -104,18 +104,18 @@ fmt: hook | $(PROTO_GO_FILES) $(GOFMT) -s -w *.go client proxy server vet: - $(GO) vet $(ALL_PACKAGES) + GOEXPERIMENT=synctest $(GO) vet $(ALL_PACKAGES) test: vet - $(GO) test -timeout $(TIMEOUT) $(TESTARGS) $(ALL_PACKAGES) + GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) $(TESTARGS) $(ALL_PACKAGES) cover: vet rm -f cover.out && \ - $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) + GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) coverhtml: vet rm -f cover.out && \ - $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) && \ + GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) && \ sed -i "/_easyjson/d" cover.out && \ sed -i "/\.pb\.go/d" cover.out && \ $(GO) tool cover -html=cover.out -o coverage.html diff --git a/backoff.go b/backoff.go index 0a37075..3d59f51 100644 --- a/backoff.go +++ b/backoff.go @@ -37,8 +37,6 @@ type exponentialBackoff struct { initial time.Duration maxWait time.Duration nextWait time.Duration - - getContextWithTimeout func(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) } func NewExponentialBackoff(initial time.Duration, maxWait time.Duration) (Backoff, error) { @@ -54,8 +52,6 @@ func NewExponentialBackoff(initial time.Duration, maxWait time.Duration) (Backof maxWait: maxWait, nextWait: initial, - - getContextWithTimeout: context.WithTimeout, }, nil } @@ -68,7 +64,7 @@ func (b *exponentialBackoff) NextWait() time.Duration { } func (b *exponentialBackoff) Wait(ctx context.Context) { - waiter, cancel := b.getContextWithTimeout(ctx, b.nextWait) + waiter, cancel := context.WithTimeout(ctx, b.nextWait) defer cancel() b.nextWait = min(b.nextWait*2, b.maxWait) diff --git a/backoff_test.go b/backoff_test.go index 7270c90..9882238 100644 --- a/backoff_test.go +++ b/backoff_test.go @@ -31,44 +31,32 @@ import ( ) func TestBackoff_Exponential(t *testing.T) { - assert := assert.New(t) - minWait := 100 * time.Millisecond - backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) - require.NoError(t, err) + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + minWait := 100 * time.Millisecond + backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) + require.NoError(t, err) - waitTimes := []time.Duration{ - minWait, - 200 * time.Millisecond, - 400 * time.Millisecond, - 500 * time.Millisecond, - 500 * time.Millisecond, - } - - for _, wait := range waitTimes { - assert.Equal(wait, backoff.NextWait()) - backoff.(*exponentialBackoff).getContextWithTimeout = func(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { - assert.Equal(wait, timeout) - return context.WithTimeout(parent, time.Millisecond) + waitTimes := []time.Duration{ + minWait, + 200 * time.Millisecond, + 400 * time.Millisecond, + 500 * time.Millisecond, + 500 * time.Millisecond, } + + for _, wait := range waitTimes { + assert.Equal(wait, backoff.NextWait()) + a := time.Now() + backoff.Wait(context.Background()) + b := time.Now() + assert.Equal(b.Sub(a), wait) + } + + backoff.Reset() + a := time.Now() backoff.Wait(context.Background()) - } - - backoff.Reset() - backoff.(*exponentialBackoff).getContextWithTimeout = func(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { - assert.Equal(minWait, timeout) - return context.WithTimeout(parent, time.Millisecond) - } - backoff.Wait(context.Background()) -} - -func TestBackoff_ExponentialRealSleep(t *testing.T) { - assert := assert.New(t) - minWait := 100 * time.Millisecond - backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) - require.NoError(t, err) - - a := time.Now() - backoff.Wait(context.Background()) - b := time.Now() - assert.GreaterOrEqual(b.Sub(a), minWait) + b := time.Now() + assert.Equal(b.Sub(a), minWait) + }) } diff --git a/deferred_executor_test.go b/deferred_executor_test.go index 0e5f04b..ed71c0c 100644 --- a/deferred_executor_test.go +++ b/deferred_executor_test.go @@ -37,25 +37,25 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { } func TestDeferredExecutor_QueueSize(t *testing.T) { - t.Parallel() - e := NewDeferredExecutor(0) - defer e.waitForStop() - defer e.Close() + SynctestTest(t, func(t *testing.T) { + e := NewDeferredExecutor(0) + defer e.waitForStop() + defer e.Close() - delay := 100 * time.Millisecond - e.Execute(func() { - time.Sleep(delay) - }) + delay := 100 * time.Millisecond + e.Execute(func() { + time.Sleep(delay) + }) - // The queue will block until the first command finishes. - a := time.Now() - e.Execute(func() { - time.Sleep(time.Millisecond) + // The queue will block until the first command finishes. + a := time.Now() + e.Execute(func() { + time.Sleep(time.Millisecond) + }) + b := time.Now() + delta := b.Sub(a) + assert.Equal(t, delay, delta) }) - b := time.Now() - delta := b.Sub(a) - // Allow one millisecond less delay to account for time variance on CI runners. - assert.GreaterOrEqual(t, delta+time.Millisecond, delay) } func TestDeferredExecutor_Order(t *testing.T) { diff --git a/hub_test.go b/hub_test.go index 94266e0..ed2c47f 100644 --- a/hub_test.go +++ b/hub_test.go @@ -1368,7 +1368,9 @@ func TestClientHelloResumeThrottle(t *testing.T) { t: t, now: time.Now(), } - th := newMemoryThrottlerForTest(t) + throttler := newMemoryThrottlerForTest(t) + th, ok := throttler.(*memoryThrottler) + require.True(ok, "expected memoryThrottler, got %T", throttler) th.getNow = timing.getNow th.doDelay = timing.doDelay hub.throttler = th diff --git a/notifier_test.go b/notifier_test.go index 5313275..ea61dcd 100644 --- a/notifier_test.go +++ b/notifier_test.go @@ -25,6 +25,7 @@ import ( "context" "sync" "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" @@ -110,32 +111,27 @@ func TestNotifierResetWillNotify(t *testing.T) { } func TestNotifierDuplicate(t *testing.T) { - t.Parallel() - var notifier Notifier - var wgStart sync.WaitGroup - var wgEnd sync.WaitGroup + SynctestTest(t, func(t *testing.T) { + var notifier Notifier + var done sync.WaitGroup - for range 2 { - wgStart.Add(1) - wgEnd.Add(1) + for range 2 { + done.Add(1) - go func() { - defer wgEnd.Done() - waiter := notifier.NewWaiter("foo") - defer notifier.Release(waiter) + go func() { + defer done.Done() + waiter := notifier.NewWaiter("foo") + defer notifier.Release(waiter) - // Goroutine has created the waiter and is ready. - wgStart.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(t, waiter.Wait(ctx)) + }() + } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - assert.NoError(t, waiter.Wait(ctx)) - }() - } + synctest.Wait() - wgStart.Wait() - - time.Sleep(100 * time.Millisecond) - notifier.Notify("foo") - wgEnd.Wait() + notifier.Notify("foo") + done.Wait() + }) } diff --git a/single_notifier_test.go b/single_notifier_test.go index a26d03a..55872cf 100644 --- a/single_notifier_test.go +++ b/single_notifier_test.go @@ -25,6 +25,7 @@ import ( "context" "sync" "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" @@ -110,32 +111,26 @@ func TestSingleNotifierResetWillNotify(t *testing.T) { } func TestSingleNotifierDuplicate(t *testing.T) { - t.Parallel() - var notifier SingleNotifier - var wgStart sync.WaitGroup - var wgEnd sync.WaitGroup + SynctestTest(t, func(t *testing.T) { + var notifier SingleNotifier + var done sync.WaitGroup - for range 2 { - wgStart.Add(1) - wgEnd.Add(1) + for range 2 { + done.Add(1) - go func() { - defer wgEnd.Done() - waiter := notifier.NewWaiter() - defer notifier.Release(waiter) + go func() { + defer done.Done() + waiter := notifier.NewWaiter() + defer notifier.Release(waiter) - // Goroutine has created the waiter and is ready. - wgStart.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(t, waiter.Wait(ctx)) + }() + } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - assert.NoError(t, waiter.Wait(ctx)) - }() - } - - wgStart.Wait() - - time.Sleep(100 * time.Millisecond) - notifier.Notify() - wgEnd.Wait() + synctest.Wait() + notifier.Notify() + done.Wait() + }) } diff --git a/synctest24_test.go b/synctest24_test.go new file mode 100644 index 0000000..dd7c194 --- /dev/null +++ b/synctest24_test.go @@ -0,0 +1,42 @@ +//go:build !go1.25 + +/** + * 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 signaling + +import ( + "testing" + "testing/synctest" +) + +func SynctestTest(t *testing.T, f func(t *testing.T)) { + t.Helper() + + synctest.Run(func() { + t.Run("synctest", func(t *testing.T) { + // synctest doesn't support t.Parallel + t.Setenv("PARALLEL_CHECK", "1") + + f(t) + }) + }) +} diff --git a/synctest25_test.go b/synctest25_test.go new file mode 100644 index 0000000..c79163b --- /dev/null +++ b/synctest25_test.go @@ -0,0 +1,32 @@ +//go:build go1.25 + +/** + * 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 signaling + +import ( + "testing/synctest" +) + +var ( + SynctestTest = synctest.Test +) diff --git a/throttle_test.go b/throttle_test.go index 20a3599..62ebf7f 100644 --- a/throttle_test.go +++ b/throttle_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/require" ) -func newMemoryThrottlerForTest(t *testing.T) *memoryThrottler { +func newMemoryThrottlerForTest(t *testing.T) Throttler { t.Helper() result, err := NewMemoryThrottler() require.NoError(t, err) @@ -39,7 +39,7 @@ func newMemoryThrottlerForTest(t *testing.T) *memoryThrottler { result.Close() }) - return result.(*memoryThrottler) + return result } type throttlerTiming struct { @@ -58,254 +58,245 @@ func (t *throttlerTiming) doDelay(ctx context.Context, duration time.Duration) { assert.Equal(t.t, t.expectedSleep, duration) } +func expectDelay(t *testing.T, f func(), delay time.Duration) { + t.Helper() + a := time.Now() + f() + b := time.Now() + assert.Equal(t, delay, b.Sub(a)) +} + func TestThrottler(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + th := newMemoryThrottlerForTest(t) - ctx := context.Background() + ctx := context.Background() - throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle1(ctx) + throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle1(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(time.Millisecond) - throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle2(ctx) + throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle2(ctx) + }, 200*time.Millisecond) - timing.now = timing.now.Add(time.Millisecond) - throttle3, err := th.CheckBruteforce(ctx, "192.168.0.2", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle3(ctx) + throttle3, err := th.CheckBruteforce(ctx, "192.168.0.2", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle3(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(time.Millisecond) - throttle4, err := th.CheckBruteforce(ctx, "192.168.0.1", "action2") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle4(ctx) + throttle4, err := th.CheckBruteforce(ctx, "192.168.0.1", "action2") + assert.NoError(err) + expectDelay(t, func() { + throttle4(ctx) + }, 100*time.Millisecond) + }) } func TestThrottlerIPv6(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + th := newMemoryThrottlerForTest(t) - ctx := context.Background() + ctx := context.Background() - // Make sure full /64 subnets are throttled for IPv6. - throttle1, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle1(ctx) + // Make sure full /64 subnets are throttled for IPv6. + throttle1, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle1(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(time.Millisecond) - throttle2, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::2", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle2(ctx) + throttle2, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::2", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle2(ctx) + }, 200*time.Millisecond) - // A diffent /64 subnet is not throttled yet. - timing.now = timing.now.Add(time.Millisecond) - throttle3, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0013::1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle3(ctx) + // A diffent /64 subnet is not throttled yet. + throttle3, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0013::1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle3(ctx) + }, 100*time.Millisecond) - // A different action is not throttled. - timing.now = timing.now.Add(time.Millisecond) - throttle4, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::1", "action2") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle4(ctx) + // A different action is not throttled. + throttle4, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::1", "action2") + assert.NoError(err) + expectDelay(t, func() { + throttle4(ctx) + }, 100*time.Millisecond) + }) } func TestThrottler_Bruteforce(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + th := newMemoryThrottlerForTest(t) - ctx := context.Background() + ctx := context.Background() - for i := range maxBruteforceAttempts { - timing.now = timing.now.Add(time.Millisecond) - throttle, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - if i == 0 { - timing.expectedSleep = 100 * time.Millisecond - } else { - timing.expectedSleep *= 2 - if timing.expectedSleep > maxThrottleDelay { - timing.expectedSleep = maxThrottleDelay + delay := 100 * time.Millisecond + for range maxBruteforceAttempts { + throttle, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle(ctx) + }, delay) + delay *= 2 + if delay > maxThrottleDelay { + delay = maxThrottleDelay } } - throttle(ctx) - } - timing.now = timing.now.Add(time.Millisecond) - _, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.ErrorIs(err, ErrBruteforceDetected) + _, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.ErrorIs(err, ErrBruteforceDetected) + }) } func TestThrottler_Cleanup(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + throttler := newMemoryThrottlerForTest(t) + th, ok := throttler.(*memoryThrottler) + require.True(t, ok, "required memoryThrottler, got %T", throttler) - ctx := context.Background() + ctx := context.Background() - throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle1(ctx) + throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle1(ctx) + }, 100*time.Millisecond) - throttle2, err := th.CheckBruteforce(ctx, "192.168.0.2", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle2(ctx) + throttle2, err := th.CheckBruteforce(ctx, "192.168.0.2", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle2(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(time.Hour) - throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action2") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle3(ctx) + time.Sleep(time.Hour) - throttle4, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle4(ctx) + throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action2") + assert.NoError(err) + expectDelay(t, func() { + throttle3(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(-time.Hour).Add(maxBruteforceAge).Add(time.Second) - th.cleanup(timing.now) + throttle4, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle4(ctx) + }, 200*time.Millisecond) - assert.Len(th.getEntries("192.168.0.1", "action1"), 1) - assert.Len(th.getEntries("192.168.0.1", "action2"), 1) + cleanupNow := time.Now().Add(-time.Hour).Add(maxBruteforceAge).Add(time.Second) + th.cleanup(cleanupNow) - th.mu.RLock() - if _, found := th.clients["192.168.0.2"]; found { - assert.Fail("should have removed client \"192.168.0.2\"") - } - th.mu.RUnlock() + assert.Len(th.getEntries("192.168.0.1", "action1"), 1) + assert.Len(th.getEntries("192.168.0.1", "action2"), 1) - throttle5, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle5(ctx) + th.mu.RLock() + if entries, found := th.clients["192.168.0.2"]; found { + assert.Fail("should have removed client \"192.168.0.2\"", "got %+v", entries) + } + th.mu.RUnlock() + + throttle5, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle5(ctx) + }, 200*time.Millisecond) + }) } func TestThrottler_ExpirePartial(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + th := newMemoryThrottlerForTest(t) - ctx := context.Background() + ctx := context.Background() - throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle1(ctx) + throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle1(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(time.Minute) + time.Sleep(time.Minute) - throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle2(ctx) + throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle2(ctx) + }, 200*time.Millisecond) - timing.now = timing.now.Add(maxBruteforceAge).Add(-time.Minute + time.Second) + time.Sleep(maxBruteforceAge - time.Minute + time.Second) - throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle3(ctx) + throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle3(ctx) + }, 200*time.Millisecond) + }) } func TestThrottler_ExpireAll(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + th := newMemoryThrottlerForTest(t) - ctx := context.Background() + ctx := context.Background() - throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle1(ctx) + throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle1(ctx) + }, 100*time.Millisecond) - timing.now = timing.now.Add(time.Millisecond) + time.Sleep(time.Millisecond) - throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 200 * time.Millisecond - throttle2(ctx) + throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle2(ctx) + }, 200*time.Millisecond) - timing.now = timing.now.Add(maxBruteforceAge).Add(time.Second) + time.Sleep(maxBruteforceAge + time.Second) - throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - assert.NoError(err) - timing.expectedSleep = 100 * time.Millisecond - throttle3(ctx) + throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + assert.NoError(err) + expectDelay(t, func() { + throttle3(ctx) + }, 100*time.Millisecond) + }) } func TestThrottler_Negative(t *testing.T) { - assert := assert.New(t) - timing := &throttlerTiming{ - t: t, - now: time.Now(), - } - th := newMemoryThrottlerForTest(t) - th.getNow = timing.getNow - th.doDelay = timing.doDelay + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + th := newMemoryThrottlerForTest(t) - ctx := context.Background() + ctx := context.Background() - for i := range maxBruteforceAttempts * 10 { - timing.now = timing.now.Add(time.Millisecond) - throttle, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") - if err != nil { - assert.ErrorIs(err, ErrBruteforceDetected) - } - if i == 0 { - timing.expectedSleep = 100 * time.Millisecond - } else { - timing.expectedSleep *= 2 - if timing.expectedSleep > maxThrottleDelay { - timing.expectedSleep = maxThrottleDelay + delay := 100 * time.Millisecond + for range maxBruteforceAttempts * 10 { + throttle, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") + if err != nil { + assert.ErrorIs(err, ErrBruteforceDetected) + } + expectDelay(t, func() { + throttle(ctx) + }, delay) + delay *= 2 + if delay > maxThrottleDelay { + delay = maxThrottleDelay } } - throttle(ctx) - } + }) } From 51326069e2fcf985fe284952fc30f1359bfc3785 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 8 Sep 2025 12:33:37 +0200 Subject: [PATCH 197/549] Add dedicated types for different session ids. --- api_async.go | 6 +-- api_backend.go | 47 +++++++++--------- api_proxy.go | 12 ++--- api_signaling.go | 60 ++++++++++++++--------- async_events.go | 6 +-- async_events_nats.go | 11 +++-- backend_configuration.go | 4 +- backend_server.go | 40 +++++++-------- backend_server_test.go | 33 +++++++------ client.go | 6 +-- client/main.go | 10 ++-- clientsession.go | 47 +++++++++--------- concurrentmap.go | 16 +++--- concurrentmap_test.go | 2 +- federation.go | 16 +++--- federation_test.go | 59 +++++++++++----------- grpc_client.go | 36 +++++++------- grpc_server.go | 24 ++++----- hub.go | 83 ++++++++++++++++++------------- hub_test.go | 89 ++++++++++++++++----------------- mcu_common.go | 16 +++--- mcu_common_test.go | 4 +- mcu_janus.go | 34 +++++++------ mcu_janus_publisher.go | 12 ++--- mcu_janus_remote_publisher.go | 2 +- mcu_janus_subscriber.go | 6 +-- mcu_janus_test.go | 22 ++++----- mcu_proxy.go | 64 ++++++++++++------------ mcu_proxy_test.go | 90 +++++++++++++++++----------------- mcu_test.go | 26 +++++----- proxy/proxy_remote.go | 4 +- proxy/proxy_server.go | 18 +++---- proxy/proxy_server_test.go | 48 +++++++++--------- proxy/proxy_session.go | 12 ++--- proxy/proxy_testclient_test.go | 2 +- remotesession.go | 4 +- room.go | 71 ++++++++++++--------------- room_test.go | 4 +- roomsessions.go | 6 +-- roomsessions_builtin.go | 16 +++--- roomsessions_test.go | 8 +-- session.go | 4 +- sessionid_codec.go | 28 +++++++---- sessionid_codec_test.go | 4 +- stringmap.go | 17 +++++++ stringmap_test.go | 34 +++++++++++++ testclient_test.go | 18 +++---- virtualsession.go | 20 ++++---- virtualsession_test.go | 20 ++++---- 49 files changed, 656 insertions(+), 565 deletions(-) diff --git a/api_async.go b/api_async.go index d3c0426..e6a87fb 100644 --- a/api_async.go +++ b/api_async.go @@ -56,12 +56,12 @@ func (m *AsyncMessage) String() string { type AsyncRoomMessage struct { Type string `json:"type"` - SessionId string `json:"sessionid,omitempty"` - ClientType string `json:"clienttype,omitempty"` + SessionId PublicSessionId `json:"sessionid,omitempty"` + ClientType string `json:"clienttype,omitempty"` } type SendOfferMessage struct { MessageId string `json:"messageid,omitempty"` - SessionId string `json:"sessionid"` + SessionId PublicSessionId `json:"sessionid"` Data *MessageClientMessageData `json:"data"` } diff --git a/api_backend.go b/api_backend.go index 49001f7..b8bccb8 100644 --- a/api_backend.go +++ b/api_backend.go @@ -124,8 +124,8 @@ type BackendRoomInviteRequest struct { } type BackendRoomDisinviteRequest struct { - UserIds []string `json:"userids,omitempty"` - SessionIds []string `json:"sessionids,omitempty"` + UserIds []string `json:"userids,omitempty"` + SessionIds []RoomSessionId `json:"sessionids,omitempty"` // TODO(jojo): We should get rid of "AllUserIds" and find a better way to // notify existing users the room has changed and they need to update it. AllUserIds []string `json:"alluserids,omitempty"` @@ -158,8 +158,11 @@ type BackendRoomMessageRequest struct { Data json.RawMessage `json:"data,omitempty"` } -type BackendRoomSwitchToSessionsList []string -type BackendRoomSwitchToSessionsMap map[string]json.RawMessage +type BackendRoomSwitchToSessionsList []RoomSessionId +type BackendRoomSwitchToSessionsMap map[RoomSessionId]json.RawMessage + +type BackendRoomSwitchToPublicSessionsList []PublicSessionId +type BackendRoomSwitchToPublicSessionsMap map[PublicSessionId]json.RawMessage type BackendRoomSwitchToMessageRequest struct { // Target room id @@ -173,8 +176,8 @@ type BackendRoomSwitchToMessageRequest struct { Sessions json.RawMessage `json:"sessions,omitempty"` // Internal properties - SessionsList BackendRoomSwitchToSessionsList `json:"sessionslist,omitempty"` - SessionsMap BackendRoomSwitchToSessionsMap `json:"sessionsmap,omitempty"` + SessionsList BackendRoomSwitchToPublicSessionsList `json:"sessionslist,omitempty"` + SessionsMap BackendRoomSwitchToPublicSessionsMap `json:"sessionsmap,omitempty"` } type BackendRoomDialoutRequest struct { @@ -299,11 +302,11 @@ type BackendClientAuthResponse struct { } type BackendClientRoomRequest struct { - Version string `json:"version"` - RoomId string `json:"roomid"` - Action string `json:"action,omitempty"` - UserId string `json:"userid"` - SessionId string `json:"sessionid"` + Version string `json:"version"` + RoomId string `json:"roomid"` + Action string `json:"action,omitempty"` + UserId string `json:"userid"` + SessionId RoomSessionId `json:"sessionid"` // For Nextcloud Talk with SIP support and for federated sessions. ActorId string `json:"actorid,omitempty"` @@ -325,7 +328,7 @@ func (r *BackendClientRoomRequest) UpdateFromSession(s Session) { } } -func NewBackendClientRoomRequest(roomid string, userid string, sessionid string) *BackendClientRequest { +func NewBackendClientRoomRequest(roomid string, userid string, sessionid RoomSessionId) *BackendClientRequest { return &BackendClientRequest{ Type: "room", Room: &BackendClientRoomRequest{ @@ -355,8 +358,8 @@ type RoomSessionData struct { } type BackendPingEntry struct { - UserId string `json:"userid,omitempty"` - SessionId string `json:"sessionid"` + UserId string `json:"userid,omitempty"` + SessionId RoomSessionId `json:"sessionid"` } type BackendClientPingRequest struct { @@ -385,7 +388,7 @@ type BackendClientSessionRequest struct { Version string `json:"version"` RoomId string `json:"roomid"` Action string `json:"action"` - SessionId string `json:"sessionid"` + SessionId PublicSessionId `json:"sessionid"` UserId string `json:"userid,omitempty"` User json.RawMessage `json:"user,omitempty"` } @@ -395,7 +398,7 @@ type BackendClientSessionResponse struct { RoomId string `json:"roomid"` } -func NewBackendClientSessionRequest(roomid string, action string, sessionid string, msg *AddSessionInternalClientMessage) *BackendClientRequest { +func NewBackendClientSessionRequest(roomid string, action string, sessionid PublicSessionId, msg *AddSessionInternalClientMessage) *BackendClientRequest { request := &BackendClientRequest{ Type: "session", Session: &BackendClientSessionRequest{ @@ -562,12 +565,12 @@ type BackendServerInfoSfu struct { } type BackendServerInfoDialout struct { - SessionId string `json:"sessionid"` - Connected bool `json:"connected"` - Address string `json:"address,omitempty"` - UserAgent string `json:"useragent,omitempty"` - Version string `json:"version,omitempty"` - Features []string `json:"features,omitempty"` + SessionId PublicSessionId `json:"sessionid"` + Connected bool `json:"connected"` + Address string `json:"address,omitempty"` + UserAgent string `json:"useragent,omitempty"` + Version string `json:"version,omitempty"` + Features []string `json:"features,omitempty"` } type BackendServerInfoNats struct { diff --git a/api_proxy.go b/api_proxy.go index 77dca04..5f7a70a 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -150,7 +150,7 @@ type TokenClaims struct { type HelloProxyClientMessage struct { Version string `json:"version"` - ResumeId string `json:"resumeid"` + ResumeId PublicSessionId `json:"resumeid"` Features []string `json:"features,omitempty"` @@ -173,7 +173,7 @@ func (m *HelloProxyClientMessage) CheckValid() error { type HelloProxyServerMessage struct { Version string `json:"version"` - SessionId string `json:"sessionid"` + SessionId PublicSessionId `json:"sessionid"` Server *WelcomeServerMessage `json:"server,omitempty"` } @@ -206,10 +206,10 @@ type NewPublisherSettings struct { type CommandProxyClientMessage struct { Type string `json:"type"` - Sid string `json:"sid,omitempty"` - StreamType StreamType `json:"streamType,omitempty"` - PublisherId string `json:"publisherId,omitempty"` - ClientId string `json:"clientId,omitempty"` + Sid string `json:"sid,omitempty"` + StreamType StreamType `json:"streamType,omitempty"` + PublisherId PublicSessionId `json:"publisherId,omitempty"` + ClientId string `json:"clientId,omitempty"` // Deprecated: use PublisherSettings instead. Bitrate int `json:"bitrate,omitempty"` diff --git a/api_signaling.go b/api_signaling.go index 9d8ff3a..0c21b6d 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -62,6 +62,20 @@ func makePtr[T any](v T) *T { return &v } +type PrivateSessionId string + +type PublicSessionId string + +type RoomSessionId string + +func (s RoomSessionId) IsFederated() bool { + return strings.HasPrefix(string(s), FederatedRoomSessionIdPrefix) +} + +func (s RoomSessionId) WithoutFederation() RoomSessionId { + return RoomSessionId(strings.TrimPrefix(string(s), FederatedRoomSessionIdPrefix)) +} + // ClientMessage is a message that is sent from a client to the server. type ClientMessage struct { json.Marshaler @@ -444,7 +458,7 @@ type HelloClientMessageAuth struct { type HelloClientMessage struct { Version string `json:"version"` - ResumeId string `json:"resumeid"` + ResumeId PrivateSessionId `json:"resumeid"` Features []string `json:"features,omitempty"` @@ -596,9 +610,9 @@ var ( type HelloServerMessage struct { Version string `json:"version"` - SessionId string `json:"sessionid"` - ResumeId string `json:"resumeid"` - UserId string `json:"userid"` + SessionId PublicSessionId `json:"sessionid"` + ResumeId PrivateSessionId `json:"resumeid"` + UserId string `json:"userid"` // TODO: Remove once all clients have switched to the "welcome" message. Server *WelcomeServerMessage `json:"server,omitempty"` @@ -621,8 +635,8 @@ type ByeServerMessage struct { // Type "room" type RoomClientMessage struct { - RoomId string `json:"roomid"` - SessionId string `json:"sessionid,omitempty"` + RoomId string `json:"roomid"` + SessionId RoomSessionId `json:"sessionid,omitempty"` Federation *RoomFederationMessage `json:"federation,omitempty"` } @@ -697,8 +711,8 @@ const ( type MessageClientMessageRecipient struct { Type string `json:"type"` - SessionId string `json:"sessionid,omitempty"` - UserId string `json:"userid,omitempty"` + SessionId PublicSessionId `json:"sessionid,omitempty"` + UserId string `json:"userid,omitempty"` } type MessageClientMessage struct { @@ -892,8 +906,8 @@ func (m *MessageClientMessage) CheckValid() error { type MessageServerMessageSender struct { Type string `json:"type"` - SessionId string `json:"sessionid,omitempty"` - UserId string `json:"userid,omitempty"` + SessionId PublicSessionId `json:"sessionid,omitempty"` + UserId string `json:"userid,omitempty"` } type MessageServerMessageDataChat struct { @@ -933,7 +947,7 @@ type ControlServerMessage struct { // Type "internal" type CommonSessionInternalClientMessage struct { - SessionId string `json:"sessionid"` + SessionId PublicSessionId `json:"sessionid"` RoomId string `json:"roomid"` } @@ -1146,9 +1160,9 @@ type RoomEventMessage struct { } type RoomFlagsServerMessage struct { - RoomId string `json:"roomid"` - SessionId string `json:"sessionid"` - Flags uint32 `json:"flags"` + RoomId string `json:"roomid"` + SessionId PublicSessionId `json:"sessionid"` + Flags uint32 `json:"flags"` } type ChatComment StringMap @@ -1169,7 +1183,7 @@ type EventServerMessage struct { // Used for target "room" Join []*EventServerMessageSessionEntry `json:"join,omitempty"` - Leave []string `json:"leave,omitempty"` + Leave []PublicSessionId `json:"leave,omitempty"` Change []*EventServerMessageSessionEntry `json:"change,omitempty"` SwitchTo *EventServerMessageSwitchTo `json:"switchto,omitempty"` Resumed *bool `json:"resumed,omitempty"` @@ -1193,11 +1207,11 @@ func (m *EventServerMessage) String() string { } type EventServerMessageSessionEntry struct { - SessionId string `json:"sessionid"` + SessionId PublicSessionId `json:"sessionid"` UserId string `json:"userid"` Features []string `json:"features,omitempty"` User json.RawMessage `json:"user,omitempty"` - RoomSessionId string `json:"roomsessionid,omitempty"` + RoomSessionId RoomSessionId `json:"roomsessionid,omitempty"` Federated bool `json:"federated,omitempty"` } @@ -1220,12 +1234,12 @@ type EventServerMessageSwitchTo struct { // MCU-related types type AnswerOfferMessage struct { - To string `json:"to"` - From string `json:"from"` - Type string `json:"type"` - RoomType string `json:"roomType"` - Payload StringMap `json:"payload"` - Sid string `json:"sid,omitempty"` + To PublicSessionId `json:"to"` + From PublicSessionId `json:"from"` + Type string `json:"type"` + RoomType string `json:"roomType"` + Payload StringMap `json:"payload"` + Sid string `json:"sid,omitempty"` } // Type "transient" diff --git a/async_events.go b/async_events.go index 4fb34d8..308b3cf 100644 --- a/async_events.go +++ b/async_events.go @@ -51,13 +51,13 @@ type AsyncEvents interface { RegisterUserListener(userId string, backend *Backend, listener AsyncUserEventListener) error UnregisterUserListener(userId string, backend *Backend, listener AsyncUserEventListener) - RegisterSessionListener(sessionId string, backend *Backend, listener AsyncSessionEventListener) error - UnregisterSessionListener(sessionId string, backend *Backend, listener AsyncSessionEventListener) + RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) error + UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) PublishBackendRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error PublishRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error PublishUserMessage(userId string, backend *Backend, message *AsyncMessage) error - PublishSessionMessage(sessionId string, backend *Backend, message *AsyncMessage) error + PublishSessionMessage(sessionId PublicSessionId, backend *Backend, message *AsyncMessage) error } func NewAsyncEvents(url string) (AsyncEvents, error) { diff --git a/async_events_nats.go b/async_events_nats.go index 46408d0..4e52487 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -22,6 +22,7 @@ package signaling import ( + "fmt" "log" "sync" "time" @@ -53,8 +54,8 @@ func GetSubjectForUserId(userId string, backend *Backend) string { return GetEncodedSubject("user", userId+"|"+backend.Id()) } -func GetSubjectForSessionId(sessionId string, backend *Backend) string { - return "session." + sessionId +func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string { + return fmt.Sprintf("session.%s", sessionId) } type asyncSubscriberNats struct { @@ -417,7 +418,7 @@ func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend } } -func (e *asyncEventsNats) RegisterSessionListener(sessionId string, backend *Backend, listener AsyncSessionEventListener) error { +func (e *asyncEventsNats) RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() @@ -435,7 +436,7 @@ func (e *asyncEventsNats) RegisterSessionListener(sessionId string, backend *Bac return nil } -func (e *asyncEventsNats) UnregisterSessionListener(sessionId string, backend *Backend, listener AsyncSessionEventListener) { +func (e *asyncEventsNats) UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() @@ -471,7 +472,7 @@ func (e *asyncEventsNats) PublishUserMessage(userId string, backend *Backend, me return e.publish(subject, message) } -func (e *asyncEventsNats) PublishSessionMessage(sessionId string, backend *Backend, message *AsyncMessage) error { +func (e *asyncEventsNats) PublishSessionMessage(sessionId PublicSessionId, backend *Backend, message *AsyncMessage) error { subject := GetSubjectForSessionId(sessionId, backend) return e.publish(subject, message) } diff --git a/backend_configuration.go b/backend_configuration.go index 154d4c9..7f61bda 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -55,7 +55,7 @@ type Backend struct { sessionLimit uint64 sessionsLock sync.Mutex - sessions map[string]bool + sessions map[PublicSessionId]bool counted bool } @@ -142,7 +142,7 @@ func (b *Backend) AddSession(session Session) error { b.sessionsLock.Lock() defer b.sessionsLock.Unlock() if b.sessions == nil { - b.sessions = make(map[string]bool) + b.sessions = make(map[PublicSessionId]bool) } else if uint64(len(b.sessions)) >= b.sessionLimit { statsBackendLimitExceededTotal.WithLabelValues(b.id).Inc() return SessionLimitExceeded diff --git a/backend_server.go b/backend_server.go index e57b36b..4c0927e 100644 --- a/backend_server.go +++ b/backend_server.go @@ -53,7 +53,7 @@ const ( randomUsernameLength = 32 - sessionIdNotInMeeting = "0" + sessionIdNotInMeeting = RoomSessionId("0") startDialoutTimeout = 45 * time.Second ) @@ -317,7 +317,7 @@ func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids } } -func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reason string, userids []string, sessionids []string) { +func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reason string, userids []string, sessionids []RoomSessionId) { msg := &AsyncMessage{ Type: "message", Message: &ServerMessage{ @@ -351,7 +351,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso } wg.Add(1) - go func(sessionid string) { + go func(sessionid RoomSessionId) { defer wg.Done() if sid, err := b.lookupByRoomSessionId(ctx, sessionid, nil); err != nil { log.Printf("Could not lookup by room session %s: %s", sessionid, err) @@ -396,7 +396,7 @@ func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified } } -func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId string, cache *ConcurrentStringStringMap) (string, error) { +func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, cache *ConcurrentMap[RoomSessionId, PublicSessionId]) (PublicSessionId, error) { if roomSessionId == sessionIdNotInMeeting { log.Printf("Trying to lookup empty room session id: %s", roomSessionId) return "", nil @@ -421,20 +421,15 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId return sid, nil } -func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentStringStringMap, users []StringMap) []StringMap { +func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentMap[RoomSessionId, PublicSessionId], users []StringMap) []StringMap { if len(users) == 0 { return users } var wg sync.WaitGroup for _, user := range users { - roomSessionIdOb, found := user["sessionId"] + roomSessionId, found := GetStringMapString[RoomSessionId](user, "sessionId") if !found { - continue - } - - roomSessionId, ok := roomSessionIdOb.(string) - if !ok { log.Printf("User %+v has invalid room session id, ignoring", user) delete(user, "sessionId") continue @@ -447,7 +442,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Add(1) - go func(roomSessionId string, u StringMap) { + go func(roomSessionId RoomSessionId, u StringMap) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -477,7 +472,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - var cache ConcurrentStringStringMap + var cache ConcurrentMap[RoomSessionId, PublicSessionId] // Convert (Nextcloud) session ids to signaling session ids. request.InCall.Users = b.fixupUserSessions(ctx, &cache, request.InCall.Users) // Entries in "Changed" are most likely already fetched through the "Users" list. @@ -501,7 +496,7 @@ func (b *BackendServer) sendRoomParticipantsUpdate(roomid string, backend *Backe // Convert (Nextcloud) session ids to signaling session ids. ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - var cache ConcurrentStringStringMap + var cache ConcurrentMap[RoomSessionId, PublicSessionId] request.Participants.Users = b.fixupUserSessions(ctx, &cache, request.Participants.Users) request.Participants.Changed = b.fixupUserSessions(ctx, &cache, request.Participants.Changed) @@ -517,7 +512,12 @@ loop: continue } - sessionId := user["sessionId"].(string) + sessionId, found := GetStringMapString[PublicSessionId](user, "sessionId") + if !found { + log.Printf("User entry has no session id: %+v", user) + continue + } + permissionsList, ok := permissionsInterface.([]any) if !ok { log.Printf("Received invalid permissions %+v (%s) for session %s", permissionsInterface, reflect.TypeOf(permissionsInterface), sessionId) @@ -534,7 +534,7 @@ loop: } wg.Add(1) - go func(sessionId string, permissions []Permission) { + go func(sessionId PublicSessionId, permissions []Permission) { defer wg.Done() message := &AsyncMessage{ Type: "permissions", @@ -583,14 +583,14 @@ func (b *BackendServer) sendRoomSwitchTo(roomid string, backend *Backend, reques return nil } - var internalSessionsList BackendRoomSwitchToSessionsList + var internalSessionsList BackendRoomSwitchToPublicSessionsList for _, roomSessionId := range sessionsList { if roomSessionId == sessionIdNotInMeeting { continue } wg.Add(1) - go func(roomSessionId string) { + go func(roomSessionId RoomSessionId) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -621,14 +621,14 @@ func (b *BackendServer) sendRoomSwitchTo(roomid string, backend *Backend, reques return nil } - internalSessionsMap := make(BackendRoomSwitchToSessionsMap) + internalSessionsMap := make(BackendRoomSwitchToPublicSessionsMap) for roomSessionId, details := range sessionsMap { if roomSessionId == sessionIdNotInMeeting { continue } wg.Add(1) - go func(roomSessionId string, details json.RawMessage) { + go func(roomSessionId RoomSessionId, details json.RawMessage) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) diff --git a/backend_server_test.go b/backend_server_test.go index a90b2d2..c21d84b 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -28,6 +28,7 @@ import ( "crypto/sha1" "encoding/base64" "encoding/json" + "fmt" "io" "net" "net/http" @@ -496,8 +497,8 @@ func RunTestBackendServer_RoomDisinvite(t *testing.T) { UserIds: []string{ testDefaultUserId, }, - SessionIds: []string{ - roomId + "-" + hello.Hello.SessionId, + SessionIds: []RoomSessionId{ + RoomSessionId(fmt.Sprintf("%s-%s"+roomId, hello.Hello.SessionId)), }, AllUserIds: []string{}, Properties: roomProperties, @@ -553,8 +554,8 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { UserIds: []string{ testDefaultUserId, }, - SessionIds: []string{ - roomId1 + "-" + hello1.Hello.SessionId, + SessionIds: []RoomSessionId{ + RoomSessionId(fmt.Sprintf("%s-%s"+roomId1, hello1.Hello.SessionId)), }, AllUserIds: []string{}, }, @@ -790,21 +791,21 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { Participants: &BackendRoomParticipantsRequest{ Changed: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, }, { - "sessionId": roomId + "-" + hello2.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, }, { - "sessionId": roomId + "-" + hello2.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, @@ -866,13 +867,13 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { Participants: &BackendRoomParticipantsRequest{ Changed: []StringMap{ { - "sessionId": roomId + "-" + hello.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{}, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{}, }, }, @@ -932,7 +933,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { InCall: json.RawMessage("7"), Changed: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, }, { @@ -942,7 +943,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, }, { @@ -979,21 +980,21 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { InCall: json.RawMessage("7"), Changed: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, }, { - "sessionId": roomId + "-" + hello2.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), "inCall": 3, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, }, { - "sessionId": roomId + "-" + hello2.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), "inCall": 3, }, }, diff --git a/client.go b/client.go index 06a267f..2d06ab8 100644 --- a/client.go +++ b/client.go @@ -133,7 +133,7 @@ type Client struct { handler ClientHandler session atomic.Pointer[Session] - sessionId atomic.Pointer[string] + sessionId atomic.Pointer[PublicSessionId] mu sync.Mutex @@ -212,11 +212,11 @@ func (c *Client) SetSession(session Session) { } } -func (c *Client) SetSessionId(sessionId string) { +func (c *Client) SetSessionId(sessionId PublicSessionId) { c.sessionId.Store(&sessionId) } -func (c *Client) GetSessionId() string { +func (c *Client) GetSessionId() PublicSessionId { sessionId := c.sessionId.Load() if sessionId == nil { session := c.GetSession() diff --git a/client/main.go b/client/main.go index 6a244d9..7145fee 100644 --- a/client/main.go +++ b/client/main.go @@ -124,8 +124,8 @@ type SignalingClient struct { stopChan chan struct{} lock sync.Mutex - privateSessionId string - publicSessionId string + privateSessionId signaling.PrivateSessionId + publicSessionId signaling.PublicSessionId userId string } @@ -208,7 +208,7 @@ func (c *SignalingClient) processMessage(message *signaling.ServerMessage) { } } -func (c *SignalingClient) privateToPublicSessionId(privateId string) string { +func (c *SignalingClient) privateToPublicSessionId(privateId signaling.PrivateSessionId) signaling.PublicSessionId { data, err := c.cookie.DecodePrivate(privateId) if err != nil { panic(fmt.Sprintf("could not decode private session id: %s", err)) @@ -230,7 +230,7 @@ func (c *SignalingClient) processHelloMessage(message *signaling.ServerMessage) c.readyWg.Done() } -func (c *SignalingClient) PublicSessionId() string { +func (c *SignalingClient) PublicSessionId() signaling.PublicSessionId { c.lock.Lock() defer c.lock.Unlock() return c.publicSessionId @@ -368,7 +368,7 @@ func (c *SignalingClient) writePump() { } func (c *SignalingClient) SendMessages(clients []*SignalingClient) { - sessionIds := make(map[*SignalingClient]string) + sessionIds := make(map[*SignalingClient]signaling.PublicSessionId) for _, c := range clients { sessionIds[c] = c.PublicSessionId() } diff --git a/clientsession.go b/clientsession.go index 1e4966b..b971851 100644 --- a/clientsession.go +++ b/clientsession.go @@ -29,7 +29,6 @@ import ( "maps" "net/url" "slices" - "strings" "sync" "sync/atomic" "time" @@ -56,8 +55,8 @@ type ResponseHandlerFunc func(message *ClientMessage) bool type ClientSession struct { hub *Hub events AsyncEvents - privateId string - publicId string + privateId PrivateSessionId + publicId PublicSessionId data *SessionIdData ctx context.Context closeFunc context.CancelFunc @@ -85,12 +84,12 @@ type ClientSession struct { federation atomic.Pointer[FederationClient] roomSessionIdLock sync.RWMutex - roomSessionId string + roomSessionId RoomSessionId publisherWaiters ChannelWaiters publishers map[StreamType]McuPublisher - subscribers map[string]McuSubscriber + subscribers map[StreamId]McuSubscriber pendingClientMessages []*ServerMessage hasPendingChat bool @@ -99,13 +98,13 @@ type ClientSession struct { virtualSessions map[*VirtualSession]bool seenJoinedLock sync.Mutex - seenJoinedEvents map[string]bool + seenJoinedEvents map[PublicSessionId]bool responseHandlersLock sync.Mutex responseHandlers map[string]ResponseHandlerFunc } -func NewClientSession(hub *Hub, privateId string, publicId string, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { +func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { ctx, closeFunc := context.WithCancel(context.Background()) s := &ClientSession{ hub: hub, @@ -145,15 +144,15 @@ func (s *ClientSession) Context() context.Context { return s.ctx } -func (s *ClientSession) PrivateId() string { +func (s *ClientSession) PrivateId() PrivateSessionId { return s.privateId } -func (s *ClientSession) PublicId() string { +func (s *ClientSession) PublicId() PublicSessionId { return s.publicId } -func (s *ClientSession) RoomSessionId() string { +func (s *ClientSession) RoomSessionId() RoomSessionId { s.roomSessionIdLock.RLock() defer s.roomSessionIdLock.RUnlock() return s.roomSessionId @@ -350,7 +349,7 @@ func (s *ClientSession) releaseMcuObjects() { s.publishers = nil } if len(s.subscribers) > 0 { - go func(subscribers map[string]McuSubscriber) { + go func(subscribers map[StreamId]McuSubscriber) { ctx := context.Background() for _, subscriber := range subscribers { subscriber.Close(ctx) @@ -402,7 +401,7 @@ func (s *ClientSession) SubscribeEvents() error { return s.events.RegisterSessionListener(s.publicId, s.backend, s) } -func (s *ClientSession) UpdateRoomSessionId(roomSessionId string) error { +func (s *ClientSession) UpdateRoomSessionId(roomSessionId RoomSessionId) error { s.roomSessionIdLock.Lock() defer s.roomSessionIdLock.Unlock() @@ -436,7 +435,7 @@ func (s *ClientSession) UpdateRoomSessionId(roomSessionId string) error { return nil } -func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId string) error { +func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId RoomSessionId) error { s.roomSessionIdLock.Lock() defer s.roomSessionIdLock.Unlock() @@ -517,9 +516,9 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { s.roomSessionIdLock.Lock() defer s.roomSessionIdLock.Unlock() - if notify && room != nil && s.roomSessionId != "" && !strings.HasPrefix(s.roomSessionId, FederatedRoomSessionIdPrefix) { + if notify && room != nil && s.roomSessionId != "" && !s.roomSessionId.IsFederated() { // Notify - go func(sid string) { + go func(sid RoomSessionId) { ctx := context.Background() request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) @@ -588,7 +587,7 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { return prev } -func (s *ClientSession) sendOffer(client McuClient, sender string, streamType StreamType, offer StringMap) { +func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, streamType StreamType, offer StringMap) { offer_message := &AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -616,7 +615,7 @@ func (s *ClientSession) sendOffer(client McuClient, sender string, streamType St s.sendMessageUnlocked(response_message) } -func (s *ClientSession) sendCandidate(client McuClient, sender string, streamType StreamType, candidate any) { +func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, streamType StreamType, candidate any) { candidate_message := &AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -954,7 +953,7 @@ func (s *ClientSession) GetOrWaitForPublisher(ctx context.Context, streamType St } } -func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id string, streamType StreamType) (McuSubscriber, error) { +func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id PublicSessionId, streamType StreamType) (McuSubscriber, error) { s.mu.Lock() defer s.mu.Unlock() @@ -971,7 +970,7 @@ func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id s return nil, err } if s.subscribers == nil { - s.subscribers = make(map[string]McuSubscriber) + s.subscribers = make(map[StreamId]McuSubscriber) } if prev, found := s.subscribers[getStreamId(id, streamType)]; found { // Another thread created the subscriber while we were waiting. @@ -989,7 +988,7 @@ func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id s return subscriber, nil } -func (s *ClientSession) GetSubscriber(id string, streamType StreamType) McuSubscriber { +func (s *ClientSession) GetSubscriber(id PublicSessionId, streamType StreamType) McuSubscriber { s.mu.Lock() defer s.mu.Unlock() @@ -1193,7 +1192,7 @@ func (s *ClientSession) filterDuplicateJoin(entries []*EventServerMessageSession } if s.seenJoinedEvents == nil { - s.seenJoinedEvents = make(map[string]bool) + s.seenJoinedEvents = make(map[PublicSessionId]bool) } s.seenJoinedEvents[e.SessionId] = true result = append(result, e) @@ -1208,12 +1207,12 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { case "participants": if message.Event.Type == "update" { m := message.Event.Update - users := make(map[string]bool) + users := make(map[any]bool) for _, entry := range m.Users { - users[entry["sessionId"].(string)] = true + users[entry["sessionId"]] = true } for _, entry := range m.Changed { - if users[entry["sessionId"].(string)] { + if users[entry["sessionId"]] { continue } m.Users = append(m.Users, entry) diff --git a/concurrentmap.go b/concurrentmap.go index 920fac4..8bc83a4 100644 --- a/concurrentmap.go +++ b/concurrentmap.go @@ -25,40 +25,40 @@ import ( "sync" ) -type ConcurrentStringStringMap struct { +type ConcurrentMap[K comparable, V any] struct { mu sync.RWMutex - d map[string]string + d map[K]V } -func (m *ConcurrentStringStringMap) Set(key, value string) { +func (m *ConcurrentMap[K, V]) Set(key K, value V) { m.mu.Lock() defer m.mu.Unlock() if m.d == nil { - m.d = make(map[string]string) + m.d = make(map[K]V) } m.d[key] = value } -func (m *ConcurrentStringStringMap) Get(key string) (string, bool) { +func (m *ConcurrentMap[K, V]) Get(key K) (V, bool) { m.mu.RLock() defer m.mu.RUnlock() s, found := m.d[key] return s, found } -func (m *ConcurrentStringStringMap) Del(key string) { +func (m *ConcurrentMap[K, V]) Del(key K) { m.mu.Lock() defer m.mu.Unlock() delete(m.d, key) } -func (m *ConcurrentStringStringMap) Len() int { +func (m *ConcurrentMap[K, V]) Len() int { m.mu.RLock() defer m.mu.RUnlock() return len(m.d) } -func (m *ConcurrentStringStringMap) Clear() { +func (m *ConcurrentMap[K, V]) Clear() { m.mu.Lock() defer m.mu.Unlock() m.d = nil diff --git a/concurrentmap_test.go b/concurrentmap_test.go index a723db5..21ad085 100644 --- a/concurrentmap_test.go +++ b/concurrentmap_test.go @@ -31,7 +31,7 @@ import ( func TestConcurrentStringStringMap(t *testing.T) { assert := assert.New(t) - var m ConcurrentStringStringMap + var m ConcurrentMap[string, string] assert.Equal(0, m.Len()) v, found := m.Get("foo") assert.False(found, "Expected missing entry, got %s", v) diff --git a/federation.go b/federation.go index 542d194..a4f5a0f 100644 --- a/federation.go +++ b/federation.go @@ -92,7 +92,7 @@ type FederationClient struct { helloMu sync.Mutex helloMsgId string helloAuth *FederationAuthParams - resumeId string + resumeId PrivateSessionId hello atomic.Pointer[HelloServerMessage] pendingMessages []*ClientMessage @@ -602,7 +602,7 @@ func (c *FederationClient) joinRoom() error { }) } -func (c *FederationClient) updateEventUsers(users []StringMap, localSessionId string, remoteSessionId string) { +func (c *FederationClient) updateEventUsers(users []StringMap, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) localCloudUrlLen := len(localCloudUrl) remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) @@ -625,10 +625,10 @@ func (c *FederationClient) updateEventUsers(users []StringMap, localSessionId st if checkSessionId { key := "sessionId" - sid, found := GetStringMapEntry[string](u, key) + sid, found := GetStringMapString[PublicSessionId](u, key) if !found { key := "sessionid" - sid, found = GetStringMapEntry[string](u, key) + sid, found = GetStringMapString[PublicSessionId](u, key) } if found && sid == remoteSessionId { u[key] = localSessionId @@ -638,13 +638,13 @@ func (c *FederationClient) updateEventUsers(users []StringMap, localSessionId st } } -func (c *FederationClient) updateSessionRecipient(recipient *MessageClientMessageRecipient, localSessionId string, remoteSessionId string) { +func (c *FederationClient) updateSessionRecipient(recipient *MessageClientMessageRecipient, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { if recipient != nil && recipient.Type == RecipientTypeSession && remoteSessionId != "" && recipient.SessionId == remoteSessionId { recipient.SessionId = localSessionId } } -func (c *FederationClient) updateSessionSender(sender *MessageServerMessageSender, localSessionId string, remoteSessionId string) { +func (c *FederationClient) updateSessionSender(sender *MessageServerMessageSender, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { if sender != nil && sender.Type == RecipientTypeSession && remoteSessionId != "" && sender.SessionId == remoteSessionId { sender.SessionId = localSessionId } @@ -652,7 +652,7 @@ func (c *FederationClient) updateSessionSender(sender *MessageServerMessageSende func (c *FederationClient) processMessage(msg *ServerMessage) { localSessionId := c.session.PublicId() - var remoteSessionId string + var remoteSessionId PublicSessionId if hello := c.hello.Load(); hello != nil { remoteSessionId = hello.SessionId } @@ -670,7 +670,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { var data StringMap 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 { + if peerId, found := GetStringMapString[PublicSessionId](data, "peerId"); found && peerId == remoteSessionId { data["peerId"] = localSessionId if d, err := json.Marshal(data); err == nil { msg.Control.Data = d diff --git a/federation_test.go b/federation_test.go index 732e609..5594ab5 100644 --- a/federation_test.go +++ b/federation_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "fmt" "strings" "testing" "time" @@ -119,7 +120,7 @@ func Test_Federation(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -137,7 +138,7 @@ func Test_Federation(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId string + var remoteSessionId PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -173,16 +174,16 @@ func Test_Federation(t *testing.T) { assert.Equal("1.0", ping.Version) assert.Len(ping.Entries, 2) // The order of entries is not defined - if ping.Entries[0].SessionId == federatedRoomId+"-"+hello2.Hello.SessionId { + if ping.Entries[0].SessionId == RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)) { assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) - assert.Equal(roomId+"-"+hello1.Hello.SessionId, ping.Entries[1].SessionId) + assert.EqualValues(fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), ping.Entries[1].SessionId) assert.Equal(hello1.Hello.UserId, ping.Entries[1].UserId) } else { - assert.Equal(roomId+"-"+hello1.Hello.SessionId, ping.Entries[0].SessionId) + assert.EqualValues(fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), ping.Entries[0].SessionId) assert.Equal(hello1.Hello.UserId, ping.Entries[0].UserId) - assert.Equal(federatedRoomId+"-"+hello2.Hello.SessionId, ping.Entries[1].SessionId) + assert.EqualValues(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId), ping.Entries[1].SessionId) assert.Equal(hello2.Hello.UserId, ping.Entries[1].UserId) } } @@ -200,7 +201,7 @@ func Test_Federation(t *testing.T) { assert.Equal(federatedRoomId, ping.RoomId) assert.Equal("1.0", ping.Version) assert.Len(ping.Entries, 1) - assert.Equal(federatedRoomId+"-"+hello2.Hello.SessionId, ping.Entries[0].SessionId) + assert.EqualValues(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId), ping.Entries[0].SessionId) assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) } } @@ -317,7 +318,7 @@ func Test_Federation(t *testing.T) { var payload StringMap if checkReceiveClientControl(ctx, t, client2, "session", hello1.Hello, &payload) { // The sessionId in "peerId" will be replaced with the local one. - forceMute["peerId"] = hello2.Hello.SessionId + forceMute["peerId"] = string(hello2.Hello.SessionId) assert.Equal(forceMute, payload) } } @@ -358,14 +359,14 @@ func Test_Federation(t *testing.T) { var event *EventServerMessage // For the local user, it's a federated user on server 2 that joined. if checkReceiveClientEvent(ctx, t, client1, "update", &event) { - assert.Equal(remoteSessionId, event.Update.Users[0]["sessionId"]) + assert.EqualValues(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. if checkReceiveClientEvent(ctx, t, client2, "update", &event) { - assert.Equal(hello2.Hello.SessionId, event.Update.Users[0]["sessionId"]) + assert.EqualValues(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) @@ -383,14 +384,14 @@ func Test_Federation(t *testing.T) { room.PublishUsersInCallChanged(users, users) // For the local user, it's a local user that joined. if checkReceiveClientEvent(ctx, t, client1, "update", &event) { - assert.Equal(hello1.Hello.SessionId, event.Update.Users[0]["sessionId"]) + assert.EqualValues(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. if checkReceiveClientEvent(ctx, t, client2, "update", &event) { - assert.Equal(hello1.Hello.SessionId, event.Update.Users[0]["sessionId"]) + assert.EqualValues(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) @@ -437,7 +438,7 @@ func Test_Federation(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello4.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello4.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -455,7 +456,7 @@ func Test_Federation(t *testing.T) { } // The client1 will see the remote session id for client4. - var remoteSessionId4 string + var remoteSessionId4 PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -529,7 +530,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -547,7 +548,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId string + var remoteSessionId PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -565,7 +566,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -636,7 +637,7 @@ func Test_FederationChangeRoom(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -659,7 +660,7 @@ func Test_FederationChangeRoom(t *testing.T) { localAddr := fed.conn.LocalAddr() // The client1 will see the remote session id for client2. - var remoteSessionId string + var remoteSessionId PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -679,7 +680,7 @@ func Test_FederationChangeRoom(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId2, - SessionId: federatedRoomId2 + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId2, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -739,7 +740,7 @@ func Test_FederationMedia(t *testing.T) { hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) roomId := "test-room" - federatedRooId := roomId + "@federated" + federatedRoomId := roomId + "@federated" room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, room1.Room.RoomId) @@ -758,8 +759,8 @@ func Test_FederationMedia(t *testing.T) { Id: "join-room-fed", Type: "room", Room: &RoomClientMessage{ - RoomId: federatedRooId, - SessionId: federatedRooId + "-" + hello2.Hello.SessionId, + RoomId: federatedRoomId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -773,11 +774,11 @@ func Test_FederationMedia(t *testing.T) { if message, ok := client2.RunUntilMessage(ctx); ok { assert.Equal(msg.Id, message.Id) require.Equal("room", message.Type) - require.Equal(federatedRooId, message.Room.RoomId) + require.Equal(federatedRoomId, message.Room.RoomId) } // The client1 will see the remote session id for client2. - var remoteSessionId string + var remoteSessionId PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -852,7 +853,7 @@ func Test_FederationResume(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -870,7 +871,7 @@ func Test_FederationResume(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId string + var remoteSessionId PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -973,7 +974,7 @@ func Test_FederationResumeNewSession(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: federatedRoomId, - SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), Federation: &RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, @@ -991,7 +992,7 @@ func Test_FederationResumeNewSession(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId string + var remoteSessionId PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] diff --git a/grpc_client.go b/grpc_client.go index f8e5b95..e92076d 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -197,12 +197,12 @@ func (c *GrpcClient) GetServerId(ctx context.Context) (string, string, error) { return response.GetServerId(), response.GetVersion(), nil } -func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId string) (*LookupResumeIdReply, error) { +func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId PrivateSessionId) (*LookupResumeIdReply, error) { statsGrpcClientCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging log.Printf("Lookup resume id %s on %s", resumeId, c.Target()) response, err := c.impl.LookupResumeId(ctx, &LookupResumeIdRequest{ - ResumeId: resumeId, + ResumeId: string(resumeId), }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { return nil, ErrNoSuchResumeId @@ -217,12 +217,12 @@ func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId string) (*Look return response, nil } -func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId string, disconnectReason string) (string, error) { +func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) { statsGrpcClientCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging log.Printf("Lookup room session %s on %s", roomSessionId, c.Target()) response, err := c.impl.LookupSessionId(ctx, &LookupSessionIdRequest{ - RoomSessionId: roomSessionId, + RoomSessionId: string(roomSessionId), DisconnectReason: disconnectReason, }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { @@ -236,15 +236,15 @@ func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId string, return "", ErrNoSuchRoomSession } - return sessionId, nil + return PublicSessionId(sessionId), nil } -func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId string, room *Room, backendUrl string) (bool, error) { +func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId PublicSessionId, room *Room, backendUrl string) (bool, error) { statsGrpcClientCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging log.Printf("Check if session %s is in call %s on %s", sessionId, room.Id(), c.Target()) response, err := c.impl.IsSessionInCall(ctx, &IsSessionInCallRequest{ - SessionId: sessionId, + SessionId: string(sessionId), RoomId: room.Id(), BackendUrl: backendUrl, }, grpc.WaitForReady(true)) @@ -257,7 +257,7 @@ func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId string, room return response.GetInCall(), nil } -func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[string]*InternalSessionData, virtual map[string]*VirtualSessionData, err error) { +func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[PublicSessionId]*InternalSessionData, virtual map[PublicSessionId]*VirtualSessionData, err error) { statsGrpcClientCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging log.Printf("Get internal sessions for %s on %s", roomId, c.Target()) @@ -277,27 +277,27 @@ func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, bac } if len(response.InternalSessions) > 0 { - internal = make(map[string]*InternalSessionData, len(response.InternalSessions)) + internal = make(map[PublicSessionId]*InternalSessionData, len(response.InternalSessions)) for _, s := range response.InternalSessions { - internal[s.SessionId] = s + internal[PublicSessionId(s.SessionId)] = s } } if len(response.VirtualSessions) > 0 { - virtual = make(map[string]*VirtualSessionData, len(response.VirtualSessions)) + virtual = make(map[PublicSessionId]*VirtualSessionData, len(response.VirtualSessions)) for _, s := range response.VirtualSessions { - virtual[s.SessionId] = s + virtual[PublicSessionId(s.SessionId)] = s } } return } -func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId string, streamType StreamType) (string, string, net.IP, string, string, error) { +func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId PublicSessionId, streamType StreamType) (PublicSessionId, string, net.IP, string, string, error) { statsGrpcClientCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging log.Printf("Get %s publisher id %s on %s", streamType, sessionId, c.Target()) response, err := c.impl.GetPublisherId(ctx, &GetPublisherIdRequest{ - SessionId: sessionId, + SessionId: string(sessionId), StreamType: string(streamType), }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { @@ -306,7 +306,7 @@ func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId string, strea return "", "", nil, "", "", err } - return response.GetPublisherId(), response.GetProxyUrl(), net.ParseIP(response.GetIp()), response.GetConnectToken(), response.GetPublisherToken(), nil + return PublicSessionId(response.GetPublisherId()), response.GetProxyUrl(), net.ParseIP(response.GetIp()), response.GetConnectToken(), response.GetPublisherToken(), nil } func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, error) { @@ -335,7 +335,7 @@ type ProxySessionReceiver interface { } type SessionProxy struct { - sessionId string + sessionId PublicSessionId receiver ProxySessionReceiver sendMu sync.Mutex @@ -381,10 +381,10 @@ func (p *SessionProxy) Close() error { return p.client.CloseSend() } -func (c *GrpcClient) ProxySession(ctx context.Context, sessionId string, receiver ProxySessionReceiver) (*SessionProxy, error) { +func (c *GrpcClient) ProxySession(ctx context.Context, sessionId PublicSessionId, receiver ProxySessionReceiver) (*SessionProxy, error) { statsGrpcClientCalls.WithLabelValues("ProxySession").Inc() md := metadata.Pairs( - "sessionId", sessionId, + "sessionId", string(sessionId), "remoteAddr", receiver.RemoteAddr(), "country", receiver.Country(), "userAgent", receiver.UserAgent(), diff --git a/grpc_server.go b/grpc_server.go index cee0ab7..9639e16 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -58,9 +58,9 @@ func init() { } type GrpcServerHub interface { - GetSessionByResumeId(resumeId string) Session - GetSessionByPublicId(sessionId string) Session - GetSessionIdByRoomSessionId(roomSessionId string) (string, error) + GetSessionByResumeId(resumeId PrivateSessionId) Session + GetSessionByPublicId(sessionId PublicSessionId) Session + GetSessionIdByRoomSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) GetRoomForBackend(roomId string, backend *Backend) *Room GetBackend(u *url.URL) *Backend @@ -131,13 +131,13 @@ func (s *GrpcServer) LookupResumeId(ctx context.Context, request *LookupResumeId statsGrpcServerCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging log.Printf("Lookup session for resume id %s", request.ResumeId) - session := s.hub.GetSessionByResumeId(request.ResumeId) + session := s.hub.GetSessionByResumeId(PrivateSessionId(request.ResumeId)) if session == nil { return nil, status.Error(codes.NotFound, "no such room session id") } return &LookupResumeIdReply{ - SessionId: session.PublicId(), + SessionId: string(session.PublicId()), }, nil } @@ -145,7 +145,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession statsGrpcServerCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging log.Printf("Lookup session id for room session id %s", request.RoomSessionId) - sid, err := s.hub.GetSessionIdByRoomSessionId(request.RoomSessionId) + sid, err := s.hub.GetSessionIdByRoomSessionId(RoomSessionId(request.RoomSessionId)) if errors.Is(err, ErrNoSuchRoomSession) { return nil, status.Error(codes.NotFound, "no such room session id") } else if err != nil { @@ -153,7 +153,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession } if sid != "" && request.DisconnectReason != "" { - if session := s.hub.GetSessionByPublicId(sid); session != nil { + if session := s.hub.GetSessionByPublicId(PublicSessionId(sid)); session != nil { log.Printf("Closing session %s because same room session %s connected", session.PublicId(), request.RoomSessionId) session.LeaveRoom(false) switch sess := session.(type) { @@ -166,7 +166,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession } } return &LookupSessionIdReply{ - SessionId: sid, + SessionId: string(sid), }, nil } @@ -174,7 +174,7 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa statsGrpcServerCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging log.Printf("Check if session %s is in call %s on %s", request.SessionId, request.RoomId, request.BackendUrl) - session := s.hub.GetSessionByPublicId(request.SessionId) + session := s.hub.GetSessionByPublicId(PublicSessionId(request.SessionId)) if session == nil { return nil, status.Error(codes.NotFound, "no such session id") } @@ -239,7 +239,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetIntern for session := range room.internalSessions { result.InternalSessions = append(result.InternalSessions, &InternalSessionData{ - SessionId: session.PublicId(), + SessionId: string(session.PublicId()), InCall: uint32(session.GetInCall()), Features: session.GetFeatures(), }) @@ -247,7 +247,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetIntern for session := range room.virtualSessions { result.VirtualSessions = append(result.VirtualSessions, &VirtualSessionData{ - SessionId: session.PublicId(), + SessionId: string(session.PublicId()), InCall: uint32(session.GetInCall()), }) } @@ -260,7 +260,7 @@ func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherId statsGrpcServerCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging log.Printf("Get %s publisher id for session %s", request.StreamType, request.SessionId) - session := s.hub.GetSessionByPublicId(request.SessionId) + session := s.hub.GetSessionByPublicId(PublicSessionId(request.SessionId)) if session == nil { return nil, status.Error(codes.NotFound, "no such session") } diff --git a/hub.go b/hub.go index 73fea89..126f140 100644 --- a/hub.go +++ b/hub.go @@ -167,7 +167,7 @@ type Hub struct { roomSessions RoomSessions roomPing *RoomPing - virtualSessions map[string]uint64 + virtualSessions map[PublicSessionId]uint64 decodeCaches []*LruCache @@ -360,7 +360,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer roomSessions: roomSessions, roomPing: roomPing, - virtualSessions: make(map[string]uint64), + virtualSessions: make(map[PublicSessionId]uint64), decodeCaches: decodeCaches, @@ -602,6 +602,14 @@ func (h *Hub) getDecodeCache(cache_key string) *LruCache { return h.decodeCaches[idx] } +func (h *Hub) invalidatePublicSessionId(id PublicSessionId) { + h.invalidateSessionId(string(id), publicSessionName) +} + +func (h *Hub) invalidatePrivateSessionId(id PrivateSessionId) { + h.invalidateSessionId(string(id), privateSessionName) +} + func (h *Hub) invalidateSessionId(id string, sessionType string) { if len(id) == 0 { return @@ -612,6 +620,14 @@ func (h *Hub) invalidateSessionId(id string, sessionType string) { cache.Remove(cache_key) } +func (h *Hub) setDecodedPublicSessionId(id PublicSessionId, data *SessionIdData) { + h.setDecodedSessionId(string(id), publicSessionName, data) +} + +func (h *Hub) setDecodedPrivateSessionId(id PrivateSessionId, data *SessionIdData) { + h.setDecodedSessionId(string(id), privateSessionName, data) +} + func (h *Hub) setDecodedSessionId(id string, sessionType string, data *SessionIdData) { if len(id) == 0 { return @@ -622,12 +638,12 @@ func (h *Hub) setDecodedSessionId(id string, sessionType string, data *SessionId cache.Set(cache_key, data) } -func (h *Hub) decodePrivateSessionId(id string) *SessionIdData { +func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { if len(id) == 0 { return nil } - cache_key := id + "|" + privateSessionName + cache_key := fmt.Sprintf("%s|%s", id, privateSessionName) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { return result.(*SessionIdData) @@ -642,12 +658,12 @@ func (h *Hub) decodePrivateSessionId(id string) *SessionIdData { return data } -func (h *Hub) decodePublicSessionId(id string) *SessionIdData { +func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { if len(id) == 0 { return nil } - cache_key := id + "|" + publicSessionName + cache_key := fmt.Sprintf("%s|%s", id, publicSessionName) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { return result.(*SessionIdData) @@ -662,7 +678,7 @@ func (h *Hub) decodePublicSessionId(id string) *SessionIdData { return data } -func (h *Hub) GetSessionByPublicId(sessionId string) Session { +func (h *Hub) GetSessionByPublicId(sessionId PublicSessionId) Session { data := h.decodePublicSessionId(sessionId) if data == nil { return nil @@ -678,7 +694,7 @@ func (h *Hub) GetSessionByPublicId(sessionId string) Session { return session } -func (h *Hub) GetSessionByResumeId(resumeId string) Session { +func (h *Hub) GetSessionByResumeId(resumeId PrivateSessionId) Session { data := h.decodePrivateSessionId(resumeId) if data == nil { return nil @@ -694,7 +710,7 @@ func (h *Hub) GetSessionByResumeId(resumeId string) Session { return session } -func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId string) (string, error) { +func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) { return h.roomSessions.GetSessionId(roomSessionId) } @@ -778,8 +794,8 @@ func (h *Hub) performHousekeeping(now time.Time) { func (h *Hub) removeSession(session Session) (removed bool) { session.LeaveRoom(true) - h.invalidateSessionId(session.PrivateId(), privateSessionName) - h.invalidateSessionId(session.PublicId(), publicSessionName) + h.invalidatePrivateSessionId(session.PrivateId()) + h.invalidatePublicSessionId(session.PublicId()) h.mu.Lock() if data := session.Data(); data != nil && data.Sid > 0 { @@ -1013,8 +1029,8 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * statsHubSessionsCurrent.WithLabelValues(backend.Id(), session.ClientType()).Inc() statsHubSessionsTotal.WithLabelValues(backend.Id(), session.ClientType()).Inc() - h.setDecodedSessionId(privateSessionId, privateSessionName, sessionIdData) - h.setDecodedSessionId(publicSessionId, publicSessionName, sessionIdData) + h.setDecodedPrivateSessionId(privateSessionId, sessionIdData) + h.setDecodedPublicSessionId(publicSessionId, sessionIdData) h.sendHelloResponse(session, message) } @@ -1139,7 +1155,7 @@ type remoteClientInfo struct { response *LookupResumeIdReply } -func (h *Hub) tryProxyResume(c HandlerClient, resumeId string, message *ClientMessage) bool { +func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message *ClientMessage) bool { client, ok := c.(*Client) if !ok { return false @@ -1195,7 +1211,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId string, message *ClientMe return false } - rs, err := NewRemoteSession(h, client, info.client, info.response.SessionId) + rs, err := NewRemoteSession(h, client, info.client, PublicSessionId(info.response.SessionId)) if err != nil { log.Printf("Could not create remote session %s on %s: %s", info.response.SessionId, info.client.Target(), err) return false @@ -1558,7 +1574,7 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) h.processRegister(client, message, backend, auth) } -func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId string, backend *Backend) { +func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, backend *Backend) { sessionId, err := h.roomSessions.LookupSessionId(ctx, roomSessionId, "room_session_reconnected") if err == ErrNoSuchRoomSession { return @@ -1715,7 +1731,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if roomSessionId == "" { // TODO(jojo): Better make the session id required in the request. log.Printf("User did not send a room session id, assuming session %s", session.PublicId()) - roomSessionId = session.PublicId() + roomSessionId = RoomSessionId(session.PublicId()) } // Prefix room session id to allow using the same signaling server for two Nextcloud instances during development. @@ -1736,7 +1752,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if roomSessionId == "" { // TODO(jojo): Better make the session id required in the request. log.Printf("User did not send a room session id, assuming session %s", session.PublicId()) - roomSessionId = session.PublicId() + roomSessionId = RoomSessionId(session.PublicId()) } if err := session.UpdateRoomSessionId(roomSessionId); err != nil { @@ -1771,7 +1787,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if sessionId == "" { // TODO(jojo): Better make the session id required in the request. log.Printf("User did not send a room session id, assuming session %s", session.PublicId()) - sessionId = session.PublicId() + sessionId = RoomSessionId(session.PublicId()) } request := NewBackendClientRoomRequest(roomId, session.UserId(), sessionId) request.Room.UpdateFromSession(session) @@ -1817,14 +1833,13 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { continue } - var sid string + var sid RoomSessionId var uid string // Use Nextcloud session id and user id - sid = strings.TrimPrefix(session.RoomSessionId(), FederatedRoomSessionIdPrefix) - uid = session.AuthUserId() - if sid == "" { + if sid = session.RoomSessionId().WithoutFederation(); sid == "" { continue } + uid = session.AuthUserId() roomId := federation.RoomId() entries, found := rooms[roomId] @@ -1969,7 +1984,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { var subject string var clientData *MessageClientMessageData var serverRecipient *MessageClientMessageRecipient - var recipientSessionId string + var recipientSessionId PublicSessionId var room *Room switch msg.Recipient.Type { case RecipientTypeSession: @@ -2049,7 +2064,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { return } - subject = "session." + msg.Recipient.SessionId + subject = GetSubjectForSessionId(msg.Recipient.SessionId, sess.Backend()) recipientSessionId = msg.Recipient.SessionId if sess, ok := sess.(*ClientSession); ok { recipient = sess @@ -2059,7 +2074,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { if sess.ClientType() == HelloClientTypeVirtual { virtualSession := sess.(*VirtualSession) clientSession := virtualSession.Session() - subject = "session." + clientSession.PublicId() + subject = GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) recipientSessionId = clientSession.PublicId() recipient = clientSession // The client should see his session id as recipient. @@ -2069,7 +2084,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } } } else { - subject = "session." + msg.Recipient.SessionId + subject = GetSubjectForSessionId(msg.Recipient.SessionId, nil) recipientSessionId = msg.Recipient.SessionId serverRecipient = &msg.Recipient } @@ -2244,7 +2259,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { var recipient *ClientSession var subject string var serverRecipient *MessageClientMessageRecipient - var recipientSessionId string + var recipientSessionId PublicSessionId var room *Room switch msg.Recipient.Type { case RecipientTypeSession: @@ -2255,7 +2270,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { return } - subject = "session." + msg.Recipient.SessionId + subject = GetSubjectForSessionId(msg.Recipient.SessionId, nil) recipientSessionId = msg.Recipient.SessionId h.mu.RLock() sess, found := h.sessions[data.Sid] @@ -2268,7 +2283,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { if sess.ClientType() == HelloClientTypeVirtual { virtualSession := sess.(*VirtualSession) clientSession := virtualSession.Session() - subject = "session." + clientSession.PublicId() + subject = GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) recipientSessionId = clientSession.PublicId() recipient = clientSession // The client should see his session id as recipient. @@ -2397,7 +2412,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { } if options := msg.Options; options != nil && options.ActorId != "" && options.ActorType != "" { - request := NewBackendClientRoomRequest(room.Id(), msg.UserId, publicSessionId) + request := NewBackendClientRoomRequest(room.Id(), msg.UserId, RoomSessionId(publicSessionId)) request.Room.ActorId = options.ActorId request.Room.ActorType = options.ActorType request.Room.InCall = sess.GetInCall() @@ -2613,7 +2628,7 @@ func sendMcuProcessingFailed(session Session, message *ClientMessage) { session.SendMessage(response) } -func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSession, senderRoom *Room, recipientSessionId string) bool { +func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSession, senderRoom *Room, recipientSessionId PublicSessionId) bool { clients := h.rpcClients.GetClients() if len(clients) == 0 { return false @@ -2647,7 +2662,7 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi return result.Load() } -func (h *Hub) isInSameCall(ctx context.Context, senderSession *ClientSession, recipientSessionId string) bool { +func (h *Hub) isInSameCall(ctx context.Context, senderSession *ClientSession, recipientSessionId PublicSessionId) bool { if senderSession.ClientType() == HelloClientTypeInternal { // Internal clients may subscribe all streams. return true @@ -2923,7 +2938,7 @@ func (h *Hub) GetServerInfoDialout() (result []BackendServerInfoDialout) { } slices.SortFunc(result, func(a, b BackendServerInfoDialout) int { - return strings.Compare(a.SessionId, b.SessionId) + return strings.Compare(string(a.SessionId), string(b.SessionId)) }) return } diff --git a/hub_test.go b/hub_test.go index ed2c47f..58e632d 100644 --- a/hub_test.go +++ b/hub_test.go @@ -33,6 +33,7 @@ import ( "encoding/json" "encoding/pem" "errors" + "fmt" "io" "net/http" "net/http/httptest" @@ -422,7 +423,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re if strings.Contains(t.Name(), "Federation") { // Check additional fields present for federated sessions. - if strings.Contains(request.Room.SessionId, "@federated") { + if strings.Contains(string(request.Room.SessionId), "@federated") { assert.Equal(ActorTypeFederatedUsers, request.Room.ActorType) assert.NotEmpty(request.Room.ActorId) } else { @@ -1269,7 +1270,7 @@ func TestSessionIdsUnordered(t *testing.T) { hub, _, _, server := CreateHubForTest(t) var mu sync.Mutex - publicSessionIds := make([]string, 0) + var publicSessionIds []PublicSessionId var wg sync.WaitGroup for range 20 { wg.Add(1) @@ -1309,7 +1310,7 @@ func TestSessionIdsUnordered(t *testing.T) { larger := 0 smaller := 0 - prevSid := "" + var prevSid PublicSessionId for i, sid := range publicSessionIds { if i > 0 { if sid > prevSid { @@ -1565,7 +1566,7 @@ func TestClientHelloResumePublicId(t *testing.T) { defer client1.CloseWithBye() // Can't resume a session with the id received from messages of a client. - require.NoError(client1.SendHelloResume(sender.SessionId)) + require.NoError(client1.SendHelloResume(PrivateSessionId(sender.SessionId))) client1.RunUntilError(ctx, "no_such_session") // nolint // Expire old sessions @@ -2553,7 +2554,7 @@ func TestJoinInvalidRoom(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: roomId, - SessionId: roomId + "-" + hello.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)), }, } require.NoError(client.WriteJSON(msg)) @@ -2592,7 +2593,7 @@ func TestJoinRoomTwice(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: roomId, - SessionId: roomId + "-" + client.publicId + "-2", + SessionId: RoomSessionId(fmt.Sprintf("%s-%s-2", roomId, client.publicId)), }, } require.NoError(client.WriteJSON(msg)) @@ -2861,7 +2862,7 @@ func TestJoinRoomSwitchClient(t *testing.T) { Type: "room", Room: &RoomClientMessage{ RoomId: roomId, - SessionId: roomId + "-" + hello.Hello.SessionId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)), }, } require.NoError(client.WriteJSON(msg)) @@ -3287,7 +3288,7 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { // Join room by id. roomId := "test-room-takeover-room-session" - roomSessionid := "room-session-id" + roomSessionid := RoomSessionId("room-session-id") roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId, roomSessionid) require.Equal(roomId, roomMsg.Room.RoomId) @@ -3545,13 +3546,13 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { Participants: &BackendRoomParticipantsRequest{ Changed: []StringMap{ { - "sessionId": roomId + "-" + hello.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, @@ -3644,13 +3645,13 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { Participants: &BackendRoomParticipantsRequest{ Changed: []StringMap{ { - "sessionId": roomId + "-" + hello.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, Users: []StringMap{ { - "sessionId": roomId + "-" + hello.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, @@ -4288,7 +4289,7 @@ func TestVirtualClientSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(3, msg.Users[0]["inCall"], "%+v", msg) } } @@ -4306,14 +4307,14 @@ func TestVirtualClientSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, unexpected[0]); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"]) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"]) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"]) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[0]["inCall"]) } } calledCtx, calledCancel := context.WithTimeout(ctx, time.Second) - virtualSessionId := "virtual-session-id" + virtualSessionId := PublicSessionId("virtual-session-id") virtualUserId := "virtual-user-id" generatedSessionId := GetVirtualSessionId(session2, virtualSessionId) @@ -4354,11 +4355,11 @@ func TestVirtualClientSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[0]["inCall"], "%+v", msg) assert.Equal(true, msg.Users[1]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[1]["inCall"], "%+v", msg) } } @@ -4380,11 +4381,11 @@ func TestVirtualClientSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[0]["inCall"], "%+v", msg) assert.Equal(true, msg.Users[1]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[1]["inCall"], "%+v", msg) } } @@ -4530,7 +4531,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(3, msg.Users[0]["inCall"], "%+v", msg) } } @@ -4548,14 +4549,14 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, unexpected[0]); ok { if assert.Len(msg.Users, 1) { assert.Equal(true, msg.Users[0]["internal"]) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"]) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"]) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[0]["inCall"]) } } calledCtx, calledCancel := context.WithTimeout(ctx, time.Second) - virtualSessionId := "virtual-session-id" + virtualSessionId := PublicSessionId("virtual-session-id") virtualUserId := "virtual-user-id" generatedSessionId := GetVirtualSessionId(session2, virtualSessionId) @@ -4595,11 +4596,11 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[0]["inCall"], "%+v", msg) assert.Equal(true, msg.Users[1]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[1]["inCall"], "%+v", msg) } } @@ -4621,11 +4622,11 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 2) { assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[0]["inCall"], "%+v", msg) assert.Equal(true, msg.Users[1]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[1]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[1]["inCall"], "%+v", msg) } } @@ -4652,7 +4653,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { }, { // Request is coming from Nextcloud, so use its session id (which is our "room session id"). - "sessionId": roomId + "-" + hello1.Hello.SessionId, + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "participantPermissions": 254, "participantType": 1, "lastPing": 234567890, @@ -4674,18 +4675,18 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 3) { assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[0]["inCall"], "%+v", msg) assert.EqualValues(246, msg.Users[0]["participantPermissions"], "%+v", msg) assert.EqualValues(4, msg.Users[0]["participantType"], "%+v", msg) - assert.Equal(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg) assert.Nil(msg.Users[1]["inCall"], "%+v", msg) assert.EqualValues(254, msg.Users[1]["participantPermissions"], "%+v", msg) assert.EqualValues(1, msg.Users[1]["participantType"], "%+v", msg) assert.Equal(true, msg.Users[2]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[2]["inCall"], "%+v", msg) } } @@ -4695,18 +4696,18 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 3) { assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[0]["inCall"], "%+v", msg) assert.EqualValues(246, msg.Users[0]["participantPermissions"], "%+v", msg) assert.EqualValues(4, msg.Users[0]["participantType"], "%+v", msg) - assert.Equal(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg) assert.Nil(msg.Users[1]["inCall"], "%+v", msg) assert.EqualValues(254, msg.Users[1]["participantPermissions"], "%+v", msg) assert.EqualValues(1, msg.Users[1]["participantType"], "%+v", msg) assert.Equal(true, msg.Users[2]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[2]["inCall"], "%+v", msg) } } @@ -4729,18 +4730,18 @@ func TestDuplicateVirtualSessions(t *testing.T) { if msg, ok := checkMessageParticipantsInCall(t, msg); ok { if assert.Len(msg.Users, 3) { assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg) - assert.Equal(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[0]["inCall"], "%+v", msg) assert.EqualValues(246, msg.Users[0]["participantPermissions"], "%+v", msg) assert.EqualValues(4, msg.Users[0]["participantType"], "%+v", msg) - assert.Equal(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg) + assert.EqualValues(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg) assert.Nil(msg.Users[1]["inCall"], "%+v", msg) assert.EqualValues(254, msg.Users[1]["participantPermissions"], "%+v", msg) assert.EqualValues(1, msg.Users[1]["participantType"], "%+v", msg) assert.Equal(true, msg.Users[2]["internal"], "%+v", msg) - assert.Equal(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg) assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[2]["inCall"], "%+v", msg) } } @@ -4784,12 +4785,12 @@ func DoTestSwitchToOne(t *testing.T, details StringMap) { client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") - roomSessionId1 := "roomsession1" + roomSessionId1 := RoomSessionId("roomsession1") roomId1 := "test-room" roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) - roomSessionId2 := "roomsession2" + roomSessionId2 := RoomSessionId("roomsession2") roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) @@ -4799,12 +4800,12 @@ func DoTestSwitchToOne(t *testing.T, details StringMap) { var sessions json.RawMessage var err error if details != nil { - sessions, err = json.Marshal(StringMap{ + sessions, err = json.Marshal(map[RoomSessionId]any{ roomSessionId1: details, }) require.NoError(err) } else { - sessions, err = json.Marshal([]string{ + sessions, err = json.Marshal([]RoomSessionId{ roomSessionId1, }) require.NoError(err) @@ -4881,12 +4882,12 @@ func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") - roomSessionId1 := "roomsession1" + roomSessionId1 := RoomSessionId("roomsession1") roomId1 := "test-room" roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) - roomSessionId2 := "roomsession2" + roomSessionId2 := RoomSessionId("roomsession2") roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) @@ -4896,13 +4897,13 @@ func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap var sessions json.RawMessage var err error if details1 != nil || details2 != nil { - sessions, err = json.Marshal(StringMap{ + sessions, err = json.Marshal(map[RoomSessionId]any{ roomSessionId1: details1, roomSessionId2: details2, }) require.NoError(err) } else { - sessions, err = json.Marshal([]string{ + sessions, err = json.Marshal([]RoomSessionId{ roomSessionId1, roomSessionId2, }) diff --git a/mcu_common.go b/mcu_common.go index 9c1e7f5..33d5a7d 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -54,7 +54,7 @@ const ( ) type McuListener interface { - PublicId() string + PublicId() PublicSessionId OnUpdateOffer(client McuClient, offer StringMap) @@ -130,8 +130,8 @@ type Mcu interface { GetStats() any GetServerInfoSfu() *BackendServerInfoSfu - NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) - NewSubscriber(ctx context.Context, listener McuListener, publisher string, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) + NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) + NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) } // PublisherStream contains the available properties when creating a @@ -164,7 +164,7 @@ type PublisherStream struct { } type RemotePublisherController interface { - PublisherId() string + PublisherId() PublicSessionId StartPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error StopPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error @@ -211,20 +211,20 @@ type McuClient interface { type McuPublisher interface { McuClient - PublisherId() string + PublisherId() PublicSessionId HasMedia(MediaType) bool SetMedia(MediaType) GetStreams(ctx context.Context) ([]PublisherStream, error) - PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error - UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error + PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error + UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error } type McuSubscriber interface { McuClient - Publisher() string + Publisher() PublicSessionId } type McuRemotePublisherProperties interface { diff --git a/mcu_common_test.go b/mcu_common_test.go index c1facd2..a54434b 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -30,10 +30,10 @@ func TestCommonMcuStats(t *testing.T) { } type MockMcuListener struct { - publicId string + publicId PublicSessionId } -func (m *MockMcuListener) PublicId() string { +func (m *MockMcuListener) PublicId() PublicSessionId { return m.publicId } diff --git a/mcu_janus.go b/mcu_janus.go index a9f095b..526ddf0 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -57,8 +57,10 @@ var ( } ) -func getStreamId(publisherId string, streamType StreamType) string { - return fmt.Sprintf("%s|%s", publisherId, streamType) +type StreamId string + +func getStreamId(publisherId PublicSessionId, streamType StreamType) StreamId { + return StreamId(fmt.Sprintf("%s|%s", publisherId, streamType)) } func getPluginValue(data janus.PluginData, pluginName string, key string) any { @@ -219,10 +221,10 @@ type mcuJanus struct { clients map[clientInterface]bool clientId atomic.Uint64 - publishers map[string]*mcuJanusPublisher + publishers map[StreamId]*mcuJanusPublisher publisherCreated Notifier publisherConnected Notifier - remotePublishers map[string]*mcuJanusRemotePublisher + remotePublishers map[StreamId]*mcuJanusRemotePublisher reconnectTimer *time.Timer reconnectInterval time.Duration @@ -247,8 +249,8 @@ func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mc closeChan: make(chan struct{}, 1), clients: make(map[clientInterface]bool), - publishers: make(map[string]*mcuJanusPublisher), - remotePublishers: make(map[string]*mcuJanusRemotePublisher), + publishers: make(map[StreamId]*mcuJanusPublisher), + remotePublishers: make(map[StreamId]*mcuJanusRemotePublisher), createJanusGateway: func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) { return NewJanusGateway(ctx, wsURL, listener) @@ -546,7 +548,7 @@ func (m *mcuJanus) sendKeepalive(ctx context.Context) { } } -func (m *mcuJanus) SubscriberConnected(id string, publisher string, streamType StreamType) { +func (m *mcuJanus) SubscriberConnected(id string, publisher PublicSessionId, streamType StreamType) { m.mu.Lock() defer m.mu.Unlock() @@ -555,7 +557,7 @@ func (m *mcuJanus) SubscriberConnected(id string, publisher string, streamType S } } -func (m *mcuJanus) SubscriberDisconnected(id string, publisher string, streamType StreamType) { +func (m *mcuJanus) SubscriberDisconnected(id string, publisher PublicSessionId, streamType StreamType) { m.mu.Lock() defer m.mu.Unlock() @@ -564,7 +566,7 @@ func (m *mcuJanus) SubscriberDisconnected(id string, publisher string, streamTyp } } -func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id string, streamType StreamType, settings NewPublisherSettings) (uint64, int, error) { +func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, int, error) { create_msg := StringMap{ "request": "create", "description": getStreamId(id, streamType), @@ -619,7 +621,7 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, return roomId, bitrate, nil } -func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id string, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, int, error) { +func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, int, error) { session := m.session if session == nil { return nil, 0, 0, 0, ErrNotConnected @@ -657,7 +659,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id string, st return handle, response.Session, roomId, bitrate, nil } -func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } @@ -700,14 +702,14 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id st go client.run(handle, client.closeChan) m.mu.Lock() m.publishers[getStreamId(id, streamType)] = client - m.publisherCreated.Notify(getStreamId(id, streamType)) + m.publisherCreated.Notify(string(getStreamId(id, streamType))) m.mu.Unlock() statsPublishersCurrent.WithLabelValues(string(streamType)).Inc() statsPublishersTotal.WithLabelValues(string(streamType)).Inc() return client, nil } -func (m *mcuJanus) getPublisher(ctx context.Context, publisher string, streamType StreamType) (*mcuJanusPublisher, error) { +func (m *mcuJanus) getPublisher(ctx context.Context, publisher PublicSessionId, streamType StreamType) (*mcuJanusPublisher, error) { // Do the direct check immediately as this should be the normal case. key := getStreamId(publisher, streamType) m.mu.Lock() @@ -716,7 +718,7 @@ func (m *mcuJanus) getPublisher(ctx context.Context, publisher string, streamTyp return result, nil } - waiter := m.publisherCreated.NewWaiter(key) + waiter := m.publisherCreated.NewWaiter(string(key)) m.mu.Unlock() defer m.publisherCreated.Release(waiter) @@ -734,7 +736,7 @@ func (m *mcuJanus) getPublisher(ctx context.Context, publisher string, streamTyp } } -func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher string, streamType StreamType) (*JanusHandle, *mcuJanusPublisher, error) { +func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher PublicSessionId, streamType StreamType) (*JanusHandle, *mcuJanusPublisher, error) { var pub *mcuJanusPublisher var err error if pub, err = m.getPublisher(ctx, publisher, streamType); err != nil { @@ -755,7 +757,7 @@ func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher st return handle, pub, nil } -func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publisher string, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index a033fa6..d57ae24 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -47,7 +47,7 @@ const ( type mcuJanusPublisher struct { mcuJanusClient - id string + id PublicSessionId settings NewPublisherSettings stats publisherStatsCounter sdpFlags Flags @@ -56,7 +56,7 @@ type mcuJanusPublisher struct { answerSdp atomic.Pointer[sdp.SessionDescription] } -func (p *mcuJanusPublisher) PublisherId() string { +func (p *mcuJanusPublisher) PublisherId() PublicSessionId { return p.id } @@ -89,7 +89,7 @@ func (p *mcuJanusPublisher) handleDetached(event *janus.DetachedMsg) { func (p *mcuJanusPublisher) handleConnected(event *janus.WebRTCUpMsg) { log.Printf("Publisher %d received connected", p.handleId) - p.mcu.publisherConnected.Notify(getStreamId(p.id, p.streamType)) + p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) } func (p *mcuJanusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { @@ -392,11 +392,11 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, return streams, nil } -func getPublisherRemoteId(id string, remoteId string, hostname string, port int, rtcpPort int) string { +func getPublisherRemoteId(id PublicSessionId, remoteId PublicSessionId, hostname string, port int, rtcpPort int) string { return fmt.Sprintf("%s-%s@%s:%d:%d", id, remoteId, hostname, port, rtcpPort) } -func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { msg := StringMap{ "request": "publish_remotely", "room": p.roomId, @@ -433,7 +433,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId string, return nil } -func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { msg := StringMap{ "request": "unpublish_remotely", "room": p.roomId, diff --git a/mcu_janus_remote_publisher.go b/mcu_janus_remote_publisher.go index 8c0b9e1..e3430ab 100644 --- a/mcu_janus_remote_publisher.go +++ b/mcu_janus_remote_publisher.go @@ -85,7 +85,7 @@ func (p *mcuJanusRemotePublisher) handleDetached(event *janus.DetachedMsg) { func (p *mcuJanusRemotePublisher) handleConnected(event *janus.WebRTCUpMsg) { log.Printf("Remote publisher %d received connected", p.handleId) - p.mcu.publisherConnected.Notify(getStreamId(p.id, p.streamType)) + p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) } func (p *mcuJanusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 4afe0dd..885ce45 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -33,10 +33,10 @@ import ( type mcuJanusSubscriber struct { mcuJanusClient - publisher string + publisher PublicSessionId } -func (p *mcuJanusSubscriber) Publisher() string { +func (p *mcuJanusSubscriber) Publisher() PublicSessionId { return p.publisher } @@ -156,7 +156,7 @@ func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelecti return } - waiter := p.mcu.publisherConnected.NewWaiter(getStreamId(p.publisher, p.streamType)) + waiter := p.mcu.publisherConnected.NewWaiter(string(getStreamId(p.publisher, p.streamType))) defer p.mcu.publisherConnected.Release(waiter) loggedNotPublishingYet := false diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 1c08884..5d567f7 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -516,10 +516,10 @@ func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { } type TestMcuListener struct { - id string + id PublicSessionId } -func (t *TestMcuListener) PublicId() string { +func (t *TestMcuListener) PublicId() PublicSessionId { return t.id } @@ -548,10 +548,10 @@ func (t *TestMcuListener) SubscriberClosed(subscriber McuSubscriber) { } type TestMcuController struct { - id string + id PublicSessionId } -func (c *TestMcuController) PublisherId() string { +func (c *TestMcuController) PublisherId() PublicSessionId { return c.id } @@ -620,7 +620,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -736,7 +736,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -848,7 +848,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -926,7 +926,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1000,7 +1000,7 @@ func Test_JanusPublisherSubscriber(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1037,7 +1037,7 @@ func Test_JanusSubscriberPublisher(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1104,7 +1104,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "publisher-id" + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } diff --git a/mcu_proxy.go b/mcu_proxy.go index cec20cc..0b3ca8f 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -141,11 +141,11 @@ func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadPr type mcuProxyPublisher struct { mcuProxyPubSubCommon - id string + id PublicSessionId settings NewPublisherSettings } -func newMcuProxyPublisher(id string, sid string, streamType StreamType, maxBitrate int, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { +func newMcuProxyPublisher(id PublicSessionId, sid string, streamType StreamType, maxBitrate int, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { return &mcuProxyPublisher{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ sid: sid, @@ -160,7 +160,7 @@ func newMcuProxyPublisher(id string, sid string, streamType StreamType, maxBitra } } -func (p *mcuProxyPublisher) PublisherId() string { +func (p *mcuProxyPublisher) PublisherId() PublicSessionId { return p.id } @@ -234,22 +234,22 @@ func (p *mcuProxyPublisher) GetStreams(ctx context.Context) ([]PublisherStream, return nil, errors.New("not implemented") } -func (p *mcuProxyPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *mcuProxyPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("remote publishing not supported for proxy publishers") } -func (p *mcuProxyPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *mcuProxyPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("remote publishing not supported for proxy publishers") } type mcuProxySubscriber struct { mcuProxyPubSubCommon - publisherId string + publisherId PublicSessionId publisherConn *mcuProxyConnection } -func newMcuProxySubscriber(publisherId string, sid string, streamType StreamType, maxBitrate int, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { +func newMcuProxySubscriber(publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate int, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { return &mcuProxySubscriber{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ sid: sid, @@ -265,7 +265,7 @@ func newMcuProxySubscriber(publisherId string, sid string, streamType StreamType } } -func (s *mcuProxySubscriber) Publisher() string { +func (s *mcuProxySubscriber) Publisher() PublicSessionId { return s.publisherId } @@ -384,7 +384,7 @@ type mcuProxyConnection struct { publishersLock sync.RWMutex publishers map[string]*mcuProxyPublisher - publisherIds map[string]string + publisherIds map[StreamId]PublicSessionId subscribersLock sync.RWMutex subscribers map[string]*mcuProxySubscriber @@ -406,7 +406,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str closedDone: NewCloser(), callbacks: make(map[string]mcuProxyCallback), publishers: make(map[string]*mcuProxyPublisher), - publisherIds: make(map[string]string), + publisherIds: make(map[StreamId]PublicSessionId), subscribers: make(map[string]*mcuProxySubscriber), } conn.reconnectInterval.Store(int64(initialReconnectInterval)) @@ -533,13 +533,13 @@ func (c *mcuProxyConnection) Features() []string { return c.features.Load().([]string) } -func (c *mcuProxyConnection) SessionId() string { +func (c *mcuProxyConnection) SessionId() PublicSessionId { sid := c.sessionId.Load() if sid == nil { return "" } - return sid.(string) + return sid.(PublicSessionId) } func (c *mcuProxyConnection) IsConnected() bool { @@ -974,7 +974,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { c.clearPublishers() c.clearSubscribers() c.clearCallbacks() - c.sessionId.Store("") + c.sessionId.Store(PublicSessionId("")) if err := c.sendHello(); err != nil { log.Printf("Could not send hello request to %s: %s", c, err) c.scheduleReconnect() @@ -1120,13 +1120,13 @@ func (c *mcuProxyConnection) processBye(msg *ProxyServerMessage) { switch bye.Reason { case "session_resumed": log.Printf("Session %s on %s was resumed by other client, resetting", c.SessionId(), c) - c.sessionId.Store("") + c.sessionId.Store(PublicSessionId("")) case "session_expired": log.Printf("Session %s expired on %s, resetting", c.SessionId(), c) - c.sessionId.Store("") + c.sessionId.Store(PublicSessionId("")) case "session_closed": log.Printf("Session %s was closed on %s, resetting", c.SessionId(), c) - c.sessionId.Store("") + c.sessionId.Store(PublicSessionId("")) default: log.Printf("Received bye with unsupported reason from %s %+v", c, bye) } @@ -1224,7 +1224,7 @@ func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyC } } -func (c *mcuProxyConnection) deferredDeletePublisher(id string, streamType StreamType, response *ProxyServerMessage) { +func (c *mcuProxyConnection) deferredDeletePublisher(id PublicSessionId, streamType StreamType, response *ProxyServerMessage) { if response.Type == "error" { log.Printf("Publisher for %s was not created at %s: %s", id, c, response.Error) return @@ -1254,7 +1254,7 @@ func (c *mcuProxyConnection) deferredDeletePublisher(id string, streamType Strea log.Printf("Deleted publisher %s at %s", proxyId, c) } -func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { +func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { msg := &ProxyClientMessage{ Type: "command", Command: &CommandProxyClientMessage{ @@ -1285,14 +1285,14 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe publisher := newMcuProxyPublisher(id, sid, streamType, response.Command.Bitrate, settings, proxyId, c, listener) c.publishersLock.Lock() c.publishers[proxyId] = publisher - c.publisherIds[getStreamId(id, streamType)] = proxyId + c.publisherIds[getStreamId(id, streamType)] = PublicSessionId(proxyId) c.publishersLock.Unlock() statsPublishersCurrent.WithLabelValues(string(streamType)).Inc() statsPublishersTotal.WithLabelValues(string(streamType)).Inc() return publisher, nil } -func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId string, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { +func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { if response.Type == "error" { log.Printf("Subscriber for %s was not created at %s: %s", publisherSessionId, c, response.Error) return @@ -1335,7 +1335,7 @@ func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId string, } } -func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuListener, publisherId string, publisherSessionId string, streamType StreamType) (McuSubscriber, error) { +func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuListener, publisherId PublicSessionId, publisherSessionId PublicSessionId, streamType StreamType) (McuSubscriber, error) { msg := &ProxyClientMessage{ Type: "command", Command: &CommandProxyClientMessage{ @@ -1368,14 +1368,14 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList return subscriber, nil } -func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId string, publisherSessionId string, streamType StreamType, publisherConn *mcuProxyConnection, remoteToken string) (McuSubscriber, error) { +func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId PublicSessionId, publisherSessionId PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, remoteToken string) (McuSubscriber, error) { if c == publisherConn { return c.newSubscriber(ctx, listener, publisherId, publisherSessionId, streamType) } if remoteToken == "" { var err error - if remoteToken, err = c.proxy.createToken(publisherId); err != nil { + if remoteToken, err = c.proxy.createToken(string(publisherId)); err != nil { return nil, err } } @@ -1465,7 +1465,7 @@ type mcuProxy struct { settings McuSettings mu sync.RWMutex - publishers map[string]*mcuProxyConnection + publishers map[StreamId]*mcuProxyConnection publisherWaiters ChannelWaiters @@ -1514,7 +1514,7 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * connectionsMap: make(map[string][]*mcuProxyConnection), settings: settings, - publishers: make(map[string]*mcuProxyConnection), + publishers: make(map[StreamId]*mcuProxyConnection), rpcClients: rpcClients, } @@ -1957,7 +1957,7 @@ func (m *mcuProxy) removePublisher(publisher *mcuProxyPublisher) { delete(m.publishers, getStreamId(publisher.id, publisher.StreamType())) } -func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuPublisher { +func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuPublisher { var maxBitrate int if streamType == StreamTypeScreen { maxBitrate = int(m.settings.MaxScreenBitrate()) @@ -1996,7 +1996,7 @@ func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id return nil } -func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { connections := m.getSortedConnections(initiator) publisher := m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections, func(c *mcuProxyConnection) bool { bw := c.Bandwidth() @@ -2047,14 +2047,14 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id st return publisher, nil } -func (m *mcuProxy) getPublisherConnection(publisher string, streamType StreamType) *mcuProxyConnection { +func (m *mcuProxy) getPublisherConnection(publisher PublicSessionId, streamType StreamType) *mcuProxyConnection { m.mu.RLock() defer m.mu.RUnlock() return m.publishers[getStreamId(publisher, streamType)] } -func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher string, streamType StreamType) *mcuProxyConnection { +func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher PublicSessionId, streamType StreamType) *mcuProxyConnection { m.mu.Lock() defer m.mu.Unlock() @@ -2086,13 +2086,13 @@ func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher str } type proxyPublisherInfo struct { - id string + id PublicSessionId conn *mcuProxyConnection token string err error } -func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, info *proxyPublisherInfo, publisher string, streamType StreamType, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { +func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, info *proxyPublisherInfo, publisher PublicSessionId, streamType StreamType, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { for _, conn := range connections { if !isAllowed(conn) || conn.IsShutdownScheduled() || conn.IsTemporary() { continue @@ -2119,7 +2119,7 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i return nil } -func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publisher string, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { var publisherInfo *proxyPublisherInfo if conn := m.getPublisherConnection(publisher, streamType); conn != nil { // Fast common path: publisher is available locally. diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 2ec0697..05d323b 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -190,7 +190,7 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { type proxyServerClientHandler func(msg *ProxyClientMessage) (*ProxyServerMessage, error) type testProxyServerPublisher struct { - id string + id PublicSessionId } type testProxyServerSubscriber struct { @@ -209,7 +209,7 @@ type testProxyServerClient struct { processMessage proxyServerClientHandler mu sync.Mutex - sessionId string + sessionId PublicSessionId } func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxyServerMessage, error) { @@ -296,7 +296,7 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( Id: msg.Id, Type: "command", Command: &CommandProxyServerMessage{ - Id: pub.id, + Id: string(pub.id), Bitrate: msg.Command.Bitrate, }, } @@ -306,14 +306,14 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( defer c.server.Wakeup() } - if pub, found := c.server.deletePublisher(msg.Command.ClientId); !found { + if pub, found := c.server.deletePublisher(PublicSessionId(msg.Command.ClientId)); !found { response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.ClientId)) } else { response = &ProxyServerMessage{ Id: msg.Id, Type: "command", Command: &CommandProxyServerMessage{ - Id: pub.id, + Id: string(pub.id), }, } c.server.updateLoad(-1) @@ -341,7 +341,7 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( }) if assert.NoError(c.t, err) { if claims, ok := token.Claims.(*TokenClaims); assert.True(c.t, token.Valid) && assert.True(c.t, ok) { - assert.Equal(c.t, msg.Command.PublisherId, claims.Subject) + assert.EqualValues(c.t, msg.Command.PublisherId, claims.Subject) } } @@ -515,8 +515,8 @@ type TestProxyServerHandler struct { load atomic.Int64 incoming atomic.Pointer[float64] outgoing atomic.Pointer[float64] - clients map[string]*testProxyServerClient - publishers map[string]*testProxyServerPublisher + clients map[PublicSessionId]*testProxyServerClient + publishers map[PublicSessionId]*testProxyServerPublisher subscribers map[string]*testProxyServerSubscriber wakeupChan chan struct{} @@ -526,7 +526,7 @@ func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { h.mu.Lock() defer h.mu.Unlock() pub := &testProxyServerPublisher{ - id: newRandomString(32), + id: PublicSessionId(newRandomString(32)), } for { @@ -534,20 +534,20 @@ func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { break } - pub.id = newRandomString(32) + pub.id = PublicSessionId(newRandomString(32)) } h.publishers[pub.id] = pub return pub } -func (h *TestProxyServerHandler) getPublisher(id string) *testProxyServerPublisher { +func (h *TestProxyServerHandler) getPublisher(id PublicSessionId) *testProxyServerPublisher { h.mu.Lock() defer h.mu.Unlock() return h.publishers[id] } -func (h *TestProxyServerHandler) deletePublisher(id string) (*testProxyServerPublisher, bool) { +func (h *TestProxyServerHandler) deletePublisher(id PublicSessionId) (*testProxyServerPublisher, bool) { h.mu.Lock() defer h.mu.Unlock() @@ -681,7 +681,7 @@ func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques t: h.t, server: h, ws: ws, - sessionId: newRandomString(32), + sessionId: PublicSessionId(newRandomString(32)), } h.mu.Lock() @@ -713,8 +713,8 @@ func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler tokens: make(map[string]*rsa.PublicKey), upgrader: &upgrader, country: country, - clients: make(map[string]*testProxyServerClient), - publishers: make(map[string]*testProxyServerPublisher), + clients: make(map[PublicSessionId]*testProxyServerClient), + publishers: make(map[PublicSessionId]*testProxyServerPublisher), subscribers: make(map[string]*testProxyServerSubscriber), wakeupChan: make(chan struct{}), } @@ -1043,7 +1043,7 @@ func Test_ProxyPublisherSubscriber(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1079,7 +1079,7 @@ func Test_ProxyPublisherCodecs(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1106,7 +1106,7 @@ func Test_ProxyWaitForPublisher(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1161,7 +1161,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pub1Id := "the-publisher-1" + pub1Id := PublicSessionId("the-publisher-1") pub1Sid := "1234567890" pub1Listener := &MockMcuListener{ publicId: pub1Id + "-public", @@ -1200,7 +1200,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { time.Sleep(time.Millisecond) } - pub2Id := "the-publisher-2" + pub2Id := PublicSessionId("the-publisher-2") pub2id := "1234567890" pub2Listener := &MockMcuListener{ publicId: pub2Id + "-public", @@ -1231,7 +1231,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pub1Id := "the-publisher-1" + pub1Id := PublicSessionId("the-publisher-1") pub1Sid := "1234567890" pub1Listener := &MockMcuListener{ publicId: pub1Id + "-public", @@ -1273,7 +1273,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { time.Sleep(time.Millisecond) } - pub2Id := "the-publisher-2" + pub2Id := PublicSessionId("the-publisher-2") pub2id := "1234567890" pub2Listener := &MockMcuListener{ publicId: pub2Id + "-public", @@ -1304,7 +1304,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pub1Id := "the-publisher-1" + pub1Id := PublicSessionId("the-publisher-1") pub1Sid := "1234567890" pub1Listener := &MockMcuListener{ publicId: pub1Id + "-public", @@ -1323,7 +1323,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { mcu.nextSort.Store(0) time.Sleep(100 * time.Millisecond) - pub2Id := "the-publisher-2" + pub2Id := PublicSessionId("the-publisher-2") pub2id := "1234567890" pub2Listener := &MockMcuListener{ publicId: pub2Id + "-public", @@ -1354,7 +1354,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubDEId := "the-publisher-de" + pubDEId := PublicSessionId("the-publisher-de") pubDESid := "1234567890" pubDEListener := &MockMcuListener{ publicId: pubDEId + "-public", @@ -1371,7 +1371,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { assert.Equal(t, serverDE.URL, pubDE.(*mcuProxyPublisher).conn.rawUrl) - pubUSId := "the-publisher-us" + pubUSId := PublicSessionId("the-publisher-us") pubUSSid := "1234567890" pubUSListener := &MockMcuListener{ publicId: pubUSId + "-public", @@ -1402,7 +1402,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubDEId := "the-publisher-de" + pubDEId := PublicSessionId("the-publisher-de") pubDESid := "1234567890" pubDEListener := &MockMcuListener{ publicId: pubDEId + "-public", @@ -1419,7 +1419,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { assert.Equal(t, serverDE.URL, pubDE.(*mcuProxyPublisher).conn.rawUrl) - pubFRId := "the-publisher-fr" + pubFRId := PublicSessionId("the-publisher-fr") pubFRSid := "1234567890" pubFRListener := &MockMcuListener{ publicId: pubFRId + "-public", @@ -1450,7 +1450,7 @@ func Test_ProxySubscriberCountry(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1494,7 +1494,7 @@ func Test_ProxySubscriberContinent(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1538,7 +1538,7 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1602,7 +1602,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1657,14 +1657,14 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { type mockGrpcServerHub struct { proxy atomic.Pointer[mcuProxy] sessionsLock sync.Mutex - sessionByPublicId map[string]Session + sessionByPublicId map[PublicSessionId]Session } func (h *mockGrpcServerHub) addSession(session *ClientSession) { h.sessionsLock.Lock() defer h.sessionsLock.Unlock() if h.sessionByPublicId == nil { - h.sessionByPublicId = make(map[string]Session) + h.sessionByPublicId = make(map[PublicSessionId]Session) } h.sessionByPublicId[session.PublicId()] = session } @@ -1675,17 +1675,17 @@ func (h *mockGrpcServerHub) removeSession(session *ClientSession) { delete(h.sessionByPublicId, session.PublicId()) } -func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId string) Session { +func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId PrivateSessionId) Session { return nil } -func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId string) Session { +func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId PublicSessionId) Session { h.sessionsLock.Lock() defer h.sessionsLock.Unlock() return h.sessionByPublicId[sessionId] } -func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId string) (string, error) { +func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) { return "", nil } @@ -1746,7 +1746,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1842,7 +1842,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1933,7 +1933,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2027,7 +2027,7 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2139,7 +2139,7 @@ func Test_ProxyConnectToken(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2225,7 +2225,7 @@ func Test_ProxyPublisherToken(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2278,7 +2278,7 @@ func Test_ProxyPublisherTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2319,7 +2319,7 @@ func Test_ProxySubscriberTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := "the-publisher" + pubId := PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", diff --git a/mcu_test.go b/mcu_test.go index defebda..4352a61 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -40,13 +40,13 @@ const ( type TestMCU struct { mu sync.Mutex - publishers map[string]*TestMCUPublisher + publishers map[PublicSessionId]*TestMCUPublisher subscribers map[string]*TestMCUSubscriber } func NewTestMCU() (*TestMCU, error) { return &TestMCU{ - publishers: make(map[string]*TestMCUPublisher), + publishers: make(map[PublicSessionId]*TestMCUPublisher), subscribers: make(map[string]*TestMCUSubscriber), }, nil } @@ -75,7 +75,7 @@ func (m *TestMCU) GetServerInfoSfu() *BackendServerInfoSfu { return nil } -func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id string, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { var maxBitrate int if streamType == StreamTypeScreen { maxBitrate = TestMaxBitrateScreen @@ -89,7 +89,7 @@ func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id str } pub := &TestMCUPublisher{ TestMCUClient: TestMCUClient{ - id: id, + id: string(id), sid: sid, streamType: streamType, }, @@ -104,7 +104,7 @@ func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id str return pub, nil } -func (m *TestMCU) GetPublishers() map[string]*TestMCUPublisher { +func (m *TestMCU) GetPublishers() map[PublicSessionId]*TestMCUPublisher { m.mu.Lock() defer m.mu.Unlock() @@ -112,14 +112,14 @@ func (m *TestMCU) GetPublishers() map[string]*TestMCUPublisher { return result } -func (m *TestMCU) GetPublisher(id string) *TestMCUPublisher { +func (m *TestMCU) GetPublisher(id PublicSessionId) *TestMCUPublisher { m.mu.Lock() defer m.mu.Unlock() return m.publishers[id] } -func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publisher string, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { m.mu.Lock() defer m.mu.Unlock() @@ -182,8 +182,8 @@ type TestMCUPublisher struct { sdp string } -func (p *TestMCUPublisher) PublisherId() string { - return p.id +func (p *TestMCUPublisher) PublisherId() PublicSessionId { + return PublicSessionId(p.id) } func (p *TestMCUPublisher) HasMedia(mt MediaType) bool { @@ -232,11 +232,11 @@ func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]PublisherStream, e return nil, errors.New("not implemented") } -func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("remote publishing not supported") } -func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("remote publishing not supported") } @@ -246,8 +246,8 @@ type TestMCUSubscriber struct { publisher *TestMCUPublisher } -func (s *TestMCUSubscriber) Publisher() string { - return s.publisher.id +func (s *TestMCUSubscriber) Publisher() PublicSessionId { + return s.publisher.PublisherId() } func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 150403b..b86dd73 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -79,7 +79,7 @@ type RemoteConnection struct { msgId atomic.Int64 helloMsgId string - sessionId string + sessionId signaling.PublicSessionId pendingMessages []*signaling.ProxyClientMessage messageCallbacks map[string]chan *signaling.ProxyServerMessage @@ -481,7 +481,7 @@ func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { // Ignore case "publisher-closed": log.Printf("Remote publisher %s was closed on %s", msg.Event.ClientId, c) - c.p.RemotePublisherDeleted(msg.Event.ClientId) + c.p.RemotePublisherDeleted(signaling.PublicSessionId(msg.Event.ClientId)) default: log.Printf("Received unsupported event %+v from %s", msg, c) } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 17a8bae..6e37202 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -747,7 +747,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { } var session *ProxySession - if resumeId := message.Hello.ResumeId; resumeId != "" { + if resumeId := signaling.PublicSessionId(message.Hello.ResumeId); resumeId != "" { if data, err := s.cookie.DecodePublic(resumeId); err == nil { session = s.GetSession(data.Sid) } @@ -830,10 +830,10 @@ type proxyRemotePublisher struct { proxy *ProxyServer remoteUrl string - publisherId string + publisherId signaling.PublicSessionId } -func (p *proxyRemotePublisher) PublisherId() string { +func (p *proxyRemotePublisher) PublisherId() signaling.PublicSessionId { return p.publisherId } @@ -847,7 +847,7 @@ func (p *proxyRemotePublisher) StartPublishing(ctx context.Context, publisher si Type: "command", Command: &signaling.CommandProxyClientMessage{ Type: "publish-remote", - ClientId: p.publisherId, + ClientId: string(p.publisherId), Hostname: p.proxy.remoteHostname, Port: publisher.Port(), RtcpPort: publisher.RtcpPort(), @@ -871,7 +871,7 @@ func (p *proxyRemotePublisher) StopPublishing(ctx context.Context, publisher sig Type: "command", Command: &signaling.CommandProxyClientMessage{ Type: "unpublish-remote", - ClientId: p.publisherId, + ClientId: string(p.publisherId), Hostname: p.proxy.remoteHostname, Port: publisher.Port(), RtcpPort: publisher.RtcpPort(), @@ -893,7 +893,7 @@ func (p *proxyRemotePublisher) GetStreams(ctx context.Context) ([]signaling.Publ Type: "command", Command: &signaling.CommandProxyClientMessage{ Type: "get-publisher-streams", - ClientId: p.publisherId, + ClientId: string(p.publisherId), }, }) if err != nil { @@ -966,7 +966,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s MediaTypes: cmd.MediaTypes, // nolint } } - publisher, err := s.mcu.NewPublisher(ctx2, session, id, cmd.Sid, cmd.StreamType, *settings, &emptyInitiator{}) + publisher, err := s.mcu.NewPublisher(ctx2, session, signaling.PublicSessionId(id), cmd.Sid, cmd.StreamType, *settings, &emptyInitiator{}) if err == context.DeadlineExceeded { log.Printf("Timeout while creating %s publisher %s for %s", cmd.StreamType, id, session.PublicId()) session.sendMessage(message.NewErrorServerMessage(TimeoutCreatingPublisher)) @@ -1034,7 +1034,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - if claims.Subject != publisherId { + if claims.Subject != string(publisherId) { session.sendMessage(message.NewErrorServerMessage(TokenAuthFailed)) return } @@ -1664,7 +1664,7 @@ func (s *ProxyServer) PublisherDeleted(publisher signaling.McuPublisher) { } } -func (s *ProxyServer) RemotePublisherDeleted(publisherId string) { +func (s *ProxyServer) RemotePublisherDeleted(publisherId signaling.PublicSessionId) { s.sessionsLock.RLock() defer s.sessionsLock.RUnlock() diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index f441d03..641d992 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -380,25 +380,25 @@ func (m *TestMCU) GetServerInfoSfu() *signaling.BackendServerInfoSfu { return nil } -func (m *TestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id string, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *TestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { return nil, errors.New("not implemented") } -func (m *TestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher string, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { +func (m *TestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { return nil, errors.New("not implemented") } type TestMCUPublisher struct { - id string + id signaling.PublicSessionId sid string streamType signaling.StreamType } func (p *TestMCUPublisher) Id() string { - return p.id + return string(p.id) } -func (p *TestMCUPublisher) PublisherId() string { +func (p *TestMCUPublisher) PublisherId() signaling.PublicSessionId { return p.id } @@ -432,11 +432,11 @@ func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]signaling.Publishe return nil, errors.New("not implemented") } -func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("not implemented") } -func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("not implemented") } @@ -464,7 +464,7 @@ func NewHangingTestMCU(t *testing.T) *HangingTestMCU { } } -func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id string, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { ctx2, cancel := context.WithTimeout(m.ctx, testTimeout*2) defer cancel() @@ -482,7 +482,7 @@ func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.Mc } } -func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher string, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { +func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { ctx2, cancel := context.WithTimeout(m.ctx, testTimeout*2) defer cancel() @@ -569,7 +569,7 @@ func NewCodecsTestMCU(t *testing.T) *CodecsTestMCU { } } -func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id string, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { assert.Equal(m.t, "opus,g722", settings.AudioCodec) assert.Equal(m.t, "vp9,vp8,av1", settings.VideoCodec) return &TestMCUPublisher{ @@ -730,8 +730,8 @@ func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signali callback(errors.New("not implemented"), nil) } -func (s *TestRemoteSubscriber) Publisher() string { - return s.publisher.Id() +func (s *TestRemoteSubscriber) Publisher() signaling.PublicSessionId { + return signaling.PublicSessionId(s.publisher.Id()) } func (m *RemoteSubscriberTestMCU) NewRemoteSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.McuRemotePublisher) (signaling.McuRemoteSubscriber, error) { @@ -778,12 +778,12 @@ func TestProxyRemoteSubscriber(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := "the-publisher-id" + publisherId := signaling.PublicSessionId("the-publisher-id") claims := &signaling.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, - Subject: publisherId, + Subject: string(publisherId), }, } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) @@ -869,12 +869,12 @@ func TestProxyCloseRemoteOnSessionClose(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := "the-publisher-id" + publisherId := signaling.PublicSessionId("the-publisher-id") claims := &signaling.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, - Subject: publisherId, + Subject: string(publisherId), }, } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) @@ -937,11 +937,11 @@ type UnpublishRemoteTestPublisher struct { t *testing.T mu sync.RWMutex - remoteId string + remoteId signaling.PublicSessionId remoteData *remotePublisherData } -func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id string, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { publisher := &UnpublishRemoteTestPublisher{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -955,7 +955,7 @@ func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener sign return publisher, nil } -func (p *UnpublishRemoteTestPublisher) getRemoteId() string { +func (p *UnpublishRemoteTestPublisher) getRemoteId() signaling.PublicSessionId { p.mu.RLock() defer p.mu.RUnlock() return p.remoteId @@ -974,7 +974,7 @@ func (p *UnpublishRemoteTestPublisher) clearRemote() { p.remoteData = nil } -func (p *UnpublishRemoteTestPublisher) PublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *UnpublishRemoteTestPublisher) PublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { p.mu.Lock() defer p.mu.Unlock() if assert.Empty(p.t, p.remoteId) { @@ -988,7 +988,7 @@ func (p *UnpublishRemoteTestPublisher) PublishRemote(ctx context.Context, remote return nil } -func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remoteId string, hostname string, port int, rtcpPort int) error { +func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { p.mu.Lock() defer p.mu.Unlock() assert.Equal(p.t, remoteId, p.remoteId) @@ -1026,7 +1026,7 @@ func TestProxyUnpublishRemote(t *testing.T) { _, err := client1.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := "the-publisher-id" + publisherId := signaling.PublicSessionId("the-publisher-id") require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ Id: "2345", Type: "command", @@ -1143,7 +1143,7 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { _, err := client1.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := "the-publisher-id" + publisherId := signaling.PublicSessionId("the-publisher-id") require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ Id: "2345", Type: "command", @@ -1275,7 +1275,7 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { _, err := client1.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := "the-publisher-id" + publisherId := signaling.PublicSessionId("the-publisher-id") require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ Id: "2345", Type: "command", diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 30d9fab..f760b2b 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -38,7 +38,7 @@ const ( ) type remotePublisherData struct { - id string + id signaling.PublicSessionId hostname string port int rtcpPort int @@ -46,7 +46,7 @@ type remotePublisherData struct { type ProxySession struct { proxy *ProxyServer - id string + id signaling.PublicSessionId sid uint64 lastUsed atomic.Int64 ctx context.Context @@ -68,7 +68,7 @@ type ProxySession struct { remotePublishers map[signaling.McuPublisher]map[string]*remotePublisherData } -func NewProxySession(proxy *ProxyServer, sid uint64, id string) *ProxySession { +func NewProxySession(proxy *ProxyServer, sid uint64, id signaling.PublicSessionId) *ProxySession { ctx, closeFunc := context.WithCancel(context.Background()) result := &ProxySession{ proxy: proxy, @@ -91,7 +91,7 @@ func (s *ProxySession) Context() context.Context { return s.ctx } -func (s *ProxySession) PublicId() string { +func (s *ProxySession) PublicId() signaling.PublicSessionId { return s.id } @@ -441,7 +441,7 @@ func (s *ProxySession) OnPublisherDeleted(publisher signaling.McuPublisher) { Type: "event", Event: &signaling.EventProxyServerMessage{ Type: "publisher-closed", - ClientId: entry.id, + ClientId: string(entry.id), }, } s.sendMessage(msg) @@ -449,7 +449,7 @@ func (s *ProxySession) OnPublisherDeleted(publisher signaling.McuPublisher) { } } -func (s *ProxySession) OnRemotePublisherDeleted(publisherId string) { +func (s *ProxySession) OnRemotePublisherDeleted(publisherId signaling.PublicSessionId) { s.subscribersLock.Lock() defer s.subscribersLock.Unlock() diff --git a/proxy/proxy_testclient_test.go b/proxy/proxy_testclient_test.go index 7930545..a239f41 100644 --- a/proxy/proxy_testclient_test.go +++ b/proxy/proxy_testclient_test.go @@ -51,7 +51,7 @@ type ProxyTestClient struct { messageChan chan []byte readErrorChan chan error - sessionId string + sessionId signaling.PublicSessionId } func NewProxyTestClient(ctx context.Context, t *testing.T, url string) *ProxyTestClient { diff --git a/remotesession.go b/remotesession.go index 3c56720..c548f9e 100644 --- a/remotesession.go +++ b/remotesession.go @@ -35,12 +35,12 @@ type RemoteSession struct { hub *Hub client *Client remoteClient *GrpcClient - sessionId string + sessionId PublicSessionId proxy atomic.Pointer[SessionProxy] } -func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessionId string) (*RemoteSession, error) { +func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessionId PublicSessionId) (*RemoteSession, error) { remoteSession := &RemoteSession{ hub: hub, client: client, diff --git a/room.go b/room.go index e1790b9..caa2310 100644 --- a/room.go +++ b/room.go @@ -71,12 +71,12 @@ type Room struct { closer *Closer mu *sync.RWMutex - sessions map[string]Session + sessions map[PublicSessionId]Session internalSessions map[*ClientSession]bool virtualSessions map[*VirtualSession]bool inCallSessions map[Session]bool - roomSessionData map[string]*RoomSessionData + roomSessionData map[PublicSessionId]*RoomSessionData statsRoomSessionsCurrent *prometheus.GaugeVec @@ -108,12 +108,12 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEv closer: NewCloser(), mu: &sync.RWMutex{}, - sessions: make(map[string]Session), + sessions: make(map[PublicSessionId]Session), internalSessions: make(map[*ClientSession]bool), virtualSessions: make(map[*VirtualSession]bool), inCallSessions: make(map[Session]bool), - roomSessionData: make(map[string]*RoomSessionData), + roomSessionData: make(map[PublicSessionId]*RoomSessionData), statsRoomSessionsCurrent: statsRoomSessionsCurrent.MustCurryWith(prometheus.Labels{ "backend": backend.Id(), @@ -340,7 +340,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { } } -func (r *Room) getOtherSessions(ignoreSessionId string) (Session, []Session) { +func (r *Room) getOtherSessions(ignoreSessionId PublicSessionId) (Session, []Session) { r.mu.Lock() defer r.mu.Unlock() @@ -356,7 +356,7 @@ func (r *Room) getOtherSessions(ignoreSessionId string) (Session, []Session) { return r.sessions[ignoreSessionId], sessions } -func (r *Room) notifySessionJoined(sessionId string) { +func (r *Room) notifySessionJoined(sessionId PublicSessionId) { session, sessions := r.getOtherSessions(sessionId) if len(sessions) == 0 { return @@ -461,7 +461,7 @@ func (r *Room) RemoveSession(session Session) bool { // Handle case where virtual session was also sent by Nextcloud. users := make([]StringMap, 0, len(r.users)) for _, u := range r.users { - if u["sessionId"] != sid { + if value, found := GetStringMapString[PublicSessionId](u, "sessionId"); !found || value != sid { users = append(users, u) } } @@ -574,7 +574,7 @@ func (r *Room) PublishSessionLeft(session Session) { Event: &EventServerMessage{ Target: "room", Type: "leave", - Leave: []string{ + Leave: []PublicSessionId{ sessionId, }, }, @@ -588,7 +588,7 @@ func (r *Room) PublishSessionLeft(session Session) { } } -func (r *Room) getClusteredInternalSessionsRLocked() (internal map[string]*InternalSessionData, virtual map[string]*VirtualSessionData) { +func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSessionId]*InternalSessionData, virtual map[PublicSessionId]*VirtualSessionData) { if r.hub.rpcClients == nil { return nil, nil } @@ -614,11 +614,11 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[string]*Inter mu.Lock() defer mu.Unlock() if internal == nil { - internal = make(map[string]*InternalSessionData, len(clientInternal)) + internal = make(map[PublicSessionId]*InternalSessionData, len(clientInternal)) } maps.Copy(internal, clientInternal) if virtual == nil { - virtual = make(map[string]*VirtualSessionData, len(clientVirtual)) + virtual = make(map[PublicSessionId]*VirtualSessionData, len(clientVirtual)) } maps.Copy(virtual, clientVirtual) }(client) @@ -643,29 +643,27 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { return users } - skipSession := make(map[string]bool) + skipSession := make(map[PublicSessionId]bool) for _, user := range users { - sessionid, found := user["sessionId"] + sessionid, found := GetStringMapString[PublicSessionId](user, "sessionId") if !found || sessionid == "" { continue } if userid, found := user["userId"]; !found || userid == "" { - if roomSessionData, found := r.roomSessionData[sessionid.(string)]; found { + if roomSessionData, found := r.roomSessionData[sessionid]; found { user["userId"] = roomSessionData.UserId - } else if sid, ok := sessionid.(string); ok { - if entry, found := clusteredVirtualSessions[sid]; found { - user["virtual"] = true - user["inCall"] = entry.GetInCall() - skipSession[sid] = true - } else { - for session := range r.virtualSessions { - if session.PublicId() == sid { - user["virtual"] = true - user["inCall"] = session.GetInCall() - skipSession[sid] = true - break - } + } else if entry, found := clusteredVirtualSessions[sessionid]; found { + user["virtual"] = true + user["inCall"] = entry.GetInCall() + skipSession[sessionid] = true + } else { + for session := range r.virtualSessions { + if session.PublicId() == sessionid { + user["virtual"] = true + user["inCall"] = session.GetInCall() + skipSession[sessionid] = true + break } } } @@ -762,19 +760,14 @@ func (r *Room) PublishUsersInCallChanged(changed []StringMap, users []StringMap) continue } - sessionIdInterface, found := user["sessionId"] + sessionId, found := GetStringMapString[PublicSessionId](user, "sessionId") if !found { - sessionIdInterface, found = user["sessionid"] + sessionId, found = GetStringMapString[PublicSessionId](user, "sessionid") if !found { continue } } - sessionId, ok := sessionIdInterface.(string) - if !ok { - continue - } - session := r.hub.GetSessionByPublicId(sessionId) if session == nil { continue @@ -824,7 +817,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { var notify []*ClientSession if inCall&FlagInCall != 0 { // All connected sessions join the call. - var joined []string + var joined []PublicSessionId for _, session := range r.sessions { clientSession, ok := session.(*ClientSession) if !ok { @@ -1055,7 +1048,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { u = parsed.String() parsedBackendUrl := parsed - var sid string + var sid RoomSessionId var uid string switch sess := session.(type) { case *ClientSession: @@ -1064,7 +1057,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { uid = sess.AuthUserId() case *VirtualSession: // Use our internal generated session id (will be added to Nextcloud). - sid = sess.PublicId() + sid = RoomSessionId(sess.PublicId()) uid = sess.UserId() default: continue @@ -1144,7 +1137,7 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { for _, sessionId := range message.SessionsList { wg.Add(1) - go func(sessionId string) { + go func(sessionId PublicSessionId) { defer wg.Done() if err := r.events.PublishSessionMessage(sessionId, r.backend, &AsyncMessage{ @@ -1160,7 +1153,7 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { if len(message.SessionsMap) > 0 { for sessionId, details := range message.SessionsMap { wg.Add(1) - go func(sessionId string, details json.RawMessage) { + go func(sessionId PublicSessionId, details json.RawMessage) { defer wg.Done() msg := &ServerMessage{ diff --git a/room_test.go b/room_test.go index 02bc169..eadcd19 100644 --- a/room_test.go +++ b/room_test.go @@ -296,7 +296,7 @@ func TestRoom_RoomJoinFeatures(t *testing.T) { if message, ok := client.RunUntilMessage(ctx); ok { if client.checkMessageJoinedSession(message, hello.Hello.SessionId, testDefaultUserId) { - assert.Equal(roomId+"-"+hello.Hello.SessionId, message.Event.Join[0].RoomSessionId) + assert.EqualValues(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), message.Event.Join[0].RoomSessionId) assert.Equal(features, message.Event.Join[0].Features) } } @@ -329,7 +329,7 @@ func TestRoom_RoomSessionData(t *testing.T) { expected := "userid-from-sessiondata" if message, ok := client.RunUntilMessage(ctx); ok { if client.checkMessageJoinedSession(message, hello.Hello.SessionId, expected) { - assert.Equal(roomId+"-"+hello.Hello.SessionId, message.Event.Join[0].RoomSessionId) + assert.EqualValues(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), message.Event.Join[0].RoomSessionId) } } diff --git a/roomsessions.go b/roomsessions.go index b984463..3ff8b5e 100644 --- a/roomsessions.go +++ b/roomsessions.go @@ -31,9 +31,9 @@ var ( ) type RoomSessions interface { - SetRoomSession(session Session, roomSessionId string) error + SetRoomSession(session Session, roomSessionId RoomSessionId) error DeleteRoomSession(session Session) - GetSessionId(roomSessionId string) (string, error) - LookupSessionId(ctx context.Context, roomSessionId string, disconnectReason string) (string, error) + GetSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) + LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) } diff --git a/roomsessions_builtin.go b/roomsessions_builtin.go index 926fe9f..c39926f 100644 --- a/roomsessions_builtin.go +++ b/roomsessions_builtin.go @@ -30,8 +30,8 @@ import ( ) type BuiltinRoomSessions struct { - sessionIdToRoomSession map[string]string - roomSessionToSessionid map[string]string + sessionIdToRoomSession map[PublicSessionId]RoomSessionId + roomSessionToSessionid map[RoomSessionId]PublicSessionId mu sync.RWMutex clients *GrpcClients @@ -39,14 +39,14 @@ type BuiltinRoomSessions struct { func NewBuiltinRoomSessions(clients *GrpcClients) (RoomSessions, error) { return &BuiltinRoomSessions{ - sessionIdToRoomSession: make(map[string]string), - roomSessionToSessionid: make(map[string]string), + sessionIdToRoomSession: make(map[PublicSessionId]RoomSessionId), + roomSessionToSessionid: make(map[RoomSessionId]PublicSessionId), clients: clients, }, nil } -func (r *BuiltinRoomSessions) SetRoomSession(session Session, roomSessionId string) error { +func (r *BuiltinRoomSessions) SetRoomSession(session Session, roomSessionId RoomSessionId) error { if roomSessionId == "" { r.DeleteRoomSession(session) return nil @@ -84,7 +84,7 @@ func (r *BuiltinRoomSessions) DeleteRoomSession(session Session) { } } -func (r *BuiltinRoomSessions) GetSessionId(roomSessionId string) (string, error) { +func (r *BuiltinRoomSessions) GetSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) { r.mu.RLock() defer r.mu.RUnlock() sid, found := r.roomSessionToSessionid[roomSessionId] @@ -95,7 +95,7 @@ func (r *BuiltinRoomSessions) GetSessionId(roomSessionId string) (string, error) return sid, nil } -func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId string, disconnectReason string) (string, error) { +func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) { sid, err := r.GetSessionId(roomSessionId) if err == nil { return sid, nil @@ -142,5 +142,5 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId return "", ErrNoSuchRoomSession } - return value.(string), nil + return value.(PublicSessionId), nil } diff --git a/roomsessions_test.go b/roomsessions_test.go index 94417fc..9a2fcc8 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -32,18 +32,18 @@ import ( ) type DummySession struct { - publicId string + publicId PublicSessionId } func (s *DummySession) Context() context.Context { return context.Background() } -func (s *DummySession) PrivateId() string { +func (s *DummySession) PrivateId() PrivateSessionId { return "" } -func (s *DummySession) PublicId() string { +func (s *DummySession) PublicId() PublicSessionId { return s.publicId } @@ -105,7 +105,7 @@ func (s *DummySession) SendMessage(message *ServerMessage) bool { return false } -func checkSession(t *testing.T, sessions RoomSessions, sessionId string, roomSessionId string) Session { +func checkSession(t *testing.T, sessions RoomSessions, sessionId PublicSessionId, roomSessionId RoomSessionId) Session { session := &DummySession{ publicId: sessionId, } diff --git a/session.go b/session.go index c2cc8d2..c0834d0 100644 --- a/session.go +++ b/session.go @@ -49,8 +49,8 @@ var ( type Session interface { Context() context.Context - PrivateId() string - PublicId() string + PrivateId() PrivateSessionId + PublicId() PublicSessionId ClientType() string Data() *SessionIdData diff --git a/sessionid_codec.go b/sessionid_codec.go index f5d9930..990596a 100644 --- a/sessionid_codec.go +++ b/sessionid_codec.go @@ -66,8 +66,13 @@ func NewSessionIdCodec(hashKey []byte, blockKey []byte) *SessionIdCodec { } } -func (c *SessionIdCodec) EncodePrivate(sessionData *SessionIdData) (string, error) { - return c.cookie.Encode(privateSessionName, sessionData) +func (c *SessionIdCodec) EncodePrivate(sessionData *SessionIdData) (PrivateSessionId, error) { + id, err := c.cookie.Encode(privateSessionName, sessionData) + if err != nil { + return "", err + } + + return PrivateSessionId(id), nil } func reverseSessionId(s string) (string, error) { @@ -83,7 +88,7 @@ func reverseSessionId(s string) (string, error) { return base64.URLEncoding.EncodeToString(decoded), nil } -func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (string, error) { +func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (PublicSessionId, error) { encoded, err := c.cookie.Encode(publicSessionName, sessionData) if err != nil { return "", err @@ -94,26 +99,31 @@ func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (string, error // (a timestamp) but the suffix the (random) hash. // By reversing we move the hash to the front, making the comparison of // session ids "random". - return reverseSessionId(encoded) + id, err := reverseSessionId(encoded) + if err != nil { + return "", err + } + + return PublicSessionId(id), nil } -func (c *SessionIdCodec) DecodePrivate(encodedData string) (*SessionIdData, error) { +func (c *SessionIdCodec) DecodePrivate(encodedData PrivateSessionId) (*SessionIdData, error) { var data SessionIdData - if err := c.cookie.Decode(privateSessionName, encodedData, &data); err != nil { + if err := c.cookie.Decode(privateSessionName, string(encodedData), &data); err != nil { return nil, err } return &data, nil } -func (c *SessionIdCodec) DecodePublic(encodedData string) (*SessionIdData, error) { - encodedData, err := reverseSessionId(encodedData) +func (c *SessionIdCodec) DecodePublic(encodedData PublicSessionId) (*SessionIdData, error) { + reversed, err := reverseSessionId(string(encodedData)) if err != nil { return nil, err } var data SessionIdData - if err := c.cookie.Decode(publicSessionName, encodedData, &data); err != nil { + if err := c.cookie.Decode(publicSessionName, reversed, &data); err != nil { return nil, err } diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 58a7d0d..35b7e9b 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -70,10 +70,10 @@ func TestPublicPrivate(t *testing.T) { require.NoError(err) assert.NotEqual(private, public) - if data, err := codec.DecodePublic(private); !assert.Error(err) { + if data, err := codec.DecodePublic(PublicSessionId(private)); !assert.Error(err) { assert.Fail("should have failed", "received %+v", data) } - if data, err := codec.DecodePrivate(public); !assert.Error(err) { + if data, err := codec.DecodePrivate(PrivateSessionId(public)); !assert.Error(err) { assert.Fail("should have failed", "received %+v", data) } } diff --git a/stringmap.go b/stringmap.go index fd6c437..ef4d3fb 100644 --- a/stringmap.go +++ b/stringmap.go @@ -51,3 +51,20 @@ func GetStringMapEntry[T any](m StringMap, key string) (s T, ok bool) { s, ok = v.(T) return } + +func GetStringMapString[T ~string](m StringMap, key string) (T, bool) { + var defaultValue T + v, found := m[key] + if !found { + return defaultValue, false + } + + switch v := v.(type) { + case T: + return v, true + case string: + return T(v), true + default: + return defaultValue, false + } +} diff --git a/stringmap_test.go b/stringmap_test.go index 7b24491..d0a5ebb 100644 --- a/stringmap_test.go +++ b/stringmap_test.go @@ -54,3 +54,37 @@ func TestConvertStringMap(t *testing.T) { }) assert.False(ok) } + +func TestGetStringMapString(t *testing.T) { + assert := assert.New(t) + + type StringMapTestString string + + var ok bool + m := StringMap{ + "foo": "bar", + "bar": StringMapTestString("baz"), + "baz": 1234, + } + if v, ok := GetStringMapString[string](m, "foo"); assert.True(ok) { + assert.Equal("bar", v) + } + if v, ok := GetStringMapString[StringMapTestString](m, "foo"); assert.True(ok) { + assert.Equal(StringMapTestString("bar"), v) + } + v, ok := GetStringMapString[string](m, "bar") + assert.False(ok, "should not find object, got %+v", v) + + if v, ok := GetStringMapString[StringMapTestString](m, "bar"); assert.True(ok) { + assert.Equal(StringMapTestString("baz"), v) + } + + _, ok = GetStringMapString[string](m, "baz") + assert.False(ok) + _, ok = GetStringMapString[StringMapTestString](m, "baz") + assert.False(ok) + _, ok = GetStringMapString[string](m, "invalid") + assert.False(ok) + _, ok = GetStringMapString[StringMapTestString](m, "invalid") + assert.False(ok) +} diff --git a/testclient_test.go b/testclient_test.go index fe6d08e..793994b 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -68,7 +68,7 @@ func getWebsocketUrl(url string) string { } } -func getPubliceSessionIdData(h *Hub, publicId string) *SessionIdData { +func getPubliceSessionIdData(h *Hub, publicId PublicSessionId) *SessionIdData { decodedPublic := h.decodePublicSessionId(publicId) if decodedPublic == nil { panic("invalid public session id") @@ -82,7 +82,7 @@ func checkMessageType(t *testing.T, message *ServerMessage, expectedType string) return false } - failed := !assert.Equal(expectedType, message.Type, "invalid message type") + failed := !assert.Equal(expectedType, message.Type, "invalid message type in %+v", message) switch message.Type { case "hello": @@ -207,7 +207,7 @@ type TestClient struct { messageChan chan []byte readErrorChan chan error - publicId string + publicId PublicSessionId } func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub) *TestClient { @@ -331,7 +331,7 @@ func (c *TestClient) WaitForClientRemoved(ctx context.Context) error { return nil } -func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId string) error { +func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId PublicSessionId) error { data := c.hub.decodePublicSessionId(sessionId) if data == nil { return fmt.Errorf("Invalid session id passed") @@ -450,7 +450,7 @@ func (c *TestClient) SendHelloV2WithTimesAndFeatures(userid string, issuedAt tim return c.SendHelloParams(c.server.URL, HelloVersionV2, "", features, params) } -func (c *TestClient) SendHelloResume(resumeId string) error { +func (c *TestClient) SendHelloResume(resumeId PrivateSessionId) error { hello := &ClientMessage{ Id: "1234", Type: "hello", @@ -776,10 +776,10 @@ func (c *TestClient) RunUntilHello(ctx context.Context) (*ServerMessage, bool) { } func (c *TestClient) JoinRoom(ctx context.Context, roomId string) (*ServerMessage, bool) { - return c.JoinRoomWithRoomSession(ctx, roomId, roomId+"-"+c.publicId) + return c.JoinRoomWithRoomSession(ctx, roomId, RoomSessionId(fmt.Sprintf("%s-%s", roomId, c.publicId))) } -func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, roomSessionId string) (message *ServerMessage, ok bool) { +func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, roomSessionId RoomSessionId) (message *ServerMessage, ok bool) { msg := &ClientMessage{ Id: "ABCD", Type: "room", @@ -822,7 +822,7 @@ func (c *TestClient) checkSingleMessageJoined(message *ServerMessage) bool { c.assert.Len(message.Event.Join, 1, "invalid number of join event entries in %+v", message) } -func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId string, userId string) bool { +func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId PublicSessionId, userId string) bool { if !c.checkSingleMessageJoined(message) { return false } @@ -898,7 +898,7 @@ func (c *TestClient) checkMessageRoomLeave(message *ServerMessage, hello *HelloS return c.checkMessageRoomLeaveSession(message, hello.SessionId) } -func (c *TestClient) checkMessageRoomLeaveSession(message *ServerMessage, sessionId string) bool { +func (c *TestClient) checkMessageRoomLeaveSession(message *ServerMessage, sessionId PublicSessionId) bool { return checkMessageType(c.t, message, "event") && c.assert.Equal("room", message.Event.Target, "invalid target in %+v", message) && c.assert.Equal("leave", message.Event.Type, "invalid event type in %+v", message) && diff --git a/virtualsession.go b/virtualsession.go index 094774c..1fd4fa3 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -38,12 +38,12 @@ const ( type VirtualSession struct { hub *Hub session *ClientSession - privateId string - publicId string + privateId PrivateSessionId + publicId PublicSessionId data *SessionIdData room atomic.Pointer[Room] - sessionId string + sessionId PublicSessionId userId string userData json.RawMessage inCall Flags @@ -53,11 +53,11 @@ type VirtualSession struct { parseUserData func() (StringMap, error) } -func GetVirtualSessionId(session Session, sessionId string) string { +func GetVirtualSessionId(session Session, sessionId PublicSessionId) PublicSessionId { return session.PublicId() + "|" + sessionId } -func NewVirtualSession(session *ClientSession, privateId string, publicId string, data *SessionIdData, msg *AddSessionInternalClientMessage) (*VirtualSession, error) { +func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, msg *AddSessionInternalClientMessage) (*VirtualSession, error) { result := &VirtualSession{ hub: session.hub, session: session, @@ -92,11 +92,11 @@ func (s *VirtualSession) Context() context.Context { return s.session.Context() } -func (s *VirtualSession) PrivateId() string { +func (s *VirtualSession) PrivateId() PrivateSessionId { return s.privateId } -func (s *VirtualSession) PublicId() string { +func (s *VirtualSession) PublicId() PublicSessionId { return s.publicId } @@ -151,7 +151,7 @@ func (s *VirtualSession) ParsedUserData() (StringMap, error) { func (s *VirtualSession) SetRoom(room *Room) { s.room.Store(room) if room != nil { - if err := s.hub.roomSessions.SetRoomSession(s, s.PublicId()); err != nil { + if err := s.hub.roomSessions.SetRoomSession(s, RoomSessionId(s.PublicId())); err != nil { log.Printf("Error adding virtual room session %s: %s", s.PublicId(), err) } } else { @@ -193,7 +193,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa defer cancel() if options := s.Options(); options != nil && options.ActorId != "" && options.ActorType != "" { - request := NewBackendClientRoomRequest(room.Id(), s.UserId(), s.PublicId()) + request := NewBackendClientRoomRequest(room.Id(), s.UserId(), RoomSessionId(s.PublicId())) request.Room.Action = "leave" request.Room.ActorId = options.ActorId request.Room.ActorType = options.ActorType @@ -243,7 +243,7 @@ func (s *VirtualSession) Session() *ClientSession { return s.session } -func (s *VirtualSession) SessionId() string { +func (s *VirtualSession) SessionId() PublicSessionId { return s.sessionId } diff --git a/virtualsession_test.go b/virtualsession_test.go index 3341cc7..79477eb 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -67,7 +67,7 @@ func TestVirtualSession(t *testing.T) { // Ignore "join" events. assert.NoError(client.DrainMessages(ctx)) - internalSessionId := "session1" + internalSessionId := PublicSessionId("session1") userId := "user1" msgAdd := &ClientMessage{ Type: "internal", @@ -101,7 +101,7 @@ func TestVirtualSession(t *testing.T) { if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { - assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) + assert.EqualValues(sessionId, updateMsg.Users[0]["sessionId"]) assert.Equal(true, updateMsg.Users[0]["virtual"]) assert.EqualValues((FlagInCall | FlagWithPhone), updateMsg.Users[0]["inCall"]) } @@ -251,7 +251,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { // Ignore "join" events. assert.NoError(client.DrainMessages(ctx)) - internalSessionId := "session1" + internalSessionId := PublicSessionId("session1") userId := "user1" msgAdd := &ClientMessage{ Type: "internal", @@ -289,7 +289,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { - assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) + assert.EqualValues(sessionId, updateMsg.Users[0]["sessionId"]) assert.Equal(true, updateMsg.Users[0]["virtual"]) assert.EqualValues((FlagInCall | FlagWithPhone), updateMsg.Users[0]["inCall"]) } @@ -403,11 +403,11 @@ func TestVirtualSessionActorInformation(t *testing.T) { } } -func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sessionId string, entryType string, inCall int) bool { +func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sessionId PublicSessionId, entryType string, inCall int) bool { assert := assert.New(t) found := false for _, entry := range message.Users { - if sid, ok := GetStringMapEntry[string](entry, "sessionId"); ok && sid == sessionId { + if sid, ok := GetStringMapString[PublicSessionId](entry, "sessionId"); ok && sid == sessionId { if value, found := GetStringMapEntry[bool](entry, entryType); !assert.True(found, "entry %s not found or invalid in %+v", entryType, entry) || !assert.True(value, "entry %s invalid in %+v", entryType, entry) { return false @@ -469,13 +469,13 @@ func TestVirtualSessionCustomInCall(t *testing.T) { if assert.Len(additional, 1) && assert.Equal("event", additional[0].Type) { assert.Equal("participants", additional[0].Event.Target) assert.Equal("update", additional[0].Event.Type) - assert.Equal(helloInternal.Hello.SessionId, additional[0].Event.Update.Users[0]["sessionId"]) + assert.EqualValues(helloInternal.Hello.SessionId, additional[0].Event.Update.Users[0]["sessionId"]) assert.EqualValues(0, additional[0].Event.Update.Users[0]["inCall"]) } } client.RunUntilJoined(ctx, helloInternal.Hello, hello.Hello) - internalSessionId := "session1" + internalSessionId := PublicSessionId("session1") userId := "user1" msgAdd := &ClientMessage{ Type: "internal", @@ -603,7 +603,7 @@ func TestVirtualSessionCleanup(t *testing.T) { // Ignore "join" events. assert.NoError(client.DrainMessages(ctx)) - internalSessionId := "session1" + internalSessionId := PublicSessionId("session1") userId := "user1" msgAdd := &ClientMessage{ Type: "internal", @@ -637,7 +637,7 @@ func TestVirtualSessionCleanup(t *testing.T) { if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { - assert.Equal(sessionId, updateMsg.Users[0]["sessionId"]) + assert.EqualValues(sessionId, updateMsg.Users[0]["sessionId"]) assert.Equal(true, updateMsg.Users[0]["virtual"]) assert.EqualValues((FlagInCall | FlagWithPhone), updateMsg.Users[0]["inCall"]) } From 1a74ea87bd87ec5b75aada5c26a67727481a277d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 8 Sep 2025 12:33:52 +0200 Subject: [PATCH 198/549] Update generated files. --- api_async_easyjson.go | 4 ++-- api_backend_easyjson.go | 28 ++++++++++++++-------------- api_proxy_easyjson.go | 6 +++--- api_signaling_easyjson.go | 38 +++++++++++++++++++------------------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/api_async_easyjson.go b/api_async_easyjson.go index 1439e78..0047f1d 100644 --- a/api_async_easyjson.go +++ b/api_async_easyjson.go @@ -39,7 +39,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe case "messageid": out.MessageId = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "data": if in.IsNull() { in.Skip() @@ -137,7 +137,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex case "type": out.Type = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "clienttype": out.ClientType = string(in.String()) default: diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index c5b853b..1da7fc3 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -1671,7 +1671,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle } switch key { case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "connected": out.Connected = bool(in.Bool()) case "address": @@ -2249,16 +2249,16 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Delim('[') if out.SessionsList == nil { if !in.IsDelim(']') { - out.SessionsList = make(BackendRoomSwitchToSessionsList, 0, 4) + out.SessionsList = make(BackendRoomSwitchToPublicSessionsList, 0, 4) } else { - out.SessionsList = BackendRoomSwitchToSessionsList{} + out.SessionsList = BackendRoomSwitchToPublicSessionsList{} } } else { out.SessionsList = (out.SessionsList)[:0] } for !in.IsDelim(']') { - var v31 string - v31 = string(in.String()) + var v31 PublicSessionId + v31 = PublicSessionId(in.String()) out.SessionsList = append(out.SessionsList, v31) in.WantComma() } @@ -2270,12 +2270,12 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle } else { in.Delim('{') if !in.IsDelim('}') { - out.SessionsMap = make(BackendRoomSwitchToSessionsMap) + out.SessionsMap = make(BackendRoomSwitchToPublicSessionsMap) } else { out.SessionsMap = nil } for !in.IsDelim('}') { - key := string(in.String()) + key := PublicSessionId(in.String()) in.WantColon() var v32 json.RawMessage if data := in.Raw(); in.Ok() { @@ -3129,16 +3129,16 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Delim('[') if out.SessionIds == nil { if !in.IsDelim(']') { - out.SessionIds = make([]string, 0, 4) + out.SessionIds = make([]RoomSessionId, 0, 4) } else { - out.SessionIds = []string{} + out.SessionIds = []RoomSessionId{} } } else { out.SessionIds = (out.SessionIds)[:0] } for !in.IsDelim(']') { - var v63 string - v63 = string(in.String()) + var v63 RoomSessionId + v63 = RoomSessionId(in.String()) out.SessionIds = append(out.SessionIds, v63) in.WantComma() } @@ -3628,7 +3628,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle case "userid": out.UserId = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = RoomSessionId(in.String()) default: in.SkipRecursive() } @@ -3926,7 +3926,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle case "action": out.Action = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "userid": out.UserId = string(in.String()) case "user": @@ -4169,7 +4169,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle case "userid": out.UserId = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = RoomSessionId(in.String()) case "actorid": out.ActorId = string(in.String()) case "actortype": diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index 93f4f12..53c0b9d 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -965,7 +965,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex case "version": out.Version = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "server": if in.IsNull() { in.Skip() @@ -1053,7 +1053,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex case "version": out.Version = string(in.String()) case "resumeid": - out.ResumeId = string(in.String()) + out.ResumeId = PublicSessionId(in.String()) case "features": if in.IsNull() { in.Skip() @@ -1644,7 +1644,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle case "streamType": out.StreamType = StreamType(in.String()) case "publisherId": - out.PublisherId = string(in.String()) + out.PublisherId = PublicSessionId(in.String()) case "clientId": out.ClientId = string(in.String()) case "bitrate": diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index ae6ef45..77ecc59 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -169,7 +169,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex *out.InCall = int(in.Int()) } case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "roomid": out.RoomId = string(in.String()) default: @@ -835,7 +835,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex case "roomid": out.RoomId = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "flags": out.Flags = uint32(in.Uint32()) default: @@ -1878,7 +1878,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle case "roomid": out.RoomId = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = RoomSessionId(in.String()) case "federation": if in.IsNull() { in.Skip() @@ -1966,7 +1966,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle case "userid": out.UserId = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "roomid": out.RoomId = string(in.String()) default: @@ -2052,7 +2052,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle case "type": out.Type = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "userid": out.UserId = string(in.String()) default: @@ -2381,7 +2381,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle case "type": out.Type = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "userid": out.UserId = string(in.String()) default: @@ -3331,9 +3331,9 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle case "version": out.Version = string(in.String()) case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "resumeid": - out.ResumeId = string(in.String()) + out.ResumeId = PrivateSessionId(in.String()) case "userid": out.UserId = string(in.String()) case "server": @@ -3521,7 +3521,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle case "version": out.Version = string(in.String()) case "resumeid": - out.ResumeId = string(in.String()) + out.ResumeId = PrivateSessionId(in.String()) case "features": if in.IsNull() { in.Skip() @@ -3970,7 +3970,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle } switch key { case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "userid": out.UserId = string(in.String()) case "features": @@ -4001,7 +4001,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.AddError((out.User).UnmarshalJSON(data)) } case "roomsessionid": - out.RoomSessionId = string(in.String()) + out.RoomSessionId = RoomSessionId(in.String()) case "federated": out.Federated = bool(in.Bool()) default: @@ -4145,16 +4145,16 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Delim('[') if out.Leave == nil { if !in.IsDelim(']') { - out.Leave = make([]string, 0, 4) + out.Leave = make([]PublicSessionId, 0, 4) } else { - out.Leave = []string{} + out.Leave = []PublicSessionId{} } } else { out.Leave = (out.Leave)[:0] } for !in.IsDelim(']') { - var v37 string - v37 = string(in.String()) + var v37 PublicSessionId + v37 = PublicSessionId(in.String()) out.Leave = append(out.Leave, v37) in.WantComma() } @@ -4872,7 +4872,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jle } switch key { case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "roomid": out.RoomId = string(in.String()) default: @@ -5334,9 +5334,9 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle } switch key { case "to": - out.To = string(in.String()) + out.To = PublicSessionId(in.String()) case "from": - out.From = string(in.String()) + out.From = PublicSessionId(in.String()) case "type": out.Type = string(in.String()) case "roomType": @@ -5584,7 +5584,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jle (*out.Options).UnmarshalEasyJSON(in) } case "sessionid": - out.SessionId = string(in.String()) + out.SessionId = PublicSessionId(in.String()) case "roomid": out.RoomId = string(in.String()) default: From 5892baa3bb34226f5cd50780c9806c357c4e00cb Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 8 Sep 2025 13:20:43 +0200 Subject: [PATCH 199/549] Use interfaces to detect support for remote publishing / streams. --- mcu_common.go | 9 ++++++ mcu_janus_test.go | 62 ++++++++++++++++++++++-------------------- mcu_proxy.go | 12 -------- proxy/proxy_server.go | 6 ++-- proxy/proxy_session.go | 20 ++++++++++---- 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/mcu_common.go b/mcu_common.go index 33d5a7d..f8e39cc 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -215,8 +215,17 @@ type McuPublisher interface { HasMedia(MediaType) bool SetMedia(MediaType) +} + +type McuPublisherWithStreams interface { + McuPublisher GetStreams(ctx context.Context) ([]PublisherStream, error) +} + +type McuRemoteAwarePublisher interface { + McuPublisher + PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 5d567f7..6d4a4f1 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -885,17 +885,19 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { }) <-done - if streams, err := pub.GetStreams(ctx); assert.NoError(err) { - if assert.Len(streams, 1) { - stream := streams[0] - assert.Equal("audio", stream.Type) - assert.Equal("audio", stream.Mid) - assert.EqualValues(0, stream.Mindex) - assert.False(stream.Disabled) - assert.Equal("opus", stream.Codec) - assert.False(stream.Stereo) - assert.False(stream.Fec) - assert.False(stream.Dtx) + if sb, ok := pub.(*mcuJanusPublisher); assert.True(ok, "expected publisher with streams support, got %T", pub) { + if streams, err := sb.GetStreams(ctx); assert.NoError(err) { + if assert.Len(streams, 1) { + stream := streams[0] + assert.Equal("audio", stream.Type) + assert.Equal("audio", stream.Mid) + assert.EqualValues(0, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("opus", stream.Codec) + assert.False(stream.Stereo) + assert.False(stream.Fec) + assert.False(stream.Dtx) + } } } } @@ -966,25 +968,27 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { <-done }() - if streams, err := pub.GetStreams(ctx); assert.NoError(err) { - if assert.Len(streams, 2) { - stream := streams[0] - assert.Equal("audio", stream.Type) - assert.Equal("audio", stream.Mid) - assert.EqualValues(0, stream.Mindex) - assert.False(stream.Disabled) - assert.Equal("opus", stream.Codec) - assert.False(stream.Stereo) - assert.False(stream.Fec) - assert.False(stream.Dtx) + if sb, ok := pub.(*mcuJanusPublisher); assert.True(ok, "expected publisher with streams support, got %T", pub) { + if streams, err := sb.GetStreams(ctx); assert.NoError(err) { + if assert.Len(streams, 2) { + stream := streams[0] + assert.Equal("audio", stream.Type) + assert.Equal("audio", stream.Mid) + assert.EqualValues(0, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("opus", stream.Codec) + assert.False(stream.Stereo) + assert.False(stream.Fec) + assert.False(stream.Dtx) - stream = streams[1] - assert.Equal("video", stream.Type) - assert.Equal("video", stream.Mid) - assert.EqualValues(1, stream.Mindex) - assert.False(stream.Disabled) - assert.Equal("H264", stream.Codec) - assert.Equal("4d0028", stream.ProfileH264) + stream = streams[1] + assert.Equal("video", stream.Type) + assert.Equal("video", stream.Mid) + assert.EqualValues(1, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("H264", stream.Codec) + assert.Equal("4d0028", stream.ProfileH264) + } } } } diff --git a/mcu_proxy.go b/mcu_proxy.go index 0b3ca8f..3bb3de6 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -230,18 +230,6 @@ func (p *mcuProxyPublisher) ProcessEvent(msg *EventProxyServerMessage) { } } -func (p *mcuProxyPublisher) GetStreams(ctx context.Context) ([]PublisherStream, error) { - return nil, errors.New("not implemented") -} - -func (p *mcuProxyPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { - return errors.New("remote publishing not supported for proxy publishers") -} - -func (p *mcuProxyPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { - return errors.New("remote publishing not supported for proxy publishers") -} - type mcuProxySubscriber struct { mcuProxyPubSubCommon diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 6e37202..627a5ca 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -1174,7 +1174,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuPublisher) + publisher, ok := client.(signaling.McuRemoteAwarePublisher) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1226,7 +1226,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuPublisher) + publisher, ok := client.(signaling.McuRemoteAwarePublisher) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1258,7 +1258,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuPublisher) + publisher, ok := client.(signaling.McuPublisherWithStreams) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index f760b2b..7479b7e 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -65,7 +65,7 @@ type ProxySession struct { subscriberIds map[signaling.McuSubscriber]string remotePublishersLock sync.Mutex - remotePublishers map[signaling.McuPublisher]map[string]*remotePublisherData + remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData } func NewProxySession(proxy *ProxyServer, sid uint64, id signaling.PublicSessionId) *ProxySession { @@ -299,7 +299,9 @@ func (s *ProxySession) DeletePublisher(publisher signaling.McuPublisher) string delete(s.publishers, id) delete(s.publisherIds, publisher) - delete(s.remotePublishers, publisher) + if rp, ok := publisher.(signaling.McuRemoteAwarePublisher); ok { + delete(s.remotePublishers, rp) + } go s.proxy.PublisherDeleted(publisher) return id } @@ -347,7 +349,7 @@ func (s *ProxySession) clearRemotePublishers() { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() - go func(remotePublishers map[signaling.McuPublisher]map[string]*remotePublisherData) { + go func(remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData) { for publisher, entries := range remotePublishers { for _, data := range entries { if err := publisher.UnpublishRemote(context.Background(), s.PublicId(), data.hostname, data.port, data.rtcpPort); err != nil { @@ -382,7 +384,7 @@ func (s *ProxySession) NotifyDisconnected() { s.clearRemotePublishers() } -func (s *ProxySession) AddRemotePublisher(publisher signaling.McuPublisher, hostname string, port int, rtcpPort int) bool { +func (s *ProxySession) AddRemotePublisher(publisher signaling.McuRemoteAwarePublisher, hostname string, port int, rtcpPort int) bool { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() @@ -390,7 +392,7 @@ func (s *ProxySession) AddRemotePublisher(publisher signaling.McuPublisher, host if !found { remote = make(map[string]*remotePublisherData) if s.remotePublishers == nil { - s.remotePublishers = make(map[signaling.McuPublisher]map[string]*remotePublisherData) + s.remotePublishers = make(map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData) } s.remotePublishers[publisher] = remote } @@ -410,7 +412,7 @@ func (s *ProxySession) AddRemotePublisher(publisher signaling.McuPublisher, host return true } -func (s *ProxySession) RemoveRemotePublisher(publisher signaling.McuPublisher, hostname string, port int, rtcpPort int) { +func (s *ProxySession) RemoveRemotePublisher(publisher signaling.McuRemoteAwarePublisher, hostname string, port int, rtcpPort int) { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() @@ -430,6 +432,12 @@ func (s *ProxySession) RemoveRemotePublisher(publisher signaling.McuPublisher, h } func (s *ProxySession) OnPublisherDeleted(publisher signaling.McuPublisher) { + if publisher, ok := publisher.(signaling.McuRemoteAwarePublisher); ok { + s.OnRemoteAwarePublisherDeleted(publisher) + } +} + +func (s *ProxySession) OnRemoteAwarePublisherDeleted(publisher signaling.McuRemoteAwarePublisher) { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() From e39886369f82b34eeb7bef06212fca9d0555bf41 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 8 Sep 2025 14:48:04 +0200 Subject: [PATCH 200/549] Add test for proxy client reconnect code. --- mcu_proxy.go | 18 +++- mcu_proxy_test.go | 217 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 226 insertions(+), 9 deletions(-) diff --git a/mcu_proxy.go b/mcu_proxy.go index 3bb3de6..96b0b3d 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1108,16 +1108,14 @@ func (c *mcuProxyConnection) processBye(msg *ProxyServerMessage) { switch bye.Reason { case "session_resumed": log.Printf("Session %s on %s was resumed by other client, resetting", c.SessionId(), c) - c.sessionId.Store(PublicSessionId("")) case "session_expired": log.Printf("Session %s expired on %s, resetting", c.SessionId(), c) - c.sessionId.Store(PublicSessionId("")) case "session_closed": log.Printf("Session %s was closed on %s, resetting", c.SessionId(), c) - c.sessionId.Store(PublicSessionId("")) default: log.Printf("Received bye with unsupported reason from %s %+v", c, bye) } + c.sessionId.Store(PublicSessionId("")) } func (c *mcuProxyConnection) sendHello() error { @@ -1618,6 +1616,20 @@ func (m *mcuProxy) hasConnections() bool { }) } +func (m *mcuProxy) WaitForDisconnected(ctx context.Context) error { + ticker := time.NewTicker(time.Millisecond) + defer ticker.Stop() + + for m.hasConnections() { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } + return nil +} + func (m *mcuProxy) WaitForConnections(ctx context.Context) error { ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 05d323b..efd87d0 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -217,6 +217,37 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer return nil, fmt.Errorf("expected hello, got %+v", msg) } + if msg.Hello.ResumeId != "" { + client := c.server.getClient(msg.Hello.ResumeId) + if client == nil { + response := &ProxyServerMessage{ + Id: msg.Id, + Type: "error", + Error: &Error{ + Code: "no_such_session", + }, + } + return response, nil + } + + c.sessionId = msg.Hello.ResumeId + c.server.setClient(c.sessionId, c) + response := &ProxyServerMessage{ + Id: msg.Id, + Type: "hello", + Hello: &HelloProxyServerMessage{ + Version: "1.0", + SessionId: c.sessionId, + Server: &WelcomeServerMessage{ + Version: "1.0", + Country: c.server.country, + }, + }, + } + c.processMessage = c.processRegularMessage + return response, nil + } + token, err := jwt.ParseWithClaims(msg.Hello.Token, &TokenClaims{}, func(token *jwt.Token) (any, error) { claims, ok := token.Claims.(*TokenClaims) if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { @@ -403,8 +434,10 @@ func (c *testProxyServerClient) close() { c.mu.Lock() defer c.mu.Unlock() - c.ws.Close() - c.ws = nil + if c.ws != nil { + c.ws.Close() + c.ws = nil + } } func (c *testProxyServerClient) handleSendMessageError(fmt string, msg *ProxyServerMessage, err error) { @@ -443,6 +476,11 @@ func (c *testProxyServerClient) sendMessage(msg *ProxyServerMessage) { if err := w.Close(); err != nil { c.handleSendMessageError("error during close of sending %+v: %s", msg, err) } + + if msg.CloseAfterSend(nil) { + c.ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) // nolint + c.ws.Close() + } } func (c *testProxyServerClient) run() { @@ -450,7 +488,7 @@ func (c *testProxyServerClient) run() { c.mu.Lock() defer c.mu.Unlock() - c.server.removeClient(c) + c.server.expireSession(30*time.Second, c) c.ws = nil }() c.processMessage = c.processHello @@ -665,9 +703,20 @@ func (h *TestProxyServerHandler) sendLoad(c *testProxyServerClient) { c.sendMessage(msg) } +func (h *TestProxyServerHandler) expireSession(timeout time.Duration, client *testProxyServerClient) { + timer := time.AfterFunc(timeout, func() { + h.removeClient(client) + }) + + h.t.Cleanup(func() { + timer.Stop() + }) +} + func (h *TestProxyServerHandler) removeClient(client *testProxyServerClient) { h.mu.Lock() defer h.mu.Unlock() + delete(h.clients, client.sessionId) } @@ -684,13 +733,35 @@ func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques sessionId: PublicSessionId(newRandomString(32)), } - h.mu.Lock() - h.clients[client.sessionId] = client - h.mu.Unlock() + h.setClient(client.sessionId, client) go client.run() } +func (h *TestProxyServerHandler) getClient(sessionId PublicSessionId) *testProxyServerClient { + h.mu.Lock() + defer h.mu.Unlock() + + return h.clients[sessionId] +} + +func (h *TestProxyServerHandler) setClient(sessionId PublicSessionId, client *testProxyServerClient) { + h.mu.Lock() + defer h.mu.Unlock() + + if prev, found := h.clients[sessionId]; found { + prev.sendMessage(&ProxyServerMessage{ + Type: "bye", + Bye: &ByeProxyServerMessage{ + Reason: "session_resumed", + }, + }) + prev.close() + } + + h.clients[sessionId] = client +} + func (h *TestProxyServerHandler) Wakeup() { h.wakeupChan <- struct{}{} } @@ -704,6 +775,24 @@ func (h *TestProxyServerHandler) WaitForWakeup(ctx context.Context) error { } } +func (h *TestProxyServerHandler) GetSingleClient() *testProxyServerClient { + h.mu.Lock() + defer h.mu.Unlock() + + for _, c := range h.clients { + return c + } + + return nil +} + +func (h *TestProxyServerHandler) ClearClients() { + h.mu.Lock() + defer h.mu.Unlock() + + clear(h.clients) +} + func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler { t.Helper() @@ -2357,3 +2446,119 @@ func Test_ProxySubscriberTimeout(t *testing.T) { // The local side will remove the (unused) subscriber from the proxy. require.NoError(server.WaitForWakeup(ctx), "unused subscriber not deleted") } + +func Test_ProxyReconnectAfter(t *testing.T) { + reasons := []string{ + "session_resumed", + "session_expired", + "session_closed", + "unknown_reason", + } + for _, reason := range reasons { + t.Run(reason, func(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{server}, + }, 0) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + + client.sendMessage(&ProxyServerMessage{ + Type: "bye", + Bye: &ByeProxyServerMessage{ + Reason: reason, + }, + }) + + // The "bye" will close the connection and reset the session id. + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.NotEqual(sessionId, connections[0].SessionId()) + } + }) + } +} + +func Test_ProxyResume(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{server}, + }, 0) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + + // Force reconnect. + client.close() + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.Equal(sessionId, connections[0].SessionId()) + } +} + +func Test_ProxyResumeFail(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{server}, + }, 0) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + server.ClearClients() + + // Force reconnect. + client.close() + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.NotEqual(sessionId, connections[0].SessionId()) + } +} From c00fc827774d0e5214b5ea9acbc113343dfb63de Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Sep 2025 12:00:34 +0200 Subject: [PATCH 201/549] Use dedicated type for "ClientType". --- api_async.go | 2 +- api_signaling.go | 14 ++++++++------ clientsession.go | 4 ++-- hub.go | 12 ++++++------ room.go | 16 ++++++++-------- roomsessions_test.go | 2 +- session.go | 2 +- testclient_test.go | 2 +- virtualsession.go | 2 +- 9 files changed, 29 insertions(+), 27 deletions(-) diff --git a/api_async.go b/api_async.go index e6a87fb..9bcde9c 100644 --- a/api_async.go +++ b/api_async.go @@ -57,7 +57,7 @@ type AsyncRoomMessage struct { Type string `json:"type"` SessionId PublicSessionId `json:"sessionid,omitempty"` - ClientType string `json:"clienttype,omitempty"` + ClientType ClientType `json:"clienttype,omitempty"` } type SendOfferMessage struct { diff --git a/api_signaling.go b/api_signaling.go index 0c21b6d..863880d 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -342,12 +342,14 @@ func (m *WelcomeServerMessage) HasFeature(feature string) bool { return false } -const ( - HelloClientTypeClient = "client" - HelloClientTypeInternal = "internal" - HelloClientTypeFederation = "federation" +type ClientType string - HelloClientTypeVirtual = "virtual" +const ( + HelloClientTypeClient = ClientType("client") + HelloClientTypeInternal = ClientType("internal") + HelloClientTypeFederation = ClientType("federation") + + HelloClientTypeVirtual = ClientType("virtual") ) func hasStandardPort(u *url.URL) bool { @@ -441,7 +443,7 @@ func (c *FederationTokenClaims) GetUserData() json.RawMessage { type HelloClientMessageAuth struct { // The client type that is connecting. Leave empty to use the default // "HelloClientTypeClient" - Type string `json:"type,omitempty"` + Type ClientType `json:"type,omitempty"` Params json.RawMessage `json:"params"` diff --git a/clientsession.go b/clientsession.go index b971851..67e5fec 100644 --- a/clientsession.go +++ b/clientsession.go @@ -61,7 +61,7 @@ type ClientSession struct { ctx context.Context closeFunc context.CancelFunc - clientType string + clientType ClientType features []string userId string userData json.RawMessage @@ -162,7 +162,7 @@ func (s *ClientSession) Data() *SessionIdData { return s.data } -func (s *ClientSession) ClientType() string { +func (s *ClientSession) ClientType() ClientType { return s.clientType } diff --git a/hub.go b/hub.go index 126f140..32c22d2 100644 --- a/hub.go +++ b/hub.go @@ -802,7 +802,7 @@ func (h *Hub) removeSession(session Session) (removed bool) { delete(h.clients, data.Sid) if _, found := h.sessions[data.Sid]; found { delete(h.sessions, data.Sid) - statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), session.ClientType()).Dec() + statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), string(session.ClientType())).Dec() removed = true } } @@ -1026,8 +1026,8 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * if country := client.Country(); IsValidCountry(country) { statsClientCountries.WithLabelValues(country).Inc() } - statsHubSessionsCurrent.WithLabelValues(backend.Id(), session.ClientType()).Inc() - statsHubSessionsTotal.WithLabelValues(backend.Id(), session.ClientType()).Inc() + statsHubSessionsCurrent.WithLabelValues(backend.Id(), string(session.ClientType())).Inc() + statsHubSessionsTotal.WithLabelValues(backend.Id(), string(session.ClientType())).Inc() h.setDecodedPrivateSessionId(privateSessionId, sessionIdData) h.setDecodedPublicSessionId(publicSessionId, sessionIdData) @@ -1299,7 +1299,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { log.Printf("Resume session from %s in %s (%s) %s (private=%s)", client.RemoteAddr(), client.Country(), client.UserAgent(), session.PublicId(), session.PrivateId()) - statsHubSessionsResumedTotal.WithLabelValues(clientSession.Backend().Id(), clientSession.ClientType()).Inc() + statsHubSessionsResumedTotal.WithLabelValues(clientSession.Backend().Id(), string(clientSession.ClientType())).Inc() h.sendHelloResponse(clientSession, message) clientSession.NotifySessionResumed(client) return @@ -2449,8 +2449,8 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { h.sessions[sessionIdData.Sid] = sess h.virtualSessions[virtualSessionId] = sessionIdData.Sid h.mu.Unlock() - statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), sess.ClientType()).Inc() - statsHubSessionsTotal.WithLabelValues(session.Backend().Id(), sess.ClientType()).Inc() + statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), string(sess.ClientType())).Inc() + statsHubSessionsTotal.WithLabelValues(session.Backend().Id(), string(sess.ClientType())).Inc() log.Printf("Session %s added virtual session %s with initial flags %d", session.PublicId(), sess.PublicId(), sess.Flags()) session.AddVirtualSession(sess) sess.SetRoom(room) diff --git a/room.go b/room.go index caa2310..8e533ec 100644 --- a/room.go +++ b/room.go @@ -201,9 +201,9 @@ func (r *Room) Close() []Session { result = append(result, s) } r.sessions = nil - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeClient}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeInternal}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeVirtual}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeClient)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeInternal)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeVirtual)}) r.mu.Unlock() return result } @@ -287,7 +287,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { _, found := r.sessions[sid] r.sessions[sid] = session if !found { - r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": session.ClientType()}).Inc() + r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": string(session.ClientType())}).Inc() } var publishUsersChanged bool switch session.ClientType() { @@ -454,7 +454,7 @@ func (r *Room) RemoveSession(session Session) bool { } sid := session.PublicId() - r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": session.ClientType()}).Dec() + r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": string(session.ClientType())}).Dec() delete(r.sessions, sid) if virtualSession, ok := session.(*VirtualSession); ok { delete(r.virtualSessions, virtualSession) @@ -482,9 +482,9 @@ func (r *Room) RemoveSession(session Session) bool { } r.hub.removeRoom(r) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeClient}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeInternal}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeVirtual}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeClient)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeInternal)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeVirtual)}) r.unsubscribeBackend() r.doClose() r.mu.Unlock() diff --git a/roomsessions_test.go b/roomsessions_test.go index 9a2fcc8..50c1f18 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -47,7 +47,7 @@ func (s *DummySession) PublicId() PublicSessionId { return s.publicId } -func (s *DummySession) ClientType() string { +func (s *DummySession) ClientType() ClientType { return "" } diff --git a/session.go b/session.go index c0834d0..7cf40db 100644 --- a/session.go +++ b/session.go @@ -51,7 +51,7 @@ type Session interface { Context() context.Context PrivateId() PrivateSessionId PublicId() PublicSessionId - ClientType() string + ClientType() ClientType Data() *SessionIdData UserId() string diff --git a/testclient_test.go b/testclient_test.go index 793994b..ca31778 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -492,7 +492,7 @@ func (c *TestClient) SendHelloInternalWithFeatures(features []string) error { return c.SendHelloParams("", HelloVersionV1, "internal", features, params) } -func (c *TestClient) SendHelloParams(url string, version string, clientType string, features []string, params any) error { +func (c *TestClient) SendHelloParams(url string, version string, clientType ClientType, features []string, params any) error { data, err := json.Marshal(params) require.NoError(c.t, err) diff --git a/virtualsession.go b/virtualsession.go index 1fd4fa3..fb2be3c 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -100,7 +100,7 @@ func (s *VirtualSession) PublicId() PublicSessionId { return s.publicId } -func (s *VirtualSession) ClientType() string { +func (s *VirtualSession) ClientType() ClientType { return HelloClientTypeVirtual } From 4409f91e28c5137a54df7804499aa3b6a8896d5a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Sep 2025 12:00:50 +0200 Subject: [PATCH 202/549] Update generated files. --- api_async_easyjson.go | 2 +- api_signaling_easyjson.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api_async_easyjson.go b/api_async_easyjson.go index 0047f1d..811a155 100644 --- a/api_async_easyjson.go +++ b/api_async_easyjson.go @@ -139,7 +139,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex case "sessionid": out.SessionId = PublicSessionId(in.String()) case "clienttype": - out.ClientType = string(in.String()) + out.ClientType = ClientType(in.String()) default: in.SkipRecursive() } diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 77ecc59..aa237b9 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -3431,7 +3431,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle } switch key { case "type": - out.Type = string(in.String()) + out.Type = ClientType(in.String()) case "params": if data := in.Raw(); in.Ok() { in.AddError((out.Params).UnmarshalJSON(data)) From 6d2ee56ada90d046e5fc125387c35f3c1112ab35 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Sep 2025 12:11:15 +0200 Subject: [PATCH 203/549] Fix typo in error message on failed test condition. --- flags_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flags_test.go b/flags_test.go index 336e39c..4bc5d6d 100644 --- a/flags_test.go +++ b/flags_test.go @@ -79,7 +79,7 @@ func TestFlagsConcurrentAdd(t *testing.T) { added.Add(1) } }) - assert.EqualValues(t, 1, added.Load(), "expected only one successfull attempt") + assert.EqualValues(t, 1, added.Load(), "expected only one successful attempt") } func TestFlagsConcurrentRemove(t *testing.T) { @@ -93,7 +93,7 @@ func TestFlagsConcurrentRemove(t *testing.T) { removed.Add(1) } }) - assert.EqualValues(t, 1, removed.Load(), "expected only one successfull attempt") + assert.EqualValues(t, 1, removed.Load(), "expected only one successful attempt") } func TestFlagsConcurrentSet(t *testing.T) { @@ -106,5 +106,5 @@ func TestFlagsConcurrentSet(t *testing.T) { set.Add(1) } }) - assert.EqualValues(t, 1, set.Load(), "expected only one successfull attempt") + assert.EqualValues(t, 1, set.Load(), "expected only one successful attempt") } From 32b99b6a52ecaf9f838b7838cbe80179afea5576 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 20:02:36 +0000 Subject: [PATCH 204/549] Bump google.golang.org/protobuf from 1.36.8 to 1.36.9 Bumps google.golang.org/protobuf from 1.36.8 to 1.36.9. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 061a15a..e86b7cd 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.75.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.8 + google.golang.org/protobuf v1.36.9 ) require ( diff --git a/go.sum b/go.sum index ab2b282..737a9c2 100644 --- a/go.sum +++ b/go.sum @@ -233,8 +233,8 @@ google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From c132ea25fab18576f6e91010d2fe317ee58f2079 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:01:50 +0000 Subject: [PATCH 205/549] Bump google.golang.org/grpc from 1.75.0 to 1.75.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.0 to 1.75.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.75.0...v1.75.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.75.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 061a15a..caa68f9 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.4 go.etcd.io/etcd/server/v3 v3.6.4 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.75.0 + google.golang.org/grpc v1.75.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.8 ) diff --git a/go.sum b/go.sum index ab2b282..d89af47 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1: google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= From 0c9d4970c4fdb37b1df6443d64a59489b51a88e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 05:46:49 +0000 Subject: [PATCH 206/549] Bump github.com/nats-io/nats-server/v2 from 2.11.8 to 2.11.9 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.8 to 2.11.9. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.8...v2.11.9) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.11.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 7 ++++--- go.sum | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f7ae76c..9066281 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.0 - github.com/nats-io/nats-server/v2 v2.11.8 + github.com/nats-io/nats-server/v2 v2.11.9 github.com/nats-io/nats.go v1.45.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -31,6 +31,7 @@ require ( ) require ( + github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -90,9 +91,9 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/net v0.43.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/time v0.13.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index e32b044..512122d 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.8 h1:7T1wwwd/SKTDWW47KGguENE7Wa8CpHxLD1imet1iW7c= -github.com/nats-io/nats-server/v2 v2.11.8/go.mod h1:C2zlzMA8PpiMMxeXSz7FkU3V+J+H15kiqrkvgtn2kS8= +github.com/nats-io/nats-server/v2 v2.11.9 h1:k7nzHZjUf51W1b08xiQih63Rdxh0yr5O4K892Mx5gQA= +github.com/nats-io/nats-server/v2 v2.11.9/go.mod h1:1MQgsAQX1tVjpf3Yzrk3x2pzdsZiNL/TVP3Amhp3CR8= github.com/nats-io/nats.go v1.45.0 h1:/wGPbnYXDM0pLKFjZTX+2JOw9TQPoIgTFrUaH97giwA= github.com/nats-io/nats.go v1.45.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -207,14 +207,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From 697e6242d61dfaa067be0eeeb1c28d72286554b1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Sep 2025 15:15:01 +0200 Subject: [PATCH 207/549] A proxy connection is only connected after a hello has been processed. Fixes flaky test "Test_ProxyResumeFail". --- mcu_proxy.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mcu_proxy.go b/mcu_proxy.go index 96b0b3d..c19d3b7 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -350,6 +350,7 @@ type mcuProxyConnection struct { closed atomic.Bool conn *websocket.Conn + helloProcessed atomic.Bool connectedSince time.Time reconnectTimer *time.Timer reconnectInterval atomic.Int64 @@ -533,7 +534,7 @@ func (c *mcuProxyConnection) SessionId() PublicSessionId { func (c *mcuProxyConnection) IsConnected() bool { c.mu.Lock() defer c.mu.Unlock() - return c.conn != nil && c.SessionId() != "" + return c.conn != nil && c.helloProcessed.Load() && c.SessionId() != "" } func (c *mcuProxyConnection) IsTemporary() bool { @@ -691,6 +692,8 @@ func (c *mcuProxyConnection) stop(ctx context.Context) { } func (c *mcuProxyConnection) close() { + c.helloProcessed.Store(false) + c.mu.Lock() defer c.mu.Unlock() @@ -803,6 +806,7 @@ func (c *mcuProxyConnection) reconnect() { log.Printf("Connected to %s", c) c.closed.Store(false) + c.helloProcessed.Store(false) c.mu.Lock() c.connectedSince = time.Now() @@ -1002,6 +1006,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { statsConnectedProxyBackendsCurrent.WithLabelValues(c.Country()).Inc() } + c.helloProcessed.Store(true) c.connectedNotifier.Notify() default: log.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) From bee1175198e0660f1219813e24fc0bee09aec1e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:01:56 +0000 Subject: [PATCH 208/549] Bump github.com/mailru/easyjson from 0.9.0 to 0.9.1 Bumps [github.com/mailru/easyjson](https://github.com/mailru/easyjson) from 0.9.0 to 0.9.1. - [Release notes](https://github.com/mailru/easyjson/releases) - [Commits](https://github.com/mailru/easyjson/compare/v0.9...v0.9.1) --- updated-dependencies: - dependency-name: github.com/mailru/easyjson dependency-version: 0.9.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9066281..0e7eb70 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 - github.com/mailru/easyjson v0.9.0 + github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.11.9 github.com/nats-io/nats.go v1.45.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 diff --git a/go.sum b/go.sum index 512122d..3a208eb 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= From d745f14f5ca85c58028f953070ec1f5263b41005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:04:04 +0000 Subject: [PATCH 209/549] Update generated files from bee1175198e0660f1219813e24fc0bee09aec1e1 --- api_async_easyjson.go | 101 ++- api_backend_easyjson.go | 1154 ++++++++++++++++++++---------- api_grpc_easyjson.go | 11 +- api_proxy_easyjson.go | 562 ++++++++++----- api_signaling_easyjson.go | 1412 +++++++++++++++++++++++++------------ 5 files changed, 2211 insertions(+), 1029 deletions(-) diff --git a/api_async_easyjson.go b/api_async_easyjson.go index 811a155..508e444 100644 --- a/api_async_easyjson.go +++ b/api_async_easyjson.go @@ -30,16 +30,19 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "messageid": - out.MessageId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.MessageId = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "data": if in.IsNull() { in.Skip() @@ -48,7 +51,11 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe if out.Data == nil { out.Data = new(MessageClientMessageData) } - (*out.Data).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Data).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -128,18 +135,25 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "clienttype": - out.ClientType = ClientType(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ClientType = ClientType(in.String()) + } default: in.SkipRecursive() } @@ -208,18 +222,21 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "sendtime": - if data := in.Raw(); in.Ok() { - in.AddError((out.SendTime).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.SendTime).UnmarshalJSON(data)) + } } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "message": if in.IsNull() { in.Skip() @@ -228,7 +245,11 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex if out.Message == nil { out.Message = new(ServerMessage) } - (*out.Message).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Message).UnmarshalEasyJSON(in) + } } case "room": if in.IsNull() { @@ -238,7 +259,11 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex if out.Room == nil { out.Room = new(BackendServerRoomRequest) } - (*out.Room).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Room).UnmarshalEasyJSON(in) + } } case "permissions": if in.IsNull() { @@ -257,7 +282,11 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex } for !in.IsDelim(']') { var v1 Permission - v1 = Permission(in.String()) + if in.IsNull() { + in.Skip() + } else { + v1 = Permission(in.String()) + } out.Permissions = append(out.Permissions, v1) in.WantComma() } @@ -271,7 +300,11 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex if out.AsyncRoom == nil { out.AsyncRoom = new(AsyncRoomMessage) } - (*out.AsyncRoom).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.AsyncRoom).UnmarshalEasyJSON(in) + } } case "sendoffer": if in.IsNull() { @@ -281,10 +314,18 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex if out.SendOffer == nil { out.SendOffer = new(SendOfferMessage) } - (*out.SendOffer).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.SendOffer).UnmarshalEasyJSON(in) + } } case "id": - out.Id = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } default: in.SkipRecursive() } diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 1da7fc3..d898e1a 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -31,18 +31,25 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "username": - out.Username = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Username = string(in.String()) + } case "password": - out.Password = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Password = string(in.String()) + } case "ttl": - out.TTL = int64(in.Int64()) + if in.IsNull() { + in.Skip() + } else { + out.TTL = int64(in.Int64()) + } case "uris": if in.IsNull() { in.Skip() @@ -60,7 +67,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe } for !in.IsDelim(']') { var v1 string - v1 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v1 = string(in.String()) + } out.URIs = append(out.URIs, v1) in.WantComma() } @@ -150,14 +161,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } default: in.SkipRecursive() } @@ -217,11 +227,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "ocs": if in.IsNull() { @@ -231,7 +236,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex if out.Ocs == nil { out.Ocs = new(OcsBody) } - (*out.Ocs).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Ocs).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -295,18 +304,25 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "status": - out.Status = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Status = string(in.String()) + } case "statuscode": - out.StatusCode = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.StatusCode = int(in.Int()) + } case "message": - out.Message = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } default: in.SkipRecursive() } @@ -375,17 +391,20 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "meta": - (out.Meta).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (out.Meta).UnmarshalEasyJSON(in) + } case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -450,14 +469,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "dialout": if in.IsNull() { in.Skip() @@ -466,7 +484,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex if out.Dialout == nil { out.Dialout = new(BackendRoomDialoutResponse) } - (*out.Dialout).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Dialout).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -531,14 +553,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "invite": if in.IsNull() { in.Skip() @@ -547,7 +568,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Invite == nil { out.Invite = new(BackendRoomInviteRequest) } - (*out.Invite).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Invite).UnmarshalEasyJSON(in) + } } case "disinvite": if in.IsNull() { @@ -557,7 +582,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Disinvite == nil { out.Disinvite = new(BackendRoomDisinviteRequest) } - (*out.Disinvite).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Disinvite).UnmarshalEasyJSON(in) + } } case "update": if in.IsNull() { @@ -567,7 +596,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Update == nil { out.Update = new(BackendRoomUpdateRequest) } - (*out.Update).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Update).UnmarshalEasyJSON(in) + } } case "delete": if in.IsNull() { @@ -577,7 +610,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Delete == nil { out.Delete = new(BackendRoomDeleteRequest) } - (*out.Delete).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Delete).UnmarshalEasyJSON(in) + } } case "incall": if in.IsNull() { @@ -587,7 +624,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.InCall == nil { out.InCall = new(BackendRoomInCallRequest) } - (*out.InCall).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.InCall).UnmarshalEasyJSON(in) + } } case "participants": if in.IsNull() { @@ -597,7 +638,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Participants == nil { out.Participants = new(BackendRoomParticipantsRequest) } - (*out.Participants).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Participants).UnmarshalEasyJSON(in) + } } case "message": if in.IsNull() { @@ -607,7 +652,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Message == nil { out.Message = new(BackendRoomMessageRequest) } - (*out.Message).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Message).UnmarshalEasyJSON(in) + } } case "switchto": if in.IsNull() { @@ -617,7 +666,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.SwitchTo == nil { out.SwitchTo = new(BackendRoomSwitchToMessageRequest) } - (*out.SwitchTo).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.SwitchTo).UnmarshalEasyJSON(in) + } } case "dialout": if in.IsNull() { @@ -627,7 +680,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Dialout == nil { out.Dialout = new(BackendRoomDialoutRequest) } - (*out.Dialout).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Dialout).UnmarshalEasyJSON(in) + } } case "transient": if in.IsNull() { @@ -637,10 +694,18 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if out.Transient == nil { out.Transient = new(BackendRoomTransientRequest) } - (*out.Transient).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Transient).UnmarshalEasyJSON(in) + } } case "received": - out.ReceivedTime = int64(in.Int64()) + if in.IsNull() { + in.Skip() + } else { + out.ReceivedTime = int64(in.Int64()) + } default: in.SkipRecursive() } @@ -754,18 +819,25 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "name": - out.Name = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Name = string(in.String()) + } case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "author": - out.Author = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Author = string(in.String()) + } default: in.SkipRecursive() } @@ -845,20 +917,31 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "url": - out.Url = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Url = string(in.String()) + } case "ip": - out.IP = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.IP = string(in.String()) + } case "connected": - out.Connected = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Connected = bool(in.Bool()) + } case "temporary": - out.Temporary = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Temporary = bool(in.Bool()) + } case "shutdown": if in.IsNull() { in.Skip() @@ -867,7 +950,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex if out.Shutdown == nil { out.Shutdown = new(bool) } - *out.Shutdown = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + *out.Shutdown = bool(in.Bool()) + } } case "uptime": if in.IsNull() { @@ -877,12 +964,20 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex if out.Uptime == nil { out.Uptime = new(time.Time) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.Uptime).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.Uptime).UnmarshalJSON(data)) + } } } case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -900,14 +995,22 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex } for !in.IsDelim(']') { var v4 string - v4 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v4 = string(in.String()) + } out.Features = append(out.Features, v4) in.WantComma() } in.Delim(']') } case "country": - out.Country = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Country = string(in.String()) + } case "load": if in.IsNull() { in.Skip() @@ -916,7 +1019,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex if out.Load == nil { out.Load = new(int64) } - *out.Load = int64(in.Int64()) + if in.IsNull() { + in.Skip() + } else { + *out.Load = int64(in.Int64()) + } } case "bandwidth": if in.IsNull() { @@ -926,7 +1033,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex if out.Bandwidth == nil { out.Bandwidth = new(EventProxyServerBandwidth) } - (*out.Bandwidth).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Bandwidth).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1045,22 +1156,37 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "url": - out.Url = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Url = string(in.String()) + } case "connected": - out.Connected = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Connected = bool(in.Bool()) + } case "name": - out.Name = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Name = string(in.String()) + } case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "author": - out.Author = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Author = string(in.String()) + } case "datachannels": if in.IsNull() { in.Skip() @@ -1069,7 +1195,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex if out.DataChannels == nil { out.DataChannels = new(bool) } - *out.DataChannels = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + *out.DataChannels = bool(in.Bool()) + } } case "fulltrickle": if in.IsNull() { @@ -1079,10 +1209,18 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex if out.FullTrickle == nil { out.FullTrickle = new(bool) } - *out.FullTrickle = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + *out.FullTrickle = bool(in.Bool()) + } } case "localip": - out.LocalIP = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.LocalIP = string(in.String()) + } case "ipv6": if in.IsNull() { in.Skip() @@ -1091,7 +1229,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex if out.IPv6 == nil { out.IPv6 = new(bool) } - *out.IPv6 = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + *out.IPv6 = bool(in.Bool()) + } } case "videoroom": if in.IsNull() { @@ -1101,7 +1243,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex if out.VideoRoom == nil { out.VideoRoom = new(BackendServerInfoVideoRoom) } - (*out.VideoRoom).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.VideoRoom).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1206,14 +1352,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "mode": - out.Mode = SfuMode(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Mode = SfuMode(in.String()) + } case "janus": if in.IsNull() { in.Skip() @@ -1222,7 +1367,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle if out.Janus == nil { out.Janus = new(BackendServerInfoSfuJanus) } - (*out.Janus).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Janus).UnmarshalEasyJSON(in) + } } case "proxies": if in.IsNull() { @@ -1241,7 +1390,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle } for !in.IsDelim(']') { var v7 BackendServerInfoSfuProxy - (v7).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (v7).UnmarshalEasyJSON(in) + } out.Proxies = append(out.Proxies, v7) in.WantComma() } @@ -1324,11 +1477,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "urls": if in.IsNull() { @@ -1347,22 +1495,46 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle } for !in.IsDelim(']') { var v10 string - v10 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v10 = string(in.String()) + } out.Urls = append(out.Urls, v10) in.WantComma() } in.Delim(']') } case "connected": - out.Connected = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Connected = bool(in.Bool()) + } case "serverurl": - out.ServerUrl = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ServerUrl = string(in.String()) + } case "serverid": - out.ServerID = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ServerID = string(in.String()) + } case "version": - out.ServerVersion = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ServerVersion = string(in.String()) + } case "clustername": - out.ClusterName = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ClusterName = string(in.String()) + } default: in.SkipRecursive() } @@ -1457,20 +1629,31 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "target": - out.Target = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Target = string(in.String()) + } case "ip": - out.IP = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.IP = string(in.String()) + } case "connected": - out.Connected = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Connected = bool(in.Bool()) + } case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } default: in.SkipRecursive() } @@ -1544,11 +1727,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "endpoints": if in.IsNull() { @@ -1567,14 +1745,22 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle } for !in.IsDelim(']') { var v13 string - v13 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v13 = string(in.String()) + } out.Endpoints = append(out.Endpoints, v13) in.WantComma() } in.Delim(']') } case "active": - out.Active = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Active = string(in.String()) + } case "connected": if in.IsNull() { in.Skip() @@ -1583,7 +1769,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle if out.Connected == nil { out.Connected = new(bool) } - *out.Connected = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + *out.Connected = bool(in.Bool()) + } } default: in.SkipRecursive() @@ -1664,22 +1854,37 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "connected": - out.Connected = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Connected = bool(in.Bool()) + } case "address": - out.Address = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Address = string(in.String()) + } case "useragent": - out.UserAgent = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserAgent = string(in.String()) + } case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -1697,7 +1902,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle } for !in.IsDelim(']') { var v16 string - v16 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v16 = string(in.String()) + } out.Features = append(out.Features, v16) in.WantComma() } @@ -1795,14 +2004,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -1820,7 +2028,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle } for !in.IsDelim(']') { var v19 string - v19 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v19 = string(in.String()) + } out.Features = append(out.Features, v19) in.WantComma() } @@ -1834,7 +2046,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle if out.Sfu == nil { out.Sfu = new(BackendServerInfoSfu) } - (*out.Sfu).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Sfu).UnmarshalEasyJSON(in) + } } case "dialout": if in.IsNull() { @@ -1853,7 +2069,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle } for !in.IsDelim(']') { var v20 BackendServerInfoDialout - (v20).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (v20).UnmarshalEasyJSON(in) + } out.Dialout = append(out.Dialout, v20) in.WantComma() } @@ -1867,7 +2087,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle if out.Nats == nil { out.Nats = new(BackendServerInfoNats) } - (*out.Nats).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Nats).UnmarshalEasyJSON(in) + } } case "grpc": if in.IsNull() { @@ -1886,7 +2110,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle } for !in.IsDelim(']') { var v21 BackendServerInfoGrpc - (v21).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (v21).UnmarshalEasyJSON(in) + } out.Grpc = append(out.Grpc, v21) in.WantComma() } @@ -1900,7 +2128,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle if out.Etcd == nil { out.Etcd = new(BackendServerInfoEtcd) } - (*out.Etcd).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Etcd).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -2019,11 +2251,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userids": if in.IsNull() { @@ -2042,15 +2269,23 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle } for !in.IsDelim(']') { var v28 string - v28 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v28 = string(in.String()) + } out.UserIds = append(out.UserIds, v28) in.WantComma() } in.Delim(']') } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -2130,16 +2365,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "action": - out.Action = TransientAction(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Action = TransientAction(in.String()) + } case "key": - out.Key = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Key = string(in.String()) + } case "value": if m, ok := out.Value.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) @@ -2149,7 +2387,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle out.Value = in.Interface() } case "ttl": - out.TTL = time.Duration(in.Int64()) + if in.IsNull() { + in.Skip() + } else { + out.TTL = time.Duration(in.Int64()) + } default: in.SkipRecursive() } @@ -2229,17 +2471,20 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "sessions": - if data := in.Raw(); in.Ok() { - in.AddError((out.Sessions).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Sessions).UnmarshalJSON(data)) + } } case "sessionslist": if in.IsNull() { @@ -2258,7 +2503,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle } for !in.IsDelim(']') { var v31 PublicSessionId - v31 = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + v31 = PublicSessionId(in.String()) + } out.SessionsList = append(out.SessionsList, v31) in.WantComma() } @@ -2278,8 +2527,12 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle key := PublicSessionId(in.String()) in.WantColon() var v32 json.RawMessage - if data := in.Raw(); in.Ok() { - in.AddError((v32).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((v32).UnmarshalJSON(data)) + } } (out.SessionsMap)[key] = v32 in.WantComma() @@ -2382,11 +2635,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "changed": if in.IsNull() { @@ -2613,15 +2861,14 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -2682,11 +2929,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userids": if in.IsNull() { @@ -2705,7 +2947,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle } for !in.IsDelim(']') { var v46 string - v46 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v46 = string(in.String()) + } out.UserIds = append(out.UserIds, v46) in.WantComma() } @@ -2728,15 +2974,23 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle } for !in.IsDelim(']') { var v47 string - v47 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v47 = string(in.String()) + } out.AllUserIds = append(out.AllUserIds, v47) in.WantComma() } in.Delim(']') } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -2835,18 +3089,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "incall": - if data := in.Raw(); in.Ok() { - in.AddError((out.InCall).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.InCall).UnmarshalJSON(data)) + } } case "all": - out.All = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.All = bool(in.Bool()) + } case "changed": if in.IsNull() { in.Skip() @@ -3092,11 +3349,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userids": if in.IsNull() { @@ -3115,7 +3367,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle } for !in.IsDelim(']') { var v62 string - v62 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v62 = string(in.String()) + } out.UserIds = append(out.UserIds, v62) in.WantComma() } @@ -3138,7 +3394,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle } for !in.IsDelim(']') { var v63 RoomSessionId - v63 = RoomSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + v63 = RoomSessionId(in.String()) + } out.SessionIds = append(out.SessionIds, v63) in.WantComma() } @@ -3161,15 +3421,23 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle } for !in.IsDelim(']') { var v64 string - v64 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v64 = string(in.String()) + } out.AllUserIds = append(out.AllUserIds, v64) in.WantComma() } in.Delim(']') } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -3287,14 +3555,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "callid": - out.CallId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.CallId = string(in.String()) + } case "error": if in.IsNull() { in.Skip() @@ -3303,7 +3570,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle if out.Error == nil { out.Error = new(Error) } - (*out.Error).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -3374,17 +3645,20 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "number": - out.Number = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Number = string(in.String()) + } case "options": - if data := in.Raw(); in.Ok() { - in.AddError((out.Options).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Options).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -3449,16 +3723,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "code": - out.Code = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Code = string(in.String()) + } case "message": - out.Message = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } default: in.SkipRecursive() } @@ -3522,11 +3799,6 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userids": if in.IsNull() { @@ -3545,7 +3817,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle } for !in.IsDelim(']') { var v71 string - v71 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v71 = string(in.String()) + } out.UserIds = append(out.UserIds, v71) in.WantComma() } @@ -3619,16 +3895,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "sessionid": - out.SessionId = RoomSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = RoomSessionId(in.String()) + } default: in.SkipRecursive() } @@ -3698,14 +3977,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "url": - out.Url = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Url = string(in.String()) + } case "urls": if in.IsNull() { in.Skip() @@ -3723,20 +4001,40 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle } for !in.IsDelim(']') { var v74 string - v74 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v74 = string(in.String()) + } out.Urls = append(out.Urls, v74) in.WantComma() } in.Delim(']') } case "secret": - out.Secret = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Secret = string(in.String()) + } case "maxstreambitrate": - out.MaxStreamBitrate = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.MaxStreamBitrate = int(in.Int()) + } case "maxscreenbitrate": - out.MaxScreenBitrate = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.MaxScreenBitrate = int(in.Int()) + } case "sessionlimit": - out.SessionLimit = uint64(in.Uint64()) + if in.IsNull() { + in.Skip() + } else { + out.SessionLimit = uint64(in.Uint64()) + } default: in.SkipRecursive() } @@ -3840,16 +4138,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } default: in.SkipRecursive() } @@ -3913,25 +4214,44 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "action": - out.Action = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Action = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "user": - if data := in.Raw(); in.Ok() { - in.AddError((out.User).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.User).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -4016,23 +4336,34 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } case "session": - if data := in.Raw(); in.Ok() { - in.AddError((out.Session).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Session).UnmarshalJSON(data)) + } } case "permissions": if in.IsNull() { @@ -4058,7 +4389,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle } for !in.IsDelim(']') { var v77 Permission - v77 = Permission(in.String()) + if in.IsNull() { + in.Skip() + } else { + v77 = Permission(in.String()) + } *out.Permissions = append(*out.Permissions, v77) in.WantComma() } @@ -4154,28 +4489,55 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "action": - out.Action = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Action = string(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "sessionid": - out.SessionId = RoomSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = RoomSessionId(in.String()) + } case "actorid": - out.ActorId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ActorId = string(in.String()) + } case "actortype": - out.ActorType = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ActorType = string(in.String()) + } case "incall": - out.InCall = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.InCall = int(in.Int()) + } default: in.SkipRecursive() } @@ -4269,16 +4631,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } default: in.SkipRecursive() } @@ -4342,14 +4707,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "error": if in.IsNull() { in.Skip() @@ -4358,7 +4722,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle if out.Error == nil { out.Error = new(Error) } - (*out.Error).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } } case "auth": if in.IsNull() { @@ -4368,7 +4736,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle if out.Auth == nil { out.Auth = new(BackendClientAuthResponse) } - (*out.Auth).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Auth).UnmarshalEasyJSON(in) + } } case "room": if in.IsNull() { @@ -4378,7 +4750,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle if out.Room == nil { out.Room = new(BackendClientRoomResponse) } - (*out.Room).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Room).UnmarshalEasyJSON(in) + } } case "ping": if in.IsNull() { @@ -4388,7 +4764,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle if out.Ping == nil { out.Ping = new(BackendClientRingResponse) } - (*out.Ping).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Ping).UnmarshalEasyJSON(in) + } } case "session": if in.IsNull() { @@ -4398,7 +4778,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle if out.Session == nil { out.Session = new(BackendClientSessionResponse) } - (*out.Session).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Session).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -4483,14 +4867,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "auth": if in.IsNull() { in.Skip() @@ -4499,7 +4882,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Auth == nil { out.Auth = new(BackendClientAuthRequest) } - (*out.Auth).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Auth).UnmarshalEasyJSON(in) + } } case "room": if in.IsNull() { @@ -4509,7 +4896,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Room == nil { out.Room = new(BackendClientRoomRequest) } - (*out.Room).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Room).UnmarshalEasyJSON(in) + } } case "ping": if in.IsNull() { @@ -4519,7 +4910,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Ping == nil { out.Ping = new(BackendClientPingRequest) } - (*out.Ping).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Ping).UnmarshalEasyJSON(in) + } } case "session": if in.IsNull() { @@ -4529,7 +4924,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Session == nil { out.Session = new(BackendClientSessionRequest) } - (*out.Session).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Session).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -4609,16 +5008,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "entries": if in.IsNull() { in.Skip() @@ -4636,7 +5038,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle } for !in.IsDelim(']') { var v80 BackendPingEntry - (v80).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (v80).UnmarshalEasyJSON(in) + } out.Entries = append(out.Entries, v80) in.WantComma() } @@ -4721,19 +5127,26 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "user": - if data := in.Raw(); in.Ok() { - in.AddError((out.User).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.User).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -4803,17 +5216,20 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "params": - if data := in.Raw(); in.Ok() { - in.AddError((out.Params).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Params).UnmarshalJSON(data)) + } } default: in.SkipRecursive() diff --git a/api_grpc_easyjson.go b/api_grpc_easyjson.go index d1678a4..fd3e48c 100644 --- a/api_grpc_easyjson.go +++ b/api_grpc_easyjson.go @@ -30,14 +30,13 @@ func easyjson5dc3c167DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "address": - out.Address = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Address = string(in.String()) + } default: in.SkipRecursive() } diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index 53c0b9d..4211cde 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -31,19 +31,26 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "iss": - out.Issuer = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Issuer = string(in.String()) + } case "sub": - out.Subject = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Subject = string(in.String()) + } case "aud": - if data := in.Raw(); in.Ok() { - in.AddError((out.Audience).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Audience).UnmarshalJSON(data)) + } } case "exp": if in.IsNull() { @@ -53,8 +60,12 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe if out.ExpiresAt == nil { out.ExpiresAt = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + } } } case "nbf": @@ -65,8 +76,12 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe if out.NotBefore == nil { out.NotBefore = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.NotBefore).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.NotBefore).UnmarshalJSON(data)) + } } } case "iat": @@ -77,12 +92,20 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe if out.IssuedAt == nil { out.IssuedAt = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + } } } case "jti": - out.ID = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ID = string(in.String()) + } default: in.SkipRecursive() } @@ -202,16 +225,19 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "id": - out.Id = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "error": if in.IsNull() { in.Skip() @@ -220,7 +246,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Error == nil { out.Error = new(Error) } - (*out.Error).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } } case "hello": if in.IsNull() { @@ -230,7 +260,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Hello == nil { out.Hello = new(HelloProxyServerMessage) } - (*out.Hello).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Hello).UnmarshalEasyJSON(in) + } } case "bye": if in.IsNull() { @@ -240,7 +274,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Bye == nil { out.Bye = new(ByeProxyServerMessage) } - (*out.Bye).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Bye).UnmarshalEasyJSON(in) + } } case "command": if in.IsNull() { @@ -250,7 +288,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Command == nil { out.Command = new(CommandProxyServerMessage) } - (*out.Command).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Command).UnmarshalEasyJSON(in) + } } case "payload": if in.IsNull() { @@ -260,7 +302,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Payload == nil { out.Payload = new(PayloadProxyServerMessage) } - (*out.Payload).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Payload).UnmarshalEasyJSON(in) + } } case "event": if in.IsNull() { @@ -270,7 +316,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Event == nil { out.Event = new(EventProxyServerMessage) } - (*out.Event).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Event).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -371,14 +421,13 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "address": - out.Address = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Address = string(in.String()) + } default: in.SkipRecursive() } @@ -437,16 +486,19 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "id": - out.Id = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "hello": if in.IsNull() { in.Skip() @@ -455,7 +507,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex if out.Hello == nil { out.Hello = new(HelloProxyClientMessage) } - (*out.Hello).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Hello).UnmarshalEasyJSON(in) + } } case "bye": if in.IsNull() { @@ -465,7 +521,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex if out.Bye == nil { out.Bye = new(ByeProxyClientMessage) } - (*out.Bye).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Bye).UnmarshalEasyJSON(in) + } } case "command": if in.IsNull() { @@ -475,7 +535,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex if out.Command == nil { out.Command = new(CommandProxyClientMessage) } - (*out.Command).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Command).UnmarshalEasyJSON(in) + } } case "payload": if in.IsNull() { @@ -485,7 +549,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex if out.Payload == nil { out.Payload = new(PayloadProxyClientMessage) } - (*out.Payload).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Payload).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -576,16 +644,19 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "clientId": - out.ClientId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ClientId = string(in.String()) + } case "payload": if in.IsNull() { in.Skip() @@ -698,18 +769,25 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "clientId": - out.ClientId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ClientId = string(in.String()) + } case "sid": - out.Sid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Sid = string(in.String()) + } case "payload": if in.IsNull() { in.Skip() @@ -829,24 +907,43 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "bitrate": - out.Bitrate = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Bitrate = int(in.Int()) + } case "mediatypes": - out.MediaTypes = MediaType(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.MediaTypes = MediaType(in.Int()) + } case "audiocodec": - out.AudioCodec = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.AudioCodec = string(in.String()) + } case "videocodec": - out.VideoCodec = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.VideoCodec = string(in.String()) + } case "vp9_profile": - out.VP9Profile = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.VP9Profile = string(in.String()) + } case "h264_profile": - out.H264Profile = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.H264Profile = string(in.String()) + } default: in.SkipRecursive() } @@ -956,16 +1053,19 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "server": if in.IsNull() { in.Skip() @@ -974,7 +1074,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex if out.Server == nil { out.Server = new(WelcomeServerMessage) } - (*out.Server).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Server).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1044,16 +1148,19 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "resumeid": - out.ResumeId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ResumeId = PublicSessionId(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -1071,14 +1178,22 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex } for !in.IsDelim(']') { var v5 string - v5 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v5 = string(in.String()) + } out.Features = append(out.Features, v5) in.WantComma() } in.Delim(']') } case "token": - out.Token = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Token = string(in.String()) + } default: in.SkipRecursive() } @@ -1161,20 +1276,31 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "clientId": - out.ClientId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ClientId = string(in.String()) + } case "load": - out.Load = int64(in.Int64()) + if in.IsNull() { + in.Skip() + } else { + out.Load = int64(in.Int64()) + } case "sid": - out.Sid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Sid = string(in.String()) + } case "bandwidth": if in.IsNull() { in.Skip() @@ -1183,7 +1309,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex if out.Bandwidth == nil { out.Bandwidth = new(EventProxyServerBandwidth) } - (*out.Bandwidth).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Bandwidth).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1263,11 +1393,6 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "incoming": if in.IsNull() { @@ -1277,7 +1402,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle if out.Incoming == nil { out.Incoming = new(float64) } - *out.Incoming = float64(in.Float64()) + if in.IsNull() { + in.Skip() + } else { + *out.Incoming = float64(in.Float64()) + } } case "outgoing": if in.IsNull() { @@ -1287,7 +1416,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle if out.Outgoing == nil { out.Outgoing = new(float64) } - *out.Outgoing = float64(in.Float64()) + if in.IsNull() { + in.Skip() + } else { + *out.Outgoing = float64(in.Float64()) + } } default: in.SkipRecursive() @@ -1358,18 +1491,25 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "id": - out.Id = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } case "sid": - out.Sid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Sid = string(in.String()) + } case "bitrate": - out.Bitrate = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Bitrate = int(in.Int()) + } case "streams": if in.IsNull() { in.Skip() @@ -1491,42 +1631,97 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "mid": - out.Mid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Mid = string(in.String()) + } case "mindex": - out.Mindex = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Mindex = int(in.Int()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "description": - out.Description = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Description = string(in.String()) + } case "disabled": - out.Disabled = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Disabled = bool(in.Bool()) + } case "codec": - out.Codec = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Codec = string(in.String()) + } case "stereo": - out.Stereo = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Stereo = bool(in.Bool()) + } case "fec": - out.Fec = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Fec = bool(in.Bool()) + } case "dtx": - out.Dtx = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Dtx = bool(in.Bool()) + } case "simulcast": - out.Simulcast = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Simulcast = bool(in.Bool()) + } case "svc": - out.Svc = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Svc = bool(in.Bool()) + } case "h264_profile": - out.ProfileH264 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ProfileH264 = string(in.String()) + } case "vp9_profile": - out.ProfileVP9 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ProfileVP9 = string(in.String()) + } case "videoorient_ext_id": - out.ExtIdVideoOrientation = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.ExtIdVideoOrientation = int(in.Int()) + } case "playoutdelay_ext_id": - out.ExtIdPlayoutDelay = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.ExtIdPlayoutDelay = int(in.Int()) + } default: in.SkipRecursive() } @@ -1631,26 +1826,49 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "sid": - out.Sid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Sid = string(in.String()) + } case "streamType": - out.StreamType = StreamType(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.StreamType = StreamType(in.String()) + } case "publisherId": - out.PublisherId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.PublisherId = PublicSessionId(in.String()) + } case "clientId": - out.ClientId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ClientId = string(in.String()) + } case "bitrate": - out.Bitrate = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Bitrate = int(in.Int()) + } case "mediatypes": - out.MediaTypes = MediaType(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.MediaTypes = MediaType(in.Int()) + } case "publisherSettings": if in.IsNull() { in.Skip() @@ -1659,18 +1877,42 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle if out.PublisherSettings == nil { out.PublisherSettings = new(NewPublisherSettings) } - (*out.PublisherSettings).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.PublisherSettings).UnmarshalEasyJSON(in) + } } case "remoteUrl": - out.RemoteUrl = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RemoteUrl = string(in.String()) + } case "remoteToken": - out.RemoteToken = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RemoteToken = string(in.String()) + } case "hostname": - out.Hostname = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Hostname = string(in.String()) + } case "port": - out.Port = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Port = int(in.Int()) + } case "rtcpPort": - out.RtcpPort = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.RtcpPort = int(in.Int()) + } default: in.SkipRecursive() } @@ -1789,14 +2031,13 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "reason": - out.Reason = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Reason = string(in.String()) + } default: in.SkipRecursive() } @@ -1855,11 +2096,6 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { default: in.SkipRecursive() diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index aa237b9..6250c50 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -32,14 +32,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -57,14 +56,22 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe } for !in.IsDelim(']') { var v1 string - v1 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v1 = string(in.String()) + } out.Features = append(out.Features, v1) in.WantComma() } in.Delim(']') } case "country": - out.Country = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Country = string(in.String()) + } default: in.SkipRecursive() } @@ -142,11 +149,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "flags": if in.IsNull() { @@ -156,7 +158,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.Flags == nil { out.Flags = new(uint32) } - *out.Flags = uint32(in.Uint32()) + if in.IsNull() { + in.Skip() + } else { + *out.Flags = uint32(in.Uint32()) + } } case "incall": if in.IsNull() { @@ -166,12 +172,24 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if out.InCall == nil { out.InCall = new(int) } - *out.InCall = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + *out.InCall = int(in.Int()) + } } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } default: in.SkipRecursive() } @@ -256,16 +274,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "key": - out.Key = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Key = string(in.String()) + } case "oldvalue": if m, ok := out.OldValue.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) @@ -418,22 +439,33 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "key": - out.Key = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Key = string(in.String()) + } case "value": - if data := in.Raw(); in.Ok() { - in.AddError((out.Value).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Value).UnmarshalJSON(data)) + } } case "ttl": - out.TTL = time.Duration(in.Int64()) + if in.IsNull() { + in.Skip() + } else { + out.TTL = time.Duration(in.Int64()) + } default: in.SkipRecursive() } @@ -507,16 +539,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "id": - out.Id = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "error": if in.IsNull() { in.Skip() @@ -525,7 +560,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Error == nil { out.Error = new(Error) } - (*out.Error).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } } case "welcome": if in.IsNull() { @@ -535,7 +574,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Welcome == nil { out.Welcome = new(WelcomeServerMessage) } - (*out.Welcome).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Welcome).UnmarshalEasyJSON(in) + } } case "hello": if in.IsNull() { @@ -545,7 +588,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Hello == nil { out.Hello = new(HelloServerMessage) } - (*out.Hello).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Hello).UnmarshalEasyJSON(in) + } } case "bye": if in.IsNull() { @@ -555,7 +602,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Bye == nil { out.Bye = new(ByeServerMessage) } - (*out.Bye).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Bye).UnmarshalEasyJSON(in) + } } case "room": if in.IsNull() { @@ -565,7 +616,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Room == nil { out.Room = new(RoomServerMessage) } - (*out.Room).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Room).UnmarshalEasyJSON(in) + } } case "message": if in.IsNull() { @@ -575,7 +630,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Message == nil { out.Message = new(MessageServerMessage) } - (*out.Message).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Message).UnmarshalEasyJSON(in) + } } case "control": if in.IsNull() { @@ -585,7 +644,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Control == nil { out.Control = new(ControlServerMessage) } - (*out.Control).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Control).UnmarshalEasyJSON(in) + } } case "event": if in.IsNull() { @@ -595,7 +658,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Event == nil { out.Event = new(EventServerMessage) } - (*out.Event).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Event).UnmarshalEasyJSON(in) + } } case "transient": if in.IsNull() { @@ -605,7 +672,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.TransientData == nil { out.TransientData = new(TransientDataServerMessage) } - (*out.TransientData).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.TransientData).UnmarshalEasyJSON(in) + } } case "internal": if in.IsNull() { @@ -615,7 +686,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Internal == nil { out.Internal = new(InternalServerMessage) } - (*out.Internal).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Internal).UnmarshalEasyJSON(in) + } } case "dialout": if in.IsNull() { @@ -625,7 +700,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex if out.Dialout == nil { out.Dialout = new(DialoutInternalClientMessage) } - (*out.Dialout).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Dialout).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -751,17 +830,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -826,18 +908,25 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "flags": - out.Flags = uint32(in.Uint32()) + if in.IsNull() { + in.Skip() + } else { + out.Flags = uint32(in.Uint32()) + } default: in.SkipRecursive() } @@ -906,20 +995,31 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex 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()) + if in.IsNull() { + in.Skip() + } else { + out.SignalingUrl = string(in.String()) + } case "url": - out.NextcloudUrl = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.NextcloudUrl = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "token": - out.Token = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Token = string(in.String()) + } default: in.SkipRecursive() } @@ -993,21 +1093,28 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } case "incall": - if data := in.Raw(); in.Ok() { - in.AddError((out.InCall).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.InCall).UnmarshalJSON(data)) + } } case "changed": if in.IsNull() { @@ -1104,7 +1211,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim(']') } case "all": - out.All = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.All = bool(in.Bool()) + } default: in.SkipRecursive() } @@ -1250,11 +1361,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "comment": if in.IsNull() { @@ -1371,14 +1477,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "chat": if in.IsNull() { in.Skip() @@ -1387,7 +1492,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle if out.Chat == nil { out.Chat = new(RoomEventMessageDataChat) } - (*out.Chat).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Chat).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1452,17 +1561,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -1527,11 +1639,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "room": if in.IsNull() { @@ -1541,7 +1648,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle if out.Room == nil { out.Room = new(RoomServerMessage) } - (*out.Room).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Room).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1605,23 +1716,34 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "reason": - out.Reason = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Reason = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "properties": - if data := in.Raw(); in.Ok() { - in.AddError((out.Properties).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Properties).UnmarshalJSON(data)) + } } case "incall": - if data := in.Raw(); in.Ok() { - in.AddError((out.InCall).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.InCall).UnmarshalJSON(data)) + } } case "changed": if in.IsNull() { @@ -1718,7 +1840,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim(']') } case "all": - out.All = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.All = bool(in.Bool()) + } default: in.SkipRecursive() } @@ -1869,16 +1995,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "sessionid": - out.SessionId = RoomSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = RoomSessionId(in.String()) + } case "federation": if in.IsNull() { in.Skip() @@ -1887,7 +2016,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle if out.Federation == nil { out.Federation = new(RoomFederationMessage) } - (*out.Federation).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Federation).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -1957,18 +2090,25 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } default: in.SkipRecursive() } @@ -2043,18 +2183,25 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } default: in.SkipRecursive() } @@ -2123,14 +2270,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "refresh": - out.Refresh = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Refresh = bool(in.Bool()) + } default: in.SkipRecursive() } @@ -2189,14 +2335,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "chat": if in.IsNull() { in.Skip() @@ -2205,7 +2350,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle if out.Chat == nil { out.Chat = new(MessageServerMessageDataChat) } - (*out.Chat).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Chat).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -2270,11 +2419,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "sender": if in.IsNull() { @@ -2284,7 +2428,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle if out.Sender == nil { out.Sender = new(MessageServerMessageSender) } - (*out.Sender).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Sender).UnmarshalEasyJSON(in) + } } case "recipient": if in.IsNull() { @@ -2294,11 +2442,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle if out.Recipient == nil { out.Recipient = new(MessageClientMessageRecipient) } - (*out.Recipient).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Recipient).UnmarshalEasyJSON(in) + } } case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -2372,18 +2528,25 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } default: in.SkipRecursive() } @@ -2452,18 +2615,25 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "sid": - out.Sid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Sid = string(in.String()) + } case "roomType": - out.RoomType = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomType = string(in.String()) + } case "payload": if in.IsNull() { in.Skip() @@ -2487,15 +2657,35 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Delim('}') } case "bitrate": - out.Bitrate = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Bitrate = int(in.Int()) + } case "audiocodec": - out.AudioCodec = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.AudioCodec = string(in.String()) + } case "videocodec": - out.VideoCodec = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.VideoCodec = string(in.String()) + } case "vp9profile": - out.VP9Profile = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.VP9Profile = string(in.String()) + } case "h264profile": - out.H264Profile = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.H264Profile = string(in.String()) + } default: in.SkipRecursive() } @@ -2616,17 +2806,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "recipient": - (out.Recipient).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (out.Recipient).UnmarshalEasyJSON(in) + } case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -2691,14 +2884,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "dialout": if in.IsNull() { in.Skip() @@ -2707,7 +2899,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle if out.Dialout == nil { out.Dialout = new(InternalServerDialoutRequest) } - (*out.Dialout).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Dialout).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -2772,16 +2968,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "backend": - out.Backend = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Backend = string(in.String()) + } case "request": if in.IsNull() { in.Skip() @@ -2790,7 +2989,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle if out.Request == nil { out.Request = new(BackendRoomDialoutRequest) } - (*out.Request).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Request).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -2864,14 +3067,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "addsession": if in.IsNull() { in.Skip() @@ -2880,7 +3082,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle if out.AddSession == nil { out.AddSession = new(AddSessionInternalClientMessage) } - (*out.AddSession).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.AddSession).UnmarshalEasyJSON(in) + } } case "updatesession": if in.IsNull() { @@ -2890,7 +3096,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle if out.UpdateSession == nil { out.UpdateSession = new(UpdateSessionInternalClientMessage) } - (*out.UpdateSession).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.UpdateSession).UnmarshalEasyJSON(in) + } } case "removesession": if in.IsNull() { @@ -2900,7 +3110,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle if out.RemoveSession == nil { out.RemoveSession = new(RemoveSessionInternalClientMessage) } - (*out.RemoveSession).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.RemoveSession).UnmarshalEasyJSON(in) + } } case "incall": if in.IsNull() { @@ -2910,7 +3124,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle if out.InCall == nil { out.InCall = new(InCallInternalClientMessage) } - (*out.InCall).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.InCall).UnmarshalEasyJSON(in) + } } case "dialout": if in.IsNull() { @@ -2920,7 +3138,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle if out.Dialout == nil { out.Dialout = new(DialoutInternalClientMessage) } - (*out.Dialout).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Dialout).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -3005,14 +3227,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "incall": - out.InCall = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.InCall = int(in.Int()) + } default: in.SkipRecursive() } @@ -3071,23 +3292,34 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle 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)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.UserData).UnmarshalJSON(data)) + } } case "iss": - out.Issuer = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Issuer = string(in.String()) + } case "sub": - out.Subject = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Subject = string(in.String()) + } case "aud": - if data := in.Raw(); in.Ok() { - in.AddError((out.Audience).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Audience).UnmarshalJSON(data)) + } } case "exp": if in.IsNull() { @@ -3097,8 +3329,12 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle if out.ExpiresAt == nil { out.ExpiresAt = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + } } } case "nbf": @@ -3109,8 +3345,12 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle if out.NotBefore == nil { out.NotBefore = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.NotBefore).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.NotBefore).UnmarshalJSON(data)) + } } } case "iat": @@ -3121,12 +3361,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle if out.IssuedAt == nil { out.IssuedAt = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + } } } case "jti": - out.ID = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ID = string(in.String()) + } default: in.SkipRecursive() } @@ -3256,14 +3504,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle 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()) + if in.IsNull() { + in.Skip() + } else { + out.Token = string(in.String()) + } default: in.SkipRecursive() } @@ -3322,20 +3569,31 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "resumeid": - out.ResumeId = PrivateSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ResumeId = PrivateSessionId(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "server": if in.IsNull() { in.Skip() @@ -3344,7 +3602,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle if out.Server == nil { out.Server = new(WelcomeServerMessage) } - (*out.Server).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Server).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -3424,20 +3686,27 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = ClientType(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = ClientType(in.String()) + } case "params": - if data := in.Raw(); in.Ok() { - in.AddError((out.Params).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Params).UnmarshalJSON(data)) + } } case "url": - out.Url = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Url = string(in.String()) + } default: in.SkipRecursive() } @@ -3512,16 +3781,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "version": - out.Version = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Version = string(in.String()) + } case "resumeid": - out.ResumeId = PrivateSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ResumeId = PrivateSessionId(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -3539,7 +3811,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle } for !in.IsDelim(']') { var v30 string - v30 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v30 = string(in.String()) + } out.Features = append(out.Features, v30) in.WantComma() } @@ -3553,7 +3829,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle if out.Auth == nil { out.Auth = new(HelloClientMessageAuth) } - (*out.Auth).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Auth).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -3637,23 +3917,34 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle 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)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.UserData).UnmarshalJSON(data)) + } } case "iss": - out.Issuer = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Issuer = string(in.String()) + } case "sub": - out.Subject = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Subject = string(in.String()) + } case "aud": - if data := in.Raw(); in.Ok() { - in.AddError((out.Audience).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Audience).UnmarshalJSON(data)) + } } case "exp": if in.IsNull() { @@ -3663,8 +3954,12 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle if out.ExpiresAt == nil { out.ExpiresAt = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + } } } case "nbf": @@ -3675,8 +3970,12 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle if out.NotBefore == nil { out.NotBefore = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.NotBefore).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.NotBefore).UnmarshalJSON(data)) + } } } case "iat": @@ -3687,12 +3986,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle if out.IssuedAt == nil { out.IssuedAt = new(_v5.NumericDate) } - if data := in.Raw(); in.Ok() { - in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + } } } case "jti": - out.ID = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ID = string(in.String()) + } default: in.SkipRecursive() } @@ -3822,14 +4129,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle 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()) + if in.IsNull() { + in.Skip() + } else { + out.Token = string(in.String()) + } default: in.SkipRecursive() } @@ -3888,17 +4194,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "details": - if data := in.Raw(); in.Ok() { - in.AddError((out.Details).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Details).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -3963,16 +4272,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "features": if in.IsNull() { in.Skip() @@ -3990,20 +4302,36 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle } for !in.IsDelim(']') { var v33 string - v33 = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + v33 = string(in.String()) + } out.Features = append(out.Features, v33) in.WantComma() } in.Delim(']') } case "user": - if data := in.Raw(); in.Ok() { - in.AddError((out.User).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.User).UnmarshalJSON(data)) + } } case "roomsessionid": - out.RoomSessionId = RoomSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomSessionId = RoomSessionId(in.String()) + } case "federated": - out.Federated = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + out.Federated = bool(in.Bool()) + } default: in.SkipRecursive() } @@ -4096,16 +4424,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "target": - out.Target = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Target = string(in.String()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "join": if in.IsNull() { in.Skip() @@ -4130,7 +4461,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if v36 == nil { v36 = new(EventServerMessageSessionEntry) } - (*v36).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*v36).UnmarshalEasyJSON(in) + } } out.Join = append(out.Join, v36) in.WantComma() @@ -4154,7 +4489,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle } for !in.IsDelim(']') { var v37 PublicSessionId - v37 = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + v37 = PublicSessionId(in.String()) + } out.Leave = append(out.Leave, v37) in.WantComma() } @@ -4184,7 +4523,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if v38 == nil { v38 = new(EventServerMessageSessionEntry) } - (*v38).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*v38).UnmarshalEasyJSON(in) + } } out.Change = append(out.Change, v38) in.WantComma() @@ -4199,7 +4542,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.SwitchTo == nil { out.SwitchTo = new(EventServerMessageSwitchTo) } - (*out.SwitchTo).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.SwitchTo).UnmarshalEasyJSON(in) + } } case "resumed": if in.IsNull() { @@ -4209,7 +4556,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Resumed == nil { out.Resumed = new(bool) } - *out.Resumed = bool(in.Bool()) + if in.IsNull() { + in.Skip() + } else { + *out.Resumed = bool(in.Bool()) + } } case "invite": if in.IsNull() { @@ -4219,7 +4570,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Invite == nil { out.Invite = new(RoomEventServerMessage) } - (*out.Invite).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Invite).UnmarshalEasyJSON(in) + } } case "disinvite": if in.IsNull() { @@ -4229,7 +4584,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Disinvite == nil { out.Disinvite = new(RoomDisinviteEventServerMessage) } - (*out.Disinvite).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Disinvite).UnmarshalEasyJSON(in) + } } case "update": if in.IsNull() { @@ -4239,7 +4598,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Update == nil { out.Update = new(RoomEventServerMessage) } - (*out.Update).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Update).UnmarshalEasyJSON(in) + } } case "flags": if in.IsNull() { @@ -4249,7 +4612,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Flags == nil { out.Flags = new(RoomFlagsServerMessage) } - (*out.Flags).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Flags).UnmarshalEasyJSON(in) + } } case "message": if in.IsNull() { @@ -4259,7 +4626,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle if out.Message == nil { out.Message = new(RoomEventMessage) } - (*out.Message).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Message).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -4409,19 +4780,26 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "code": - out.Code = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Code = string(in.String()) + } case "message": - out.Message = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } case "details": - if data := in.Raw(); in.Ok() { - in.AddError((out.Details).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Details).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -4491,22 +4869,37 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "callid": - out.CallId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.CallId = string(in.String()) + } case "status": - out.Status = DialoutStatus(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Status = DialoutStatus(in.String()) + } case "cause": - out.Cause = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Cause = string(in.String()) + } case "code": - out.Code = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + out.Code = int(in.Int()) + } case "message": - out.Message = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } default: in.SkipRecursive() } @@ -4585,16 +4978,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } case "error": if in.IsNull() { in.Skip() @@ -4603,7 +4999,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle if out.Error == nil { out.Error = new(Error) } - (*out.Error).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } } case "status": if in.IsNull() { @@ -4613,7 +5013,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle if out.Status == nil { out.Status = new(DialoutStatusInternalClientMessage) } - (*out.Status).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Status).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -4688,11 +5092,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "sender": if in.IsNull() { @@ -4702,7 +5101,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle if out.Sender == nil { out.Sender = new(MessageServerMessageSender) } - (*out.Sender).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Sender).UnmarshalEasyJSON(in) + } } case "recipient": if in.IsNull() { @@ -4712,11 +5115,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle if out.Recipient == nil { out.Recipient = new(MessageClientMessageRecipient) } - (*out.Recipient).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Recipient).UnmarshalEasyJSON(in) + } } case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -4790,17 +5201,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "recipient": - (out.Recipient).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (out.Recipient).UnmarshalEasyJSON(in) + } case "data": - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } } default: in.SkipRecursive() @@ -4865,16 +5279,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } default: in.SkipRecursive() } @@ -4938,18 +5355,25 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "random": - out.Random = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Random = string(in.String()) + } case "token": - out.Token = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Token = string(in.String()) + } case "backend": - out.Backend = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Backend = string(in.String()) + } default: in.SkipRecursive() } @@ -5018,16 +5442,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "id": - out.Id = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "hello": if in.IsNull() { in.Skip() @@ -5036,7 +5463,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.Hello == nil { out.Hello = new(HelloClientMessage) } - (*out.Hello).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Hello).UnmarshalEasyJSON(in) + } } case "bye": if in.IsNull() { @@ -5046,7 +5477,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.Bye == nil { out.Bye = new(ByeClientMessage) } - (*out.Bye).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Bye).UnmarshalEasyJSON(in) + } } case "room": if in.IsNull() { @@ -5056,7 +5491,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.Room == nil { out.Room = new(RoomClientMessage) } - (*out.Room).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Room).UnmarshalEasyJSON(in) + } } case "message": if in.IsNull() { @@ -5066,7 +5505,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.Message == nil { out.Message = new(MessageClientMessage) } - (*out.Message).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Message).UnmarshalEasyJSON(in) + } } case "control": if in.IsNull() { @@ -5076,7 +5519,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.Control == nil { out.Control = new(ControlClientMessage) } - (*out.Control).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Control).UnmarshalEasyJSON(in) + } } case "internal": if in.IsNull() { @@ -5086,7 +5533,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.Internal == nil { out.Internal = new(InternalClientMessage) } - (*out.Internal).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Internal).UnmarshalEasyJSON(in) + } } case "transient": if in.IsNull() { @@ -5096,7 +5547,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle if out.TransientData == nil { out.TransientData = new(TransientDataClientMessage) } - (*out.TransientData).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.TransientData).UnmarshalEasyJSON(in) + } } default: in.SkipRecursive() @@ -5202,14 +5657,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "reason": - out.Reason = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Reason = string(in.String()) + } default: in.SkipRecursive() } @@ -5268,11 +5722,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { default: in.SkipRecursive() @@ -5327,20 +5776,31 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "to": - out.To = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.To = PublicSessionId(in.String()) + } case "from": - out.From = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.From = PublicSessionId(in.String()) + } case "type": - out.Type = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } case "roomType": - out.RoomType = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomType = string(in.String()) + } case "payload": if in.IsNull() { in.Skip() @@ -5364,7 +5824,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Delim('}') } case "sid": - out.Sid = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.Sid = string(in.String()) + } default: in.SkipRecursive() } @@ -5470,16 +5934,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "actorId": - out.ActorId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ActorId = string(in.String()) + } case "actorType": - out.ActorType = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.ActorType = string(in.String()) + } default: in.SkipRecursive() } @@ -5549,20 +6016,27 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jle for !in.IsDelim('}') { key := in.UnsafeFieldName(false) in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } switch key { case "userid": - out.UserId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.UserId = string(in.String()) + } case "user": - if data := in.Raw(); in.Ok() { - in.AddError((out.User).UnmarshalJSON(data)) + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.User).UnmarshalJSON(data)) + } } case "flags": - out.Flags = uint32(in.Uint32()) + if in.IsNull() { + in.Skip() + } else { + out.Flags = uint32(in.Uint32()) + } case "incall": if in.IsNull() { in.Skip() @@ -5571,7 +6045,11 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jle if out.InCall == nil { out.InCall = new(int) } - *out.InCall = int(in.Int()) + if in.IsNull() { + in.Skip() + } else { + *out.InCall = int(in.Int()) + } } case "options": if in.IsNull() { @@ -5581,12 +6059,24 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jle if out.Options == nil { out.Options = new(AddSessionOptions) } - (*out.Options).UnmarshalEasyJSON(in) + if in.IsNull() { + in.Skip() + } else { + (*out.Options).UnmarshalEasyJSON(in) + } } case "sessionid": - out.SessionId = PublicSessionId(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.SessionId = PublicSessionId(in.String()) + } case "roomid": - out.RoomId = string(in.String()) + if in.IsNull() { + in.Skip() + } else { + out.RoomId = string(in.String()) + } default: in.SkipRecursive() } From 374476a419923df1c445c02bac6e79db2afc29d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:01:50 +0000 Subject: [PATCH 210/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.4 to 3.6.5 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.4...v3.6.5) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.4 to 3.6.5 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.4...v3.6.5) Updates `go.etcd.io/etcd/client/v3` from 3.6.4 to 3.6.5 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.4...v3.6.5) Updates `go.etcd.io/etcd/server/v3` from 3.6.4 to 3.6.5 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.4...v3.6.5) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 9066281..ad1990d 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/api/v3 v3.6.4 - go.etcd.io/etcd/client/pkg/v3 v3.6.4 - go.etcd.io/etcd/client/v3 v3.6.4 - go.etcd.io/etcd/server/v3 v3.6.4 + go.etcd.io/etcd/api/v3 v3.6.5 + go.etcd.io/etcd/client/pkg/v3 v3.6.5 + go.etcd.io/etcd/client/v3 v3.6.5 + go.etcd.io/etcd/server/v3 v3.6.5 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.75.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -75,8 +75,8 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.4.2 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.4 // indirect + go.etcd.io/bbolt v1.4.3 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.5 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index 512122d..d476715 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,8 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= @@ -141,18 +141,18 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= -go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= -go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= -go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= -go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= -go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= +go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= +go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= +go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= +go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= +go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= +go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM= +go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU= +go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0= +go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From 41728572fee8b39b68002590fc64c800289e2004 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 26 Sep 2025 13:50:18 +0200 Subject: [PATCH 211/549] Move "StringMap" class to api module. --- stringmap.go => api/stringmap.go | 17 ++- stringmap_test.go => api/stringmap_test.go | 2 +- api_backend.go | 14 ++- api_backend_easyjson.go | 33 ++--- api_proxy.go | 12 +- api_proxy_easyjson.go | 5 +- api_signaling.go | 38 +++--- api_signaling_easyjson.go | 39 +++--- backend_server.go | 12 +- backend_server_test.go | 18 +-- capabilities.go | 16 +-- capabilities_test.go | 8 +- clientsession.go | 18 +-- clientsession_test.go | 6 +- federation.go | 18 +-- federation_test.go | 26 ++-- hub.go | 12 +- hub_test.go | 88 +++++++------- janus_client.go | 14 ++- mcu_common.go | 6 +- mcu_common_test.go | 4 +- mcu_janus.go | 8 +- mcu_janus_client.go | 18 +-- mcu_janus_publisher.go | 14 ++- mcu_janus_remote_publisher.go | 6 +- mcu_janus_stream_selection.go | 6 +- mcu_janus_subscriber.go | 16 +-- mcu_janus_test.go | 134 +++++++++++---------- mcu_proxy.go | 10 +- mcu_test.go | 12 +- proxy/proxy_server.go | 7 +- proxy/proxy_server_test.go | 8 +- proxy/proxy_session.go | 7 +- room.go | 32 ++--- roomsessions_test.go | 4 +- session.go | 10 +- testclient_test.go | 28 +++-- transient_data.go | 10 +- transient_data_test.go | 4 +- virtualsession.go | 8 +- virtualsession_test.go | 8 +- 41 files changed, 414 insertions(+), 342 deletions(-) rename stringmap.go => api/stringmap.go (90%) rename stringmap_test.go => api/stringmap_test.go (99%) diff --git a/stringmap.go b/api/stringmap.go similarity index 90% rename from stringmap.go rename to api/stringmap.go index ef4d3fb..d4f7a73 100644 --- a/stringmap.go +++ b/api/stringmap.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 api // StringMap maps string keys to arbitrary values. type StringMap map[string]any @@ -29,15 +29,14 @@ func ConvertStringMap(ob any) (StringMap, bool) { return nil, true } - if m, ok := ob.(map[string]any); ok { - return StringMap(m), true + switch ob := ob.(type) { + case map[string]any: + return StringMap(ob), true + case StringMap: + return ob, true + default: + return nil, false } - - if m, ok := ob.(StringMap); ok { - return m, true - } - - return nil, false } // GetStringMapEntry returns an entry from a string map in a given type. diff --git a/stringmap_test.go b/api/stringmap_test.go similarity index 99% rename from stringmap_test.go rename to api/stringmap_test.go index d0a5ebb..1d45a20 100644 --- a/stringmap_test.go +++ b/api/stringmap_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 api import ( "testing" diff --git a/api_backend.go b/api_backend.go index b8bccb8..dc853e7 100644 --- a/api_backend.go +++ b/api_backend.go @@ -35,6 +35,8 @@ import ( "slices" "strings" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -145,13 +147,13 @@ type BackendRoomInCallRequest struct { // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. InCall json.RawMessage `json:"incall,omitempty"` All bool `json:"all,omitempty"` - Changed []StringMap `json:"changed,omitempty"` - Users []StringMap `json:"users,omitempty"` + Changed []api.StringMap `json:"changed,omitempty"` + Users []api.StringMap `json:"users,omitempty"` } type BackendRoomParticipantsRequest struct { - Changed []StringMap `json:"changed,omitempty"` - Users []StringMap `json:"users,omitempty"` + Changed []api.StringMap `json:"changed,omitempty"` + Users []api.StringMap `json:"users,omitempty"` } type BackendRoomMessageRequest struct { @@ -318,8 +320,8 @@ func (r *BackendClientRoomRequest) UpdateFromSession(s Session) { if s.ClientType() == HelloClientTypeFederation { // Need to send additional data for requests of federated users. if u, err := s.ParsedUserData(); err == nil && len(u) > 0 { - if actorType, found := GetStringMapEntry[string](u, "actorType"); found { - if actorId, found := GetStringMapEntry[string](u, "actorId"); found { + if actorType, found := api.GetStringMapEntry[string](u, "actorType"); found { + if actorId, found := api.GetStringMapEntry[string](u, "actorId"); found { r.ActorId = actorId r.ActorType = actorType } diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index d898e1a..fe51e3b 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -7,6 +7,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + api "github.com/strukturag/nextcloud-spreed-signaling/api" time "time" ) @@ -2644,21 +2645,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]StringMap, 0, 8) + out.Changed = make([]api.StringMap, 0, 8) } else { - out.Changed = []StringMap{} + out.Changed = []api.StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v36 StringMap + var v36 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v36 = make(StringMap) + v36 = make(api.StringMap) } else { v36 = nil } @@ -2691,21 +2692,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]StringMap, 0, 8) + out.Users = make([]api.StringMap, 0, 8) } else { - out.Users = []StringMap{} + out.Users = []api.StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v38 StringMap + var v38 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v38 = make(StringMap) + v38 = make(api.StringMap) } else { v38 = nil } @@ -3112,21 +3113,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]StringMap, 0, 8) + out.Changed = make([]api.StringMap, 0, 8) } else { - out.Changed = []StringMap{} + out.Changed = []api.StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v52 StringMap + var v52 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v52 = make(StringMap) + v52 = make(api.StringMap) } else { v52 = nil } @@ -3159,21 +3160,21 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]StringMap, 0, 8) + out.Users = make([]api.StringMap, 0, 8) } else { - out.Users = []StringMap{} + out.Users = []api.StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v54 StringMap + var v54 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v54 = make(StringMap) + v54 = make(api.StringMap) } else { v54 = nil } diff --git a/api_proxy.go b/api_proxy.go index 5f7a70a..ac51760 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -27,6 +27,8 @@ import ( "net/url" "github.com/golang-jwt/jwt/v5" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type ProxyClientMessage struct { @@ -277,9 +279,9 @@ type CommandProxyServerMessage struct { type PayloadProxyClientMessage struct { Type string `json:"type"` - ClientId string `json:"clientId"` - Sid string `json:"sid,omitempty"` - Payload StringMap `json:"payload,omitempty"` + ClientId string `json:"clientId"` + Sid string `json:"sid,omitempty"` + Payload api.StringMap `json:"payload,omitempty"` } func (m *PayloadProxyClientMessage) CheckValid() error { @@ -308,8 +310,8 @@ func (m *PayloadProxyClientMessage) CheckValid() error { type PayloadProxyServerMessage struct { Type string `json:"type"` - ClientId string `json:"clientId"` - Payload StringMap `json:"payload"` + ClientId string `json:"clientId"` + Payload api.StringMap `json:"payload"` } // Type "event" diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index 4211cde..2eb6996 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -8,6 +8,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + api "github.com/strukturag/nextcloud-spreed-signaling/api" ) // suppress unused package warning @@ -662,7 +663,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex in.Skip() } else { in.Delim('{') - out.Payload = make(StringMap) + out.Payload = make(api.StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() @@ -794,7 +795,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex } else { in.Delim('{') if !in.IsDelim('}') { - out.Payload = make(StringMap) + out.Payload = make(api.StringMap) } else { out.Payload = nil } diff --git a/api_signaling.go b/api_signaling.go index 863880d..75b81e6 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -35,6 +35,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/pion/ice/v4" "github.com/pion/sdp/v3" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -724,10 +726,10 @@ type MessageClientMessage struct { } type MessageClientMessageData struct { - Type string `json:"type"` - Sid string `json:"sid"` - RoomType string `json:"roomType"` - Payload StringMap `json:"payload"` + Type string `json:"type"` + Sid string `json:"sid"` + RoomType string `json:"roomType"` + Payload api.StringMap `json:"payload"` // Only supported if Type == "offer" Bitrate int `json:"bitrate,omitempty"` @@ -752,7 +754,7 @@ func (m *MessageClientMessageData) String() string { func parseSDP(s string) (*sdp.SessionDescription, error) { var sdp sdp.SessionDescription if err := sdp.UnmarshalString(s); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", StringMap{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", api.StringMap{ "error": err.Error(), }) } @@ -764,7 +766,7 @@ func parseSDP(s string) (*sdp.SessionDescription, error) { } if _, err := ice.UnmarshalCandidate(a.Value); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", StringMap{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", api.StringMap{ "media": m.MediaName.Media, "idx": idx, "error": err.Error(), @@ -786,7 +788,7 @@ func (m *MessageClientMessageData) CheckValid() error { } switch m.Type { case "offer", "answer": - sdpText, ok := GetStringMapEntry[string](m.Payload, "sdp") + sdpText, ok := api.GetStringMapEntry[string](m.Payload, "sdp") if !ok { return ErrInvalidSdp } @@ -807,11 +809,11 @@ func (m *MessageClientMessageData) CheckValid() error { if !found { return ErrNoCandidate } - candItem, ok := ConvertStringMap(candValue) + candItem, ok := api.ConvertStringMap(candValue) if !ok { return ErrInvalidCandidate } - candText, ok := GetStringMapEntry[string](candItem, "candidate") + candText, ok := api.GetStringMapEntry[string](candItem, "candidate") if !ok { return ErrInvalidCandidate } @@ -821,7 +823,7 @@ func (m *MessageClientMessageData) CheckValid() error { } else { cand, err := ice.UnmarshalCandidate(candText) if err != nil { - return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", StringMap{ + return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", api.StringMap{ "error": err.Error(), }) } @@ -1131,8 +1133,8 @@ type RoomEventServerMessage struct { Properties json.RawMessage `json:"properties,omitempty"` // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. InCall json.RawMessage `json:"incall,omitempty"` - Changed []StringMap `json:"changed,omitempty"` - Users []StringMap `json:"users,omitempty"` + Changed []api.StringMap `json:"changed,omitempty"` + Users []api.StringMap `json:"users,omitempty"` All bool `json:"all,omitempty"` } @@ -1167,7 +1169,7 @@ type RoomFlagsServerMessage struct { Flags uint32 `json:"flags"` } -type ChatComment StringMap +type ChatComment api.StringMap type RoomEventMessageDataChat struct { Comment *ChatComment `json:"comment,omitempty"` @@ -1240,7 +1242,7 @@ type AnswerOfferMessage struct { From PublicSessionId `json:"from"` Type string `json:"type"` RoomType string `json:"roomType"` - Payload StringMap `json:"payload"` + Payload api.StringMap `json:"payload"` Sid string `json:"sid,omitempty"` } @@ -1272,8 +1274,8 @@ func (m *TransientDataClientMessage) CheckValid() error { type TransientDataServerMessage struct { Type string `json:"type"` - Key string `json:"key,omitempty"` - OldValue any `json:"oldvalue,omitempty"` - Value any `json:"value,omitempty"` - Data StringMap `json:"data,omitempty"` + Key string `json:"key,omitempty"` + OldValue any `json:"oldvalue,omitempty"` + Value any `json:"value,omitempty"` + Data api.StringMap `json:"data,omitempty"` } diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 6250c50..9f0f67b 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -8,6 +8,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + api "github.com/strukturag/nextcloud-spreed-signaling/api" time "time" ) @@ -309,7 +310,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex } else { in.Delim('{') if !in.IsDelim('}') { - out.Data = make(StringMap) + out.Data = make(api.StringMap) } else { out.Data = nil } @@ -1124,21 +1125,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]StringMap, 0, 8) + out.Changed = make([]api.StringMap, 0, 8) } else { - out.Changed = []StringMap{} + out.Changed = []api.StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v6 StringMap + var v6 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v6 = make(StringMap) + v6 = make(api.StringMap) } else { v6 = nil } @@ -1171,21 +1172,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]StringMap, 0, 8) + out.Users = make([]api.StringMap, 0, 8) } else { - out.Users = []StringMap{} + out.Users = []api.StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v8 StringMap + var v8 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v8 = make(StringMap) + v8 = make(api.StringMap) } else { v8 = nil } @@ -1753,21 +1754,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]StringMap, 0, 8) + out.Changed = make([]api.StringMap, 0, 8) } else { - out.Changed = []StringMap{} + out.Changed = []api.StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v18 StringMap + var v18 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v18 = make(StringMap) + v18 = make(api.StringMap) } else { v18 = nil } @@ -1800,21 +1801,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]StringMap, 0, 8) + out.Users = make([]api.StringMap, 0, 8) } else { - out.Users = []StringMap{} + out.Users = []api.StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v20 StringMap + var v20 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v20 = make(StringMap) + v20 = make(api.StringMap) } else { v20 = nil } @@ -2639,7 +2640,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Skip() } else { in.Delim('{') - out.Payload = make(StringMap) + out.Payload = make(api.StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() @@ -5806,7 +5807,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Skip() } else { in.Delim('{') - out.Payload = make(StringMap) + out.Payload = make(api.StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() diff --git a/backend_server.go b/backend_server.go index 4c0927e..53672c7 100644 --- a/backend_server.go +++ b/backend_server.go @@ -46,6 +46,8 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -421,14 +423,14 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId return sid, nil } -func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentMap[RoomSessionId, PublicSessionId], users []StringMap) []StringMap { +func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentMap[RoomSessionId, PublicSessionId], users []api.StringMap) []api.StringMap { if len(users) == 0 { return users } var wg sync.WaitGroup for _, user := range users { - roomSessionId, found := GetStringMapString[RoomSessionId](user, "sessionId") + roomSessionId, found := api.GetStringMapString[RoomSessionId](user, "sessionId") if !found { log.Printf("User %+v has invalid room session id, ignoring", user) delete(user, "sessionId") @@ -442,7 +444,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Add(1) - go func(roomSessionId RoomSessionId, u StringMap) { + go func(roomSessionId RoomSessionId, u api.StringMap) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -457,7 +459,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent } wg.Wait() - result := make([]StringMap, 0, len(users)) + result := make([]api.StringMap, 0, len(users)) for _, user := range users { if _, found := user["sessionId"]; found { result = append(result, user) @@ -512,7 +514,7 @@ loop: continue } - sessionId, found := GetStringMapString[PublicSessionId](user, "sessionId") + sessionId, found := api.GetStringMapString[PublicSessionId](user, "sessionId") if !found { log.Printf("User entry has no session id: %+v", user) continue diff --git a/backend_server_test.go b/backend_server_test.go index c21d84b..7e9f828 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -45,6 +45,8 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) var ( @@ -789,7 +791,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []StringMap{ + Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, @@ -799,7 +801,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, @@ -865,13 +867,13 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []StringMap{ + Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{}, }, }, - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{}, @@ -931,7 +933,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), - Changed: []StringMap{ + Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, @@ -941,7 +943,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { "inCall": 3, }, }, - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, @@ -978,7 +980,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), - Changed: []StringMap{ + Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, @@ -988,7 +990,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { "inCall": 3, }, }, - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), "inCall": 7, diff --git a/capabilities.go b/capabilities.go index 4cc8d57..7f16344 100644 --- a/capabilities.go +++ b/capabilities.go @@ -33,6 +33,8 @@ import ( "time" "github.com/pquerna/cachecontrol/cacheobject" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -64,7 +66,7 @@ type capabilitiesEntry struct { nextUpdate time.Time etag string mustRevalidate bool - capabilities StringMap + capabilities api.StringMap } func newCapabilitiesEntry(c *Capabilities) *capabilitiesEntry { @@ -211,7 +213,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return false, nil } - var capa StringMap + var capa api.StringMap if err := json.Unmarshal(capaObj, &capa); err != nil { log.Printf("Unsupported capabilities received for app spreed from %s: %+v", url, capaResponse) e.capabilities = nil @@ -223,7 +225,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim return true, nil } -func (e *capabilitiesEntry) GetCapabilities() StringMap { +func (e *capabilitiesEntry) GetCapabilities() api.StringMap { e.mu.RLock() defer e.mu.RUnlock() @@ -322,7 +324,7 @@ func (c *Capabilities) getKeyForUrl(u *url.URL) string { return key } -func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (StringMap, bool, error) { +func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (api.StringMap, bool, error) { key := c.getKeyForUrl(u) entry, valid := c.getCapabilities(key) if valid { @@ -363,7 +365,7 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea return false } -func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (StringMap, bool, bool) { +func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (api.StringMap, bool, bool) { caps, cached, err := c.loadCapabilities(ctx, u) if err != nil { log.Printf("Could not get capabilities for %s: %s", u, err) @@ -375,7 +377,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str return nil, cached, false } - config, ok := ConvertStringMap(configInterface) + config, ok := api.ConvertStringMap(configInterface) if !ok { log.Printf("Invalid config mapping received from %s: %+v", u, configInterface) return nil, cached, false @@ -386,7 +388,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str return nil, cached, false } - groupConfig, ok := ConvertStringMap(groupInterface) + groupConfig, ok := api.ConvertStringMap(groupInterface) if !ok { log.Printf("Invalid group mapping \"%s\" received from %s: %+v", group, u, groupInterface) return nil, cached, false diff --git a/capabilities_test.go b/capabilities_test.go index e74e732..72a7f00 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -40,6 +40,8 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*CapabilitiesResponse, http.ResponseWriter) error) (*url.URL, *Capabilities) { @@ -66,14 +68,14 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie if strings.Contains(t.Name(), "V3Api") { features = append(features, "signaling-v3") } - signaling := StringMap{ + signaling := api.StringMap{ "foo": "bar", "baz": 42, } - config := StringMap{ + config := api.StringMap{ "signaling": signaling, } - spreedCapa, _ := json.Marshal(StringMap{ + spreedCapa, _ := json.Marshal(api.StringMap{ "features": features, "config": config, }) diff --git a/clientsession.go b/clientsession.go index 67e5fec..0f5f420 100644 --- a/clientsession.go +++ b/clientsession.go @@ -34,6 +34,8 @@ import ( "time" "github.com/pion/sdp/v3" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) var ( @@ -66,7 +68,7 @@ type ClientSession struct { userId string userData json.RawMessage - parseUserData func() (StringMap, error) + parseUserData func() (api.StringMap, error) inCall Flags supportsPermissions bool @@ -288,7 +290,7 @@ func (s *ClientSession) UserData() json.RawMessage { return s.userData } -func (s *ClientSession) ParsedUserData() (StringMap, error) { +func (s *ClientSession) ParsedUserData() (api.StringMap, error) { return s.parseUserData() } @@ -523,7 +525,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) request.Room.Action = "leave" - var response StringMap + var response api.StringMap if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { log.Printf("Could not notify about room session %s left room %s: %s", sid, room.Id(), err) } else { @@ -587,7 +589,7 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { return prev } -func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, streamType StreamType, offer StringMap) { +func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, streamType StreamType, offer api.StringMap) { offer_message := &AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -621,7 +623,7 @@ func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, From: sender, Type: "candidate", RoomType: string(streamType), - Payload: StringMap{ + Payload: api.StringMap{ "candidate": candidate, }, Sid: client.Sid(), @@ -686,7 +688,7 @@ func (s *ClientSession) SendMessages(messages []*ServerMessage) bool { return true } -func (s *ClientSession) OnUpdateOffer(client McuClient, offer StringMap) { +func (s *ClientSession) OnUpdateOffer(client McuClient, offer api.StringMap) { s.mu.Lock() defer s.mu.Unlock() @@ -1081,7 +1083,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { return } - mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response StringMap) { + mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response api.StringMap) { if err != nil { log.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ @@ -1144,7 +1146,7 @@ func filterDisplayNames(events []*EventServerMessageSessionEntry) []*EventServer continue } - var userdata StringMap + var userdata api.StringMap if err := json.Unmarshal(event.User, &userdata); err != nil { result = append(result, event) continue diff --git a/clientsession_test.go b/clientsession_test.go index daf98cc..ea10196 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -28,6 +28,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func TestBandwidth_Client(t *testing.T) { @@ -67,7 +69,7 @@ func TestBandwidth_Client(t *testing.T) { Sid: "54321", RoomType: "video", Bitrate: bitrate, - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -139,7 +141,7 @@ func TestBandwidth_Backend(t *testing.T) { Sid: "54321", RoomType: string(streamType), Bitrate: bitrate, - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) diff --git a/federation.go b/federation.go index a4f5a0f..1f9747d 100644 --- a/federation.go +++ b/federation.go @@ -36,7 +36,9 @@ import ( "time" "github.com/gorilla/websocket" - easyjson "github.com/mailru/easyjson" + "github.com/mailru/easyjson" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -602,14 +604,14 @@ func (c *FederationClient) joinRoom() error { }) } -func (c *FederationClient) updateEventUsers(users []StringMap, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { +func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { 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 { + if actorType, found := api.GetStringMapEntry[string](u, "actorType"); found { + if actorId, found := api.GetStringMapEntry[string](u, "actorId"); found { switch actorType { case ActorTypeFederatedUsers: if strings.HasSuffix(actorId, localCloudUrl) { @@ -625,10 +627,10 @@ func (c *FederationClient) updateEventUsers(users []StringMap, localSessionId Pu if checkSessionId { key := "sessionId" - sid, found := GetStringMapString[PublicSessionId](u, key) + sid, found := api.GetStringMapString[PublicSessionId](u, key) if !found { key := "sessionid" - sid, found = GetStringMapString[PublicSessionId](u, key) + sid, found = api.GetStringMapString[PublicSessionId](u, key) } if found && sid == remoteSessionId { u[key] = localSessionId @@ -667,10 +669,10 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { c.updateSessionSender(msg.Control.Sender, localSessionId, remoteSessionId) // Special handling for "forceMute" event. if len(msg.Control.Data) > 0 && msg.Control.Data[0] == '{' { - var data StringMap + var data api.StringMap if err := json.Unmarshal(msg.Control.Data, &data); err == nil { if action, found := data["action"]; found && action == "forceMute" { - if peerId, found := GetStringMapString[PublicSessionId](data, "peerId"); found && peerId == remoteSessionId { + if peerId, found := api.GetStringMapString[PublicSessionId](data, "peerId"); found && peerId == remoteSessionId { data["peerId"] = localSessionId if d, err := json.Marshal(data); err == nil { msg.Control.Data = d diff --git a/federation_test.go b/federation_test.go index 5594ab5..726b998 100644 --- a/federation_test.go +++ b/federation_test.go @@ -31,6 +31,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func Test_FederationInvalidToken(t *testing.T) { @@ -107,7 +109,7 @@ func Test_Federation(t *testing.T) { require.NotNil(room) now := time.Now() - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -307,7 +309,7 @@ func Test_Federation(t *testing.T) { } // Special handling for the "forceMute" control event. - forceMute := StringMap{ + forceMute := api.StringMap{ "action": "forceMute", "peerId": remoteSessionId, } @@ -315,7 +317,7 @@ func Test_Federation(t *testing.T) { Type: "session", SessionId: remoteSessionId, }, forceMute)) { - var payload StringMap + var payload api.StringMap if checkReceiveClientControl(ctx, t, client2, "session", hello1.Hello, &payload) { // The sessionId in "peerId" will be replaced with the local one. forceMute["peerId"] = string(hello2.Hello.SessionId) @@ -347,7 +349,7 @@ func Test_Federation(t *testing.T) { } // Simulate request from the backend that a federated user joined the call. - users := []StringMap{ + users := []api.StringMap{ { "sessionId": remoteSessionId, "inCall": 1, @@ -373,7 +375,7 @@ func Test_Federation(t *testing.T) { } // Simulate request from the backend that a local user joined the call. - users = []StringMap{ + users = []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -425,7 +427,7 @@ func Test_Federation(t *testing.T) { hello4 := MustSucceed1(t, client4.RunUntilHello, ctx) - userdata = StringMap{ + userdata = api.StringMap{ "displayname": "Federated user 2", "actorType": "federated_users", "actorId": "the-other-federated-user-id", @@ -517,7 +519,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -624,7 +626,7 @@ func Test_FederationChangeRoom(t *testing.T) { client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -747,7 +749,7 @@ func Test_FederationMedia(t *testing.T) { client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -798,7 +800,7 @@ func Test_FederationMedia(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "screen", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -840,7 +842,7 @@ func Test_FederationResume(t *testing.T) { client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", @@ -961,7 +963,7 @@ func Test_FederationResumeNewSession(t *testing.T) { client1.RunUntilJoined(ctx, hello1.Hello) now := time.Now() - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Federated user", "actorType": "federated_users", "actorId": "the-federated-user-id", diff --git a/hub.go b/hub.go index 32c22d2..97f8de9 100644 --- a/hub.go +++ b/hub.go @@ -51,6 +51,8 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/websocket" "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) var ( @@ -2169,7 +2171,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { return } - mc.SendMessage(session.Context(), msg, clientData, func(err error, response StringMap) { + mc.SendMessage(session.Context(), msg, clientData, func(err error, response api.StringMap) { if err != nil { log.Printf("Could not send MCU message %+v for session %s to %s: %s", clientData, session.PublicId(), recipient.PublicId(), err) sendMcuProcessingFailed(session, message) @@ -2763,7 +2765,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe return } - mc.SendMessage(session.Context(), message, data, func(err error, response StringMap) { + mc.SendMessage(session.Context(), message, data, func(err error, response api.StringMap) { if err != nil { if !errors.Is(err, ErrCandidateFiltered) { log.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) @@ -2779,7 +2781,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe }) } -func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response StringMap) { +func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response api.StringMap) { var response_message *ServerMessage switch response["type"] { case "answer": @@ -2894,8 +2896,8 @@ func (h *Hub) processRoomParticipants(message *BackendServerRoomRequest) { room.PublishUsersChanged(message.Participants.Changed, message.Participants.Users) } -func (h *Hub) GetStats() StringMap { - result := make(StringMap) +func (h *Hub) GetStats() api.StringMap { + result := make(api.StringMap) h.ru.RLock() result["rooms"] = len(h.rooms) h.ru.RUnlock() diff --git a/hub_test.go b/hub_test.go index 58e632d..604844d 100644 --- a/hub_test.go +++ b/hub_test.go @@ -51,6 +51,8 @@ import ( "github.com/nats-io/nats-server/v2/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -699,11 +701,11 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "Federation") { features = append(features, "federation-v2") } - signaling := StringMap{ + signaling := api.StringMap{ "foo": "bar", "baz": 42, } - config := StringMap{ + config := api.StringMap{ "signaling": signaling, } if strings.Contains(t.Name(), "MultiRoom") { @@ -735,7 +737,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { signaling[ConfigKeyHelloV2TokenKey] = string(public) } } - spreedCapa, _ := json.Marshal(StringMap{ + spreedCapa, _ := json.Marshal(api.StringMap{ "features": features, "config": config, }) @@ -1716,7 +1718,7 @@ func TestClientHelloResumeProxy(t *testing.T) { room2 := hub2.getRoom(roomId) require.Nil(room2, "Should not have gotten room %s", roomId) - users := []StringMap{ + users := []api.StringMap{ { "sessionId": "the-session-id", "inCall": 1, @@ -1928,7 +1930,7 @@ func TestClientMessageToSessionId(t *testing.T) { SessionId: hello2.Hello.SessionId, } - data1 := StringMap{ + data1 := api.StringMap{ "type": "test", "message": "from-1-to-2", } @@ -1940,7 +1942,7 @@ func TestClientMessageToSessionId(t *testing.T) { if checkReceiveClientMessage(ctx, t, client1, "session", hello2.Hello, &payload1) { assert.Equal(data2, payload1) } - var payload2 StringMap + var payload2 api.StringMap if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload2) { assert.Equal(data1, payload2) } @@ -2344,7 +2346,7 @@ func TestClientMessageToCall(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []StringMap{ + users := []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2377,7 +2379,7 @@ func TestClientMessageToCall(t *testing.T) { client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // Simulate request from the backend that somebody joined the call. - users = []StringMap{ + users = []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2449,7 +2451,7 @@ func TestClientControlToCall(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []StringMap{ + users := []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -2482,7 +2484,7 @@ func TestClientControlToCall(t *testing.T) { client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // Simulate request from the backend that somebody joined the call. - users = []StringMap{ + users = []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -3146,7 +3148,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { // The two chat messages should get combined into one when receiving pending messages. chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" - var data1 StringMap + var data1 api.StringMap require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint client1.SendMessage(recipient2, data1) // nolint @@ -3163,7 +3165,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { assert.Equal(hello2.Hello.ResumeId, hello3.Hello.ResumeId, "%+v", hello3.Hello) } - var payload StringMap + var payload api.StringMap if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } @@ -3202,7 +3204,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that somebody joined the call. - users := []StringMap{ + users := []api.StringMap{ { "sessionId": "the-session-id", "inCall": 1, @@ -3227,7 +3229,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { } chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" - var data1 StringMap + var data1 api.StringMap require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint @@ -3244,7 +3246,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { // TODO(jojo): Check contents of message and try with multiple users. checkReceiveClientEvent(ctx, t, client2, "update", nil) - var payload StringMap + var payload api.StringMap if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } @@ -3404,7 +3406,7 @@ func TestClientSendOfferPermissions(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "screen", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3470,7 +3472,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3486,7 +3488,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioOnly, }, })) @@ -3533,7 +3535,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3544,13 +3546,13 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []StringMap{ + Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, }, }, - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, @@ -3632,7 +3634,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3643,13 +3645,13 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ - Changed: []StringMap{ + Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, }, }, - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, @@ -3741,7 +3743,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "screen", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -3782,7 +3784,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.True(checkMessageError(t, msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. - users1 := []StringMap{ + users1 := []api.StringMap{ { "sessionId": hello2.Hello.SessionId, "inCall": 1, @@ -3808,7 +3810,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.True(checkMessageError(t, msg, "not_allowed")) // Simulate request from the backend that somebody joined the call. - users2 := []StringMap{ + users2 := []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -3839,7 +3841,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { Type: "answer", Sid: "12345", RoomType: "screen", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpAnswerAudioAndVideo, }, })) @@ -4150,7 +4152,7 @@ func TestClientSendOffer(t *testing.T) { Type: "offer", Sid: "12345", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -4212,7 +4214,7 @@ func TestClientUnshareScreen(t *testing.T) { Type: "offer", Sid: "54321", RoomType: "screen", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioOnly, }, })) @@ -4644,7 +4646,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: []byte("0"), - Users: []StringMap{ + Users: []api.StringMap{ { "sessionId": virtualSession.PublicId(), "participantPermissions": 246, @@ -4758,7 +4760,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } -func DoTestSwitchToOne(t *testing.T, details StringMap) { +func DoTestSwitchToOne(t *testing.T, details api.StringMap) { CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { @@ -4846,7 +4848,7 @@ func DoTestSwitchToOne(t *testing.T, details StringMap) { } func TestSwitchToOneMap(t *testing.T) { - DoTestSwitchToOne(t, StringMap{ + DoTestSwitchToOne(t, api.StringMap{ "foo": "bar", }) } @@ -4855,7 +4857,7 @@ func TestSwitchToOneList(t *testing.T) { DoTestSwitchToOne(t, nil) } -func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap) { +func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.StringMap) { CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { @@ -4945,9 +4947,9 @@ func DoTestSwitchToMultiple(t *testing.T, details1 StringMap, details2 StringMap } func TestSwitchToMultipleMap(t *testing.T) { - DoTestSwitchToMultiple(t, StringMap{ + DoTestSwitchToMultiple(t, api.StringMap{ "foo": "bar", - }, StringMap{ + }, api.StringMap{ "bar": "baz", }) } @@ -4957,7 +4959,7 @@ func TestSwitchToMultipleList(t *testing.T) { } func TestSwitchToMultipleMixed(t *testing.T) { - DoTestSwitchToMultiple(t, StringMap{ + DoTestSwitchToMultiple(t, api.StringMap{ "foo": "bar", }, nil) } @@ -5079,7 +5081,7 @@ func TestDialoutStatus(t *testing.T) { key := "callstatus_" + callId if msg, ok := client.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, key, StringMap{ + checkMessageTransientSet(t, msg, key, api.StringMap{ "callid": callId, "status": "accepted", }, nil) @@ -5095,10 +5097,10 @@ func TestDialoutStatus(t *testing.T) { })) if msg, ok := client.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, key, StringMap{ + checkMessageTransientSet(t, msg, key, api.StringMap{ "callid": callId, "status": "ringing", - }, StringMap{ + }, api.StringMap{ "callid": callId, "status": "accepted", }) @@ -5122,11 +5124,11 @@ func TestDialoutStatus(t *testing.T) { })) if msg, ok := client.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, key, StringMap{ + checkMessageTransientSet(t, msg, key, api.StringMap{ "callid": callId, "status": "cleared", "cause": clearedCause, - }, StringMap{ + }, api.StringMap{ "callid": callId, "status": "ringing", }) @@ -5136,7 +5138,7 @@ func TestDialoutStatus(t *testing.T) { defer cancel() if msg, ok := client.RunUntilMessage(ctx2); ok { - checkMessageTransientRemove(t, msg, key, StringMap{ + checkMessageTransientRemove(t, msg, key, api.StringMap{ "callid": callId, "status": "cleared", "cause": clearedCause, diff --git a/janus_client.go b/janus_client.go index fbc568a..3aa8f6f 100644 --- a/janus_client.go +++ b/janus_client.go @@ -42,6 +42,8 @@ import ( "github.com/gorilla/websocket" "github.com/notedit/janus-go" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -204,8 +206,8 @@ func newTransaction() *transaction { return t } -func newRequest(method string) (StringMap, *transaction) { - req := make(StringMap, 8) +func newRequest(method string) (api.StringMap, *transaction) { + req := make(api.StringMap, 8) req["janus"] = method return req, newTransaction() } @@ -225,7 +227,7 @@ type JanusGatewayInterface interface { Create(context.Context) (*JanusSession, error) Close() error - send(StringMap, *transaction) (uint64, error) + send(api.StringMap, *transaction) (uint64, error) removeTransaction(uint64) removeSession(*JanusSession) @@ -338,7 +340,7 @@ func (gateway *JanusGateway) removeTransaction(id uint64) { } } -func (gateway *JanusGateway) send(msg StringMap, t *transaction) (uint64, error) { +func (gateway *JanusGateway) send(msg api.StringMap, t *transaction) (uint64, error) { id := gateway.nextTransaction.Add(1) msg["transaction"] = strconv.FormatUint(id, 10) data, err := json.Marshal(msg) @@ -599,7 +601,7 @@ type JanusSession struct { gateway JanusGatewayInterface } -func (session *JanusSession) send(msg StringMap, t *transaction) (uint64, error) { +func (session *JanusSession) send(msg api.StringMap, t *transaction) (uint64, error) { msg["session_id"] = session.Id return session.gateway.send(msg, t) } @@ -711,7 +713,7 @@ type JanusHandle struct { session *JanusSession } -func (handle *JanusHandle) send(msg StringMap, t *transaction) (uint64, error) { +func (handle *JanusHandle) send(msg api.StringMap, t *transaction) (uint64, error) { msg["handle_id"] = handle.Id return handle.session.send(msg, t) } diff --git a/mcu_common.go b/mcu_common.go index f8e39cc..5f77f7e 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -29,6 +29,8 @@ import ( "time" "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -56,7 +58,7 @@ const ( type McuListener interface { PublicId() PublicSessionId - OnUpdateOffer(client McuClient, offer StringMap) + OnUpdateOffer(client McuClient, offer api.StringMap) OnIceCandidate(client McuClient, candidate any) OnIceCompleted(client McuClient) @@ -205,7 +207,7 @@ type McuClient interface { Close(ctx context.Context) - SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) + SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) } type McuPublisher interface { diff --git a/mcu_common_test.go b/mcu_common_test.go index a54434b..7632f92 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -23,6 +23,8 @@ package signaling import ( "testing" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func TestCommonMcuStats(t *testing.T) { @@ -37,7 +39,7 @@ func (m *MockMcuListener) PublicId() PublicSessionId { return m.publicId } -func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer StringMap) { +func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer api.StringMap) { } diff --git a/mcu_janus.go b/mcu_janus.go index 526ddf0..d8229a7 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -34,6 +34,8 @@ import ( "github.com/dlintw/goconf" "github.com/notedit/janus-go" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -567,7 +569,7 @@ func (m *mcuJanus) SubscriberDisconnected(id string, publisher PublicSessionId, } func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, int, error) { - create_msg := StringMap{ + create_msg := api.StringMap{ "request": "create", "description": getStreamId(id, streamType), // We publish every stream in its own Janus room. @@ -641,7 +643,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSess return nil, 0, 0, 0, err } - msg := StringMap{ + msg := api.StringMap{ "request": "join", "ptype": "publisher", "room": roomId, @@ -833,7 +835,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re return nil, err } - response, err := handle.Request(ctx, StringMap{ + response, err := handle.Request(ctx, api.StringMap{ "request": "add_remote_publisher", "room": roomId, "id": streamTypeUserIds[streamType], diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 8d7ae8f..2165509 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -29,6 +29,8 @@ import ( "sync" "github.com/notedit/janus-go" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type mcuJanusClient struct { @@ -75,7 +77,7 @@ func (c *mcuJanusClient) MaxBitrate() int { func (c *mcuJanusClient) Close(ctx context.Context) { } -func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { } func (c *mcuJanusClient) closeClient(ctx context.Context) bool { @@ -124,14 +126,14 @@ loop: } } -func (c *mcuJanusClient) sendOffer(ctx context.Context, offer StringMap, callback func(error, StringMap)) { +func (c *mcuJanusClient) sendOffer(ctx context.Context, offer api.StringMap, callback func(error, api.StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) return } - configure_msg := StringMap{ + configure_msg := api.StringMap{ "request": "configure", "audio": true, "video": true, @@ -146,14 +148,14 @@ func (c *mcuJanusClient) sendOffer(ctx context.Context, offer StringMap, callbac callback(nil, answer_msg.Jsep) } -func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer StringMap, callback func(error, StringMap)) { +func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer api.StringMap, callback func(error, api.StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) return } - start_msg := StringMap{ + start_msg := api.StringMap{ "request": "start", "room": c.roomId, } @@ -166,7 +168,7 @@ func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer StringMap, callb callback(nil, nil) } -func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, StringMap)) { +func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, api.StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) @@ -188,7 +190,7 @@ func (c *mcuJanusClient) handleTrickle(event *TrickleMsg) { } } -func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, StringMap)) { +func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { handle := c.handle if handle == nil { callback(ErrNotConnected, nil) @@ -200,7 +202,7 @@ func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelecti return } - configure_msg := StringMap{ + configure_msg := api.StringMap{ "request": "configure", } if stream != nil { diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index d57ae24..c4b7e7f 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -32,6 +32,8 @@ import ( "github.com/notedit/janus-go" "github.com/pion/sdp/v3" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -139,7 +141,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { notify := false p.mu.Lock() if handle := p.handle; handle != nil && p.roomId != 0 { - destroy_msg := StringMap{ + destroy_msg := api.StringMap{ "request": "destroy", "room": p.roomId, } @@ -167,7 +169,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { @@ -201,13 +203,13 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli msgctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() - p.sendOffer(msgctx, jsep_msg, func(err error, jsep StringMap) { + p.sendOffer(msgctx, jsep_msg, func(err error, jsep api.StringMap) { if err != nil { callback(err, jsep) return } - sdpString, found := GetStringMapEntry[string](jsep, "sdp") + sdpString, found := api.GetStringMapEntry[string](jsep, "sdp") if !found { log.Printf("No/invalid sdp found in answer %+v", jsep) } else if answerSdp, err := parseSDP(sdpString); err != nil { @@ -397,7 +399,7 @@ func getPublisherRemoteId(id PublicSessionId, remoteId PublicSessionId, hostname } func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { - msg := StringMap{ + msg := api.StringMap{ "request": "publish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], @@ -434,7 +436,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSe } func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { - msg := StringMap{ + msg := api.StringMap{ "request": "unpublish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], diff --git a/mcu_janus_remote_publisher.go b/mcu_janus_remote_publisher.go index e3430ab..f2bafe6 100644 --- a/mcu_janus_remote_publisher.go +++ b/mcu_janus_remote_publisher.go @@ -27,6 +27,8 @@ import ( "sync/atomic" "github.com/notedit/janus-go" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type mcuJanusRemotePublisher struct { @@ -124,7 +126,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { p.mu.Lock() if handle := p.handle; handle != nil { - response, err := p.handle.Request(ctx, StringMap{ + response, err := p.handle.Request(ctx, api.StringMap{ "request": "remove_remote_publisher", "room": p.roomId, "id": streamTypeUserIds[p.streamType], @@ -135,7 +137,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { log.Printf("Removed remote publisher: %+v", response) } if p.roomId != 0 { - destroy_msg := StringMap{ + destroy_msg := api.StringMap{ "request": "destroy", "room": p.roomId, } diff --git a/mcu_janus_stream_selection.go b/mcu_janus_stream_selection.go index 2787d85..0458909 100644 --- a/mcu_janus_stream_selection.go +++ b/mcu_janus_stream_selection.go @@ -24,6 +24,8 @@ package signaling import ( "database/sql" "fmt" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type streamSelection struct { @@ -37,7 +39,7 @@ func (s *streamSelection) HasValues() bool { return s.substream.Valid || s.temporal.Valid || s.audio.Valid || s.video.Valid } -func (s *streamSelection) AddToMessage(message StringMap) { +func (s *streamSelection) AddToMessage(message api.StringMap) { if s.substream.Valid { message["substream"] = s.substream.Int16 } @@ -52,7 +54,7 @@ func (s *streamSelection) AddToMessage(message StringMap) { } } -func parseStreamSelection(payload StringMap) (*streamSelection, error) { +func parseStreamSelection(payload api.StringMap) (*streamSelection, error) { var stream streamSelection if value, found := payload["substream"]; found { switch value := value.(type) { diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 885ce45..9699cef 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -28,6 +28,8 @@ import ( "strconv" "github.com/notedit/janus-go" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type mcuJanusSubscriber struct { @@ -55,7 +57,7 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { } for _, stream := range streams { - if stream, ok := ConvertStringMap(stream); ok { + if stream, ok := api.ConvertStringMap(stream); ok { if (stream["type"] == "audio" || stream["type"] == "video") && stream["active"] != false { return } @@ -149,7 +151,7 @@ func (p *mcuJanusSubscriber) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, StringMap)) { +func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { handle := p.handle if handle == nil { callback(ErrNotConnected, nil) @@ -161,13 +163,13 @@ func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelecti loggedNotPublishingYet := false retry: - join_msg := StringMap{ + join_msg := api.StringMap{ "request": "join", "ptype": "subscriber", "room": p.roomId, } if p.mcu.isMultistream() { - join_msg["streams"] = []StringMap{ + join_msg["streams"] = []api.StringMap{ { "feed": streamTypeUserIds[p.streamType], }, @@ -255,14 +257,14 @@ retry: callback(nil, join_response.Jsep) } -func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, StringMap)) { +func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { handle := p.handle if handle == nil { callback(ErrNotConnected, nil) return } - configure_msg := StringMap{ + configure_msg := api.StringMap{ "request": "configure", "update": true, } @@ -278,7 +280,7 @@ func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection callback(nil, configure_response.Jsep) } -func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 6d4a4f1..1bfa955 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -35,6 +35,8 @@ import ( "github.com/notedit/janus-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type TestJanusHandle struct { @@ -49,7 +51,7 @@ type TestJanusRoom struct { publisher atomic.Pointer[TestJanusHandle] } -type TestJanusHandler func(room *TestJanusRoom, body StringMap, jsep StringMap) (any, *janus.ErrorMsg) +type TestJanusHandler func(room *TestJanusRoom, body api.StringMap, jsep api.StringMap) (any, *janus.ErrorMsg) type TestJanusGateway struct { t *testing.T @@ -140,7 +142,7 @@ func (g *TestJanusGateway) Close() error { return nil } -func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body StringMap, jsep StringMap) any { +func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body api.StringMap, jsep api.StringMap) any { request := body["request"].(string) switch request { case "create": @@ -152,7 +154,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "room": room.id, }, }, @@ -180,7 +182,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "error_code": error_code, }, }, @@ -191,7 +193,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, }, }, @@ -215,7 +217,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan Handle: handle.id, Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "room": room.id, }, }, @@ -226,7 +228,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED, }, }, @@ -235,7 +237,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan sdp := publisher.sdp.Load() return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "type": "offer", "sdp": sdp.(string), }, @@ -264,7 +266,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{}, + Data: api.StringMap{}, }, } default: @@ -330,7 +332,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan return nil } -func (g *TestJanusGateway) processRequest(msg StringMap) any { +func (g *TestJanusGateway) processRequest(msg api.StringMap) any { method, found := msg["janus"] if !found { return nil @@ -407,10 +409,10 @@ func (g *TestJanusGateway) processRequest(msg StringMap) any { var result any switch method { case "message": - body, ok := ConvertStringMap(msg["body"]) + body, ok := api.ConvertStringMap(msg["body"]) assert.True(g.t, ok, "not a string map: %+v", msg["body"]) if jsepOb, found := msg["jsep"]; found { - if jsep, ok := ConvertStringMap(jsepOb); assert.True(g.t, ok, "not a string map: %+v", jsepOb) { + if jsep, ok := api.ConvertStringMap(jsepOb); assert.True(g.t, ok, "not a string map: %+v", jsepOb) { result = g.processMessage(session, handle, body, jsep) } } else { @@ -451,7 +453,7 @@ func (g *TestJanusGateway) processRequest(msg StringMap) any { return nil } -func (g *TestJanusGateway) send(msg StringMap, t *transaction) (uint64, error) { +func (g *TestJanusGateway) send(msg api.StringMap, t *transaction) (uint64, error) { tid := g.tid.Add(1) data, err := json.Marshal(msg) @@ -523,7 +525,7 @@ func (t *TestMcuListener) PublicId() PublicSessionId { return t.id } -func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer StringMap) { +func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer api.StringMap) { } @@ -593,7 +595,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { // The SDP received by Janus will be filtered from blocked candidates. @@ -606,12 +608,12 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { } return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "sdp": MockSdpAnswerAudioOnly, }, }, nil }, - "trickle": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "trickle": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.AckMsg{}, nil }, @@ -637,7 +639,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { // Send offer containing candidates that will be blocked / filtered. data := &MessageClientMessageData{ Type: "offer", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioOnly, }, } @@ -645,7 +647,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() if assert.NoError(err) { @@ -661,15 +663,15 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: StringMap{ - "candidate": StringMap{ + Payload: api.StringMap{ + "candidate": api.StringMap{ "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -679,15 +681,15 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: StringMap{ - "candidate": StringMap{ + Payload: api.StringMap{ + "candidate": api.StringMap{ "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.NoError(err) @@ -704,7 +706,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "start": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "start": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { // The SDP received by Janus will be filtered from blocked candidates. @@ -719,7 +721,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { return &janus.EventMsg{ Plugindata: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "room": room.id, "started": true, "videoroom": "event", @@ -727,7 +729,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { }, }, nil }, - "trickle": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "trickle": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.AckMsg{}, nil }, @@ -764,7 +766,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { // Send answer containing candidates that will be blocked / filtered. data := &MessageClientMessageData{ Type: "answer", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpAnswerAudioOnly, }, } @@ -772,7 +774,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() if assert.NoError(err) { @@ -783,15 +785,15 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: StringMap{ - "candidate": StringMap{ + Payload: api.StringMap{ + "candidate": api.StringMap{ "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -801,15 +803,15 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { data = &MessageClientMessageData{ Type: "candidate", - Payload: StringMap{ - "candidate": StringMap{ + Payload: api.StringMap{ + "candidate": api.StringMap{ "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", }, }, } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.NoError(err) @@ -826,7 +828,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { if sdpValue, found := jsep["sdp"]; assert.True(found) { @@ -838,7 +840,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { } return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "sdp": MockSdpAnswerAudioOnly, }, }, nil @@ -864,14 +866,14 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { data := &MessageClientMessageData{ Type: "offer", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioOnly, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { @@ -910,7 +912,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { _, found := jsep["sdp"] @@ -918,7 +920,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { } return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "sdp": MockSdpAnswerAudioAndVideo, }, }, nil @@ -944,7 +946,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { data := &MessageClientMessageData{ Type: "offer", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, } @@ -953,7 +955,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { // Defer sending of offer / answer so "GetStreams" will wait. go func() { done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { @@ -1089,7 +1091,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) if assert.NotNil(jsep) { if sdp, found := jsep["sdp"]; assert.True(found) { @@ -1098,7 +1100,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { } return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "sdp": MockSdpAnswerAudioAndVideo, }, }, nil @@ -1136,14 +1138,14 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { go func() { data := &MessageClientMessageData{ Type: "offer", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { @@ -1164,7 +1166,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { require.NoError(data.CheckValid()) done := make(chan struct{}) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m StringMap) { + sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { @@ -1192,11 +1194,11 @@ func Test_JanusRemotePublisher(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "add_remote_publisher": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "add_remote_publisher": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) assert.Nil(jsep) if streams := body["streams"].([]any); assert.Len(streams, 1) { - if stream, ok := ConvertStringMap(streams[0]); assert.True(ok, "not a string map: %+v", streams[0]) { + if stream, ok := api.ConvertStringMap(streams[0]); assert.True(ok, "not a string map: %+v", streams[0]) { assert.Equal("0", stream["mid"]) assert.EqualValues(0, stream["mindex"]) assert.Equal("audio", stream["type"]) @@ -1207,7 +1209,7 @@ func Test_JanusRemotePublisher(t *testing.T) { return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{ + Data: api.StringMap{ "id": 12345, "port": 10000, "rtcp_port": 10001, @@ -1215,14 +1217,14 @@ func Test_JanusRemotePublisher(t *testing.T) { }, }, nil }, - "remove_remote_publisher": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "remove_remote_publisher": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) assert.Nil(jsep) removed.Add(1) return &janus.SuccessMsg{ PluginData: janus.PluginData{ Plugin: pluginVideoRoom, - Data: StringMap{}, + Data: api.StringMap{}, }, }, nil }, @@ -1280,10 +1282,10 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1316,7 +1318,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []StringMap{ + users1 := []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1338,7 +1340,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -1380,10 +1382,10 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1416,7 +1418,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []StringMap{ + users1 := []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1438,7 +1440,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) @@ -1490,10 +1492,10 @@ func Test_JanusSubscriberTimeout(t *testing.T) { mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep StringMap) (any, *janus.ErrorMsg) { + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) return &janus.EventMsg{ - Jsep: StringMap{ + Jsep: api.StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }, @@ -1526,7 +1528,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) // Simulate request from the backend that sessions joined the call. - users1 := []StringMap{ + users1 := []api.StringMap{ { "sessionId": hello1.Hello.SessionId, "inCall": 1, @@ -1548,7 +1550,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { }, MessageClientMessageData{ Type: "offer", RoomType: "video", - Payload: StringMap{ + Payload: api.StringMap{ "sdp": MockSdpOfferAudioAndVideo, }, })) diff --git a/mcu_proxy.go b/mcu_proxy.go index c19d3b7..ac8997d 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -45,6 +45,8 @@ import ( "github.com/dlintw/goconf" "github.com/golang-jwt/jwt/v5" "github.com/gorilla/websocket" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -101,7 +103,7 @@ func (c *mcuProxyPubSubCommon) MaxBitrate() int { return c.maxBitrate } -func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, StringMap)) { +func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, api.StringMap)) { c.conn.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { if err != nil { callback(err, nil) @@ -124,7 +126,7 @@ func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClie func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadProxyServerMessage) { switch msg.Type { case "offer": - offer, ok := ConvertStringMap(msg.Payload["offer"]) + offer, ok := api.ConvertStringMap(msg.Payload["offer"]) if !ok { log.Printf("Unsupported payload from %s: %+v", c.conn, msg) return @@ -201,7 +203,7 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { log.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) } -func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ @@ -301,7 +303,7 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { } } -func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ diff --git a/mcu_test.go b/mcu_test.go index 4352a61..62f6d2a 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -31,6 +31,8 @@ import ( "sync/atomic" "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -194,7 +196,7 @@ func (p *TestMCUPublisher) SetMedia(mt MediaType) { p.settings.MediaTypes = mt } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { go func() { if p.isClosed() { callback(fmt.Errorf("Already closed"), nil) @@ -208,13 +210,13 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClie p.sdp = sdp switch sdp { case MockSdpOfferAudioOnly: - callback(nil, StringMap{ + callback(nil, api.StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioOnly, }) return case MockSdpOfferAudioAndVideo: - callback(nil, StringMap{ + callback(nil, api.StringMap{ "type": "answer", "sdp": MockSdpAnswerAudioAndVideo, }) @@ -250,7 +252,7 @@ func (s *TestMCUSubscriber) Publisher() PublicSessionId { return s.publisher.PublisherId() } -func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, StringMap)) { +func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { go func() { if s.isClosed() { callback(fmt.Errorf("Already closed"), nil) @@ -267,7 +269,7 @@ func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageCli return } - callback(nil, StringMap{ + callback(nil, api.StringMap{ "type": "offer", "sdp": sdp, }) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 627a5ca..b7179c1 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -52,6 +52,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -1344,7 +1345,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s ctx2, cancel := context.WithTimeout(ctx, s.mcuTimeout) defer cancel() - mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response signaling.StringMap) { + mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response api.StringMap) { var responseMsg *signaling.ProxyServerMessage if errors.Is(err, signaling.ErrCandidateFiltered) { // Silently ignore filtered candidates. @@ -1586,8 +1587,8 @@ func (s *ProxyServer) GetClientId(client signaling.McuClient) string { return s.clientIds[client.Id()] } -func (s *ProxyServer) getStats() signaling.StringMap { - result := signaling.StringMap{ +func (s *ProxyServer) getStats() api.StringMap { + result := api.StringMap{ "sessions": s.GetSessionsCount(), "load": s.load.Load(), "mcu": s.mcu.GetStats(), diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 641d992..d4e0be5 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -43,7 +43,9 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -417,7 +419,7 @@ func (p *TestMCUPublisher) MaxBitrate() int { func (p *TestMCUPublisher) Close(ctx context.Context) { } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, signaling.StringMap)) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, api.StringMap)) { callback(errors.New("not implemented"), nil) } @@ -670,7 +672,7 @@ func (p *TestRemotePublisher) Close(ctx context.Context) { } } -func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, signaling.StringMap)) { +func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, api.StringMap)) { callback(errors.New("not implemented"), nil) } @@ -726,7 +728,7 @@ func (s *TestRemoteSubscriber) Close(ctx context.Context) { s.closeFunc() } -func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, signaling.StringMap)) { +func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, api.StringMap)) { callback(errors.New("not implemented"), nil) } diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 7479b7e..308708d 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -30,6 +30,7 @@ import ( "time" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -158,7 +159,7 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient { return prev } -func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer signaling.StringMap) { +func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer api.StringMap) { id := s.proxy.GetClientId(client) if id == "" { log.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client) @@ -170,7 +171,7 @@ func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer signaling Payload: &signaling.PayloadProxyServerMessage{ Type: "offer", ClientId: id, - Payload: signaling.StringMap{ + Payload: api.StringMap{ "offer": offer, }, }, @@ -190,7 +191,7 @@ func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate any) Payload: &signaling.PayloadProxyServerMessage{ Type: "candidate", ClientId: id, - Payload: signaling.StringMap{ + Payload: api.StringMap{ "candidate": candidate, }, }, diff --git a/room.go b/room.go index 8e533ec..7149f46 100644 --- a/room.go +++ b/room.go @@ -35,6 +35,8 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -81,7 +83,7 @@ type Room struct { statsRoomSessionsCurrent *prometheus.GaugeVec // Users currently in the room - users []StringMap + users []api.StringMap // Timestamps of last backend requests for the different types. lastRoomRequests map[string]int64 @@ -459,9 +461,9 @@ func (r *Room) RemoveSession(session Session) bool { if virtualSession, ok := session.(*VirtualSession); ok { delete(r.virtualSessions, virtualSession) // Handle case where virtual session was also sent by Nextcloud. - users := make([]StringMap, 0, len(r.users)) + users := make([]api.StringMap, 0, len(r.users)) for _, u := range r.users { - if value, found := GetStringMapString[PublicSessionId](u, "sessionId"); !found || value != sid { + if value, found := api.GetStringMapString[PublicSessionId](u, "sessionId"); !found || value != sid { users = append(users, u) } } @@ -628,7 +630,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSession return } -func (r *Room) addInternalSessions(users []StringMap) []StringMap { +func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap { now := time.Now().Unix() r.mu.RLock() defer r.mu.RUnlock() @@ -645,7 +647,7 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { skipSession := make(map[PublicSessionId]bool) for _, user := range users { - sessionid, found := GetStringMapString[PublicSessionId](user, "sessionId") + sessionid, found := api.GetStringMapString[PublicSessionId](user, "sessionId") if !found || sessionid == "" { continue } @@ -670,7 +672,7 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { } } for session := range r.internalSessions { - u := StringMap{ + u := api.StringMap{ "inCall": session.GetInCall(), "sessionId": session.PublicId(), "lastPing": now, @@ -682,7 +684,7 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { users = append(users, u) } for _, session := range clusteredInternalSessions { - u := StringMap{ + u := api.StringMap{ "inCall": session.GetInCall(), "sessionId": session.GetSessionId(), "lastPing": now, @@ -699,7 +701,7 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { continue } skipSession[sid] = true - users = append(users, StringMap{ + users = append(users, api.StringMap{ "inCall": session.GetInCall(), "sessionId": sid, "lastPing": now, @@ -711,7 +713,7 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { continue } - users = append(users, StringMap{ + users = append(users, api.StringMap{ "inCall": session.GetInCall(), "sessionId": sid, "lastPing": now, @@ -721,7 +723,7 @@ func (r *Room) addInternalSessions(users []StringMap) []StringMap { return users } -func (r *Room) filterPermissions(users []StringMap) []StringMap { +func (r *Room) filterPermissions(users []api.StringMap) []api.StringMap { for _, user := range users { delete(user, "permissions") } @@ -748,7 +750,7 @@ func IsInCall(value any) (bool, bool) { } } -func (r *Room) PublishUsersInCallChanged(changed []StringMap, users []StringMap) { +func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.StringMap) { r.users = users for _, user := range changed { inCallInterface, found := user["inCall"] @@ -760,9 +762,9 @@ func (r *Room) PublishUsersInCallChanged(changed []StringMap, users []StringMap) continue } - sessionId, found := GetStringMapString[PublicSessionId](user, "sessionId") + sessionId, found := api.GetStringMapString[PublicSessionId](user, "sessionId") if !found { - sessionId, found = GetStringMapString[PublicSessionId](user, "sessionid") + sessionId, found = api.GetStringMapString[PublicSessionId](user, "sessionid") if !found { continue } @@ -898,7 +900,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { } } -func (r *Room) PublishUsersChanged(changed []StringMap, users []StringMap) { +func (r *Room) PublishUsersChanged(changed []api.StringMap, users []api.StringMap) { changed = r.filterPermissions(changed) users = r.filterPermissions(users) @@ -919,7 +921,7 @@ func (r *Room) PublishUsersChanged(changed []StringMap, users []StringMap) { } } -func (r *Room) getParticipantsUpdateMessage(users []StringMap) *ServerMessage { +func (r *Room) getParticipantsUpdateMessage(users []api.StringMap) *ServerMessage { users = r.filterPermissions(users) message := &ServerMessage{ diff --git a/roomsessions_test.go b/roomsessions_test.go index 50c1f18..a83e518 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type DummySession struct { @@ -63,7 +65,7 @@ func (s *DummySession) UserData() json.RawMessage { return nil } -func (s *DummySession) ParsedUserData() (StringMap, error) { +func (s *DummySession) ParsedUserData() (api.StringMap, error) { return nil, nil } diff --git a/session.go b/session.go index 7cf40db..718b8ac 100644 --- a/session.go +++ b/session.go @@ -26,6 +26,8 @@ import ( "encoding/json" "net/url" "sync" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type Permission string @@ -56,7 +58,7 @@ type Session interface { UserId() string UserData() json.RawMessage - ParsedUserData() (StringMap, error) + ParsedUserData() (api.StringMap, error) Backend() *Backend BackendUrl() string @@ -74,13 +76,13 @@ type Session interface { SendMessage(message *ServerMessage) bool } -func parseUserData(data json.RawMessage) func() (StringMap, error) { - return sync.OnceValues(func() (StringMap, error) { +func parseUserData(data json.RawMessage) func() (api.StringMap, error) { + return sync.OnceValues(func() (api.StringMap, error) { if len(data) == 0 { return nil, nil } - var m StringMap + var m api.StringMap if err := json.Unmarshal(data, &m); err != nil { return nil, err } diff --git a/testclient_test.go b/testclient_test.go index ca31778..ec00fde 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -41,6 +41,8 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) var ( @@ -396,7 +398,7 @@ func (c *TestClient) SendHelloV2WithFeatures(userid string, features []string) e return c.SendHelloV2WithTimesAndFeatures(userid, now, now.Add(time.Minute), features) } -func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time.Time, expiresAt time.Time, userdata StringMap) (string, error) { +func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time.Time, expiresAt time.Time, userdata api.StringMap) (string, error) { data, err := json.Marshal(userdata) if err != nil { return "", err @@ -429,7 +431,7 @@ func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time } func (c *TestClient) CreateHelloV2Token(userid string, issuedAt time.Time, expiresAt time.Time) (string, error) { - userdata := StringMap{ + userdata := api.StringMap{ "displayname": "Displayname " + userid, } @@ -1013,24 +1015,24 @@ func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) bool { return false } - var data StringMap + var data api.StringMap if err := json.Unmarshal(message.Message.Data, &data); !c.assert.NoError(err) { return false } - if dt, ok := GetStringMapEntry[string](data, "type"); !c.assert.True(ok, "no/invalid type in %+v", data) || + if dt, ok := api.GetStringMapEntry[string](data, "type"); !c.assert.True(ok, "no/invalid type in %+v", data) || !c.assert.Equal("offer", dt, "invalid data type in %+v", data) { return false } - if payload, ok := ConvertStringMap(data["payload"]); !c.assert.True(ok, "not a string map, got %+v", data["payload"]) { + if payload, ok := api.ConvertStringMap(data["payload"]); !c.assert.True(ok, "not a string map, got %+v", data["payload"]) { return false } else { - if pt, ok := GetStringMapEntry[string](payload, "type"); !c.assert.True(ok, "no/invalid type in payload %+v", payload) || + if pt, ok := api.GetStringMapEntry[string](payload, "type"); !c.assert.True(ok, "no/invalid type in payload %+v", payload) || !c.assert.Equal("offer", pt, "invalid payload type in %+v", payload) { return false } - if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !c.assert.True(ok, "no/invalid sdp in payload %+v", payload) || + if sdp, ok := api.GetStringMapEntry[string](payload, "sdp"); !c.assert.True(ok, "no/invalid sdp in payload %+v", payload) || !c.assert.Equal(offer, sdp, "invalid payload offer") { return false } @@ -1058,24 +1060,24 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string } } - var data StringMap + var data api.StringMap if err := json.Unmarshal(message.Message.Data, &data); !c.assert.NoError(err) { return false } - if dt, ok := GetStringMapEntry[string](data, "type"); !c.assert.True(ok, "no/invalid type in %+v", data) || + if dt, ok := api.GetStringMapEntry[string](data, "type"); !c.assert.True(ok, "no/invalid type in %+v", data) || !c.assert.Equal("answer", dt, "invalid data type in %+v", data) { return false } - if payload, ok := ConvertStringMap(data["payload"]); !c.assert.True(ok, "not a string map, got %+v", data["payload"]) { + if payload, ok := api.ConvertStringMap(data["payload"]); !c.assert.True(ok, "not a string map, got %+v", data["payload"]) { return false } else { - if pt, ok := GetStringMapEntry[string](payload, "type"); !c.assert.True(ok, "no/invalid type in payload %+v", payload) || + if pt, ok := api.GetStringMapEntry[string](payload, "type"); !c.assert.True(ok, "no/invalid type in payload %+v", payload) || !c.assert.Equal("answer", pt, "invalid payload type in %+v", payload) { return false } - if sdp, ok := GetStringMapEntry[string](payload, "sdp"); !c.assert.True(ok, "no/invalid sdp in payload %+v", payload) || + if sdp, ok := api.GetStringMapEntry[string](payload, "sdp"); !c.assert.True(ok, "no/invalid sdp in payload %+v", payload) || !c.assert.Equal(answer, sdp, "invalid payload answer") { return false } @@ -1101,7 +1103,7 @@ func checkMessageTransientRemove(t *testing.T, message *ServerMessage, key strin assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value in %+v", message) } -func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data StringMap) bool { +func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data api.StringMap) bool { assert := assert.New(t) return checkMessageType(t, message, "transient") && assert.Equal("initial", message.TransientData.Type, "invalid message type in %+v", message) && diff --git a/transient_data.go b/transient_data.go index ac3ffb9..7e6f7d8 100644 --- a/transient_data.go +++ b/transient_data.go @@ -26,6 +26,8 @@ import ( "reflect" "sync" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type TransientListener interface { @@ -34,7 +36,7 @@ type TransientListener interface { type TransientData struct { mu sync.Mutex - data StringMap + data api.StringMap listeners map[TransientListener]bool timers map[string]*time.Timer ttlCh chan<- struct{} @@ -147,7 +149,7 @@ func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) func (t *TransientData) doSet(key string, value any, prev any, ttl time.Duration) { if t.data == nil { - t.data = make(StringMap) + t.data = make(api.StringMap) } t.data[key] = value t.notifySet(key, prev, value) @@ -252,11 +254,11 @@ func (t *TransientData) compareAndRemove(key string, old any) bool { } // GetData returns a copy of the internal data. -func (t *TransientData) GetData() StringMap { +func (t *TransientData) GetData() api.StringMap { t.mu.Lock() defer t.mu.Unlock() - result := make(StringMap) + result := make(api.StringMap) maps.Copy(result, t.data) return result } diff --git a/transient_data_test.go b/transient_data_test.go index b60aecb..feea5fb 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func (t *TransientData) SetTTLChannel(ch chan<- struct{}) { @@ -247,7 +249,7 @@ func Test_TransientMessages(t *testing.T) { require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } - checkMessageTransientInitial(t, msg, StringMap{ + checkMessageTransientInitial(t, msg, api.StringMap{ "abc": data, }) diff --git a/virtualsession.go b/virtualsession.go index fb2be3c..4ec31cf 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -27,6 +27,8 @@ import ( "log" "net/url" "sync/atomic" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -50,7 +52,7 @@ type VirtualSession struct { flags Flags options *AddSessionOptions - parseUserData func() (StringMap, error) + parseUserData func() (api.StringMap, error) } func GetVirtualSessionId(session Session, sessionId PublicSessionId) PublicSessionId { @@ -144,7 +146,7 @@ func (s *VirtualSession) UserData() json.RawMessage { return s.userData } -func (s *VirtualSession) ParsedUserData() (StringMap, error) { +func (s *VirtualSession) ParsedUserData() (api.StringMap, error) { return s.parseUserData() } @@ -290,7 +292,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { message.Message.Event.Disinvite != nil && message.Message.Event.Disinvite.RoomId == room.Id() { log.Printf("Virtual session %s was disinvited from room %s, hanging up", s.PublicId(), room.Id()) - payload := StringMap{ + payload := api.StringMap{ "type": "hangup", "hangup": map[string]string{ "reason": "disinvited", diff --git a/virtualsession_test.go b/virtualsession_test.go index 79477eb..2f741df 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func TestVirtualSession(t *testing.T) { @@ -407,13 +409,13 @@ func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sess assert := assert.New(t) found := false for _, entry := range message.Users { - if sid, ok := GetStringMapString[PublicSessionId](entry, "sessionId"); ok && sid == sessionId { - if value, found := GetStringMapEntry[bool](entry, entryType); !assert.True(found, "entry %s not found or invalid in %+v", entryType, entry) || + if sid, ok := api.GetStringMapString[PublicSessionId](entry, "sessionId"); ok && sid == sessionId { + if value, found := api.GetStringMapEntry[bool](entry, entryType); !assert.True(found, "entry %s not found or invalid in %+v", entryType, entry) || !assert.True(value, "entry %s invalid in %+v", entryType, entry) { return false } - if value, found := GetStringMapEntry[float64](entry, "inCall"); !assert.True(found, "inCall not found or invalid in %+v", entry) || + if value, found := api.GetStringMapEntry[float64](entry, "inCall"); !assert.True(found, "inCall not found or invalid in %+v", entry) || !assert.EqualValues(value, inCall, "invalid inCall") { return false } From 12c16bce614ebeab4fe4a443551d8f90b60e6d40 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 26 Sep 2025 13:51:44 +0200 Subject: [PATCH 212/549] make: Always test all packages. --- Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 80ddc34..b5843dd 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ VENDORDIR := "$(CURDIR)/vendor" VERSION := $(shell "$(CURDIR)/scripts/get-version.sh") TARVERSION := $(shell "$(CURDIR)/scripts/get-version.sh" --tar) PACKAGENAME := github.com/strukturag/nextcloud-spreed-signaling -ALL_PACKAGES := $(PACKAGENAME) $(PACKAGENAME)/client $(PACKAGENAME)/proxy $(PACKAGENAME)/server GRPC_PROTO_FILES := $(basename $(wildcard grpc_*.proto)) PROTOBUF_VERSION := $(shell grep google.golang.org/protobuf go.mod | xargs | cut -d ' ' -f 2) PROTO_FILES := $(filter-out $(GRPC_PROTO_FILES),$(basename $(wildcard *.proto))) @@ -104,18 +103,18 @@ fmt: hook | $(PROTO_GO_FILES) $(GOFMT) -s -w *.go client proxy server vet: - GOEXPERIMENT=synctest $(GO) vet $(ALL_PACKAGES) + GOEXPERIMENT=synctest $(GO) vet ./... test: vet - GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) $(TESTARGS) $(ALL_PACKAGES) + GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) $(TESTARGS) ./... cover: vet rm -f cover.out && \ - GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) + GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... coverhtml: vet rm -f cover.out && \ - GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) && \ + GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... && \ sed -i "/_easyjson/d" cover.out && \ sed -i "/\.pb\.go/d" cover.out && \ $(GO) tool cover -html=cover.out -o coverage.html From c06fe02c61e482bafa6ce8bc48b160d27fc6950c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 26 Sep 2025 21:10:51 +0200 Subject: [PATCH 213/549] CI: Disable "stdversion" check of govet. Using "synctest.Wait" is fine as we set GOEXPERIMENT=synctest when running affected code. --- .golangci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 20ae68c..68a75bd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,6 +3,9 @@ linters: enable: - revive settings: + govet: + disable: + - stdversion revive: severity: warning rules: From 40bd1d71ce975412070d14e1604e61bc9a96ee45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 19:15:33 +0000 Subject: [PATCH 214/549] Bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.45.0 to 1.46.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.45.0...v1.46.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8dc7cbe..70e1a12 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.11.9 - github.com/nats-io/nats.go v1.45.0 + github.com/nats-io/nats.go v1.46.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 diff --git a/go.sum b/go.sum index 3ae9e49..fd9d683 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.11.9 h1:k7nzHZjUf51W1b08xiQih63Rdxh0yr5O4K892Mx5gQA= github.com/nats-io/nats-server/v2 v2.11.9/go.mod h1:1MQgsAQX1tVjpf3Yzrk3x2pzdsZiNL/TVP3Amhp3CR8= -github.com/nats-io/nats.go v1.45.0 h1:/wGPbnYXDM0pLKFjZTX+2JOw9TQPoIgTFrUaH97giwA= -github.com/nats-io/nats.go v1.45.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8= +github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From a068a4952763f47f7e62841af32ef763de7383f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 19:24:47 +0000 Subject: [PATCH 215/549] Bump github.com/nats-io/nats-server/v2 from 2.11.9 to 2.12.0 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.9 to 2.12.0. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.9...v2.12.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 70e1a12..4d6d6e3 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 - github.com/nats-io/nats-server/v2 v2.11.9 + github.com/nats-io/nats-server/v2 v2.12.0 github.com/nats-io/nats.go v1.46.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -55,7 +55,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/jwt/v2 v2.7.4 // indirect + github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pion/dtls/v3 v3.0.6 // indirect @@ -89,10 +89,10 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.41.0 // indirect + golang.org/x/crypto v0.42.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.13.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect diff --git a/go.sum b/go.sum index fd9d683..3979437 100644 --- a/go.sum +++ b/go.sum @@ -76,10 +76,10 @@ github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= -github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.11.9 h1:k7nzHZjUf51W1b08xiQih63Rdxh0yr5O4K892Mx5gQA= -github.com/nats-io/nats-server/v2 v2.11.9/go.mod h1:1MQgsAQX1tVjpf3Yzrk3x2pzdsZiNL/TVP3Amhp3CR8= +github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= +github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= +github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74= +github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww= github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8= github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -186,8 +186,8 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -200,8 +200,8 @@ golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -211,8 +211,8 @@ golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 89522b05cfa484e7c99f6d13e979c59c761dc03e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Sat, 27 Sep 2025 20:12:39 +0200 Subject: [PATCH 216/549] CI: Use codecov components. --- .codecov.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index b1e33c6..cbb4046 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -5,9 +5,28 @@ coverage: threshold: 2% comment: - layout: "header, diff, flags, files" + layout: "header, diff, flags, components, files" after_n_builds: 2 ignore: - "*_easyjson.go" - "*.pb.go" + +component_management: + individual_components: + - component_id: module_api + name: api + paths: + - api/** + - component_id: module_client + name: client + paths: + - client/** + - component_id: module_proxy + name: proxy + paths: + - proxy/** + - component_id: module_server + name: server + paths: + - server/** From 70e01b4816fecb960ddcade78e1c7b13d4b22a92 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 14:10:42 +0200 Subject: [PATCH 217/549] Fix URL to send federated ping requests. --- hub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub.go b/hub.go index 97f8de9..6c93b13 100644 --- a/hub.go +++ b/hub.go @@ -1852,7 +1852,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { e, found := entries[u] if !found { - p := session.ParsedBackendUrl() + p := session.ParsedBackendOcsUrl() if p == nil { // Should not happen, invalid URLs should get rejected earlier. continue From 105518b1eea37c743c690b2d895fb9b32ef47b7b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 14:11:03 +0200 Subject: [PATCH 218/549] Test that backend URLs are containing the full OCS path. --- hub_test.go | 2 ++ room_ping_test.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/hub_test.go b/hub_test.go index 604844d..c6f7425 100644 --- a/hub_test.go +++ b/hub_test.go @@ -670,6 +670,8 @@ func registerBackendHandler(t *testing.T, router *mux.Router) { func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { handleFunc := validateBackendChecksum(t, func(w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { + assert.Regexp(t, "/ocs/v2\\.php/apps/spreed/api/v[\\d]/signaling/backend$", r.URL.Path, "invalid url for backend request %+v", request) + switch request.Type { case "auth": return processAuthRequest(t, w, r, request) diff --git a/room_ping_test.go b/room_ping_test.go index 0bc775d..58ce74d 100644 --- a/room_ping_test.go +++ b/room_ping_test.go @@ -51,7 +51,7 @@ func NewRoomPingForTest(t *testing.T) (*url.URL, *RoomPing) { p, err := NewRoomPing(backend, backend.capabilities) require.NoError(err) - u, err := url.Parse(server.URL) + u, err := url.Parse(server.URL + "/" + PathToOcsSignalingBackend) require.NoError(err) return u, p From 9a992f365b0858809d4129e26b8c87d79aaf4025 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 19 Nov 2024 12:30:07 +0100 Subject: [PATCH 219/549] Document chat relay. --- docs/standalone-signaling-api-v1.md | 66 +++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 1f903bb..c080194 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -173,6 +173,15 @@ future version. Clients should use the data from the [`welcome` message](#welcome-message) instead. +## Client features + +The following client feature flags are currently supported: +- `chat-relay`: The client understands chat message events containing the actual + message payload. +- `internal-incall`: The (internal) client can set the `inCall` flag. +- `start-dialout`: The (internal) client can start outgoing phone calls. + + ### Protocol version "1.0" For protocol version `1.0` in the `hello` request, the `params` from the `auth` @@ -804,10 +813,9 @@ Message format (Server -> Client, incall change): ## Room messages The server can notify clients about events that happened in a room. Currently -such messages are only sent out when chat messages are posted to notify clients -they should load the new messages. +such messages are only sent out when chat messages are posted. -Message format (Server -> Client, chat messages available): +Message format (Server -> Client, new messages available, refresh chat): { "type": "event" @@ -827,6 +835,28 @@ Message format (Server -> Client, chat messages available): } +Message format (Server -> Client, new message was posted): + + { + "type": "event" + "event": { + "target": "room", + "type": "message", + "message": { + "roomid": "the-room-id", + "data": { + "type": "chat", + "chat": { + "comment": { + ...properties of the chat message... + } + } + } + } + } + } + + ## Sending messages between clients Messages between clients are sent realtime and not stored by the server, i.e. @@ -1778,6 +1808,36 @@ Message format (Backend -> Server) } +#### Send chat room message + +Usually, clients do a regular poll against Talk to fetch chat messages. In order +to reduce the load, the clients can be notified about new messages (which then +causes them to fetch them), or the messages can be sent directly to them. + +Depending on the client feature `chat-relay`, clients will either get the +event with `"refresh": true`, or they get the full chat `comment` object. This +is available if the signaling server supports the feature flag `chat-relay`. +If not, the full event will be sent to the clients (containing both `refresh` +and `comment`). + +Message format (Backend -> Server) + + { + "type": "message" + "message" { + "data": { + "type": "chat", + "chat": { + "refresh": true, + "comment": { + ...properties of the comment written in the chat... + } + } + } + } + } + + ### Notify sessions to switch to a different room This can be used to let sessions in a room know that they switch to a different From b6e25424a3502782575949347ec6c001d4f0a602 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 19 Nov 2024 12:34:35 +0100 Subject: [PATCH 220/549] Implement relaying of chat messages. Also fix combining chat refresh messages which was checking the wrong message type. --- api_signaling.go | 60 ++++++++++++++++++----------- api_signaling_test.go | 20 +++++++--- clientsession.go | 38 ++++++++++++++++--- clientsession_test.go | 87 +++++++++++++++++++++++++++++++++++++++++++ hub_test.go | 76 +++++++++++++++++++++++++++++++++++-- 5 files changed, 244 insertions(+), 37 deletions(-) diff --git a/api_signaling.go b/api_signaling.go index 75b81e6..08a7ef6 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -228,12 +228,13 @@ func (r *ServerMessage) CloseAfterSend(session Session) bool { } func (r *ServerMessage) IsChatRefresh() bool { - if r.Type != "message" || r.Message == nil || len(r.Message.Data) == 0 { + if r.Type != "event" || r.Event == nil || + r.Event.Type != "message" || r.Event.Message == nil || len(r.Event.Message.Data) == 0 { return false } - var data MessageServerMessageData - if err := json.Unmarshal(r.Message.Data, &data); err != nil { + data, err := r.Event.Message.GetData() + if data == nil || err != nil { return false } @@ -556,11 +557,13 @@ const ( ServerFeatureJoinFeatures = "join-features" ServerFeatureOfferCodecs = "offer-codecs" ServerFeatureServerInfo = "serverinfo" + ServerFeatureChatRelay = "chat-relay" // Features to send to internal clients only. ServerFeatureInternalVirtualSessions = "virtual-sessions" // Possible client features from the "hello" request. + ClientFeatureChatRelay = "chat-relay" ClientFeatureInternalInCall = "internal-incall" ClientFeatureStartDialout = "start-dialout" ) @@ -579,6 +582,7 @@ var ( ServerFeatureJoinFeatures, ServerFeatureOfferCodecs, ServerFeatureServerInfo, + ServerFeatureChatRelay, } DefaultFeaturesInternal = []string{ ServerFeatureInternalVirtualSessions, @@ -593,6 +597,7 @@ var ( ServerFeatureJoinFeatures, ServerFeatureOfferCodecs, ServerFeatureServerInfo, + ServerFeatureChatRelay, } DefaultWelcomeFeatures = []string{ ServerFeatureAudioVideoPermissions, @@ -608,6 +613,7 @@ var ( ServerFeatureJoinFeatures, ServerFeatureOfferCodecs, ServerFeatureServerInfo, + ServerFeatureChatRelay, } ) @@ -914,14 +920,8 @@ type MessageServerMessageSender struct { UserId string `json:"userid,omitempty"` } -type MessageServerMessageDataChat struct { - Refresh bool `json:"refresh"` -} - type MessageServerMessageData struct { Type string `json:"type"` - - Chat *MessageServerMessageDataChat `json:"chat,omitempty"` } type MessageServerMessage struct { @@ -1158,21 +1158,14 @@ type RoomDisinviteEventServerMessage struct { Reason string `json:"reason"` } -type RoomEventMessage struct { - RoomId string `json:"roomid"` - Data json.RawMessage `json:"data,omitempty"` -} - -type RoomFlagsServerMessage struct { - RoomId string `json:"roomid"` - SessionId PublicSessionId `json:"sessionid"` - Flags uint32 `json:"flags"` -} - type ChatComment api.StringMap type RoomEventMessageDataChat struct { - Comment *ChatComment `json:"comment,omitempty"` + // Refresh will be included if the client does not support the "chat-relay" feature. + Refresh bool `json:"refresh,omitempty"` + + // Comment will be included if the client supports the "chat-relay" feature. + Comment json.RawMessage `json:"comment,omitempty"` } type RoomEventMessageData struct { @@ -1181,6 +1174,31 @@ type RoomEventMessageData struct { Chat *RoomEventMessageDataChat `json:"chat,omitempty"` } +type RoomEventMessage struct { + RoomId string `json:"roomid"` + Data json.RawMessage `json:"data,omitempty"` +} + +func (m *RoomEventMessage) GetData() (*RoomEventMessageData, error) { + if len(m.Data) == 0 { + return nil, nil + } + + // TODO: Cache parsed result. + var data RoomEventMessageData + if err := json.Unmarshal(m.Data, &data); err != nil { + return nil, err + } + + return &data, nil +} + +type RoomFlagsServerMessage struct { + RoomId string `json:"roomid"` + SessionId PublicSessionId `json:"sessionid"` + Flags uint32 `json:"flags"` +} + type EventServerMessage struct { Target string `json:"target"` Type string `json:"type"` diff --git a/api_signaling_test.go b/api_signaling_test.go index b384a7a..d711860 100644 --- a/api_signaling_test.go +++ b/api_signaling_test.go @@ -376,18 +376,26 @@ func TestIsChatRefresh(t *testing.T) { var msg ServerMessage data_true := []byte("{\"type\":\"chat\",\"chat\":{\"refresh\":true}}") msg = ServerMessage{ - Type: "message", - Message: &MessageServerMessage{ - Data: data_true, + Type: "event", + Event: &EventServerMessage{ + Type: "message", + Message: &RoomEventMessage{ + RoomId: "foo", + Data: data_true, + }, }, } assert.True(t, msg.IsChatRefresh()) data_false := []byte("{\"type\":\"chat\",\"chat\":{\"refresh\":false}}") msg = ServerMessage{ - Type: "message", - Message: &MessageServerMessage{ - Data: data_false, + Type: "event", + Event: &EventServerMessage{ + Type: "message", + Message: &RoomEventMessage{ + RoomId: "foo", + Data: data_false, + }, }, } assert.False(t, msg.IsChatRefresh()) diff --git a/clientsession.go b/clientsession.go index 0f5f420..894dd74 100644 --- a/clientsession.go +++ b/clientsession.go @@ -1268,18 +1268,44 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { delete(s.seenJoinedEvents, e) } case "message": - if message.Event.Message == nil || len(message.Event.Message.Data) == 0 || !s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { + if message.Event.Message == nil || len(message.Event.Message.Data) == 0 { return message } - var data RoomEventMessageData - if err := json.Unmarshal(message.Event.Message.Data, &data); err != nil { + data, err := message.Event.Message.GetData() + if data == nil || err != nil { return message } - if data.Type == "chat" && data.Chat != nil && data.Chat.Comment != nil { - if displayName, found := (*data.Chat.Comment)["actorDisplayName"]; found && displayName != "" { - (*data.Chat.Comment)["actorDisplayName"] = "" + if data.Type == "chat" && data.Chat != nil { + update := false + if data.Chat.Refresh && len(data.Chat.Comment) > 0 { + // New-style chat event, check what the client supports. + if s.HasFeature(ClientFeatureChatRelay) { + data.Chat.Refresh = false + } else { + data.Chat.Comment = nil + } + update = true + } + + if len(data.Chat.Comment) > 0 && s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { + var comment ChatComment + if err := json.Unmarshal(data.Chat.Comment, &comment); err != nil { + return message + } + + if displayName, found := comment["actorDisplayName"]; found && displayName != "" { + comment["actorDisplayName"] = "" + var err error + if data.Chat.Comment, err = json.Marshal(comment); err != nil { + return message + } + update = true + } + } + + if update { if encoded, err := json.Marshal(data); err == nil { // Create unique copy of message for only this client. message = &ServerMessage{ diff --git a/clientsession_test.go b/clientsession_test.go index ea10196..514eaf7 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -23,6 +23,7 @@ package signaling import ( "context" + "encoding/json" "net/url" "testing" @@ -161,3 +162,89 @@ func TestBandwidth_Backend(t *testing.T) { }) } } + +func TestFeatureChatRelay(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + + testFunc := func(feature bool) func(t *testing.T) { + return func(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + + client := NewTestClient(t, server, hub) + defer client.CloseWithBye() + var features []string + if feature { + features = append(features, ClientFeatureChatRelay) + } + require.NoError(client.SendHelloClientWithFeatures(testDefaultUserId, features)) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello := MustSucceed1(t, client.RunUntilHello, ctx) + + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client.RunUntilJoined(ctx, hello.Hello) + + room := hub.getRoom(roomId) + require.NotNil(room) + + chatComment := api.StringMap{ + "foo": "bar", + "baz": true, + "lala": map[string]any{ + "one": "eins", + }, + } + message := api.StringMap{ + "type": "chat", + "chat": api.StringMap{ + "refresh": true, + "comment": chatComment, + }, + } + data, err := json.Marshal(message) + require.NoError(err) + + // Simulate request from the backend. + room.ProcessBackendRoomRequest(&AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "message", + Message: &BackendRoomMessageRequest{ + Data: data, + }, + }, + }) + + if msg, ok := client.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + if feature { + assert.EqualValues(chatComment, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } else { + assert.Equal(true, chat["refresh"]) + _, found := chat["comment"] + assert.False(found, "the comment should not be included") + } + } + } + } + } + } + + t.Run("without-chat-relay", testFunc(false)) + t.Run("with-chat-relay", testFunc(true)) +} diff --git a/hub_test.go b/hub_test.go index c6f7425..591bf6b 100644 --- a/hub_test.go +++ b/hub_test.go @@ -3148,12 +3148,10 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { SessionId: hello2.Hello.SessionId, } - // The two chat messages should get combined into one when receiving pending messages. - chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" + chat_refresh := "{\"type\":\"foo\",\"foo\":{\"testing\":true}}" var data1 api.StringMap require.NoError(json.Unmarshal([]byte(chat_refresh), &data1)) client1.SendMessage(recipient2, data1) // nolint - client1.SendMessage(recipient2, data1) // nolint // Simulate some time until client resumes the session. time.Sleep(10 * time.Millisecond) @@ -3171,11 +3169,81 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { if checkReceiveClientMessage(ctx, t, client2, "session", hello1.Hello, &payload) { assert.Equal(data1, payload) } +} + +func TestCombineChatRefreshWhileDisconnected(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client.RunUntilJoined(ctx, hello.Hello) + + room := hub.getRoom(roomId) + require.NotNil(room) + + client.Close() + assert.NoError(client.WaitForClientRemoved(ctx)) + + // The two chat messages should get combined into one when receiving pending messages. + chat_refresh := "{\"type\":\"chat\",\"chat\":{\"refresh\":true}}" + var data api.StringMap + require.NoError(json.Unmarshal([]byte(chat_refresh), &data)) + + // Simulate requests from the backend. + room.ProcessBackendRoomRequest(&AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "message", + Message: &BackendRoomMessageRequest{ + Data: json.RawMessage(chat_refresh), + }, + }, + }) + room.ProcessBackendRoomRequest(&AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "message", + Message: &BackendRoomMessageRequest{ + Data: json.RawMessage(chat_refresh), + }, + }, + }) + + // Simulate some time until client resumes the session. + time.Sleep(10 * time.Millisecond) + + client = NewTestClient(t, server, hub) + defer client.CloseWithBye() + require.NoError(client.SendHelloResume(hello.Hello.ResumeId)) + if hello2, ok := client.RunUntilHello(ctx); ok { + assert.Equal(hello.Hello.UserId, hello2.Hello.UserId) + assert.Equal(hello.Hello.SessionId, hello2.Hello.SessionId) + assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId) + } + + if msg, ok := client.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var payload api.StringMap + if err := json.Unmarshal(msg.Data, &payload); assert.NoError(err) { + assert.Equal(data, payload) + } + } ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) + client.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) } func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { From 9d4702207526e790bbfa03842c3f7d436c252da2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 19 Nov 2024 12:36:18 +0100 Subject: [PATCH 221/549] Update generated files. --- api_signaling_easyjson.go | 806 ++++++++++++++++---------------------- 1 file changed, 345 insertions(+), 461 deletions(-) diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 9f0f67b..7dbc9f0 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -1363,38 +1363,18 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex key := in.UnsafeFieldName(false) in.WantColon() switch key { + case "refresh": + if in.IsNull() { + in.Skip() + } else { + out.Refresh = bool(in.Bool()) + } case "comment": if in.IsNull() { in.Skip() - out.Comment = nil } else { - if out.Comment == nil { - out.Comment = new(ChatComment) - } - if in.IsNull() { - in.Skip() - } else { - in.Delim('{') - if !in.IsDelim('}') { - *out.Comment = make(ChatComment) - } else { - *out.Comment = nil - } - for !in.IsDelim('}') { - key := string(in.String()) - in.WantColon() - var v16 interface{} - if m, ok := v16.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := v16.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - v16 = in.Interface() - } - (*out.Comment)[key] = v16 - in.WantComma() - } - in.Delim('}') + if data := in.Raw(); in.Ok() { + in.AddError((out.Comment).UnmarshalJSON(data)) } } default: @@ -1411,33 +1391,21 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr out.RawByte('{') first := true _ = first - if in.Comment != nil { - const prefix string = ",\"comment\":" + if in.Refresh { + const prefix string = ",\"refresh\":" first = false out.RawString(prefix[1:]) - if *in.Comment == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { - out.RawString(`null`) + out.Bool(bool(in.Refresh)) + } + if len(in.Comment) != 0 { + const prefix string = ",\"comment\":" + if first { + first = false + out.RawString(prefix[1:]) } else { - out.RawByte('{') - v17First := true - for v17Name, v17Value := range *in.Comment { - if v17First { - v17First = false - } else { - out.RawByte(',') - } - out.String(string(v17Name)) - out.RawByte(':') - if m, ok := v17Value.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := v17Value.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(v17Value)) - } - } - out.RawByte('}') + out.RawString(prefix) } + out.Raw((in.Comment).MarshalJSON()) } out.RawByte('}') } @@ -1761,6 +1729,53 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle } else { out.Changed = (out.Changed)[:0] } + for !in.IsDelim(']') { + var v16 api.StringMap + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + v16 = make(api.StringMap) + } else { + v16 = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v17 interface{} + if m, ok := v17.(easyjson.Unmarshaler); ok { + m.UnmarshalEasyJSON(in) + } else if m, ok := v17.(json.Unmarshaler); ok { + _ = m.UnmarshalJSON(in.Raw()) + } else { + v17 = in.Interface() + } + (v16)[key] = v17 + in.WantComma() + } + in.Delim('}') + } + out.Changed = append(out.Changed, v16) + in.WantComma() + } + in.Delim(']') + } + case "users": + if in.IsNull() { + in.Skip() + out.Users = nil + } else { + in.Delim('[') + if out.Users == nil { + if !in.IsDelim(']') { + out.Users = make([]api.StringMap, 0, 8) + } else { + out.Users = []api.StringMap{} + } + } else { + out.Users = (out.Users)[:0] + } for !in.IsDelim(']') { var v18 api.StringMap if in.IsNull() { @@ -1788,54 +1803,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle } in.Delim('}') } - out.Changed = append(out.Changed, v18) - in.WantComma() - } - in.Delim(']') - } - case "users": - if in.IsNull() { - in.Skip() - out.Users = nil - } else { - in.Delim('[') - if out.Users == nil { - if !in.IsDelim(']') { - out.Users = make([]api.StringMap, 0, 8) - } else { - out.Users = []api.StringMap{} - } - } else { - out.Users = (out.Users)[:0] - } - for !in.IsDelim(']') { - var v20 api.StringMap - if in.IsNull() { - in.Skip() - } else { - in.Delim('{') - if !in.IsDelim('}') { - v20 = make(api.StringMap) - } else { - v20 = nil - } - for !in.IsDelim('}') { - key := string(in.String()) - in.WantColon() - var v21 interface{} - if m, ok := v21.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := v21.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - v21 = in.Interface() - } - (v20)[key] = v21 - in.WantComma() - } - in.Delim('}') - } - out.Users = append(out.Users, v20) + out.Users = append(out.Users, v18) in.WantComma() } in.Delim(']') @@ -1885,29 +1853,29 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw out.RawString(prefix) { out.RawByte('[') - for v22, v23 := range in.Changed { - if v22 > 0 { + for v20, v21 := range in.Changed { + if v20 > 0 { out.RawByte(',') } - if v23 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + if v21 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { out.RawString(`null`) } else { out.RawByte('{') - v24First := true - for v24Name, v24Value := range v23 { - if v24First { - v24First = false + v22First := true + for v22Name, v22Value := range v21 { + if v22First { + v22First = false } else { out.RawByte(',') } - out.String(string(v24Name)) + out.String(string(v22Name)) out.RawByte(':') - if m, ok := v24Value.(easyjson.Marshaler); ok { + if m, ok := v22Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v24Value.(json.Marshaler); ok { + } else if m, ok := v22Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v24Value)) + out.Raw(json.Marshal(v22Value)) } } out.RawByte('}') @@ -1921,29 +1889,29 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw out.RawString(prefix) { out.RawByte('[') - for v25, v26 := range in.Users { - if v25 > 0 { + for v23, v24 := range in.Users { + if v23 > 0 { out.RawByte(',') } - if v26 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + if v24 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { out.RawString(`null`) } else { out.RawByte('{') - v27First := true - for v27Name, v27Value := range v26 { - if v27First { - v27First = false + v25First := true + for v25Name, v25Value := range v24 { + if v25First { + v25First = false } else { out.RawByte(',') } - out.String(string(v27Name)) + out.String(string(v25Name)) out.RawByte(':') - if m, ok := v27Value.(easyjson.Marshaler); ok { + if m, ok := v25Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v27Value.(json.Marshaler); ok { + } else if m, ok := v25Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v27Value)) + out.Raw(json.Marshal(v25Value)) } } out.RawByte('}') @@ -2258,72 +2226,7 @@ func (v *MessageServerMessageSender) UnmarshalJSON(data []byte) error { func (v *MessageServerMessageSender) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *MessageServerMessageDataChat) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "refresh": - if in.IsNull() { - in.Skip() - } else { - out.Refresh = bool(in.Bool()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in MessageServerMessageDataChat) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"refresh\":" - out.RawString(prefix[1:]) - out.Bool(bool(in.Refresh)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v MessageServerMessageDataChat) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v MessageServerMessageDataChat) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *MessageServerMessageDataChat) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *MessageServerMessageDataChat) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) -} -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *MessageServerMessageData) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *MessageServerMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2343,20 +2246,6 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle } else { out.Type = string(in.String()) } - case "chat": - if in.IsNull() { - in.Skip() - out.Chat = nil - } else { - if out.Chat == nil { - out.Chat = new(MessageServerMessageDataChat) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Chat).UnmarshalEasyJSON(in) - } - } default: in.SkipRecursive() } @@ -2367,7 +2256,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in MessageServerMessageData) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in MessageServerMessageData) { out.RawByte('{') first := true _ = first @@ -2376,38 +2265,33 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw out.RawString(prefix[1:]) out.String(string(in.Type)) } - if in.Chat != nil { - const prefix string = ",\"chat\":" - out.RawString(prefix) - (*in.Chat).MarshalEasyJSON(out) - } out.RawByte('}') } // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *MessageServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *MessageServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2467,7 +2351,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in MessageServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in MessageServerMessage) { out.RawByte('{') first := true _ = first @@ -2496,27 +2380,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *MessageClientMessageRecipient) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *MessageClientMessageRecipient) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2558,7 +2442,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in MessageClientMessageRecipient) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in MessageClientMessageRecipient) { out.RawByte('{') first := true _ = first @@ -2583,27 +2467,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageRecipient) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageRecipient) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *MessageClientMessageData) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *MessageClientMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2644,15 +2528,15 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v28 interface{} - if m, ok := v28.(easyjson.Unmarshaler); ok { + var v26 interface{} + if m, ok := v26.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v28.(json.Unmarshaler); ok { + } else if m, ok := v26.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v28 = in.Interface() + v26 = in.Interface() } - (out.Payload)[key] = v28 + (out.Payload)[key] = v26 in.WantComma() } in.Delim('}') @@ -2697,7 +2581,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in MessageClientMessageData) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in MessageClientMessageData) { out.RawByte('{') first := true _ = first @@ -2723,21 +2607,21 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw out.RawString(`null`) } else { out.RawByte('{') - v29First := true - for v29Name, v29Value := range in.Payload { - if v29First { - v29First = false + v27First := true + for v27Name, v27Value := range in.Payload { + if v27First { + v27First = false } else { out.RawByte(',') } - out.String(string(v29Name)) + out.String(string(v27Name)) out.RawByte(':') - if m, ok := v29Value.(easyjson.Marshaler); ok { + if m, ok := v27Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v29Value.(json.Marshaler); ok { + } else if m, ok := v27Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v29Value)) + out.Raw(json.Marshal(v27Value)) } } out.RawByte('}') @@ -2774,27 +2658,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *MessageClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *MessageClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2832,7 +2716,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in MessageClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in MessageClientMessage) { out.RawByte('{') first := true _ = first @@ -2852,27 +2736,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *InternalServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *InternalServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2916,7 +2800,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in InternalServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in InternalServerMessage) { out.RawByte('{') first := true _ = first @@ -2936,27 +2820,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *InternalServerDialoutRequest) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *InternalServerDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3006,7 +2890,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in InternalServerDialoutRequest) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in InternalServerDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3035,27 +2919,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalServerDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *InternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *InternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3155,7 +3039,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in InternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in InternalClientMessage) { out.RawByte('{') first := true _ = first @@ -3195,27 +3079,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *InCallInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *InCallInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3245,7 +3129,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in InCallInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in InCallInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -3260,27 +3144,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v InCallInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InCallInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *HelloV2TokenClaims) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *HelloV2TokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3386,7 +3270,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in HelloV2TokenClaims) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in HelloV2TokenClaims) { out.RawByte('{') first := true _ = first @@ -3472,27 +3356,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloV2TokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2TokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *HelloV2AuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *HelloV2AuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3522,7 +3406,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in HelloV2AuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in HelloV2AuthParams) { out.RawByte('{') first := true _ = first @@ -3537,27 +3421,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloV2AuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2AuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *HelloServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *HelloServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3619,7 +3503,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in HelloServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in HelloServerMessage) { out.RawByte('{') first := true _ = first @@ -3654,27 +3538,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *HelloClientMessageAuth) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *HelloClientMessageAuth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3718,7 +3602,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in HelloClientMessageAuth) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in HelloClientMessageAuth) { out.RawByte('{') first := true _ = first @@ -3749,27 +3633,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloClientMessageAuth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessageAuth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *HelloClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *HelloClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3811,13 +3695,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle out.Features = (out.Features)[:0] } for !in.IsDelim(']') { - var v30 string + var v28 string if in.IsNull() { in.Skip() } else { - v30 = string(in.String()) + v28 = string(in.String()) } - out.Features = append(out.Features, v30) + out.Features = append(out.Features, v28) in.WantComma() } in.Delim(']') @@ -3846,7 +3730,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in HelloClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in HelloClientMessage) { out.RawByte('{') first := true _ = first @@ -3865,11 +3749,11 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw out.RawString(prefix) { out.RawByte('[') - for v31, v32 := range in.Features { - if v31 > 0 { + for v29, v30 := range in.Features { + if v29 > 0 { out.RawByte(',') } - out.String(string(v32)) + out.String(string(v30)) } out.RawByte(']') } @@ -3885,27 +3769,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *FederationTokenClaims) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *FederationTokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4011,7 +3895,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in FederationTokenClaims) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in FederationTokenClaims) { out.RawByte('{') first := true _ = first @@ -4097,27 +3981,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v FederationTokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationTokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *FederationAuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *FederationAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4147,7 +4031,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in FederationAuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in FederationAuthParams) { out.RawByte('{') first := true _ = first @@ -4162,27 +4046,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v FederationAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4220,7 +4104,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in EventServerMessageSwitchTo) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in EventServerMessageSwitchTo) { out.RawByte('{') first := true _ = first @@ -4240,27 +4124,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSwitchTo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSwitchTo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4302,13 +4186,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle out.Features = (out.Features)[:0] } for !in.IsDelim(']') { - var v33 string + var v31 string if in.IsNull() { in.Skip() } else { - v33 = string(in.String()) + v31 = string(in.String()) } - out.Features = append(out.Features, v33) + out.Features = append(out.Features, v31) in.WantComma() } in.Delim(']') @@ -4343,7 +4227,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in EventServerMessageSessionEntry) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in EventServerMessageSessionEntry) { out.RawByte('{') first := true _ = first @@ -4362,11 +4246,11 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw out.RawString(prefix) { out.RawByte('[') - for v34, v35 := range in.Features { - if v34 > 0 { + for v32, v33 := range in.Features { + if v32 > 0 { out.RawByte(',') } - out.String(string(v35)) + out.String(string(v33)) } out.RawByte(']') } @@ -4392,27 +4276,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSessionEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSessionEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *EventServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4454,21 +4338,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle out.Join = (out.Join)[:0] } for !in.IsDelim(']') { - var v36 *EventServerMessageSessionEntry + var v34 *EventServerMessageSessionEntry if in.IsNull() { in.Skip() - v36 = nil + v34 = nil } else { - if v36 == nil { - v36 = new(EventServerMessageSessionEntry) + if v34 == nil { + v34 = new(EventServerMessageSessionEntry) } if in.IsNull() { in.Skip() } else { - (*v36).UnmarshalEasyJSON(in) + (*v34).UnmarshalEasyJSON(in) } } - out.Join = append(out.Join, v36) + out.Join = append(out.Join, v34) in.WantComma() } in.Delim(']') @@ -4489,13 +4373,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle out.Leave = (out.Leave)[:0] } for !in.IsDelim(']') { - var v37 PublicSessionId + var v35 PublicSessionId if in.IsNull() { in.Skip() } else { - v37 = PublicSessionId(in.String()) + v35 = PublicSessionId(in.String()) } - out.Leave = append(out.Leave, v37) + out.Leave = append(out.Leave, v35) in.WantComma() } in.Delim(']') @@ -4516,21 +4400,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle out.Change = (out.Change)[:0] } for !in.IsDelim(']') { - var v38 *EventServerMessageSessionEntry + var v36 *EventServerMessageSessionEntry if in.IsNull() { in.Skip() - v38 = nil + v36 = nil } else { - if v38 == nil { - v38 = new(EventServerMessageSessionEntry) + if v36 == nil { + v36 = new(EventServerMessageSessionEntry) } if in.IsNull() { in.Skip() } else { - (*v38).UnmarshalEasyJSON(in) + (*v36).UnmarshalEasyJSON(in) } } - out.Change = append(out.Change, v38) + out.Change = append(out.Change, v36) in.WantComma() } in.Delim(']') @@ -4643,7 +4527,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in EventServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -4662,14 +4546,14 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw out.RawString(prefix) { out.RawByte('[') - for v39, v40 := range in.Join { - if v39 > 0 { + for v37, v38 := range in.Join { + if v37 > 0 { out.RawByte(',') } - if v40 == nil { + if v38 == nil { out.RawString("null") } else { - (*v40).MarshalEasyJSON(out) + (*v38).MarshalEasyJSON(out) } } out.RawByte(']') @@ -4680,11 +4564,11 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw out.RawString(prefix) { out.RawByte('[') - for v41, v42 := range in.Leave { - if v41 > 0 { + for v39, v40 := range in.Leave { + if v39 > 0 { out.RawByte(',') } - out.String(string(v42)) + out.String(string(v40)) } out.RawByte(']') } @@ -4694,14 +4578,14 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw out.RawString(prefix) { out.RawByte('[') - for v43, v44 := range in.Change { - if v43 > 0 { + for v41, v42 := range in.Change { + if v41 > 0 { out.RawByte(',') } - if v44 == nil { + if v42 == nil { out.RawString("null") } else { - (*v44).MarshalEasyJSON(out) + (*v42).MarshalEasyJSON(out) } } out.RawByte(']') @@ -4748,27 +4632,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *Error) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *Error) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4812,7 +4696,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in Error) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in Error) { out.RawByte('{') first := true _ = first @@ -4837,27 +4721,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jw // MarshalJSON supports json.Marshaler interface func (v Error) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v Error) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *Error) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4911,7 +4795,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4946,27 +4830,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *DialoutInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *DialoutInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5030,7 +4914,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in DialoutInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in DialoutInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5060,27 +4944,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ControlServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *ControlServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5140,7 +5024,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ControlServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in ControlServerMessage) { out.RawByte('{') first := true _ = first @@ -5169,27 +5053,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *ControlClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ControlClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5227,7 +5111,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in ControlClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ControlClientMessage) { out.RawByte('{') first := true _ = first @@ -5247,27 +5131,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5303,7 +5187,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in CommonSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in CommonSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5323,27 +5207,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jw // MarshalJSON supports json.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5385,7 +5269,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ClientTypeInternalAuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in ClientTypeInternalAuthParams) { out.RawByte('{') first := true _ = first @@ -5410,27 +5294,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *ClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5564,7 +5448,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in ClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ClientMessage) { out.RawByte('{') first := true _ = first @@ -5625,27 +5509,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *ByeServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5675,7 +5559,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in ByeServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -5690,27 +5574,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *ByeClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5734,7 +5618,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in ByeClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -5744,27 +5628,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jlexer.Lexer, out *AnswerOfferMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *AnswerOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5811,15 +5695,15 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v45 interface{} - if m, ok := v45.(easyjson.Unmarshaler); ok { + var v43 interface{} + if m, ok := v43.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v45.(json.Unmarshaler); ok { + } else if m, ok := v43.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v45 = in.Interface() + v43 = in.Interface() } - (out.Payload)[key] = v45 + (out.Payload)[key] = v43 in.WantComma() } in.Delim('}') @@ -5840,7 +5724,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jwriter.Writer, in AnswerOfferMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in AnswerOfferMessage) { out.RawByte('{') first := true _ = first @@ -5871,21 +5755,21 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jw out.RawString(`null`) } else { out.RawByte('{') - v46First := true - for v46Name, v46Value := range in.Payload { - if v46First { - v46First = false + v44First := true + for v44Name, v44Value := range in.Payload { + if v44First { + v44First = false } else { out.RawByte(',') } - out.String(string(v46Name)) + out.String(string(v44Name)) out.RawByte(':') - if m, ok := v46Value.(easyjson.Marshaler); ok { + if m, ok := v44Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v46Value.(json.Marshaler); ok { + } else if m, ok := v44Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v46Value)) + out.Raw(json.Marshal(v44Value)) } } out.RawByte('}') @@ -5902,27 +5786,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jw // MarshalJSON supports json.Marshaler interface func (v AnswerOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AnswerOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jlexer.Lexer, out *AddSessionOptions) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jlexer.Lexer, out *AddSessionOptions) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5958,7 +5842,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jwriter.Writer, in AddSessionOptions) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jwriter.Writer, in AddSessionOptions) { out.RawByte('{') first := true _ = first @@ -5984,27 +5868,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionOptions) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionOptions) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionOptions) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionOptions) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -6088,7 +5972,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(out *jwriter.Writer, in AddSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jwriter.Writer, in AddSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -6159,23 +6043,23 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(l, v) } From b7989d6aa882b4d04296a8142bb747b6eb45e2c8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 19 Nov 2024 13:58:49 +0100 Subject: [PATCH 222/549] Improve test coverage. --- clientsession_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/clientsession_test.go b/clientsession_test.go index 514eaf7..ef4d427 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -26,6 +26,7 @@ import ( "encoding/json" "net/url" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -248,3 +249,118 @@ func TestFeatureChatRelay(t *testing.T) { t.Run("without-chat-relay", testFunc(false)) t.Run("with-chat-relay", testFunc(true)) } + +func TestPermissionHideDisplayNames(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + + testFunc := func(permission bool) func(t *testing.T) { + return func(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client.RunUntilJoined(ctx, hello.Hello) + + room := hub.getRoom(roomId) + require.NotNil(room) + + if permission { + session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) + require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) + + // Client may not receive display names. + session.SetPermissions([]Permission{PERMISSION_HIDE_DISPLAYNAMES}) + } + + chatComment := api.StringMap{ + "actorDisplayName": "John Doe", + "baz": true, + "lala": map[string]any{ + "one": "eins", + }, + } + message := api.StringMap{ + "type": "chat", + "chat": api.StringMap{ + "comment": chatComment, + }, + } + data, err := json.Marshal(message) + require.NoError(err) + + // Simulate request from the backend. + room.ProcessBackendRoomRequest(&AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "message", + Message: &BackendRoomMessageRequest{ + Data: data, + }, + }, + }) + + if msg, ok := client.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + comment, found := chat["comment"] + if assert.True(found, "comment is missing in %+v", chat) { + if permission { + displayName, found := comment.(map[string]any)["actorDisplayName"] + assert.True(!found || displayName == "", "the display name should not be included in %+v", comment) + } else { + displayName, found := comment.(map[string]any)["actorDisplayName"] + assert.True(found && displayName != "", "the display name should be included in %+v", comment) + } + } + } + } + + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + + roomMsg2 := MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg2.Room.RoomId) + + client.RunUntilJoined(ctx, hello2.Hello) + client2.RunUntilJoined(ctx, hello.Hello, hello2.Hello) + + recipient1 := MessageClientMessageRecipient{ + Type: "session", + SessionId: hello.Hello.SessionId, + } + data1 := api.StringMap{ + "type": "nickChanged", + "message": "from-1-to-2", + } + client2.SendMessage(recipient1, data1) // nolint + if permission { + ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel2() + + client.RunUntilErrorIs(ctx2, context.DeadlineExceeded) + } else { + var payload2 api.StringMap + if ok := checkReceiveClientMessage(ctx, t, client, "session", hello2.Hello, &payload2); ok { + assert.Equal(data1, payload2) + } + } + } + } + } + + t.Run("without-hide-displaynames", testFunc(false)) + t.Run("with-hide-displaynames", testFunc(true)) +} From e6dd45b2cc7417a1873d86eddfa1fefac5fe9cdf Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Sat, 27 Sep 2025 17:25:29 +0200 Subject: [PATCH 223/549] Add test for chat relay with federated clients. --- clientsession_test.go | 157 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/clientsession_test.go b/clientsession_test.go index ef4d427..7dc6205 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "fmt" "net/url" "testing" "time" @@ -250,6 +251,162 @@ func TestFeatureChatRelay(t *testing.T) { t.Run("with-chat-relay", testFunc(true)) } +func TestFeatureChatRelayFederation(t *testing.T) { + CatchLogForTest(t) + + var testFunc = func(feature bool) func(t *testing.T) { + return func(t *testing.T) { + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + localFeatures := []string{ + ClientFeatureChatRelay, + } + var federatedFeatures []string + if feature { + federatedFeatures = append(federatedFeatures, ClientFeatureChatRelay) + } + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloClientWithFeatures(testDefaultUserId+"1", localFeatures)) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloClientWithFeatures(testDefaultUserId+"2", federatedFeatures)) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, room1.Room.RoomId) + + client1.RunUntilJoined(ctx, hello1.Hello) + + now := time.Now() + userdata := api.StringMap{ + "displayname": "Federated user", + "actorType": "federated_users", + "actorId": "the-federated-user-id", + } + token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, ok := client2.RunUntilMessage(ctx); ok { + 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 PublicSessionId + if message, ok := client1.RunUntilMessage(ctx); ok { + 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. + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) + + room := hub1.getRoom(roomId) + require.NotNil(room) + + chatComment := api.StringMap{ + "foo": "bar", + "baz": true, + "lala": map[string]any{ + "one": "eins", + }, + } + message := api.StringMap{ + "type": "chat", + "chat": api.StringMap{ + "refresh": true, + "comment": chatComment, + }, + } + data, err := json.Marshal(message) + require.NoError(err) + + // Simulate request from the backend. + room.ProcessBackendRoomRequest(&AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "message", + Message: &BackendRoomMessageRequest{ + Data: data, + }, + }, + }) + + // The first client will receive the message for the local room (always including the actual message). + if msg, ok := client1.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + assert.EqualValues(chatComment, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } + } + } + + // The second client will receive the message from the federated room (either as refresh or with the message). + if msg, ok := client2.RunUntilRoomMessage(ctx); ok { + assert.Equal(federatedRoomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + if feature { + assert.EqualValues(chatComment, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } else { + assert.Equal(true, chat["refresh"]) + _, found := chat["comment"] + assert.False(found, "the comment should not be included") + } + } + } + } + } + } + + t.Run("without-chat-relay", testFunc(false)) + t.Run("with-chat-relay", testFunc(true)) +} + func TestPermissionHideDisplayNames(t *testing.T) { t.Parallel() CatchLogForTest(t) From 426df7e083744b5f36b8aa156194f9ca0a1f0f61 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 21:06:45 +0200 Subject: [PATCH 224/549] Move functions to canonicalize urls to internal package. --- .codecov.yml | 4 + api_backend.go | 14 ++-- api_signaling.go | 20 ++--- backend_configuration.go | 6 +- backend_storage_static.go | 5 +- internal/canonicalize_url.go | 60 ++++++++++++++ internal/canonicalize_url_test.go | 125 ++++++++++++++++++++++++++++++ room.go | 8 +- 8 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 internal/canonicalize_url.go create mode 100644 internal/canonicalize_url_test.go diff --git a/.codecov.yml b/.codecov.yml index cbb4046..1109104 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -22,6 +22,10 @@ component_management: name: client paths: - client/** + - component_id: module_internal + name: internal + paths: + - internal/** - component_id: module_proxy name: proxy paths: diff --git a/api_backend.go b/api_backend.go index dc853e7..8aeb826 100644 --- a/api_backend.go +++ b/api_backend.go @@ -33,10 +33,10 @@ import ( "net/url" "regexp" "slices" - "strings" "time" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -475,13 +475,11 @@ func (p *BackendInformationEtcd) CheckValid() (err error) { return fmt.Errorf("invalid url %s: %w", u, err) } - if strings.Contains(parsedUrl.Host, ":") && hasStandardPort(parsedUrl) { - parsedUrl.Host = parsedUrl.Hostname() + var changed bool + if parsedUrl, changed = internal.CanonicalizeUrl(parsedUrl); changed { u = parsedUrl.String() - p.Urls[outIdx] = u - } else { - p.Urls[outIdx] = u } + p.Urls[outIdx] = u if seen[u] { continue } @@ -498,8 +496,8 @@ func (p *BackendInformationEtcd) CheckValid() (err error) { if err != nil { return fmt.Errorf("invalid url: %w", err) } - if strings.Contains(parsedUrl.Host, ":") && hasStandardPort(parsedUrl) { - parsedUrl.Host = parsedUrl.Hostname() + var changed bool + if parsedUrl, changed = internal.CanonicalizeUrl(parsedUrl); changed { p.Url = parsedUrl.String() } diff --git a/api_signaling.go b/api_signaling.go index 75b81e6..d6fb1cb 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -37,6 +37,7 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -354,17 +355,6 @@ const ( HelloClientTypeVirtual = ClientType("virtual") ) -func hasStandardPort(u *url.URL) bool { - switch u.Scheme { - case "http": - return u.Port() == "80" - case "https": - return u.Port() == "443" - default: - return false - } -} - type ClientTypeInternalAuthParams struct { Random string `json:"random"` Token string `json:"token"` @@ -384,8 +374,8 @@ func (p *ClientTypeInternalAuthParams) CheckValid() error { if u, err := url.Parse(p.Backend); err != nil { return err } else { - if strings.Contains(u.Host, ":") && hasStandardPort(u) { - u.Host = u.Hostname() + var changed bool + if u, changed = internal.CanonicalizeUrl(u); changed { p.Backend = u.String() } @@ -499,8 +489,8 @@ func (m *HelloClientMessage) CheckValid() error { if u, err := url.ParseRequestURI(m.Auth.Url); err != nil { return err } else { - if strings.Contains(u.Host, ":") && hasStandardPort(u) { - u.Host = u.Hostname() + var changed bool + if u, changed = internal.CanonicalizeUrl(u); changed { m.Auth.Url = u.String() } diff --git a/backend_configuration.go b/backend_configuration.go index 7f61bda..2da8703 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -30,6 +30,7 @@ import ( "sync" "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -260,10 +261,7 @@ func (b *BackendConfiguration) GetCompatBackend() *Backend { } func (b *BackendConfiguration) GetBackend(u *url.URL) *Backend { - if strings.Contains(u.Host, ":") && hasStandardPort(u) { - u.Host = u.Hostname() - } - + u, _ = internal.CanonicalizeUrl(u) return b.storage.GetBackend(u) } diff --git a/backend_storage_static.go b/backend_storage_static.go index 6855ebb..1fb77b4 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) type backendStorageStatic struct { @@ -335,8 +336,8 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr continue } - if strings.Contains(parsed.Host, ":") && hasStandardPort(parsed) { - parsed.Host = parsed.Hostname() + var changed bool + if parsed, changed = internal.CanonicalizeUrl(parsed); changed { u = parsed.String() } diff --git a/internal/canonicalize_url.go b/internal/canonicalize_url.go new file mode 100644 index 0000000..dabdf31 --- /dev/null +++ b/internal/canonicalize_url.go @@ -0,0 +1,60 @@ +/** + * 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 internal + +import ( + "net/url" + "strings" +) + +func hasStandardPort(u *url.URL) bool { + switch u.Scheme { + case "http": + return u.Port() == "80" + case "https": + return u.Port() == "443" + default: + return false + } +} + +func CanonicalizeUrl(u *url.URL) (*url.URL, bool) { + var changed bool + if strings.Contains(u.Host, ":") && hasStandardPort(u) { + u.Host = u.Hostname() + changed = true + } + return u, changed +} + +func CanonicalizeUrlString(s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return s, err + } + + if strings.Contains(u.Host, ":") && hasStandardPort(u) { + u.Host = u.Hostname() + s = u.String() + } + return s, nil +} diff --git a/internal/canonicalize_url_test.go b/internal/canonicalize_url_test.go new file mode 100644 index 0000000..65d5fee --- /dev/null +++ b/internal/canonicalize_url_test.go @@ -0,0 +1,125 @@ +/** + * 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 internal + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCanonicalizeUrl(t *testing.T) { + mustParse := func(s string) *url.URL { + t.Helper() + u, err := url.Parse(s) + require.NoError(t, err) + return u + } + testcases := []struct { + url *url.URL + expected *url.URL + }{ + { + url: mustParse("http://server.domain.tld:80/foo/"), + expected: mustParse("http://server.domain.tld/foo/"), + }, + { + url: mustParse("http://server.domain.tld:81/foo/"), + expected: mustParse("http://server.domain.tld:81/foo/"), + }, + { + url: mustParse("https://server.domain.tld:443/foo/"), + expected: mustParse("https://server.domain.tld/foo/"), + }, + { + url: mustParse("https://server.domain.tld:444/foo/"), + expected: mustParse("https://server.domain.tld:444/foo/"), + }, + { + url: mustParse("foo://server.domain.tld:443/foo/"), + expected: mustParse("foo://server.domain.tld:443/foo/"), + }, + } + + assert := assert.New(t) + for idx, tc := range testcases { + expectChanged := tc.url.String() != tc.expected.String() + canonicalized, changed := CanonicalizeUrl(tc.url) + assert.Equal(tc.url, canonicalized) //urls will be changed inplace + if !expectChanged { + assert.False(changed, "testcase %d should not have changed the url", idx) + continue + } + + if assert.True(changed, "testcase %d: should have changed the url", idx) { + assert.Equal(tc.expected, canonicalized, "testcase %d failed", idx) + } + } +} + +func TestCanonicalizeUrlString(t *testing.T) { + testcases := []struct { + s string + expected string + err string + }{ + { + s: "http://server.domain.tld:80/foo/", + expected: "http://server.domain.tld/foo/", + }, + { + s: "http://server.domain.tld:81/foo/", + expected: "http://server.domain.tld:81/foo/", + }, + { + s: "https://server.domain.tld:443/foo/", + expected: "https://server.domain.tld/foo/", + }, + { + s: "https://server.domain.tld:444/foo/", + expected: "https://server.domain.tld:444/foo/", + }, + { + s: "foo://server.domain.tld:443/foo/", + expected: "foo://server.domain.tld:443/foo/", + }, + { + s: "://server.domain.tld:443/foo/", + err: "missing protocol", + }, + } + + assert := assert.New(t) + for idx, tc := range testcases { + canonicalized, err := CanonicalizeUrlString(tc.s) + if tc.err != "" { + assert.ErrorContains(err, tc.err, "testcase %d failed", idx) + continue + } + + if assert.NoError(err, "testcase %d: should not have failed", idx) { + assert.Equal(tc.expected, canonicalized, "testcase %d failed", idx) + } + } +} diff --git a/room.go b/room.go index 7149f46..c6e8997 100644 --- a/room.go +++ b/room.go @@ -30,13 +30,13 @@ import ( "maps" "net/url" "strconv" - "strings" "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -1043,11 +1043,11 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { continue } - if strings.Contains(parsed.Host, ":") && hasStandardPort(parsed) { - parsed.Host = parsed.Hostname() + var changed bool + if parsed, changed = internal.CanonicalizeUrl(parsed); changed { + u = parsed.String() } - u = parsed.String() parsedBackendUrl := parsed var sid RoomSessionId From 628aedef9e44630fbbc956265fe6b5db0d99f3d7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 21:10:09 +0200 Subject: [PATCH 225/549] Move "MakePtr" function to internal package. --- api_signaling.go | 4 ---- etcd_client.go | 4 +++- federation.go | 5 +++-- internal/make_ptr.go | 26 ++++++++++++++++++++++++++ mcu_janus.go | 7 ++++--- mcu_proxy.go | 5 +++-- 6 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 internal/make_ptr.go diff --git a/api_signaling.go b/api_signaling.go index d6fb1cb..960a987 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -61,10 +61,6 @@ var ( ErrCandidateFiltered = errors.New("candidate was filtered") ) -func makePtr[T any](v T) *T { - return &v -} - type PrivateSessionId string type PublicSessionId string diff --git a/etcd_client.go b/etcd_client.go index d83a926..459f14b 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -38,6 +38,8 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "google.golang.org/grpc/connectivity" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) type EtcdClientListener interface { @@ -82,7 +84,7 @@ func (c *EtcdClient) GetServerInfoEtcd() *BackendServerInfoEtcd { conn := client.ActiveConnection() if conn != nil { result.Active = conn.Target() - result.Connected = makePtr(conn.GetState() == connectivity.Ready) + result.Connected = internal.MakePtr(conn.GetState() == connectivity.Ready) } return result diff --git a/federation.go b/federation.go index 1f9747d..458b265 100644 --- a/federation.go +++ b/federation.go @@ -39,6 +39,7 @@ import ( "github.com/mailru/easyjson" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -539,7 +540,7 @@ func (c *FederationClient) processHello(msg *ServerMessage) { Event: &EventServerMessage{ Target: "room", Type: "federation_resumed", - Resumed: makePtr(false), + Resumed: internal.MakePtr(false), }, }) // Setting the federation client will reset any information on previously @@ -556,7 +557,7 @@ func (c *FederationClient) processHello(msg *ServerMessage) { Event: &EventServerMessage{ Target: "room", Type: "federation_resumed", - Resumed: makePtr(true), + Resumed: internal.MakePtr(true), }, }) diff --git a/internal/make_ptr.go b/internal/make_ptr.go new file mode 100644 index 0000000..b262aa6 --- /dev/null +++ b/internal/make_ptr.go @@ -0,0 +1,26 @@ +/** + * 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 internal + +func MakePtr[T any](v T) *T { + return &v +} diff --git a/mcu_janus.go b/mcu_janus.go index d8229a7..df68df0 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -36,6 +36,7 @@ import ( "github.com/notedit/janus-go" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -461,10 +462,10 @@ func (m *mcuJanus) GetServerInfoSfu() *BackendServerInfoSfu { janus.Name = info.Name janus.Version = info.VersionString janus.Author = info.Author - janus.DataChannels = makePtr(info.DataChannels) - janus.FullTrickle = makePtr(info.FullTrickle) + janus.DataChannels = internal.MakePtr(info.DataChannels) + janus.FullTrickle = internal.MakePtr(info.FullTrickle) janus.LocalIP = info.LocalIP - janus.IPv6 = makePtr(info.IPv6) + janus.IPv6 = internal.MakePtr(info.IPv6) if plugin, found := info.Plugins[pluginVideoRoom]; found { janus.VideoRoom = &BackendServerInfoVideoRoom{ diff --git a/mcu_proxy.go b/mcu_proxy.go index ac8997d..5bcf45c 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -47,6 +47,7 @@ import ( "github.com/gorilla/websocket" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -1833,12 +1834,12 @@ func (m *mcuProxy) GetServerInfoSfu() *BackendServerInfoSfu { } if c.IsConnected() { proxy.Connected = true - proxy.Shutdown = makePtr(c.IsShutdownScheduled()) + proxy.Shutdown = internal.MakePtr(c.IsShutdownScheduled()) proxy.Uptime = &c.connectedSince proxy.Version = c.Version() proxy.Features = c.Features() proxy.Country = c.Country() - proxy.Load = makePtr(c.Load()) + proxy.Load = internal.MakePtr(c.Load()) proxy.Bandwidth = c.Bandwidth() } sfu.Proxies = append(sfu.Proxies, proxy) From 89c71e4a3c9ccf5671cac6746ca5d4fd34b3f0ce Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 21:18:38 +0200 Subject: [PATCH 226/549] Add interface for method "GetInCall". --- room.go | 10 ++-------- session.go | 4 ++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/room.go b/room.go index 7149f46..3458b2f 100644 --- a/room.go +++ b/room.go @@ -957,14 +957,8 @@ func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) { if flags&SessionChangeInCall != 0 { joinLeave := 0 - if clientSession, ok := session.(*ClientSession); ok { - if clientSession.GetInCall()&FlagInCall != 0 { - joinLeave = 1 - } else { - joinLeave = 2 - } - } else if virtual, ok := session.(*VirtualSession); ok { - if virtual.GetInCall()&FlagInCall != 0 { + if session, ok := session.(SessionWithInCall); ok { + if session.GetInCall()&FlagInCall != 0 { joinLeave = 1 } else { joinLeave = 2 diff --git a/session.go b/session.go index 718b8ac..a27efea 100644 --- a/session.go +++ b/session.go @@ -76,6 +76,10 @@ type Session interface { SendMessage(message *ServerMessage) bool } +type SessionWithInCall interface { + GetInCall() int +} + func parseUserData(data json.RawMessage) func() (api.StringMap, error) { return sync.OnceValues(func() (api.StringMap, error) { if len(data) == 0 { From 63b658574a748190743df41825bf574ee059afe4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 10:56:03 +0200 Subject: [PATCH 227/549] Rewrite chat actor information for federation. --- api/stringmap.go | 9 ++++ api/stringmap_test.go | 25 +++++++++ clientsession_test.go | 60 ++++++++++++++++++--- federation.go | 121 ++++++++++++++++++++++++++++++++++-------- testutils_test.go | 18 +++++++ 5 files changed, 204 insertions(+), 29 deletions(-) diff --git a/api/stringmap.go b/api/stringmap.go index d4f7a73..749a3dc 100644 --- a/api/stringmap.go +++ b/api/stringmap.go @@ -24,6 +24,15 @@ package api // StringMap maps string keys to arbitrary values. type StringMap map[string]any +func (m StringMap) GetStringMap(key string) (StringMap, bool) { + v, found := m[key] + if !found { + return nil, false + } + + return ConvertStringMap(v) +} + func ConvertStringMap(ob any) (StringMap, bool) { if ob == nil { return nil, true diff --git a/api/stringmap_test.go b/api/stringmap_test.go index 1d45a20..941c7f9 100644 --- a/api/stringmap_test.go +++ b/api/stringmap_test.go @@ -88,3 +88,28 @@ func TestGetStringMapString(t *testing.T) { _, ok = GetStringMapString[StringMapTestString](m, "invalid") assert.False(ok) } + +func TestGetStringMapStringMap(t *testing.T) { + assert := assert.New(t) + + m := StringMap{ + "foo": map[string]any{ + "bar": 1, + }, + "bar": StringMap{ + "baz": 2, + }, + } + if v, ok := m.GetStringMap("foo"); assert.True(ok) { + assert.EqualValues(map[string]any{ + "bar": 1, + }, v) + } + if v, ok := m.GetStringMap("bar"); assert.True(ok) { + assert.EqualValues(map[string]any{ + "baz": 2, + }, v) + } + v, ok := m.GetStringMap("baz") + assert.False(ok, "expected missing entry, got %+v", v) +} diff --git a/clientsession_test.go b/clientsession_test.go index 7dc6205..12ef1f2 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -339,11 +339,57 @@ func TestFeatureChatRelayFederation(t *testing.T) { room := hub1.getRoom(roomId) require.NotNil(room) - chatComment := api.StringMap{ - "foo": "bar", - "baz": true, - "lala": map[string]any{ - "one": "eins", + chatComment := map[string]any{ + "actorId": hello1.Hello.UserId, + "actorType": "users", + "lastEditActorId": hello1.Hello.UserId, + "lastEditActorType": "users", + "parent": map[string]any{ + "actorId": hello1.Hello.UserId, + "actorType": "users", + "lastEditActorId": hello1.Hello.UserId, + "lastEditActorType": "users", + }, + "messageParameters": map[string]map[string]any{ + "mention-local-user": { + "type": "user", + "id": hello1.Hello.UserId, + "name": "User 1", + }, + "mention-remote-user": { + "type": "user", + "id": hello2.Hello.UserId, + "name": "User 2", + "mention-id": "federated_user/" + hello2.Hello.UserId + "@" + getCloudUrl(server2.URL), + "server": server2.URL, + }, + }, + } + federatedChatComment := map[string]any{ + "actorId": hello1.Hello.UserId + "@" + getCloudUrl(server1.URL), + "actorType": "federated_users", + "lastEditActorId": hello1.Hello.UserId + "@" + getCloudUrl(server1.URL), + "lastEditActorType": "federated_users", + "parent": map[string]any{ + "actorId": hello1.Hello.UserId + "@" + getCloudUrl(server1.URL), + "actorType": "federated_users", + "lastEditActorId": hello1.Hello.UserId + "@" + getCloudUrl(server1.URL), + "lastEditActorType": "federated_users", + }, + "messageParameters": map[string]map[string]any{ + "mention-local-user": { + "type": "user", + "id": hello1.Hello.UserId, + "mention-id": hello1.Hello.UserId, + "name": "User 1", + "server": server1.URL, + }, + "mention-remote-user": { + "type": "user", + "id": hello2.Hello.UserId, + "name": "User 2", + "mention-id": "federated_user/" + hello2.Hello.UserId + "@" + getCloudUrl(server2.URL), + }, }, } message := api.StringMap{ @@ -374,7 +420,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { assert.Equal("chat", data["type"], "invalid type entry in %+v", data) if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { - assert.EqualValues(chatComment, chat["comment"]) + AssertEqualSerialized(t, chatComment, chat["comment"]) _, found := chat["refresh"] assert.False(found, "refresh should not be included") } @@ -389,7 +435,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { assert.Equal("chat", data["type"], "invalid type entry in %+v", data) if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { if feature { - assert.EqualValues(chatComment, chat["comment"]) + AssertEqualSerialized(t, federatedChatComment, chat["comment"]) _, found := chat["refresh"] assert.False(found, "refresh should not be included") } else { diff --git a/federation.go b/federation.go index 1f9747d..2d5cb4a 100644 --- a/federation.go +++ b/federation.go @@ -59,11 +59,7 @@ func isClosedError(err error) bool { strings.Contains(err.Error(), net.ErrClosed.Error()) } -func getCloudUrl(s string) string { - var found bool - if s, found = strings.CutPrefix(s, "https://"); !found { - s = strings.TrimPrefix(s, "http://") - } +func getCloudUrlWithoutPath(s string) string { if pos := strings.Index(s, "/ocs/v"); pos != -1 { s = s[:pos] } else { @@ -72,6 +68,14 @@ func getCloudUrl(s string) string { return s } +func getCloudUrl(s string) string { + var found bool + if s, found = strings.CutPrefix(s, "https://"); !found { + s = strings.TrimPrefix(s, "http://") + } + return getCloudUrlWithoutPath(s) +} + type FederationClient struct { hub *Hub session *ClientSession @@ -604,26 +608,73 @@ func (c *FederationClient) joinRoom() error { }) } -func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { - localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) - localCloudUrlLen := len(localCloudUrl) - remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) - checkSessionId := true - for _, u := range users { - if actorType, found := api.GetStringMapEntry[string](u, "actorType"); found { - if actorId, found := api.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 +func (c *FederationClient) updateActor(u api.StringMap, actorIdKey, actorTypeKey string, localCloudUrl string, remoteCloudUrl string) (changed bool) { + if actorType, found := api.GetStringMapEntry[string](u, actorTypeKey); found { + if actorId, found := api.GetStringMapEntry[string](u, actorIdKey); found { + switch actorType { + case ActorTypeFederatedUsers: + if strings.HasSuffix(actorId, localCloudUrl) { + u[actorIdKey] = actorId[:len(actorId)-len(localCloudUrl)] + u[actorTypeKey] = ActorTypeUsers + changed = true + } + case ActorTypeUsers: + u[actorIdKey] = actorId + remoteCloudUrl + u[actorTypeKey] = ActorTypeFederatedUsers + changed = true + } + } + } + return +} + +func (c *FederationClient) updateComment(comment api.StringMap, localCloudUrl string, remoteCloudUrl string) bool { + changed := c.updateActor(comment, "actorId", "actorType", localCloudUrl, remoteCloudUrl) + if c.updateActor(comment, "lastEditActorId", "lastEditActorType", localCloudUrl, remoteCloudUrl) { + changed = true + } + + if params, found := api.GetStringMapEntry[map[string]any](comment, "messageParameters"); found { + localUrl := getCloudUrlWithoutPath(c.session.BackendUrl()) + remoteUrl := getCloudUrlWithoutPath(c.federation.Load().NextcloudUrl) + for key, paramOb := range params { + if !strings.HasPrefix(key, "mention-") { + // Only need to process mentions. + continue + } + + param, ok := api.ConvertStringMap(paramOb) + if !ok { + continue + } + + if ptype, found := api.GetStringMapString[string](param, "type"); found && ptype == "user" { + if server, found := api.GetStringMapString[string](param, "server"); found && server == localUrl { + delete(param, "server") + params[key] = param + changed = true + continue + } + + if _, found := api.GetStringMapString[string](param, "mention-id"); !found { + param["mention-id"] = param["id"] + param["server"] = remoteUrl + params[key] = param + changed = true + continue } } } + } + return changed +} + +func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { + localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) + remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) + checkSessionId := true + for _, u := range users { + c.updateActor(u, "actorId", "actorType", localCloudUrl, remoteCloudUrl) if checkSessionId { key := "sessionId" @@ -732,6 +783,32 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { if c.changeRoomId.Load() && msg.Event.Message.RoomId == remoteRoomId { msg.Event.Message.RoomId = roomId } + if msg.Event.Type == "message" && msg.Event.Message != nil { + if data, err := msg.Event.Message.GetData(); err == nil { + if data.Type == "chat" && data.Chat != nil && len(data.Chat.Comment) > 0 { + var comment api.StringMap + if err := json.Unmarshal(data.Chat.Comment, &comment); err == nil { + localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) + remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) + changed := c.updateComment(comment, localCloudUrl, remoteCloudUrl) + if parent, found := comment.GetStringMap("parent"); found { + if c.updateComment(parent, localCloudUrl, remoteCloudUrl) { + comment["parent"] = parent + changed = true + } + } + if changed { + if encoded, err := json.Marshal(comment); err == nil { + data.Chat.Comment = encoded + if encoded, err = json.Marshal(data); err == nil { + msg.Event.Message.Data = encoded + } + } + } + } + } + } + } } case "roomlist": switch msg.Event.Type { diff --git a/testutils_test.go b/testutils_test.go index dbf91e3..60932af 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -24,6 +24,7 @@ package signaling import ( "bytes" "context" + "encoding/json" "io" "os" "os/signal" @@ -32,6 +33,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -132,3 +134,19 @@ func MustSucceed3[T any, A1 any, A2 any, A3 any](t *testing.T, f func(a1 A1, a2 } return result } + +func AssertEqualSerialized(t *testing.T, expected any, actual any, msgAndArgs ...any) bool { + t.Helper() + + e, err := json.MarshalIndent(expected, "", " ") + if !assert.NoError(t, err) { + return false + } + + a, err := json.MarshalIndent(actual, "", " ") + if !assert.NoError(t, err) { + return false + } + + return assert.Equal(t, string(a), string(e), msgAndArgs...) +} From 962f0254b114c3b261c3b19485ea3f38703a036d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 14:36:23 +0200 Subject: [PATCH 228/549] Rewrite chat room mentions for federation. --- clientsession_test.go | 8 ++++++++ federation.go | 36 +++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/clientsession_test.go b/clientsession_test.go index 12ef1f2..c455f6e 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -363,6 +363,10 @@ func TestFeatureChatRelayFederation(t *testing.T) { "mention-id": "federated_user/" + hello2.Hello.UserId + "@" + getCloudUrl(server2.URL), "server": server2.URL, }, + "mention-call": { + "type": "call", + "id": roomId, + }, }, } federatedChatComment := map[string]any{ @@ -390,6 +394,10 @@ func TestFeatureChatRelayFederation(t *testing.T) { "name": "User 2", "mention-id": "federated_user/" + hello2.Hello.UserId + "@" + getCloudUrl(server2.URL), }, + "mention-call": { + "type": "call", + "id": federatedRoomId, + }, }, } message := api.StringMap{ diff --git a/federation.go b/federation.go index 2d5cb4a..f19e955 100644 --- a/federation.go +++ b/federation.go @@ -648,20 +648,30 @@ func (c *FederationClient) updateComment(comment api.StringMap, localCloudUrl st continue } - if ptype, found := api.GetStringMapString[string](param, "type"); found && ptype == "user" { - if server, found := api.GetStringMapString[string](param, "server"); found && server == localUrl { - delete(param, "server") - params[key] = param - changed = true - continue - } + if ptype, found := api.GetStringMapString[string](param, "type"); found { + switch ptype { + case "user": + if server, found := api.GetStringMapString[string](param, "server"); found && server == localUrl { + delete(param, "server") + params[key] = param + changed = true + continue + } - if _, found := api.GetStringMapString[string](param, "mention-id"); !found { - param["mention-id"] = param["id"] - param["server"] = remoteUrl - params[key] = param - changed = true - continue + if _, found := api.GetStringMapString[string](param, "mention-id"); !found { + param["mention-id"] = param["id"] + param["server"] = remoteUrl + params[key] = param + changed = true + continue + } + case "call": + roomId := c.RoomId() + remoteRoomId := c.RemoteRoomId() + // TODO: Should we also rewrite the room avatar url in "icon-url"? + if c.changeRoomId.Load() && param["id"] == remoteRoomId { + param["id"] = roomId + } } } } From 8613bc85cb5f0a5279d487af95bb9a6373f41442 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 21:44:30 +0200 Subject: [PATCH 229/549] codecov: Add fallback "root" module. --- .codecov.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 1109104..3acf08a 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -14,6 +14,10 @@ ignore: component_management: individual_components: + - component_id: module_root + name: root + paths: + - "*.go" - component_id: module_api name: api paths: From a2a7cf04769f0e87fb24d661fc387bd1d997c365 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 23:46:22 +0200 Subject: [PATCH 230/549] Add "localhost" to generated self-signed test certificates. --- grpc_common_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/grpc_common_test.go b/grpc_common_test.go index a18deca..fbe1f0e 100644 --- a/grpc_common_test.go +++ b/grpc_common_test.go @@ -70,6 +70,7 @@ func GenerateSelfSignedCertificateForTesting(t *testing.T, bits int, organizatio x509.ExtKeyUsageServerAuth, }, BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, } From 78347e84913f6a8c9c77e82809e4131d3be51327 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 29 Sep 2025 23:46:36 +0200 Subject: [PATCH 231/549] Add etcd TLS tests. --- etcd_client_test.go | 129 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/etcd_client_test.go b/etcd_client_test.go index 7986e2e..c48e580 100644 --- a/etcd_client_test.go +++ b/etcd_client_test.go @@ -23,10 +23,13 @@ package signaling import ( "context" + "crypto/rand" + "crypto/rsa" "errors" "net" "net/url" "os" + "path" "runtime" "strconv" "syscall" @@ -67,16 +70,41 @@ func isErrorAddressAlreadyInUse(err error) bool { return false } -func NewEtcdForTest(t *testing.T) *embed.Etcd { +func NewEtcdForTestWithTls(t *testing.T, withTLS bool) (*embed.Etcd, string, string) { + t.Helper() + require := require.New(t) cfg := embed.NewConfig() cfg.Dir = t.TempDir() os.Chmod(cfg.Dir, 0700) // nolint cfg.LogLevel = "warn" + cfg.Name = "signalingtest" u, err := url.Parse(etcdListenUrl) require.NoError(err) + var keyfile string + var certfile string + if withTLS { + u.Scheme = "https" + + tmpdir := t.TempDir() + key, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + keyfile = path.Join(tmpdir, "etcd.key") + require.NoError(WritePrivateKey(key, keyfile)) + cfg.ClientTLSInfo.KeyFile = keyfile + cfg.PeerTLSInfo.KeyFile = keyfile + + cert := GenerateSelfSignedCertificateForTesting(t, 1024, "etcd", key) + certfile = path.Join(tmpdir, "etcd.pem") + require.NoError(os.WriteFile(certfile, cert, 0755)) + cfg.ClientTLSInfo.CertFile = certfile + cfg.ClientTLSInfo.TrustedCAFile = certfile + cfg.PeerTLSInfo.CertFile = certfile + cfg.PeerTLSInfo.TrustedCAFile = certfile + } + // Find a free port to bind the server to. var etcd *embed.Etcd for port := 50000; port < 50100; port++ { @@ -90,7 +118,7 @@ func NewEtcdForTest(t *testing.T) *embed.Etcd { peerListener.Host = net.JoinHostPort("localhost", strconv.Itoa(port+2)) cfg.ListenPeerUrls = []url.URL{*peerListener} cfg.AdvertisePeerUrls = []url.URL{*peerListener} - cfg.InitialCluster = "default=" + peerListener.String() + cfg.InitialCluster = "signalingtest=" + peerListener.String() cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))) etcd, err = embed.StartEtcd(cfg) if isErrorAddressAlreadyInUse(err) { @@ -109,6 +137,13 @@ func NewEtcdForTest(t *testing.T) *embed.Etcd { // Wait for server to be ready. <-etcd.Server.ReadyNotify() + return etcd, keyfile, certfile +} + +func NewEtcdForTest(t *testing.T) *embed.Etcd { + t.Helper() + + etcd, _, _ := NewEtcdForTestWithTls(t, false) return etcd } @@ -127,6 +162,24 @@ func NewEtcdClientForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { return etcd, client } +func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { + etcd, keyfile, certfile := NewEtcdForTestWithTls(t, true) + + config := goconf.NewConfigFile() + config.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) + config.AddOption("etcd", "loglevel", "error") + config.AddOption("etcd", "clientkey", keyfile) + config.AddOption("etcd", "clientcert", certfile) + config.AddOption("etcd", "cacert", certfile) + + client, err := NewEtcdClient(config, "") + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, client.Close()) + }) + return etcd, client +} + func SetEtcdValue(etcd *embed.Etcd, key string, value []byte) { if kv := etcd.Server.KV(); kv != nil { kv.Put([]byte(key), value, lease.NoLease) @@ -145,8 +198,34 @@ func Test_EtcdClient_Get(t *testing.T) { t.Parallel() CatchLogForTest(t) assert := assert.New(t) + require := require.New(t) etcd, client := NewEtcdClientForTest(t) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + if info := client.GetServerInfoEtcd(); assert.NotNil(info) { + assert.NotEmpty(info.Active) + assert.Equal([]string{ + etcd.Config().ListenClientUrls[0].String(), + }, info.Endpoints) + if connected := info.Connected; assert.NotNil(connected) { + assert.False(*connected) + } + } + + require.NoError(client.WaitForConnection(ctx)) + + if info := client.GetServerInfoEtcd(); assert.NotNil(info) { + assert.NotEmpty(info.Active) + assert.Equal([]string{ + etcd.Config().ListenClientUrls[0].String(), + }, info.Endpoints) + if connected := info.Connected; assert.NotNil(connected) { + assert.True(*connected) + } + } + if response, err := client.Get(context.Background(), "foo"); assert.NoError(err) { assert.EqualValues(0, response.Count) } @@ -161,6 +240,52 @@ func Test_EtcdClient_Get(t *testing.T) { } } +func Test_EtcdClientTLS_Get(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + assert := assert.New(t) + require := require.New(t) + etcd, client := NewEtcdClientWithTLSForTest(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + if info := client.GetServerInfoEtcd(); assert.NotNil(info) { + assert.NotEmpty(info.Active) + assert.Equal([]string{ + etcd.Config().ListenClientUrls[0].String(), + }, info.Endpoints) + if connected := info.Connected; assert.NotNil(connected) { + assert.False(*connected) + } + } + + require.NoError(client.WaitForConnection(ctx)) + + if info := client.GetServerInfoEtcd(); assert.NotNil(info) { + assert.NotEmpty(info.Active) + assert.Equal([]string{ + etcd.Config().ListenClientUrls[0].String(), + }, info.Endpoints) + if connected := info.Connected; assert.NotNil(connected) { + assert.True(*connected) + } + } + + if response, err := client.Get(ctx, "foo"); assert.NoError(err) { + assert.EqualValues(0, response.Count) + } + + SetEtcdValue(etcd, "foo", []byte("bar")) + + if response, err := client.Get(ctx, "foo"); assert.NoError(err) { + if assert.EqualValues(1, response.Count) { + assert.Equal("foo", string(response.Kvs[0].Key)) + assert.Equal("bar", string(response.Kvs[0].Value)) + } + } +} + func Test_EtcdClient_GetPrefix(t *testing.T) { t.Parallel() CatchLogForTest(t) From 030e20f5e47c90bb31eef2f36fc167bf0fa0d6f4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 30 Sep 2025 09:40:33 +0200 Subject: [PATCH 232/549] Register "statsHubSessionsResumedTotal" so it gets included in metrics. --- hub_stats_prometheus.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hub_stats_prometheus.go b/hub_stats_prometheus.go index f3d5c1a..0c793d7 100644 --- a/hub_stats_prometheus.go +++ b/hub_stats_prometheus.go @@ -61,6 +61,7 @@ var ( statsHubRoomsCurrent, statsHubSessionsCurrent, statsHubSessionsTotal, + statsHubSessionsResumedTotal, statsHubSessionResumeFailed, } ) From 178503fef76244a823dbc3a6d14478595004a7e3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 30 Sep 2025 09:55:57 +0200 Subject: [PATCH 233/549] Make LruCache typed through generics. --- hub.go | 12 +++++------ lru.go | 35 ++++++++++++++++--------------- lru_test.go | 42 +++++++++++++++----------------------- proxy/proxy_tokens_etcd.go | 6 +++--- 4 files changed, 43 insertions(+), 52 deletions(-) diff --git a/hub.go b/hub.go index 6c93b13..29fb003 100644 --- a/hub.go +++ b/hub.go @@ -171,7 +171,7 @@ type Hub struct { roomPing *RoomPing virtualSessions map[PublicSessionId]uint64 - decodeCaches []*LruCache + decodeCaches []*LruCache[*SessionIdData] mcu Mcu mcuTimeout time.Duration @@ -285,9 +285,9 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } - decodeCaches := make([]*LruCache, 0, numDecodeCaches) + decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, NewLruCache(decodeCacheSize)) + decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) } roomSessions, err := NewBuiltinRoomSessions(rpcClients) @@ -597,7 +597,7 @@ func (h *Hub) Reload(config *goconf.ConfigFile) { h.rpcClients.Reload(config) } -func (h *Hub) getDecodeCache(cache_key string) *LruCache { +func (h *Hub) getDecodeCache(cache_key string) *LruCache[*SessionIdData] { hash := fnv.New32a() hash.Write([]byte(cache_key)) // nolint idx := hash.Sum32() % uint32(len(h.decodeCaches)) @@ -648,7 +648,7 @@ func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { cache_key := fmt.Sprintf("%s|%s", id, privateSessionName) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { - return result.(*SessionIdData) + return result } data, err := h.cookie.DecodePrivate(id) @@ -668,7 +668,7 @@ func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { cache_key := fmt.Sprintf("%s|%s", id, publicSessionName) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { - return result.(*SessionIdData) + return result } data, err := h.cookie.DecodePublic(id) diff --git a/lru.go b/lru.go index d46234a..6bf8bbf 100644 --- a/lru.go +++ b/lru.go @@ -26,36 +26,36 @@ import ( "sync" ) -type cacheEntry struct { +type cacheEntry[T any] struct { key string - value any + value T } -type LruCache struct { +type LruCache[T any] struct { size int mu sync.Mutex entries *list.List data map[string]*list.Element } -func NewLruCache(size int) *LruCache { - return &LruCache{ +func NewLruCache[T any](size int) *LruCache[T] { + return &LruCache[T]{ size: size, entries: list.New(), data: make(map[string]*list.Element), } } -func (c *LruCache) Set(key string, value any) { +func (c *LruCache[T]) Set(key string, value T) { c.mu.Lock() if v, found := c.data[key]; found { c.entries.MoveToFront(v) - v.Value.(*cacheEntry).value = value + v.Value.(*cacheEntry[T]).value = value c.mu.Unlock() return } - v := c.entries.PushFront(&cacheEntry{ + v := c.entries.PushFront(&cacheEntry[T]{ key: key, value: value, }) @@ -66,20 +66,21 @@ func (c *LruCache) Set(key string, value any) { c.mu.Unlock() } -func (c *LruCache) Get(key string) any { +func (c *LruCache[T]) Get(key string) T { c.mu.Lock() if v, found := c.data[key]; found { c.entries.MoveToFront(v) - value := v.Value.(*cacheEntry).value + value := v.Value.(*cacheEntry[T]).value c.mu.Unlock() return value } c.mu.Unlock() - return nil + var defaultValue T + return defaultValue } -func (c *LruCache) Remove(key string) { +func (c *LruCache[T]) Remove(key string) { c.mu.Lock() if v, found := c.data[key]; found { c.removeElement(v) @@ -87,26 +88,26 @@ func (c *LruCache) Remove(key string) { c.mu.Unlock() } -func (c *LruCache) removeOldestLocked() { +func (c *LruCache[T]) removeOldestLocked() { v := c.entries.Back() if v != nil { c.removeElement(v) } } -func (c *LruCache) RemoveOldest() { +func (c *LruCache[T]) RemoveOldest() { c.mu.Lock() c.removeOldestLocked() c.mu.Unlock() } -func (c *LruCache) removeElement(e *list.Element) { +func (c *LruCache[T]) removeElement(e *list.Element) { c.entries.Remove(e) - entry := e.Value.(*cacheEntry) + entry := e.Value.(*cacheEntry[T]) delete(c.data, entry.key) } -func (c *LruCache) Len() int { +func (c *LruCache[T]) Len() int { c.mu.Lock() defer c.mu.Unlock() return c.entries.Len() diff --git a/lru_test.go b/lru_test.go index 93445ac..572b5d5 100644 --- a/lru_test.go +++ b/lru_test.go @@ -30,7 +30,7 @@ import ( func TestLruUnbound(t *testing.T) { assert := assert.New(t) - lru := NewLruCache(0) + lru := NewLruCache[int](0) count := 10 for i := range count { key := fmt.Sprintf("%d", i) @@ -39,9 +39,8 @@ func TestLruUnbound(t *testing.T) { assert.Equal(count, lru.Len()) for i := range count { key := fmt.Sprintf("%d", i) - if value := lru.Get(key); assert.NotNil(value, "No value found for %s", key) { - assert.EqualValues(i, value) - } + value := lru.Get(key) + assert.EqualValues(i, value, "Failed for %s", key) } // The first key ("0") is now the oldest. lru.RemoveOldest() @@ -49,12 +48,7 @@ func TestLruUnbound(t *testing.T) { for i := range count { key := fmt.Sprintf("%d", i) value := lru.Get(key) - if i == 0 { - assert.Nil(value, "The value for key %s should have been removed", key) - continue - } else if assert.NotNil(value, "No value found for %s", key) { - assert.EqualValues(i, value) - } + assert.EqualValues(i, value, "Failed for %s", key) } // NOTE: Key "0" no longer exists below, so make sure to not set it again. @@ -68,9 +62,8 @@ func TestLruUnbound(t *testing.T) { // NOTE: The same ordering as the Set calls above. for i := count - 1; i >= 1; i-- { key := fmt.Sprintf("%d", i) - if value := lru.Get(key); assert.NotNil(value, "No value found for %s", key) { - assert.EqualValues(i, value) - } + value := lru.Get(key) + assert.EqualValues(i, value, "Failed for %s", key) } // The last key ("9") is now the oldest. @@ -80,10 +73,9 @@ func TestLruUnbound(t *testing.T) { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i == 0 || i == count-1 { - assert.Nil(value, "The value for key %s should have been removed", key) - continue - } else if assert.NotNil(value, "No value found for %s", key) { - assert.EqualValues(i, value) + assert.EqualValues(0, value, "The value for key %s should have been removed", key) + } else { + assert.EqualValues(i, value, "Failed for %s", key) } } @@ -95,10 +87,9 @@ func TestLruUnbound(t *testing.T) { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i == 0 || i == count-1 || i == count/2 { - assert.Nil(value, "The value for key %s should have been removed", key) - continue - } else if assert.NotNil(value, "No value found for %s", key) { - assert.EqualValues(i, value) + assert.EqualValues(0, value, "The value for key %s should have been removed", key) + } else { + assert.EqualValues(i, value, "Failed for %s", key) } } } @@ -106,7 +97,7 @@ func TestLruUnbound(t *testing.T) { func TestLruBound(t *testing.T) { assert := assert.New(t) size := 2 - lru := NewLruCache(size) + lru := NewLruCache[int](size) count := 10 for i := range count { key := fmt.Sprintf("%d", i) @@ -118,10 +109,9 @@ func TestLruBound(t *testing.T) { key := fmt.Sprintf("%d", i) value := lru.Get(key) if i < count-size { - assert.Nil(value, "The value for key %s should have been removed", key) - continue - } else if assert.NotNil(value, "No value found for %s", key) { - assert.EqualValues(i, value) + assert.EqualValues(0, value, "The value for key %s should have been removed", key) + } else { + assert.EqualValues(i, value, "Failed for %s", key) } } } diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index 3e6e602..f542d62 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -49,7 +49,7 @@ type tokensEtcd struct { client *signaling.EtcdClient tokenFormats atomic.Value - tokenCache *signaling.LruCache + tokenCache *signaling.LruCache[*tokenCacheEntry] } func NewProxyTokensEtcd(config *goconf.ConfigFile) (ProxyTokens, error) { @@ -64,7 +64,7 @@ func NewProxyTokensEtcd(config *goconf.ConfigFile) (ProxyTokens, error) { result := &tokensEtcd{ client: client, - tokenCache: signaling.NewLruCache(tokenCacheSize), + tokenCache: signaling.NewLruCache[*tokenCacheEntry](tokenCacheSize), } if err := result.load(config, false); err != nil { return nil, err @@ -98,7 +98,7 @@ func (t *tokensEtcd) getByKey(id string, key string) (*ProxyToken, error) { } keyValue := resp.Kvs[len(resp.Kvs)-1].Value - cached, _ := t.tokenCache.Get(key).(*tokenCacheEntry) + cached := t.tokenCache.Get(key) if cached == nil || !bytes.Equal(cached.keyValue, keyValue) { // Parsed public keys are cached to avoid the parse overhead. publicKey, err := jwt.ParseRSAPublicKeyFromPEM(keyValue) From e749c7038a1c317a277c357139516dd35dc394fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:01:56 +0000 Subject: [PATCH 234/549] Bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.0 to 1.46.1. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.46.0...v1.46.1) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.46.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4d6d6e3..f449dfd 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.12.0 - github.com/nats-io/nats.go v1.46.0 + github.com/nats-io/nats.go v1.46.1 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 diff --git a/go.sum b/go.sum index 3979437..cc0b771 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74= github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww= -github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8= -github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.46.1 h1:bqQ2ZcxVd2lpYI97xYASeRTY3I5boe/IVmuUDPitHfo= +github.com/nats-io/nats.go v1.46.1/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From c5055a891616153a5e187259394b23ce69a50d0e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 1 Oct 2025 10:57:14 +0200 Subject: [PATCH 235/549] Add commands to the readme on how to build Docker images locally. --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22508e5..48df0c9 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,19 @@ systemctl start signaling.service ### Running with Docker -Official docker containers for the signaling server and -proxy are available on +Official docker images for the signaling server and -proxy are available on Docker Hub at https://hub.docker.com/r/strukturag/nextcloud-spreed-signaling -See the `README.md` in the `docker` subfolder for details. +See the `README.md` in the `docker` subfolder for details on how to use and +configure them. +To build the images locally, run the following commands (replace the parameter +after `-t` with the name the image should be tagged as): + +```bash +docker build -f docker/server/Dockerfile -t nextcloud-spreed-signaling . +docker build -f docker/proxy/Dockerfile -t nextcloud-spreed-signaling-proxy . +``` #### Docker Compose From cfd8cf6718e295e582feddc049b51c9df55fa32d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 26 Sep 2025 17:20:17 +0200 Subject: [PATCH 236/549] Use gvisor checklocks for static lock analysis. Also fix locking issues found along the way. See https://github.com/google/gvisor/tree/master/tools/checklocks for details. --- .github/workflows/lint.yml | 16 +++ Makefile | 6 ++ api_signaling.go | 2 +- async_events.go | 4 + async_events_nats.go | 10 +- backend_configuration.go | 6 +- backend_configuration_stats_prometheus.go | 2 +- backend_configuration_test.go | 4 + backend_server_test.go | 4 +- backend_storage_static.go | 2 + capabilities.go | 29 ++++-- channel_waiter.go | 6 +- client.go | 3 +- client/main.go | 11 +- clientsession.go | 62 ++++++++--- concurrentmap.go | 3 +- dns_monitor.go | 34 ++++--- dns_monitor_test.go | 15 ++- etcd_client.go | 5 +- federation.go | 1 + geoip.go | 52 ++++------ grpc_client.go | 18 +++- grpc_stats_prometheus.go | 2 +- http_client_pool.go | 5 +- hub.go | 54 +++++++--- hub_stats_prometheus.go | 2 +- hub_test.go | 5 +- janus_client.go | 6 +- lru.go | 10 +- mcu_janus.go | 27 ++--- mcu_janus_client.go | 16 +-- mcu_janus_publisher.go | 42 +++++--- mcu_janus_remote_publisher.go | 33 +++--- mcu_janus_remote_subscriber.go | 26 +++-- mcu_janus_subscriber.go | 44 ++++---- mcu_janus_test.go | 37 ++++--- mcu_proxy.go | 50 ++++++--- mcu_proxy_test.go | 27 +++-- mcu_test.go | 6 +- natsclient_loopback.go | 8 +- notifier.go | 4 +- proxy/proxy_remote.go | 119 +++++++++++++++------- proxy/proxy_server.go | 18 ++-- proxy/proxy_server_test.go | 10 +- proxy/proxy_session.go | 27 +++-- proxy/proxy_testclient_test.go | 5 +- proxy_config_etcd.go | 9 +- proxy_config_static.go | 8 +- proxy_config_test.go | 85 +++++++++++----- publisher_stats_counter.go | 2 + room.go | 19 ++-- room_ping.go | 1 + roomsessions_builtin.go | 4 +- server/main.go | 3 +- session.go | 2 +- single_notifier.go | 4 +- testclient_test.go | 3 +- throttle.go | 3 +- transient_data.go | 20 +++- transient_data_test.go | 1 + virtualsession_test.go | 8 +- 61 files changed, 706 insertions(+), 344 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fbce48d..25ab73b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,7 @@ on: - '.golangci.yml' - '**.go' - 'go.*' + - 'Makefile' pull_request: branches: [ master ] paths: @@ -15,6 +16,7 @@ on: - '.golangci.yml' - '**.go' - 'go.*' + - 'Makefile' permissions: contents: read @@ -53,6 +55,20 @@ jobs: run: | GOEXPERIMENT=synctest go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... + checklocks: + name: checklocks + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: "1.24" + + - name: checklocks + run: | + make checklocks + dependencies: name: dependencies runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index b5843dd..de8898e 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,9 @@ $(GOPATHBIN)/protoc-gen-go: go.mod go.sum $(GOPATHBIN)/protoc-gen-go-grpc: go.mod go.sum $(GO) install google.golang.org/grpc/cmd/protoc-gen-go-grpc +$(GOPATHBIN)/checklocks: go.mod go.sum + $(GO) install gvisor.dev/gvisor/tools/checklocks/cmd/checklocks@go + continentmap.go: $(CURDIR)/scripts/get_continent_map.py $@ @@ -108,6 +111,9 @@ vet: test: vet GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) $(TESTARGS) ./... +checklocks: $(GOPATHBIN)/checklocks + GOEXPERIMENT=synctest go vet -vettool=$(GOPATHBIN)/checklocks ./... + cover: vet rm -f cover.out && \ GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... diff --git a/api_signaling.go b/api_signaling.go index 960a987..af5c86b 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -52,7 +52,7 @@ const ( ) var ( - ErrNoSdp = NewError("no_sdp", "Payload does not contain a SDP.") + ErrNoSdp = NewError("no_sdp", "Payload does not contain a SDP.") // +checklocksignore: Global readonly variable. ErrInvalidSdp = NewError("invalid_sdp", "Payload does not contain a valid SDP.") ErrNoCandidate = NewError("no_candidate", "Payload does not contain a candidate.") diff --git a/async_events.go b/async_events.go index 308b3cf..d8bb0c9 100644 --- a/async_events.go +++ b/async_events.go @@ -72,6 +72,7 @@ func NewAsyncEvents(url string) (AsyncEvents, error) { type asyncBackendRoomSubscriber struct { mu sync.Mutex + // +checklocks:mu listeners map[AsyncBackendRoomEventListener]bool } @@ -107,6 +108,7 @@ func (s *asyncBackendRoomSubscriber) removeListener(listener AsyncBackendRoomEve type asyncRoomSubscriber struct { mu sync.Mutex + // +checklocks:mu listeners map[AsyncRoomEventListener]bool } @@ -142,6 +144,7 @@ func (s *asyncRoomSubscriber) removeListener(listener AsyncRoomEventListener) bo type asyncUserSubscriber struct { mu sync.Mutex + // +checklocks:mu listeners map[AsyncUserEventListener]bool } @@ -177,6 +180,7 @@ func (s *asyncUserSubscriber) removeListener(listener AsyncUserEventListener) bo type asyncSessionSubscriber struct { mu sync.Mutex + // +checklocks:mu listeners map[AsyncSessionEventListener]bool } diff --git a/async_events_nats.go b/async_events_nats.go index 4e52487..b5c3ae5 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -231,10 +231,14 @@ type asyncEventsNats struct { mu sync.Mutex client NatsClient + // +checklocks:mu backendRoomSubscriptions map[string]*asyncBackendRoomSubscriberNats - roomSubscriptions map[string]*asyncRoomSubscriberNats - userSubscriptions map[string]*asyncUserSubscriberNats - sessionSubscriptions map[string]*asyncSessionSubscriberNats + // +checklocks:mu + roomSubscriptions map[string]*asyncRoomSubscriberNats + // +checklocks:mu + userSubscriptions map[string]*asyncUserSubscriberNats + // +checklocks:mu + sessionSubscriptions map[string]*asyncSessionSubscriberNats } func NewAsyncEventsNats(client NatsClient) (AsyncEvents, error) { diff --git a/backend_configuration.go b/backend_configuration.go index 2da8703..bc604e6 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -56,7 +56,8 @@ type Backend struct { sessionLimit uint64 sessionsLock sync.Mutex - sessions map[PublicSessionId]bool + // +checklocks:sessionsLock + sessions map[PublicSessionId]bool counted bool } @@ -170,7 +171,8 @@ type BackendStorage interface { } type backendStorageCommon struct { - mu sync.RWMutex + mu sync.RWMutex + // +checklocks:mu backends map[string][]*Backend } diff --git a/backend_configuration_stats_prometheus.go b/backend_configuration_stats_prometheus.go index d19d7f9..13664ad 100644 --- a/backend_configuration_stats_prometheus.go +++ b/backend_configuration_stats_prometheus.go @@ -32,7 +32,7 @@ var ( Name: "session_limit", Help: "The session limit of a backend", }, []string{"backend"}) - statsBackendLimitExceededTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + statsBackendLimitExceededTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ // +checklocksignore: Global readonly variable. Namespace: "signaling", Subsystem: "backend", Name: "session_limit_exceeded_total", diff --git a/backend_configuration_test.go b/backend_configuration_test.go index f882acd..d5d7bdd 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -573,7 +573,9 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { assert.Equal(secret3, string(backends[0].secret)) } + storage.mu.RLock() _, found := storage.backends["domain1.invalid"] + storage.mu.RUnlock() assert.False(found, "Should have removed host information") } @@ -806,6 +808,8 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { <-ch checkStatsValue(t, statsBackendsCurrent, 0) + storage.mu.RLock() _, found := storage.backends["domain1.invalid"] + storage.mu.RUnlock() assert.False(found, "Should have removed host information") } diff --git a/backend_server_test.go b/backend_server_test.go index 7e9f828..5d9eb95 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -628,7 +628,7 @@ func RunTestBackendServer_RoomUpdate(t *testing.T) { emptyProperties := json.RawMessage("{}") backend := hub.backend.GetBackend(u) require.NotNil(backend, "Did not find backend") - room, err := hub.createRoom(roomId, emptyProperties, backend) + room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err, "Could not create room") defer room.Close() @@ -696,7 +696,7 @@ func RunTestBackendServer_RoomDelete(t *testing.T) { emptyProperties := json.RawMessage("{}") backend := hub.backend.GetBackend(u) require.NotNil(backend, "Did not find backend") - _, err = hub.createRoom(roomId, emptyProperties, backend) + _, err = hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) userid := "test-userid" diff --git a/backend_storage_static.go b/backend_storage_static.go index 1fb77b4..f6e32a4 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -151,6 +151,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) func (s *backendStorageStatic) Close() { } +// +checklocks:s.mu func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[string]seenState) { if oldBackends := s.backends[host]; len(oldBackends) > 0 { deleted := 0 @@ -185,6 +186,7 @@ const ( seenDeleted ) +// +checklocks:s.mu func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen map[string]seenState) { for existingIndex, existingBackend := range s.backends[host] { found := false diff --git a/capabilities.go b/capabilities.go index 7f16344..c5bb1e0 100644 --- a/capabilities.go +++ b/capabilities.go @@ -57,16 +57,22 @@ const ( ) var ( - ErrUnexpectedHttpStatus = errors.New("unexpected_http_status") + ErrUnexpectedHttpStatus = errors.New("unexpected_http_status") // +checklocksignore: Global readonly variable. ) type capabilitiesEntry struct { - c *Capabilities - mu sync.RWMutex - nextUpdate time.Time - etag string + mu sync.RWMutex + // +checklocks:mu + c *Capabilities + + // +checklocks:mu + nextUpdate time.Time + // +checklocks:mu + etag string + // +checklocks:mu mustRevalidate bool - capabilities api.StringMap + // +checklocks:mu + capabilities api.StringMap } func newCapabilitiesEntry(c *Capabilities) *capabilitiesEntry { @@ -82,10 +88,12 @@ func (e *capabilitiesEntry) valid(now time.Time) bool { return e.validLocked(now) } +// +checklocksread:e.mu func (e *capabilitiesEntry) validLocked(now time.Time) bool { return e.nextUpdate.After(now) } +// +checklocks:e.mu func (e *capabilitiesEntry) updateRequest(r *http.Request) { if e.etag != "" { r.Header.Set("If-None-Match", e.etag) @@ -99,6 +107,7 @@ func (e *capabilitiesEntry) invalidate() { e.nextUpdate = time.Now() } +// +checklocks:e.mu func (e *capabilitiesEntry) errorIfMustRevalidate(err error) (bool, error) { if !e.mustRevalidate { return false, nil @@ -238,9 +247,11 @@ type Capabilities struct { // Can be overwritten by tests. getNow func() time.Time - version string - pool *HttpClientPool - entries map[string]*capabilitiesEntry + version string + pool *HttpClientPool + // +checklocks:mu + entries map[string]*capabilitiesEntry + // +checklocks:mu nextInvalidate map[string]time.Time buffers BufferPool diff --git a/channel_waiter.go b/channel_waiter.go index 20b0883..dde6cfd 100644 --- a/channel_waiter.go +++ b/channel_waiter.go @@ -26,8 +26,10 @@ import ( ) type ChannelWaiters struct { - mu sync.RWMutex - id uint64 + mu sync.RWMutex + // +checklocks:mu + id uint64 + // +checklocks:mu waiters map[uint64]chan struct{} } diff --git a/client.go b/client.go index 2d06ab8..53f8967 100644 --- a/client.go +++ b/client.go @@ -130,7 +130,8 @@ type Client struct { logRTT bool handlerMu sync.RWMutex - handler ClientHandler + // +checklocks:handlerMu + handler ClientHandler session atomic.Pointer[Session] sessionId atomic.Pointer[PublicSessionId] diff --git a/client/main.go b/client/main.go index 7145fee..3102cff 100644 --- a/client/main.go +++ b/client/main.go @@ -113,7 +113,7 @@ type MessagePayload struct { } type SignalingClient struct { - readyWg *sync.WaitGroup + readyWg *sync.WaitGroup // +checklocksignore: Only written to from constructor. cookie *signaling.SessionIdCodec conn *websocket.Conn @@ -123,10 +123,13 @@ type SignalingClient struct { stopChan chan struct{} - lock sync.Mutex + lock sync.Mutex + // +checklocks:lock privateSessionId signaling.PrivateSessionId - publicSessionId signaling.PublicSessionId - userId string + // +checklocks:lock + publicSessionId signaling.PublicSessionId + // +checklocks:lock + userId string } func NewSignalingClient(cookie *signaling.SessionIdCodec, url string, stats *Stats, readyWg *sync.WaitGroup, doneWg *sync.WaitGroup) (*SignalingClient, error) { diff --git a/clientsession.go b/clientsession.go index 0f5f420..dd52e88 100644 --- a/clientsession.go +++ b/clientsession.go @@ -70,9 +70,11 @@ type ClientSession struct { parseUserData func() (api.StringMap, error) - inCall Flags + inCall Flags + // +checklocks:mu supportsPermissions bool - permissions map[Permission]bool + // +checklocks:mu + permissions map[Permission]bool backend *Backend backendUrl string @@ -80,30 +82,40 @@ type ClientSession struct { mu sync.Mutex + // +checklocks:mu client HandlerClient room atomic.Pointer[Room] roomJoinTime atomic.Int64 federation atomic.Pointer[FederationClient] roomSessionIdLock sync.RWMutex - roomSessionId RoomSessionId + // +checklocks:roomSessionIdLock + roomSessionId RoomSessionId - publisherWaiters ChannelWaiters + publisherWaiters ChannelWaiters // +checklocksignore - publishers map[StreamType]McuPublisher + // +checklocks:mu + publishers map[StreamType]McuPublisher + // +checklocks:mu subscribers map[StreamId]McuSubscriber - pendingClientMessages []*ServerMessage - hasPendingChat bool + // +checklocks:mu + pendingClientMessages []*ServerMessage + // +checklocks:mu + hasPendingChat bool + // +checklocks:mu hasPendingParticipantsUpdate bool + // +checklocks:mu virtualSessions map[*VirtualSession]bool - seenJoinedLock sync.Mutex + seenJoinedLock sync.Mutex + // +checklocks:seenJoinedLock seenJoinedEvents map[PublicSessionId]bool responseHandlersLock sync.Mutex - responseHandlers map[string]ResponseHandlerFunc + // +checklocks:responseHandlersLock + responseHandlers map[string]ResponseHandlerFunc } func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { @@ -197,6 +209,19 @@ func (s *ClientSession) HasPermission(permission Permission) bool { return s.hasPermissionLocked(permission) } +func (s *ClientSession) GetPermissions() []Permission { + s.mu.Lock() + defer s.mu.Unlock() + + result := make([]Permission, len(s.permissions)) + for p, ok := range s.permissions { + if ok { + result = append(result, p) + } + } + return result +} + // HasAnyPermission checks if the session has one of the passed permissions. func (s *ClientSession) HasAnyPermission(permission ...Permission) bool { if len(permission) == 0 { @@ -209,16 +234,16 @@ func (s *ClientSession) HasAnyPermission(permission ...Permission) bool { return s.hasAnyPermissionLocked(permission...) } +// +checklocks:s.mu func (s *ClientSession) hasAnyPermissionLocked(permission ...Permission) bool { if len(permission) == 0 { return false } - return slices.ContainsFunc(permission, func(p Permission) bool { - return s.hasPermissionLocked(p) - }) + return slices.ContainsFunc(permission, s.hasPermissionLocked) } +// +checklocks:s.mu func (s *ClientSession) hasPermissionLocked(permission Permission) bool { if !s.supportsPermissions { // Old-style session that doesn't receive permissions from Nextcloud. @@ -340,6 +365,7 @@ func (s *ClientSession) getRoomJoinTime() time.Time { return time.Unix(0, t) } +// +checklocks:s.mu func (s *ClientSession) releaseMcuObjects() { if len(s.publishers) > 0 { go func(publishers map[StreamType]McuPublisher) { @@ -489,6 +515,7 @@ func (s *ClientSession) LeaveRoomWithMessage(notify bool, message *ClientMessage return s.doLeaveRoom(notify) } +// +checklocks:s.mu func (s *ClientSession) doLeaveRoom(notify bool) *Room { room := s.GetRoom() if room == nil { @@ -543,6 +570,7 @@ func (s *ClientSession) ClearClient(client HandlerClient) { s.clearClientLocked(client) } +// +checklocks:s.mu func (s *ClientSession) clearClientLocked(client HandlerClient) { if s.client == nil { return @@ -563,6 +591,7 @@ func (s *ClientSession) GetClient() HandlerClient { return s.getClientUnlocked() } +// +checklocks:s.mu func (s *ClientSession) getClientUnlocked() HandlerClient { return s.client } @@ -589,6 +618,7 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { return prev } +// +checklocks:s.mu func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, streamType StreamType, offer api.StringMap) { offer_message := &AnswerOfferMessage{ To: s.PublicId(), @@ -617,6 +647,7 @@ func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, stre s.sendMessageUnlocked(response_message) } +// +checklocks:s.mu func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, streamType StreamType, candidate any) { candidate_message := &AnswerOfferMessage{ To: s.PublicId(), @@ -647,6 +678,7 @@ func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, s.sendMessageUnlocked(response_message) } +// +checklocks:s.mu func (s *ClientSession) sendMessageUnlocked(message *ServerMessage) bool { if c := s.getClientUnlocked(); c != nil { if c.SendMessage(message) { @@ -768,6 +800,7 @@ func (e *PermissionError) Error() string { return fmt.Sprintf("permission \"%s\" not found", e.permission) } +// +checklocks:s.mu func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (MediaType, error) { if sdp == nil { // Should have already been checked when data was validated. @@ -832,6 +865,7 @@ func (s *ClientSession) CheckOfferType(streamType StreamType, data *MessageClien return s.checkOfferTypeLocked(streamType, data) } +// +checklocks:s.mu func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *MessageClientMessageData) (MediaType, error) { if streamType == StreamTypeScreen { if !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { @@ -893,6 +927,8 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea if err != nil { return nil, err } + s.mu.Lock() + defer s.mu.Unlock() if s.publishers == nil { s.publishers = make(map[StreamType]McuPublisher) } @@ -915,6 +951,7 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea return publisher, nil } +// +checklocks:s.mu func (s *ClientSession) getPublisherLocked(streamType StreamType) McuPublisher { return s.publishers[streamType] } @@ -1120,6 +1157,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { s.SendMessage(serverMessage) } +// +checklocks:s.mu func (s *ClientSession) storePendingMessage(message *ServerMessage) { if message.IsChatRefresh() { if s.hasPendingChat { diff --git a/concurrentmap.go b/concurrentmap.go index 8bc83a4..255d2a1 100644 --- a/concurrentmap.go +++ b/concurrentmap.go @@ -27,7 +27,8 @@ import ( type ConcurrentMap[K comparable, V any] struct { mu sync.RWMutex - d map[K]V + // +checklocks:mu + d map[K]V // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 } func (m *ConcurrentMap[K, V]) Set(key K, value V) { diff --git a/dns_monitor.go b/dns_monitor.go index d324f04..dcfba64 100644 --- a/dns_monitor.go +++ b/dns_monitor.go @@ -57,11 +57,28 @@ type dnsMonitorEntry struct { hostname string hostIP net.IP - mu sync.Mutex - ips []net.IP + mu sync.Mutex + // +checklocks:mu + ips []net.IP + // +checklocks:mu entries map[*DnsMonitorEntry]bool } +func (e *dnsMonitorEntry) clearRemoved() bool { + e.mu.Lock() + defer e.mu.Unlock() + + deleted := false + for entry := range e.entries { + if entry.entry.Load() == nil { + delete(e.entries, entry) + deleted = true + } + } + + return deleted && len(e.entries) == 0 +} + func (e *dnsMonitorEntry) setIPs(ips []net.IP, fromIP bool) { e.mu.Lock() defer e.mu.Unlock() @@ -134,6 +151,7 @@ func (e *dnsMonitorEntry) removeEntry(entry *DnsMonitorEntry) bool { return len(e.entries) == 0 } +// +checklocks:e.mu func (e *dnsMonitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) { for entry := range e.entries { entry.callback(entry, all, add, keep, remove) @@ -247,7 +265,7 @@ func (m *DnsMonitor) Remove(entry *DnsMonitorEntry) { m.hasRemoved.Store(true) return } - defer m.mu.Unlock() + defer m.mu.Unlock() // +checklocksforce: only executed if the TryLock above succeeded. e, found := m.hostnames[oldEntry.hostname] if !found { @@ -268,15 +286,7 @@ func (m *DnsMonitor) clearRemoved() { defer m.mu.Unlock() for hostname, entry := range m.hostnames { - deleted := false - for e := range entry.entries { - if e.entry.Load() == nil { - delete(entry.entries, e) - deleted = true - } - } - - if deleted && len(entry.entries) == 0 { + if entry.clearRemoved() { delete(m.hostnames, hostname) } } diff --git a/dns_monitor_test.go b/dns_monitor_test.go index fbb7762..daa92ef 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -38,6 +38,7 @@ import ( type mockDnsLookup struct { sync.RWMutex + // +checklocks:RWMutex ips map[string][]net.IP } @@ -118,14 +119,16 @@ func (r *dnsMonitorReceiverRecord) String() string { } var ( - expectNone = &dnsMonitorReceiverRecord{} + expectNone = &dnsMonitorReceiverRecord{} // +checklocksignore: Global readonly variable. ) type dnsMonitorReceiver struct { sync.Mutex - t *testing.T + t *testing.T + // +checklocks:Mutex expected *dnsMonitorReceiverRecord + // +checklocks:Mutex received *dnsMonitorReceiverRecord } @@ -328,13 +331,15 @@ func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { type deadlockMonitorReceiver struct { t *testing.T - monitor *DnsMonitor + monitor *DnsMonitor // +checklocksignore: Only written to from constructor. mu sync.RWMutex wg sync.WaitGroup - entry *DnsMonitorEntry - started chan struct{} + // +checklocks:mu + entry *DnsMonitorEntry + started chan struct{} + // +checklocks:mu triggered bool closed atomic.Bool } diff --git a/etcd_client.go b/etcd_client.go index 459f14b..1780679 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -55,8 +55,9 @@ type EtcdClientWatcher interface { type EtcdClient struct { compatSection string - mu sync.Mutex - client atomic.Value + mu sync.Mutex + client atomic.Value + // +checklocks:mu listeners map[EtcdClientListener]bool } diff --git a/federation.go b/federation.go index 458b265..f491616 100644 --- a/federation.go +++ b/federation.go @@ -98,6 +98,7 @@ type FederationClient struct { resumeId PrivateSessionId hello atomic.Pointer[HelloServerMessage] + // +checklocks:helloMu pendingMessages []*ClientMessage closeOnLeave atomic.Bool diff --git a/geoip.go b/geoip.go index ec461e5..1051324 100644 --- a/geoip.go +++ b/geoip.go @@ -32,7 +32,7 @@ import ( "net/url" "os" "strings" - "sync" + "sync/atomic" "time" "github.com/dlintw/goconf" @@ -59,12 +59,11 @@ type GeoLookup struct { url string isFile bool client http.Client - mu sync.Mutex - lastModifiedHeader string - lastModifiedTime time.Time + lastModifiedHeader atomic.Value + lastModifiedTime atomic.Int64 - reader *maxminddb.Reader + reader atomic.Pointer[maxminddb.Reader] } func NewGeoLookupFromUrl(url string) (*GeoLookup, error) { @@ -87,12 +86,9 @@ func NewGeoLookupFromFile(filename string) (*GeoLookup, error) { } func (g *GeoLookup) Close() { - g.mu.Lock() - if g.reader != nil { - g.reader.Close() - g.reader = nil + if reader := g.reader.Swap(nil); reader != nil { + reader.Close() } - g.mu.Unlock() } func (g *GeoLookup) Update() error { @@ -109,7 +105,7 @@ func (g *GeoLookup) updateFile() error { return err } - if info.ModTime().Equal(g.lastModifiedTime) { + if info.ModTime().UnixNano() == g.lastModifiedTime.Load() { return nil } @@ -125,13 +121,11 @@ func (g *GeoLookup) updateFile() error { metadata := reader.Metadata log.Printf("Using %s GeoIP database from %s (built on %s)", metadata.DatabaseType, g.url, time.Unix(int64(metadata.BuildEpoch), 0).UTC()) - g.mu.Lock() - if g.reader != nil { - g.reader.Close() + if old := g.reader.Swap(reader); old != nil { + old.Close() } - g.reader = reader - g.lastModifiedTime = info.ModTime() - g.mu.Unlock() + + g.lastModifiedTime.Store(info.ModTime().UnixNano()) return nil } @@ -140,8 +134,8 @@ func (g *GeoLookup) updateUrl() error { if err != nil { return err } - if g.lastModifiedHeader != "" { - request.Header.Add("If-Modified-Since", g.lastModifiedHeader) + if header := g.lastModifiedHeader.Load(); header != nil { + request.Header.Add("If-Modified-Since", header.(string)) } response, err := g.client.Do(request) if err != nil { @@ -210,13 +204,11 @@ func (g *GeoLookup) updateUrl() error { metadata := reader.Metadata log.Printf("Using %s GeoIP database from %s (built on %s)", metadata.DatabaseType, g.url, time.Unix(int64(metadata.BuildEpoch), 0).UTC()) - g.mu.Lock() - if g.reader != nil { - g.reader.Close() + if old := g.reader.Swap(reader); old != nil { + old.Close() } - g.reader = reader - g.lastModifiedHeader = response.Header.Get("Last-Modified") - g.mu.Unlock() + + g.lastModifiedHeader.Store(response.Header.Get("Last-Modified")) return nil } @@ -227,14 +219,12 @@ func (g *GeoLookup) LookupCountry(ip net.IP) (string, error) { } `maxminddb:"country"` } - g.mu.Lock() - if g.reader == nil { - g.mu.Unlock() + reader := g.reader.Load() + if reader == nil { return "", ErrDatabaseNotInitialized } - err := g.reader.Lookup(ip, &record) - g.mu.Unlock() - if err != nil { + + if err := reader.Lookup(ip, &record); err != nil { return "", err } diff --git a/grpc_client.go b/grpc_client.go index e92076d..38a4270 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -414,14 +414,18 @@ type GrpcClients struct { mu sync.RWMutex version string + // +checklocks:mu clientsMap map[string]*grpcClientsList - clients []*GrpcClient + // +checklocks:mu + clients []*GrpcClient - dnsMonitor *DnsMonitor + dnsMonitor *DnsMonitor + // +checklocks:mu dnsDiscovery bool - etcdClient *EtcdClient - targetPrefix string + etcdClient *EtcdClient // +checklocksignore: Only written to from constructor. + targetPrefix string + // +checklocks:mu targetInformation map[string]*GrpcTargetInformationEtcd dialOptions atomic.Value // []grpc.DialOption creds credentials.TransportCredentials @@ -432,7 +436,7 @@ type GrpcClients struct { selfCheckWaitGroup sync.WaitGroup closeCtx context.Context - closeFunc context.CancelFunc + closeFunc context.CancelFunc // +checklocksignore: No locking necessary. } func NewGrpcClients(config *goconf.ConfigFile, etcdClient *EtcdClient, dnsMonitor *DnsMonitor, version string) (*GrpcClients, error) { @@ -755,6 +759,9 @@ func (c *GrpcClients) onLookup(entry *DnsMonitorEntry, all []net.IP, added []net } func (c *GrpcClients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool, opts ...grpc.DialOption) error { + c.mu.Lock() + defer c.mu.Unlock() + if !c.etcdClient.IsConfigured() { return fmt.Errorf("no etcd endpoints configured") } @@ -894,6 +901,7 @@ func (c *GrpcClients) EtcdKeyDeleted(client *EtcdClient, key string, prevValue [ c.removeEtcdClientLocked(key) } +// +checklocks:c.mu func (c *GrpcClients) removeEtcdClientLocked(key string) { info, found := c.targetInformation[key] if !found { diff --git a/grpc_stats_prometheus.go b/grpc_stats_prometheus.go index fa37bdc..4697fc3 100644 --- a/grpc_stats_prometheus.go +++ b/grpc_stats_prometheus.go @@ -26,7 +26,7 @@ import ( ) var ( - statsGrpcClients = prometheus.NewGauge(prometheus.GaugeOpts{ + statsGrpcClients = prometheus.NewGauge(prometheus.GaugeOpts{ // +checklocksignore: Global readonly variable. Namespace: "signaling", Subsystem: "grpc", Name: "clients", diff --git a/http_client_pool.go b/http_client_pool.go index 65c19dc..b257962 100644 --- a/http_client_pool.go +++ b/http_client_pool.go @@ -79,9 +79,10 @@ type HttpClientPool struct { mu sync.Mutex transport *http.Transport - clients map[string]*Pool + // +checklocks:mu + clients map[string]*Pool - maxConcurrentRequestsPerHost int + maxConcurrentRequestsPerHost int // +checklocksignore: Only written to from constructor. } func NewHttpClientPool(maxConcurrentRequestsPerHost int, skipVerify bool) (*HttpClientPool, error) { diff --git a/hub.go b/hub.go index 29fb003..90abc74 100644 --- a/hub.go +++ b/hub.go @@ -162,13 +162,17 @@ type Hub struct { mu sync.RWMutex ru sync.RWMutex - sid atomic.Uint64 - clients map[uint64]HandlerClient + sid atomic.Uint64 + // +checklocks:mu + clients map[uint64]HandlerClient + // +checklocks:mu sessions map[uint64]Session - rooms map[string]*Room + // +checklocks:ru + rooms map[string]*Room - roomSessions RoomSessions - roomPing *RoomPing + roomSessions RoomSessions + roomPing *RoomPing + // +checklocks:mu virtualSessions map[PublicSessionId]uint64 decodeCaches []*LruCache[*SessionIdData] @@ -179,12 +183,18 @@ type Hub struct { allowSubscribeAnyStream bool - expiredSessions map[Session]time.Time - anonymousSessions map[*ClientSession]time.Time + // +checklocks:mu + expiredSessions map[Session]time.Time + // +checklocks:mu + anonymousSessions map[*ClientSession]time.Time + // +checklocks:mu expectHelloClients map[HandlerClient]time.Time - dialoutSessions map[*ClientSession]bool - remoteSessions map[*RemoteSession]bool - federatedSessions map[*ClientSession]bool + // +checklocks:mu + dialoutSessions map[*ClientSession]bool + // +checklocks:mu + remoteSessions map[*RemoteSession]bool + // +checklocks:mu + federatedSessions map[*ClientSession]bool backendTimeout time.Duration backend *BackendClient @@ -748,6 +758,7 @@ func (h *Hub) CreateProxyToken(publisherId string) (string, error) { return proxy.createToken(publisherId) } +// +checklocks:h.mu func (h *Hub) checkExpiredSessions(now time.Time) { for session, expires := range h.expiredSessions { if now.After(expires) { @@ -761,6 +772,7 @@ func (h *Hub) checkExpiredSessions(now time.Time) { } } +// +checklocks:h.mu func (h *Hub) checkAnonymousSessions(now time.Time) { for session, timeout := range h.anonymousSessions { if now.After(timeout) { @@ -775,6 +787,7 @@ func (h *Hub) checkAnonymousSessions(now time.Time) { } } +// +checklocks:h.mu func (h *Hub) checkInitialHello(now time.Time) { for client, timeout := range h.expectHelloClients { if now.After(timeout) { @@ -788,10 +801,11 @@ func (h *Hub) checkInitialHello(now time.Time) { func (h *Hub) performHousekeeping(now time.Time) { h.mu.Lock() + defer h.mu.Unlock() + h.checkExpiredSessions(now) h.checkAnonymousSessions(now) h.checkInitialHello(now) - h.mu.Unlock() } func (h *Hub) removeSession(session Session) (removed bool) { @@ -820,6 +834,7 @@ func (h *Hub) removeSession(session Session) (removed bool) { return } +// +checklocksread:h.mu func (h *Hub) hasSessionsLocked(withInternal bool) bool { if withInternal { return len(h.sessions) > 0 @@ -841,6 +856,7 @@ func (h *Hub) startWaitAnonymousSessionRoom(session *ClientSession) { h.startWaitAnonymousSessionRoomLocked(session) } +// +checklocks:h.mu func (h *Hub) startWaitAnonymousSessionRoomLocked(session *ClientSession) { if session.ClientType() == HelloClientTypeInternal { // Internal clients don't need to join a room. @@ -1629,7 +1645,7 @@ func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Roo } else { response.Room = &RoomServerMessage{ RoomId: room.id, - Properties: room.properties, + Properties: room.Properties(), } } return session.SendMessage(response) @@ -1764,7 +1780,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { NewErrorDetail("already_joined", "Already joined this room.", &RoomErrorDetails{ Room: &RoomServerMessage{ RoomId: room.id, - Properties: room.properties, + Properties: room.Properties(), }, }), )) @@ -1907,7 +1923,15 @@ func (h *Hub) removeRoom(room *Room) { h.roomPing.DeleteRoom(room.Id()) } -func (h *Hub) createRoom(id string, properties json.RawMessage, backend *Backend) (*Room, error) { +func (h *Hub) CreateRoom(id string, properties json.RawMessage, backend *Backend) (*Room, error) { + h.ru.Lock() + defer h.ru.Unlock() + + return h.createRoomLocked(id, properties, backend) +} + +// +checklocks:h.ru +func (h *Hub) createRoomLocked(id string, properties json.RawMessage, backend *Backend) (*Room, error) { // Note the write lock must be held. room, err := NewRoom(id, properties, h, h.events, backend) if err != nil { @@ -1947,7 +1971,7 @@ func (h *Hub) processJoinRoom(session *ClientSession, message *ClientMessage, ro r, found := h.rooms[internalRoomId] if !found { var err error - if r, err = h.createRoom(roomId, room.Room.Properties, session.Backend()); err != nil { + if r, err = h.createRoomLocked(roomId, room.Room.Properties, session.Backend()); err != nil { h.ru.Unlock() session.SendMessage(message.NewWrappedErrorServerMessage(err)) // The session (implicitly) left the room due to an error. diff --git a/hub_stats_prometheus.go b/hub_stats_prometheus.go index 0c793d7..2e847c9 100644 --- a/hub_stats_prometheus.go +++ b/hub_stats_prometheus.go @@ -26,7 +26,7 @@ import ( ) var ( - statsHubRoomsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + statsHubRoomsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // +checklocksignore: Global readonly variable. Namespace: "signaling", Subsystem: "hub", Name: "rooms", diff --git a/hub_test.go b/hub_test.go index c6f7425..3b9d28e 100644 --- a/hub_test.go +++ b/hub_test.go @@ -466,6 +466,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re var ( sessionRequestHander struct { sync.Mutex + // +checklocks:Mutex handlers map[*testing.T]func(*BackendClientSessionRequest) } ) @@ -2843,8 +2844,8 @@ func TestInitialRoomPermissions(t *testing.T) { session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) - assert.True(session.HasPermission(PERMISSION_MAY_PUBLISH_AUDIO), "Session %s should have %s, got %+v", session.PublicId(), PERMISSION_MAY_PUBLISH_AUDIO, session.permissions) - assert.False(session.HasPermission(PERMISSION_MAY_PUBLISH_VIDEO), "Session %s should not have %s, got %+v", session.PublicId(), PERMISSION_MAY_PUBLISH_VIDEO, session.permissions) + assert.True(session.HasPermission(PERMISSION_MAY_PUBLISH_AUDIO), "Session %s should have %s, got %+v", session.PublicId(), PERMISSION_MAY_PUBLISH_AUDIO, session.GetPermissions()) + assert.False(session.HasPermission(PERMISSION_MAY_PUBLISH_VIDEO), "Session %s should not have %s, got %+v", session.PublicId(), PERMISSION_MAY_PUBLISH_VIDEO, session.GetPermissions()) } func TestJoinRoomSwitchClient(t *testing.T) { diff --git a/janus_client.go b/janus_client.go index 3aa8f6f..8686dea 100644 --- a/janus_client.go +++ b/janus_client.go @@ -238,15 +238,18 @@ type JanusGateway struct { listener GatewayListener // Sessions is a map of the currently active sessions to the gateway. + // +checklocks:Mutex Sessions map[uint64]*JanusSession // Access to the Sessions map should be synchronized with the Gateway.Lock() // and Gateway.Unlock() methods provided by the embedded sync.Mutex. sync.Mutex + // +checklocks:writeMu conn *websocket.Conn nextTransaction atomic.Uint64 - transactions map[uint64]*transaction + // +checklocks:Mutex + transactions map[uint64]*transaction closer *Closer @@ -592,6 +595,7 @@ type JanusSession struct { Id uint64 // Handles is a map of plugin handles within this session + // +checklocks:Mutex Handles map[uint64]*JanusHandle // Access to the Handles map should be synchronized with the Session.Lock() diff --git a/lru.go b/lru.go index 6bf8bbf..781e8be 100644 --- a/lru.go +++ b/lru.go @@ -32,10 +32,12 @@ type cacheEntry[T any] struct { } type LruCache[T any] struct { - size int - mu sync.Mutex + size int // +checklocksignore: Only written to from constructor. + mu sync.Mutex + // +checklocks:mu entries *list.List - data map[string]*list.Element + // +checklocks:mu + data map[string]*list.Element } func NewLruCache[T any](size int) *LruCache[T] { @@ -88,6 +90,7 @@ func (c *LruCache[T]) Remove(key string) { c.mu.Unlock() } +// +checklocks:c.mu func (c *LruCache[T]) removeOldestLocked() { v := c.entries.Back() if v != nil { @@ -101,6 +104,7 @@ func (c *LruCache[T]) RemoveOldest() { c.mu.Unlock() } +// +checklocks:c.mu func (c *LruCache[T]) removeElement(e *list.Element) { c.entries.Remove(e) entry := e.Value.(*cacheEntry[T]) diff --git a/mcu_janus.go b/mcu_janus.go index df68df0..72c8472 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -221,13 +221,16 @@ type mcuJanus struct { closeChan chan struct{} muClients sync.Mutex - clients map[clientInterface]bool - clientId atomic.Uint64 + // +checklocks:muClients + clients map[clientInterface]bool + clientId atomic.Uint64 + // +checklocks:mu publishers map[StreamId]*mcuJanusPublisher publisherCreated Notifier publisherConnected Notifier - remotePublishers map[StreamId]*mcuJanusRemotePublisher + // +checklocks:mu + remotePublishers map[StreamId]*mcuJanusRemotePublisher reconnectTimer *time.Timer reconnectInterval time.Duration @@ -684,8 +687,6 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu streamType: streamType, maxBitrate: maxBitrate, - handle: handle, - handleId: handle.Id, closeChan: make(chan struct{}, 1), deferred: make(chan func(), 64), }, @@ -693,6 +694,8 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu id: id, settings: settings, } + client.handle.Store(handle) + client.handleId.Store(handle.Id) client.mcuJanusClient.handleEvent = client.handleEvent client.mcuJanusClient.handleHangup = client.handleHangup client.mcuJanusClient.handleDetached = client.handleDetached @@ -701,7 +704,7 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu client.mcuJanusClient.handleMedia = client.handleMedia m.registerClient(client) - log.Printf("Publisher %s is using handle %d", client.id, client.handleId) + log.Printf("Publisher %s is using handle %d", client.id, handle.Id) go client.run(handle, client.closeChan) m.mu.Lock() m.publishers[getStreamId(id, streamType)] = client @@ -781,13 +784,13 @@ func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publ streamType: streamType, maxBitrate: pub.MaxBitrate(), - handle: handle, - handleId: handle.Id, closeChan: make(chan struct{}, 1), deferred: make(chan func(), 64), }, publisher: publisher, } + client.handle.Store(handle) + client.handleId.Store(handle.Id) client.mcuJanusClient.handleEvent = client.handleEvent client.mcuJanusClient.handleHangup = client.handleHangup client.mcuJanusClient.handleDetached = client.handleDetached @@ -865,8 +868,6 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re streamType: streamType, maxBitrate: maxBitrate, - handle: handle, - handleId: handle.Id, closeChan: make(chan struct{}, 1), deferred: make(chan func(), 64), }, @@ -881,6 +882,8 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re port: int(port), rtcpPort: int(rtcp_port), } + pub.handle.Store(handle) + pub.handleId.Store(handle.Id) pub.mcuJanusClient.handleEvent = pub.handleEvent pub.mcuJanusClient.handleHangup = pub.handleHangup pub.mcuJanusClient.handleDetached = pub.handleDetached @@ -946,8 +949,6 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener streamType: publisher.StreamType(), maxBitrate: pub.MaxBitrate(), - handle: handle, - handleId: handle.Id, closeChan: make(chan struct{}, 1), deferred: make(chan func(), 64), }, @@ -956,6 +957,8 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener } client.remote.Store(pub) pub.addRef() + client.handle.Store(handle) + client.handleId.Store(handle.Id) client.mcuJanusClient.handleEvent = client.handleEvent client.mcuJanusClient.handleHangup = client.handleHangup client.mcuJanusClient.handleDetached = client.handleDetached diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 2165509..88d4688 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -27,6 +27,7 @@ import ( "reflect" "strconv" "sync" + "sync/atomic" "github.com/notedit/janus-go" @@ -45,8 +46,8 @@ type mcuJanusClient struct { streamType StreamType maxBitrate int - handle *JanusHandle - handleId uint64 + handle atomic.Pointer[JanusHandle] + handleId atomic.Uint64 closeChan chan struct{} deferred chan func() @@ -81,8 +82,7 @@ func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClient } func (c *mcuJanusClient) closeClient(ctx context.Context) bool { - if handle := c.handle; handle != nil { - c.handle = nil + if handle := c.handle.Swap(nil); handle != nil { close(c.closeChan) if _, err := handle.Detach(ctx); err != nil { if e, ok := err.(*janus.ErrorMsg); !ok || e.Err.Code != JANUS_ERROR_HANDLE_NOT_FOUND { @@ -127,7 +127,7 @@ loop: } func (c *mcuJanusClient) sendOffer(ctx context.Context, offer api.StringMap, callback func(error, api.StringMap)) { - handle := c.handle + handle := c.handle.Load() if handle == nil { callback(ErrNotConnected, nil) return @@ -149,7 +149,7 @@ func (c *mcuJanusClient) sendOffer(ctx context.Context, offer api.StringMap, cal } func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer api.StringMap, callback func(error, api.StringMap)) { - handle := c.handle + handle := c.handle.Load() if handle == nil { callback(ErrNotConnected, nil) return @@ -169,7 +169,7 @@ func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer api.StringMap, c } func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, api.StringMap)) { - handle := c.handle + handle := c.handle.Load() if handle == nil { callback(ErrNotConnected, nil) return @@ -191,7 +191,7 @@ func (c *mcuJanusClient) handleTrickle(event *TrickleMsg) { } func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { - handle := c.handle + handle := c.handle.Load() if handle == nil { callback(ErrNotConnected, nil) return diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index c4b7e7f..c0135f2 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -67,38 +67,38 @@ func (p *mcuJanusPublisher) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Publisher %d: associated room has been destroyed, closing", p.handleId) + log.Printf("Publisher %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom publisher event in %d: %+v", p.handleId, event) + log.Printf("Unsupported videoroom publisher event in %d: %+v", p.handleId.Load(), event) } } else { - log.Printf("Unsupported publisher event in %d: %+v", p.handleId, event) + log.Printf("Unsupported publisher event in %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusPublisher) handleHangup(event *janus.HangupMsg) { - log.Printf("Publisher %d received hangup (%s), closing", p.handleId, event.Reason) + log.Printf("Publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusPublisher) handleDetached(event *janus.DetachedMsg) { - log.Printf("Publisher %d received detached, closing", p.handleId) + log.Printf("Publisher %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusPublisher) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Publisher %d received connected", p.handleId) + log.Printf("Publisher %d received connected", p.handleId.Load()) p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) } func (p *mcuJanusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -129,18 +129,22 @@ func (p *mcuJanusPublisher) NotifyReconnected() { return } - p.handle = handle - p.handleId = handle.Id + if prev := p.handle.Swap(handle); prev != nil { + if _, err := prev.Detach(context.Background()); err != nil { + log.Printf("Error detaching old publisher handle %d: %s", prev.Id, err) + } + } + p.handleId.Store(handle.Id) p.session = session p.roomId = roomId - log.Printf("Publisher %s reconnected on handle %d", p.id, p.handleId) + log.Printf("Publisher %s reconnected on handle %d", p.id, p.handleId.Load()) } func (p *mcuJanusPublisher) Close(ctx context.Context) { notify := false p.mu.Lock() - if handle := p.handle; handle != nil && p.roomId != 0 { + if handle := p.handle.Load(); handle != nil && p.roomId != 0 { destroy_msg := api.StringMap{ "request": "destroy", "room": p.roomId, @@ -399,6 +403,11 @@ func getPublisherRemoteId(id PublicSessionId, remoteId PublicSessionId, hostname } func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { + handle := p.handle.Load() + if handle == nil { + return ErrNotConnected + } + msg := api.StringMap{ "request": "publish_remotely", "room": p.roomId, @@ -408,7 +417,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSe "port": port, "rtcp_port": rtcpPort, } - response, err := p.handle.Request(ctx, msg) + response, err := handle.Request(ctx, msg) if err != nil { return err } @@ -436,13 +445,18 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSe } func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { + handle := p.handle.Load() + if handle == nil { + return ErrNotConnected + } + msg := api.StringMap{ "request": "unpublish_remotely", "room": p.roomId, "publisher_id": streamTypeUserIds[p.streamType], "remote_id": getPublisherRemoteId(p.id, remoteId, hostname, port, rtcpPort), } - response, err := p.handle.Request(ctx, msg) + response, err := handle.Request(ctx, msg) if err != nil { return err } diff --git a/mcu_janus_remote_publisher.go b/mcu_janus_remote_publisher.go index f2bafe6..078adc5 100644 --- a/mcu_janus_remote_publisher.go +++ b/mcu_janus_remote_publisher.go @@ -63,38 +63,38 @@ func (p *mcuJanusRemotePublisher) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Remote publisher %d: associated room has been destroyed, closing", p.handleId) + log.Printf("Remote publisher %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom remote publisher event in %d: %+v", p.handleId, event) + log.Printf("Unsupported videoroom remote publisher event in %d: %+v", p.handleId.Load(), event) } } else { - log.Printf("Unsupported remote publisher event in %d: %+v", p.handleId, event) + log.Printf("Unsupported remote publisher event in %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusRemotePublisher) handleHangup(event *janus.HangupMsg) { - log.Printf("Remote publisher %d received hangup (%s), closing", p.handleId, event.Reason) + log.Printf("Remote publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusRemotePublisher) handleDetached(event *janus.DetachedMsg) { - log.Printf("Remote publisher %d received detached, closing", p.handleId) + log.Printf("Remote publisher %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusRemotePublisher) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Remote publisher %d received connected", p.handleId) + log.Printf("Remote publisher %d received connected", p.handleId.Load()) p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) } func (p *mcuJanusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Remote publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Remote publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Remote publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Remote publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -107,12 +107,16 @@ func (p *mcuJanusRemotePublisher) NotifyReconnected() { return } - p.handle = handle - p.handleId = handle.Id + if prev := p.handle.Swap(handle); prev != nil { + if _, err := prev.Detach(context.Background()); err != nil { + log.Printf("Error detaching old remote publisher handle %d: %s", prev.Id, err) + } + } + p.handleId.Store(handle.Id) p.session = session p.roomId = roomId - log.Printf("Remote publisher %s reconnected on handle %d", p.id, p.handleId) + log.Printf("Remote publisher %s reconnected on handle %d", p.id, p.handleId.Load()) } func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { @@ -125,8 +129,10 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { } p.mu.Lock() - if handle := p.handle; handle != nil { - response, err := p.handle.Request(ctx, api.StringMap{ + defer p.mu.Unlock() + + if handle := p.handle.Load(); handle != nil { + response, err := handle.Request(ctx, api.StringMap{ "request": "remove_remote_publisher", "room": p.roomId, "id": streamTypeUserIds[p.streamType], @@ -154,5 +160,4 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { } p.closeClient(ctx) - p.mu.Unlock() } diff --git a/mcu_janus_remote_subscriber.go b/mcu_janus_remote_subscriber.go index 8f7fe5e..356bb65 100644 --- a/mcu_janus_remote_subscriber.go +++ b/mcu_janus_remote_subscriber.go @@ -41,7 +41,7 @@ func (p *mcuJanusRemoteSubscriber) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Remote subscriber %d: associated room has been destroyed, closing", p.handleId) + log.Printf("Remote subscriber %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "event": // Handle renegotiations, but ignore other events like selected @@ -53,33 +53,33 @@ func (p *mcuJanusRemoteSubscriber) handleEvent(event *janus.EventMsg) { case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom event %s for remote subscriber %d: %+v", videoroom, p.handleId, event) + log.Printf("Unsupported videoroom event %s for remote subscriber %d: %+v", videoroom, p.handleId.Load(), event) } } else { - log.Printf("Unsupported event for remote subscriber %d: %+v", p.handleId, event) + log.Printf("Unsupported event for remote subscriber %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusRemoteSubscriber) handleHangup(event *janus.HangupMsg) { - log.Printf("Remote subscriber %d received hangup (%s), closing", p.handleId, event.Reason) + log.Printf("Remote subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusRemoteSubscriber) handleDetached(event *janus.DetachedMsg) { - log.Printf("Remote subscriber %d received detached, closing", p.handleId) + log.Printf("Remote subscriber %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusRemoteSubscriber) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Remote subscriber %d received connected", p.handleId) + log.Printf("Remote subscriber %d received connected", p.handleId.Load()) p.mcu.SubscriberConnected(p.Id(), p.publisher, p.streamType) } func (p *mcuJanusRemoteSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -98,12 +98,16 @@ func (p *mcuJanusRemoteSubscriber) NotifyReconnected() { return } - p.handle = handle - p.handleId = handle.Id + if prev := p.handle.Swap(handle); prev != nil { + if _, err := prev.Detach(context.Background()); err != nil { + log.Printf("Error detaching old remote subscriber handle %d: %s", prev.Id, err) + } + } + p.handleId.Store(handle.Id) p.roomId = pub.roomId p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) - log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId) + log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) } func (p *mcuJanusRemoteSubscriber) Close(ctx context.Context) { diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 9699cef..28dd31d 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -47,7 +47,7 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId) + log.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "updated": streams, ok := getPluginValue(event.Plugindata, pluginVideoRoom, "streams").([]any) @@ -64,7 +64,7 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { } } - log.Printf("Subscriber %d: received updated event with no active media streams, closing", p.handleId) + log.Printf("Subscriber %d: received updated event with no active media streams, closing", p.handleId.Load()) go p.Close(ctx) case "event": // Handle renegotiations, but ignore other events like selected @@ -76,33 +76,33 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom event %s for subscriber %d: %+v", videoroom, p.handleId, event) + log.Printf("Unsupported videoroom event %s for subscriber %d: %+v", videoroom, p.handleId.Load(), event) } } else { - log.Printf("Unsupported event for subscriber %d: %+v", p.handleId, event) + log.Printf("Unsupported event for subscriber %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusSubscriber) handleHangup(event *janus.HangupMsg) { - log.Printf("Subscriber %d received hangup (%s), closing", p.handleId, event.Reason) + log.Printf("Subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusSubscriber) handleDetached(event *janus.DetachedMsg) { - log.Printf("Subscriber %d received detached, closing", p.handleId) + log.Printf("Subscriber %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusSubscriber) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Subscriber %d received connected", p.handleId) + log.Printf("Subscriber %d received connected", p.handleId.Load()) p.mcu.SubscriberConnected(p.Id(), p.publisher, p.streamType) } func (p *mcuJanusSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId, event.Lost) + log.Printf("Subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -121,12 +121,16 @@ func (p *mcuJanusSubscriber) NotifyReconnected() { return } - p.handle = handle - p.handleId = handle.Id + if prev := p.handle.Swap(handle); prev != nil { + if _, err := prev.Detach(context.Background()); err != nil { + log.Printf("Error detaching old subscriber handle %d: %s", prev.Id, err) + } + } + p.handleId.Store(handle.Id) p.roomId = pub.roomId p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) - log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId) + log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) } func (p *mcuJanusSubscriber) closeClient(ctx context.Context) bool { @@ -152,7 +156,7 @@ func (p *mcuJanusSubscriber) Close(ctx context.Context) { } func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { - handle := p.handle + handle := p.handle.Load() if handle == nil { callback(ErrNotConnected, nil) return @@ -210,15 +214,19 @@ retry: return } - p.handle = handle - p.handleId = handle.Id + if prev := p.handle.Swap(handle); prev != nil { + if _, err := prev.Detach(context.Background()); err != nil { + log.Printf("Error detaching old subscriber handle %d: %s", prev.Id, err) + } + } + p.handleId.Store(handle.Id) p.roomId = pub.roomId p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) p.closeChan = make(chan struct{}, 1) statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Inc() - go p.run(p.handle, p.closeChan) - log.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId) + go p.run(handle, p.closeChan) + log.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId.Load()) goto retry case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: fallthrough @@ -258,7 +266,7 @@ retry: } func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { - handle := p.handle + handle := p.handle.Load() if handle == nil { callback(ErrNotConnected, nil) return diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 1bfa955..aaa33eb 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -58,19 +58,27 @@ type TestJanusGateway struct { sid atomic.Uint64 tid atomic.Uint64 - hid atomic.Uint64 - rid atomic.Uint64 + hid atomic.Uint64 // +checklocksignore: Atomic + rid atomic.Uint64 // +checklocksignore: Atomic mu sync.Mutex - sessions map[uint64]*JanusSession + // +checklocks:mu + sessions map[uint64]*JanusSession + // +checklocks:mu transactions map[uint64]*transaction - handles map[uint64]*TestJanusHandle - rooms map[uint64]*TestJanusRoom - handlers map[string]TestJanusHandler + // +checklocks:mu + handles map[uint64]*TestJanusHandle + // +checklocks:mu + rooms map[uint64]*TestJanusRoom + // +checklocks:mu + handlers map[string]TestJanusHandler - attachCount atomic.Int32 - joinCount atomic.Int32 + // +checklocks:mu + attachCount int + // +checklocks:mu + joinCount int + // +checklocks:mu handleRooms map[*TestJanusHandle]*TestJanusRoom } @@ -142,6 +150,7 @@ func (g *TestJanusGateway) Close() error { return nil } +// +checklocks:g.mu func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body api.StringMap, jsep api.StringMap) any { request := body["request"].(string) switch request { @@ -165,15 +174,18 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan error_code := JANUS_OK if body["ptype"] == "subscriber" { if strings.Contains(g.t.Name(), "NoSuchRoom") { - if g.joinCount.Add(1) == 1 { + g.joinCount++ + if g.joinCount == 1 { error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM } } else if strings.Contains(g.t.Name(), "AlreadyJoined") { - if g.joinCount.Add(1) == 1 { + g.joinCount++ + if g.joinCount == 1 { error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED } } else if strings.Contains(g.t.Name(), "SubscriberTimeout") { - if g.joinCount.Add(1) == 1 { + g.joinCount++ + if g.joinCount == 1 { error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED } } @@ -354,7 +366,8 @@ func (g *TestJanusGateway) processRequest(msg api.StringMap) any { switch method { case "attach": if strings.Contains(g.t.Name(), "AlreadyJoinedAttachError") { - if g.attachCount.Add(1) == 4 { + g.attachCount++ + if g.attachCount == 4 { return &janus.ErrorMsg{ Err: janus.ErrorData{ Code: JANUS_ERROR_UNKNOWN, diff --git a/mcu_proxy.go b/mcu_proxy.go index 5bcf45c..167a2af 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -351,10 +351,11 @@ type mcuProxyConnection struct { closer *Closer closedDone *Closer closed atomic.Bool - conn *websocket.Conn + // +checklocks:mu + conn *websocket.Conn helloProcessed atomic.Bool - connectedSince time.Time + connectedSince atomic.Int64 reconnectTimer *time.Timer reconnectInterval atomic.Int64 shutdownScheduled atomic.Bool @@ -371,15 +372,20 @@ type mcuProxyConnection struct { version atomic.Value features atomic.Value - callbacks map[string]mcuProxyCallback + // +checklocks:mu + callbacks map[string]mcuProxyCallback + // +checklocks:mu deferredCallbacks map[string]mcuProxyCallback publishersLock sync.RWMutex - publishers map[string]*mcuProxyPublisher - publisherIds map[StreamId]PublicSessionId + // +checklocks:publishersLock + publishers map[string]*mcuProxyPublisher + // +checklocks:publishersLock + publisherIds map[StreamId]PublicSessionId subscribersLock sync.RWMutex - subscribers map[string]*mcuProxySubscriber + // +checklocks:subscribersLock + subscribers map[string]*mcuProxySubscriber } func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token string) (*mcuProxyConnection, error) { @@ -486,7 +492,10 @@ func (c *mcuProxyConnection) GetStats() *mcuProxyConnectionStats { c.mu.Lock() if c.conn != nil { result.Connected = true - result.Uptime = &c.connectedSince + if since := c.connectedSince.Load(); since != 0 { + t := time.UnixMicro(since) + result.Uptime = &t + } load := c.Load() result.Load = &load shutdown := c.IsShutdownScheduled() @@ -705,6 +714,7 @@ func (c *mcuProxyConnection) close() { if c.conn != nil { c.conn.Close() c.conn = nil + c.connectedSince.Store(0) if c.trackClose.CompareAndSwap(true, false) { statsConnectedProxyBackendsCurrent.WithLabelValues(c.Country()).Dec() } @@ -810,9 +820,9 @@ func (c *mcuProxyConnection) reconnect() { log.Printf("Connected to %s", c) c.closed.Store(false) c.helloProcessed.Store(false) + c.connectedSince.Store(time.Now().UnixMicro()) c.mu.Lock() - c.connectedSince = time.Now() c.conn = conn c.mu.Unlock() @@ -1157,6 +1167,7 @@ func (c *mcuProxyConnection) sendMessage(msg *ProxyClientMessage) error { return c.sendMessageLocked(msg) } +// +checklocks:c.mu func (c *mcuProxyConnection) sendMessageLocked(msg *ProxyClientMessage) error { if proxyDebugMessages { log.Printf("Send message to %s: %+v", c, msg) @@ -1449,16 +1460,19 @@ type mcuProxy struct { tokenKey *rsa.PrivateKey config ProxyConfig - dialer *websocket.Dialer - connections []*mcuProxyConnection + dialer *websocket.Dialer + connectionsMu sync.RWMutex + // +checklocks:connectionsMu + connections []*mcuProxyConnection + // +checklocks:connectionsMu connectionsMap map[string][]*mcuProxyConnection - connectionsMu sync.RWMutex connRequests atomic.Int64 nextSort atomic.Int64 settings McuSettings - mu sync.RWMutex + mu sync.RWMutex + // +checklocks:mu publishers map[StreamId]*mcuProxyConnection publisherWaiters ChannelWaiters @@ -1615,6 +1629,13 @@ func (m *mcuProxy) createToken(subject string) (string, error) { return tokenString, nil } +func (m *mcuProxy) getConnections() []*mcuProxyConnection { + m.connectionsMu.RLock() + defer m.connectionsMu.RUnlock() + + return m.connections +} + func (m *mcuProxy) hasConnections() bool { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() @@ -1835,7 +1856,10 @@ func (m *mcuProxy) GetServerInfoSfu() *BackendServerInfoSfu { if c.IsConnected() { proxy.Connected = true proxy.Shutdown = internal.MakePtr(c.IsShutdownScheduled()) - proxy.Uptime = &c.connectedSince + if since := c.connectedSince.Load(); since != 0 { + t := time.UnixMicro(since) + proxy.Uptime = &t + } proxy.Version = c.Version() proxy.Features = c.Features() proxy.Country = c.Country() diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index efd87d0..5f3e64e 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -204,7 +204,8 @@ type testProxyServerSubscriber struct { type testProxyServerClient struct { t *testing.T - server *TestProxyServerHandler + server *TestProxyServerHandler + // +checklocks:mu ws *websocket.Conn processMessage proxyServerClientHandler @@ -549,12 +550,15 @@ type TestProxyServerHandler struct { upgrader *websocket.Upgrader country string - mu sync.Mutex - load atomic.Int64 - incoming atomic.Pointer[float64] - outgoing atomic.Pointer[float64] - clients map[PublicSessionId]*testProxyServerClient - publishers map[PublicSessionId]*testProxyServerPublisher + mu sync.Mutex + load atomic.Int64 + incoming atomic.Pointer[float64] + outgoing atomic.Pointer[float64] + // +checklocks:mu + clients map[PublicSessionId]*testProxyServerClient + // +checklocks:mu + publishers map[PublicSessionId]*testProxyServerPublisher + // +checklocks:mu subscribers map[string]*testProxyServerSubscriber wakeupChan chan struct{} @@ -1039,8 +1043,8 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { }, }, 0) - if assert.NotNil(mcu.connections[0].ip) { - assert.True(ip1.Equal(mcu.connections[0].ip), "ip addresses differ: expected %s, got %s", ip1.String(), mcu.connections[0].ip.String()) + if connections := mcu.getConnections(); assert.Len(connections, 1) && assert.NotNil(connections[0].ip) { + assert.True(ip1.Equal(connections[0].ip), "ip addresses differ: expected %s, got %s", ip1.String(), connections[0].ip.String()) } dnsMonitor := mcu.config.(*proxyConfigStatic).dnsMonitor @@ -1744,8 +1748,9 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { } type mockGrpcServerHub struct { - proxy atomic.Pointer[mcuProxy] - sessionsLock sync.Mutex + proxy atomic.Pointer[mcuProxy] + sessionsLock sync.Mutex + // +checklocks:sessionsLock sessionByPublicId map[PublicSessionId]Session } diff --git a/mcu_test.go b/mcu_test.go index 62f6d2a..9f26eb5 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -41,8 +41,10 @@ const ( ) type TestMCU struct { - mu sync.Mutex - publishers map[PublicSessionId]*TestMCUPublisher + mu sync.Mutex + // +checklocks:mu + publishers map[PublicSessionId]*TestMCUPublisher + // +checklocks:mu subscribers map[string]*TestMCUSubscriber } diff --git a/natsclient_loopback.go b/natsclient_loopback.go index 952f27a..6b8f56a 100644 --- a/natsclient_loopback.go +++ b/natsclient_loopback.go @@ -32,10 +32,13 @@ import ( ) type LoopbackNatsClient struct { - mu sync.Mutex + mu sync.Mutex + // +checklocks:mu subscriptions map[string]map[*loopbackNatsSubscription]bool - wakeup sync.Cond + // +checklocks:mu + wakeup sync.Cond + // +checklocks:mu incoming list.List } @@ -65,6 +68,7 @@ func (c *LoopbackNatsClient) processMessages() { } } +// +checklocks:c.mu func (c *LoopbackNatsClient) processMessage(msg *nats.Msg) { subs, found := c.subscriptions[msg.Subject] if !found { diff --git a/notifier.go b/notifier.go index 3466f45..800711d 100644 --- a/notifier.go +++ b/notifier.go @@ -39,7 +39,9 @@ func (w *Waiter) Wait(ctx context.Context) error { type Notifier struct { sync.Mutex - waiters map[string]*Waiter + // +checklocks:Mutex + waiters map[string]*Waiter + // +checklocks:Mutex waiterMap map[string]map[*Waiter]bool } diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index b86dd73..fea973d 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -58,13 +58,14 @@ const ( ) var ( - ErrNotConnected = errors.New("not connected") + ErrNotConnected = errors.New("not connected") // +checklocksignore: Global readonly variable. ) type RemoteConnection struct { - mu sync.Mutex - p *ProxyServer - url *url.URL + mu sync.Mutex + p *ProxyServer + url *url.URL + // +checklocks:mu conn *websocket.Conn closer *signaling.Closer closed atomic.Bool @@ -73,15 +74,20 @@ type RemoteConnection struct { tokenKey *rsa.PrivateKey tlsConfig *tls.Config + // +checklocks:mu connectedSince time.Time reconnectTimer *time.Timer reconnectInterval atomic.Int64 - msgId atomic.Int64 + msgId atomic.Int64 + // +checklocks:mu helloMsgId string - sessionId signaling.PublicSessionId + // +checklocks:mu + sessionId signaling.PublicSessionId - pendingMessages []*signaling.ProxyClientMessage + // +checklocks:mu + pendingMessages []*signaling.ProxyClientMessage + // +checklocks:mu messageCallbacks map[string]chan *signaling.ProxyServerMessage } @@ -151,20 +157,35 @@ func (c *RemoteConnection) reconnect() { c.reconnectInterval.Store(int64(initialReconnectInterval)) - if err := c.sendHello(); err != nil { - log.Printf("Error sending hello request to proxy at %s: %s", c, err) + if !c.sendReconnectHello() || !c.sendPing() { c.scheduleReconnect() return } - if !c.sendPing() { - return - } - go c.readPump(conn) } +func (c *RemoteConnection) sendReconnectHello() bool { + c.mu.Lock() + defer c.mu.Unlock() + + if err := c.sendHello(context.Background()); err != nil { + log.Printf("Error sending hello request to proxy at %s: %s", c, err) + return false + } + + return true +} + func (c *RemoteConnection) scheduleReconnect() { + c.mu.Lock() + defer c.mu.Unlock() + + c.scheduleReconnectLocked() +} + +// +checklocks:c.mu +func (c *RemoteConnection) scheduleReconnectLocked() { if err := c.sendClose(); err != nil && err != ErrNotConnected { log.Printf("Could not send close message to %s: %s", c, err) } @@ -180,7 +201,8 @@ func (c *RemoteConnection) scheduleReconnect() { c.reconnectInterval.Store(interval) } -func (c *RemoteConnection) sendHello() error { +// +checklocks:c.mu +func (c *RemoteConnection) sendHello(ctx context.Context) error { c.helloMsgId = strconv.FormatInt(c.msgId.Add(1), 10) msg := &signaling.ProxyClientMessage{ Id: c.helloMsgId, @@ -200,7 +222,7 @@ func (c *RemoteConnection) sendHello() error { msg.Hello.Token = tokenString } - return c.SendMessage(msg) + return c.sendMessageLocked(ctx, msg) } func (c *RemoteConnection) sendClose() error { @@ -210,6 +232,7 @@ func (c *RemoteConnection) sendClose() error { return c.sendCloseLocked() } +// +checklocks:c.mu func (c *RemoteConnection) sendCloseLocked() error { if c.conn == nil { return ErrNotConnected @@ -276,6 +299,7 @@ func (c *RemoteConnection) SendMessage(msg *signaling.ProxyClientMessage) error return c.sendMessageLocked(context.Background(), msg) } +// +checklocks:c.mu func (c *RemoteConnection) deferMessage(ctx context.Context, msg *signaling.ProxyClientMessage) { c.pendingMessages = append(c.pendingMessages, msg) if ctx.Done() != nil { @@ -294,6 +318,7 @@ func (c *RemoteConnection) deferMessage(ctx context.Context, msg *signaling.Prox } } +// +checklocks:c.mu func (c *RemoteConnection) sendMessageLocked(ctx context.Context, msg *signaling.ProxyClientMessage) error { if c.conn == nil { // Defer until connected. @@ -397,21 +422,24 @@ func (c *RemoteConnection) writePump() { } func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { + c.mu.Lock() + defer c.mu.Unlock() + c.helloMsgId = "" switch msg.Type { case "error": if msg.Error.Code == "no_such_session" { log.Printf("Session %s could not be resumed on %s, registering new", c.sessionId, c) c.sessionId = "" - if err := c.sendHello(); err != nil { + if err := c.sendHello(context.Background()); err != nil { log.Printf("Could not send hello request to %s: %s", c, err) - c.scheduleReconnect() + c.scheduleReconnectLocked() } return } log.Printf("Hello connection to %s failed with %+v, reconnecting", c, msg.Error) - c.scheduleReconnect() + c.scheduleReconnectLocked() case "hello": resumed := c.sessionId == msg.Hello.SessionId c.sessionId = msg.Hello.SessionId @@ -443,21 +471,32 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { } default: log.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) - c.scheduleReconnect() + c.scheduleReconnectLocked() } } -func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { - if msg.Id != "" { - c.mu.Lock() - ch, found := c.messageCallbacks[msg.Id] - if found { - delete(c.messageCallbacks, msg.Id) - c.mu.Unlock() - ch <- msg - return - } +func (c *RemoteConnection) handleCallback(msg *signaling.ProxyServerMessage) bool { + if msg.Id == "" { + return false + } + + c.mu.Lock() + ch, found := c.messageCallbacks[msg.Id] + if !found { c.mu.Unlock() + return false + } + + delete(c.messageCallbacks, msg.Id) + c.mu.Unlock() + + ch <- msg + return true +} + +func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { + if c.handleCallback(msg) { + return } switch msg.Type { @@ -467,7 +506,9 @@ func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { log.Printf("Connection to %s was closed: %s", c, msg.Bye.Reason) if msg.Bye.Reason == "session_expired" { // Don't try to resume expired session. + c.mu.Lock() c.sessionId = "" + c.mu.Unlock() } c.scheduleReconnect() default: @@ -487,21 +528,31 @@ func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { } } -func (c *RemoteConnection) RequestMessage(ctx context.Context, msg *signaling.ProxyClientMessage) (*signaling.ProxyServerMessage, error) { +func (c *RemoteConnection) sendMessageWithCallbackLocked(ctx context.Context, msg *signaling.ProxyClientMessage) (string, <-chan *signaling.ProxyServerMessage, error) { msg.Id = strconv.FormatInt(c.msgId.Add(1), 10) c.mu.Lock() defer c.mu.Unlock() - if err := c.sendMessageLocked(ctx, msg); err != nil { - return nil, err + msg.Id = "" + return "", nil, err } + ch := make(chan *signaling.ProxyServerMessage, 1) c.messageCallbacks[msg.Id] = ch - c.mu.Unlock() + return msg.Id, ch, nil +} + +func (c *RemoteConnection) RequestMessage(ctx context.Context, msg *signaling.ProxyClientMessage) (*signaling.ProxyServerMessage, error) { + id, ch, err := c.sendMessageWithCallbackLocked(ctx, msg) + if err != nil { + return nil, err + } + defer func() { c.mu.Lock() - delete(c.messageCallbacks, msg.Id) + defer c.mu.Unlock() + delete(c.messageCallbacks, id) }() select { diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index b7179c1..c5b6a21 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -133,20 +133,25 @@ type ProxyServer struct { sid atomic.Uint64 cookie *signaling.SessionIdCodec - sessions map[uint64]*ProxySession sessionsLock sync.RWMutex + // +checklocks:sessionsLock + sessions map[uint64]*ProxySession - clients map[string]signaling.McuClient - clientIds map[string]string clientsLock sync.RWMutex + // +checklocks:clientsLock + clients map[string]signaling.McuClient + // +checklocks:clientsLock + clientIds map[string]string tokenId string tokenKey *rsa.PrivateKey - remoteTlsConfig *tls.Config + remoteTlsConfig *tls.Config // +checklocksignore: Only written to from constructor. remoteHostname string - remoteConnections map[string]*RemoteConnection remoteConnectionsLock sync.Mutex - remotePublishers map[string]map[*proxyRemotePublisher]bool + // +checklocks:remoteConnectionsLock + remoteConnections map[string]*RemoteConnection + // +checklocks:remoteConnectionsLock + remotePublishers map[string]map[*proxyRemotePublisher]bool } func IsPublicIP(IP net.IP) bool { @@ -1503,6 +1508,7 @@ func (s *ProxyServer) DeleteSession(id uint64) { s.deleteSessionLocked(id) } +// +checklocks:s.sessionsLock func (s *ProxyServer) deleteSessionLocked(id uint64) { if session, found := s.sessions[id]; found { delete(s.sessions, id) diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index d4e0be5..3d70c97 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -89,7 +89,7 @@ func WaitForProxyServer(ctx context.Context, t *testing.T, proxy *ProxyServer) { case <-ctx.Done(): proxy.clientsLock.Lock() proxy.remoteConnectionsLock.Lock() - assert.Fail(t, "Error waiting for proxy to terminate", "clients %+v / sessions %+v / remoteConnections %+v: %+v", proxy.clients, proxy.sessions, proxy.remoteConnections, ctx.Err()) + assert.Fail(t, "Error waiting for proxy to terminate", "clients %+v / sessions %+v / remoteConnections %+v: %+v", clients, sessions, remoteConnections, ctx.Err()) proxy.remoteConnectionsLock.Unlock() proxy.clientsLock.Unlock() return @@ -936,10 +936,12 @@ func NewUnpublishRemoteTestMCU(t *testing.T) *UnpublishRemoteTestMCU { type UnpublishRemoteTestPublisher struct { TestMCUPublisher - t *testing.T + t *testing.T // +checklocksignore: Only written to from constructor. - mu sync.RWMutex - remoteId signaling.PublicSessionId + mu sync.RWMutex + // +checklocks:mu + remoteId signaling.PublicSessionId + // +checklocks:mu remoteData *remotePublisherData } diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 308708d..c97acc5 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -53,20 +53,27 @@ type ProxySession struct { ctx context.Context closeFunc context.CancelFunc - clientLock sync.Mutex - client *ProxyClient + clientLock sync.Mutex + // +checklocks:clientLock + client *ProxyClient + // +checklocks:clientLock pendingMessages []*signaling.ProxyServerMessage publishersLock sync.Mutex - publishers map[string]signaling.McuPublisher - publisherIds map[signaling.McuPublisher]string + // +checklocks:publishersLock + publishers map[string]signaling.McuPublisher + // +checklocks:publishersLock + publisherIds map[signaling.McuPublisher]string subscribersLock sync.Mutex - subscribers map[string]signaling.McuSubscriber - subscriberIds map[signaling.McuSubscriber]string + // +checklocks:subscribersLock + subscribers map[string]signaling.McuSubscriber + // +checklocks:subscribersLock + subscriberIds map[signaling.McuSubscriber]string remotePublishersLock sync.Mutex - remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData + // +checklocks:remotePublishersLock + remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData } func NewProxySession(proxy *ProxyServer, sid uint64, id signaling.PublicSessionId) *ProxySession { @@ -301,6 +308,8 @@ func (s *ProxySession) DeletePublisher(publisher signaling.McuPublisher) string delete(s.publishers, id) delete(s.publisherIds, publisher) if rp, ok := publisher.(signaling.McuRemoteAwarePublisher); ok { + s.remotePublishersLock.Lock() + defer s.remotePublishersLock.Unlock() delete(s.remotePublishers, rp) } go s.proxy.PublisherDeleted(publisher) @@ -363,8 +372,8 @@ func (s *ProxySession) clearRemotePublishers() { } func (s *ProxySession) clearSubscribers() { - s.publishersLock.Lock() - defer s.publishersLock.Unlock() + s.subscribersLock.Lock() + defer s.subscribersLock.Unlock() go func(subscribers map[string]signaling.McuSubscriber) { for id, subscriber := range subscribers { diff --git a/proxy/proxy_testclient_test.go b/proxy/proxy_testclient_test.go index a239f41..6b983b2 100644 --- a/proxy/proxy_testclient_test.go +++ b/proxy/proxy_testclient_test.go @@ -43,10 +43,11 @@ var ( type ProxyTestClient struct { t *testing.T - assert *assert.Assertions + assert *assert.Assertions // +checklocksignore: Only written to from constructor. require *require.Assertions - mu sync.Mutex + mu sync.Mutex + // +checklocks:mu conn *websocket.Conn messageChan chan []byte readErrorChan chan error diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index 68cc996..faf1582 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -35,12 +35,14 @@ import ( type proxyConfigEtcd struct { mu sync.Mutex - proxy McuProxy + proxy McuProxy // +checklocksignore: Only written to from constructor. client *EtcdClient keyPrefix string - keyInfos map[string]*ProxyInformationEtcd - urlToKey map[string]string + // +checklocks:mu + keyInfos map[string]*ProxyInformationEtcd + // +checklocks:mu + urlToKey map[string]string closeCtx context.Context closeFunc context.CancelFunc @@ -211,6 +213,7 @@ func (p *proxyConfigEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prevVal p.removeEtcdProxyLocked(key) } +// +checklocks:p.mu func (p *proxyConfigEtcd) removeEtcdProxyLocked(key string) { info, found := p.keyInfos[key] if !found { diff --git a/proxy_config_static.go b/proxy_config_static.go index b3b2573..dcdaa59 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -43,9 +43,11 @@ type proxyConfigStatic struct { mu sync.Mutex proxy McuProxy - dnsMonitor *DnsMonitor + dnsMonitor *DnsMonitor + // +checklocks:mu dnsDiscovery bool + // +checklocks:mu connectionsMap map[string]*ipList } @@ -107,7 +109,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool } if dnsDiscovery { - p.connectionsMap[u] = &ipList{ + p.connectionsMap[u] = &ipList{ // +checklocksignore: Not supported for iter loops yet, see https://github.com/google/gvisor/issues/12176 hostname: parsed.Host, } continue @@ -124,7 +126,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool } } - p.connectionsMap[u] = &ipList{ + p.connectionsMap[u] = &ipList{ // +checklocksignore: Not supported for iter loops yet, see https://github.com/google/gvisor/issues/12176 hostname: parsed.Host, } } diff --git a/proxy_config_test.go b/proxy_config_test.go index 2a65b7c..817d268 100644 --- a/proxy_config_test.go +++ b/proxy_config_test.go @@ -52,10 +52,12 @@ type proxyConfigEvent struct { } type mcuProxyForConfig struct { - t *testing.T + t *testing.T + mu sync.Mutex + // +checklocks:mu expected []proxyConfigEvent - mu sync.Mutex - waiters []chan struct{} + // +checklocks:mu + waiters []chan struct{} } func newMcuProxyForConfig(t *testing.T) *mcuProxyForConfig { @@ -63,6 +65,8 @@ func newMcuProxyForConfig(t *testing.T) *mcuProxyForConfig { t: t, } t.Cleanup(func() { + proxy.mu.Lock() + defer proxy.mu.Unlock() assert.Empty(t, proxy.expected) }) return proxy @@ -83,20 +87,29 @@ func (p *mcuProxyForConfig) Expect(action string, url string, ips ...net.IP) { }) } -func (p *mcuProxyForConfig) WaitForEvents(ctx context.Context) { +func (p *mcuProxyForConfig) addWaiter() chan struct{} { p.t.Helper() p.mu.Lock() defer p.mu.Unlock() if len(p.expected) == 0 { - return + return nil } waiter := make(chan struct{}) p.waiters = append(p.waiters, waiter) - p.mu.Unlock() - defer p.mu.Lock() + return waiter +} + +func (p *mcuProxyForConfig) WaitForEvents(ctx context.Context) { + p.t.Helper() + + waiter := p.addWaiter() + if waiter == nil { + return + } + select { case <-ctx.Done(): assert.NoError(p.t, ctx.Err()) @@ -104,6 +117,32 @@ func (p *mcuProxyForConfig) WaitForEvents(ctx context.Context) { } } +func (p *mcuProxyForConfig) getWaitersIfEmpty() []chan struct{} { + p.mu.Lock() + defer p.mu.Unlock() + + if len(p.expected) != 0 { + return nil + } + + waiters := p.waiters + p.waiters = nil + return waiters +} + +func (p *mcuProxyForConfig) getExpectedEvent() *proxyConfigEvent { + p.mu.Lock() + defer p.mu.Unlock() + + if len(p.expected) == 0 { + return nil + } + + expected := p.expected[0] + p.expected = p.expected[1:] + return &expected +} + func (p *mcuProxyForConfig) checkEvent(event *proxyConfigEvent) { p.t.Helper() pc := make([]uintptr, 32) @@ -121,32 +160,24 @@ func (p *mcuProxyForConfig) checkEvent(event *proxyConfigEvent) { } } - p.mu.Lock() - defer p.mu.Unlock() - - if len(p.expected) == 0 { + expected := p.getExpectedEvent() + if expected == nil { assert.Fail(p.t, "no event expected", "received %+v from %s:%d", event, caller.File, caller.Line) return } - defer func() { - if len(p.expected) == 0 { - waiters := p.waiters - p.waiters = nil - p.mu.Unlock() - defer p.mu.Lock() - - for _, ch := range waiters { - ch <- struct{}{} - } - } - }() - - expected := p.expected[0] - p.expected = p.expected[1:] - if !reflect.DeepEqual(expected, *event) { + if !reflect.DeepEqual(expected, event) { assert.Fail(p.t, "wrong event", "expected %+v, received %+v from %s:%d", expected, event, caller.File, caller.Line) } + + waiters := p.getWaitersIfEmpty() + if len(waiters) == 0 { + return + } + + for _, ch := range waiters { + ch <- struct{}{} + } } func (p *mcuProxyForConfig) AddConnection(ignoreErrors bool, url string, ips ...net.IP) error { diff --git a/publisher_stats_counter.go b/publisher_stats_counter.go index ba8b293..10257bb 100644 --- a/publisher_stats_counter.go +++ b/publisher_stats_counter.go @@ -28,7 +28,9 @@ import ( type publisherStatsCounter struct { mu sync.Mutex + // +checklocks:mu streamTypes map[StreamType]bool + // +checklocks:mu subscribers map[string]bool } diff --git a/room.go b/room.go index 9f7a98f..372cf5b 100644 --- a/room.go +++ b/room.go @@ -69,17 +69,23 @@ type Room struct { events AsyncEvents backend *Backend + // +checklocks:mu properties json.RawMessage - closer *Closer - mu *sync.RWMutex + closer *Closer + mu *sync.RWMutex + // +checklocks:mu sessions map[PublicSessionId]Session - + // +checklocks:mu internalSessions map[*ClientSession]bool - virtualSessions map[*VirtualSession]bool - inCallSessions map[Session]bool - roomSessionData map[PublicSessionId]*RoomSessionData + // +checklocks:mu + virtualSessions map[*VirtualSession]bool + // +checklocks:mu + inCallSessions map[Session]bool + // +checklocks:mu + roomSessionData map[PublicSessionId]*RoomSessionData + // +checklocks:mu statsRoomSessionsCurrent *prometheus.GaugeVec // Users currently in the room @@ -590,6 +596,7 @@ func (r *Room) PublishSessionLeft(session Session) { } } +// +checklocksread:r.mu func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSessionId]*InternalSessionData, virtual map[PublicSessionId]*VirtualSessionData) { if r.hub.rpcClients == nil { return nil, nil diff --git a/room_ping.go b/room_ping.go index 8370c7c..3e9d708 100644 --- a/room_ping.go +++ b/room_ping.go @@ -70,6 +70,7 @@ type RoomPing struct { backend *BackendClient capabilities *Capabilities + // +checklocks:mu entries map[string]*pingEntries } diff --git a/roomsessions_builtin.go b/roomsessions_builtin.go index c39926f..02a53c4 100644 --- a/roomsessions_builtin.go +++ b/roomsessions_builtin.go @@ -30,9 +30,11 @@ import ( ) type BuiltinRoomSessions struct { + mu sync.RWMutex + // +checklocks:mu sessionIdToRoomSession map[PublicSessionId]RoomSessionId + // +checklocks:mu roomSessionToSessionid map[RoomSessionId]PublicSessionId - mu sync.RWMutex clients *GrpcClients } diff --git a/server/main.go b/server/main.go index eedf316..6ee0688 100644 --- a/server/main.go +++ b/server/main.go @@ -94,7 +94,8 @@ func createTLSListener(addr string, certFile, keyFile string) (net.Listener, err } type Listeners struct { - mu sync.Mutex + mu sync.Mutex + // +checklocks:mu listeners []net.Listener } diff --git a/session.go b/session.go index a27efea..0a188e8 100644 --- a/session.go +++ b/session.go @@ -44,7 +44,7 @@ var ( // DefaultPermissionOverrides contains permission overrides for users where // no permissions have been set by the server. If a permission is not set in // this map, it's assumed the user has that permission. - DefaultPermissionOverrides = map[Permission]bool{ + DefaultPermissionOverrides = map[Permission]bool{ // +checklocksignore: Global readonly variable. PERMISSION_HIDE_DISPLAYNAMES: false, } ) diff --git a/single_notifier.go b/single_notifier.go index 921542a..2a41f7d 100644 --- a/single_notifier.go +++ b/single_notifier.go @@ -67,7 +67,9 @@ func (w *SingleWaiter) cancel() { type SingleNotifier struct { sync.Mutex - waiter *SingleWaiter + // +checklocks:Mutex + waiter *SingleWaiter + // +checklocks:Mutex waiters map[*SingleWaiter]bool } diff --git a/testclient_test.go b/testclient_test.go index ec00fde..8ce9081 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -202,7 +202,8 @@ type TestClient struct { hub *Hub server *httptest.Server - mu sync.Mutex + mu sync.Mutex + // +checklocks:mu conn *websocket.Conn localAddr net.Addr diff --git a/throttle.go b/throttle.go index 8c96eac..6e78582 100644 --- a/throttle.go +++ b/throttle.go @@ -94,7 +94,8 @@ type memoryThrottler struct { getNow func() time.Time doDelay func(context.Context, time.Duration) - mu sync.RWMutex + mu sync.RWMutex + // +checklocks:mu clients map[string]map[string][]throttleEntry closer *Closer diff --git a/transient_data.go b/transient_data.go index 7e6f7d8..f3fbf9f 100644 --- a/transient_data.go +++ b/transient_data.go @@ -35,11 +35,15 @@ type TransientListener interface { } type TransientData struct { - mu sync.Mutex - data api.StringMap + mu sync.Mutex + // +checklocks:mu + data api.StringMap + // +checklocks:mu listeners map[TransientListener]bool - timers map[string]*time.Timer - ttlCh chan<- struct{} + // +checklocks:mu + timers map[string]*time.Timer + // +checklocks:mu + ttlCh chan<- struct{} } // NewTransientData creates a new transient data container. @@ -47,6 +51,7 @@ func NewTransientData() *TransientData { return &TransientData{} } +// +checklocks:t.mu func (t *TransientData) sendMessageToListener(listener TransientListener, message *ServerMessage) { t.mu.Unlock() defer t.mu.Lock() @@ -54,6 +59,7 @@ func (t *TransientData) sendMessageToListener(listener TransientListener, messag listener.SendMessage(message) } +// +checklocks:t.mu func (t *TransientData) notifySet(key string, prev, value any) { msg := &ServerMessage{ Type: "transient", @@ -69,6 +75,7 @@ func (t *TransientData) notifySet(key string, prev, value any) { } } +// +checklocks:t.mu func (t *TransientData) notifyDeleted(key string, prev any) { msg := &ServerMessage{ Type: "transient", @@ -112,6 +119,7 @@ func (t *TransientData) RemoveListener(listener TransientListener) { delete(t.listeners, listener) } +// +checklocks:t.mu func (t *TransientData) updateTTL(key string, value any, ttl time.Duration) { if ttl <= 0 { delete(t.timers, key) @@ -120,6 +128,7 @@ func (t *TransientData) updateTTL(key string, value any, ttl time.Duration) { } } +// +checklocks:t.mu func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) { if ttl <= 0 { return @@ -147,6 +156,7 @@ func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) t.timers[key] = timer } +// +checklocks:t.mu func (t *TransientData) doSet(key string, value any, prev any, ttl time.Duration) { if t.data == nil { t.data = make(api.StringMap) @@ -210,6 +220,7 @@ func (t *TransientData) CompareAndSetTTL(key string, old, value any, ttl time.Du return true } +// +checklocks:t.mu func (t *TransientData) doRemove(key string, prev any) { delete(t.data, key) if old, found := t.timers[key]; found { @@ -243,6 +254,7 @@ func (t *TransientData) CompareAndRemove(key string, old any) bool { return t.compareAndRemove(key, old) } +// +checklocks:t.mu func (t *TransientData) compareAndRemove(key string, old any) bool { prev, found := t.data[key] if !found || !reflect.DeepEqual(prev, old) { diff --git a/transient_data_test.go b/transient_data_test.go index feea5fb..ca554fc 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -92,6 +92,7 @@ type MockTransientListener struct { sending chan struct{} done chan struct{} + // +checklocks:mu data *TransientData } diff --git a/virtualsession_test.go b/virtualsession_test.go index 2f741df..3e7ddf1 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -45,7 +45,7 @@ func TestVirtualSession(t *testing.T) { backend := &Backend{ id: "compat", } - room, err := hub.createRoom(roomId, emptyProperties, backend) + room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() @@ -229,7 +229,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { backend := &Backend{ id: "compat", } - room, err := hub.createRoom(roomId, emptyProperties, backend) + room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() @@ -439,7 +439,7 @@ func TestVirtualSessionCustomInCall(t *testing.T) { backend := &Backend{ id: "compat", } - room, err := hub.createRoom(roomId, emptyProperties, backend) + room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() @@ -581,7 +581,7 @@ func TestVirtualSessionCleanup(t *testing.T) { backend := &Backend{ id: "compat", } - room, err := hub.createRoom(roomId, emptyProperties, backend) + room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() From b34b8acb6dcce0eb16e32e907ce633aa6130b5e1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 26 Sep 2025 22:23:09 +0200 Subject: [PATCH 237/549] Test and fix reconnections in remote proxy connections. --- proxy/proxy_remote.go | 75 ++++++++++------ proxy/proxy_remote_test.go | 176 +++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 25 deletions(-) create mode 100644 proxy/proxy_remote_test.go diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index fea973d..6eaad05 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -66,9 +66,9 @@ type RemoteConnection struct { p *ProxyServer url *url.URL // +checklocks:mu - conn *websocket.Conn - closer *signaling.Closer - closed atomic.Bool + conn *websocket.Conn + closeCtx context.Context + closeFunc context.CancelFunc // +checklocksignore: Only written to from constructor. tokenId string tokenKey *rsa.PrivateKey @@ -84,6 +84,8 @@ type RemoteConnection struct { helloMsgId string // +checklocks:mu sessionId signaling.PublicSessionId + // +checklocks:mu + helloReceived bool // +checklocks:mu pendingMessages []*signaling.ProxyClientMessage @@ -97,10 +99,13 @@ func NewRemoteConnection(p *ProxyServer, proxyUrl string, tokenId string, tokenK return nil, err } + closeCtx, closeFunc := context.WithCancel(context.Background()) + result := &RemoteConnection{ - p: p, - url: u, - closer: signaling.NewCloser(), + p: p, + url: u, + closeCtx: closeCtx, + closeFunc: closeFunc, tokenId: tokenId, tokenKey: tokenKey, @@ -121,6 +126,12 @@ func (c *RemoteConnection) String() string { return c.url.String() } +func (c *RemoteConnection) SessionId() signaling.PublicSessionId { + c.mu.Lock() + defer c.mu.Unlock() + return c.sessionId +} + func (c *RemoteConnection) reconnect() { u, err := c.url.Parse("proxy") if err != nil { @@ -148,7 +159,6 @@ func (c *RemoteConnection) reconnect() { } log.Printf("Connected to %s", c) - c.closed.Store(false) c.mu.Lock() c.connectedSince = time.Now() @@ -169,7 +179,7 @@ func (c *RemoteConnection) sendReconnectHello() bool { c.mu.Lock() defer c.mu.Unlock() - if err := c.sendHello(context.Background()); err != nil { + if err := c.sendHello(c.closeCtx); err != nil { log.Printf("Error sending hello request to proxy at %s: %s", c, err) return false } @@ -186,10 +196,10 @@ func (c *RemoteConnection) scheduleReconnect() { // +checklocks:c.mu func (c *RemoteConnection) scheduleReconnectLocked() { - if err := c.sendClose(); err != nil && err != ErrNotConnected { + if err := c.sendCloseLocked(); err != nil && err != ErrNotConnected { log.Printf("Could not send close message to %s: %s", c, err) } - c.close() + c.closeLocked() interval := c.reconnectInterval.Load() // Prevent all servers from reconnecting at the same time in case of an @@ -225,13 +235,6 @@ func (c *RemoteConnection) sendHello(ctx context.Context) error { return c.sendMessageLocked(ctx, msg) } -func (c *RemoteConnection) sendClose() error { - c.mu.Lock() - defer c.mu.Unlock() - - return c.sendCloseLocked() -} - // +checklocks:c.mu func (c *RemoteConnection) sendCloseLocked() error { if c.conn == nil { @@ -246,10 +249,17 @@ func (c *RemoteConnection) close() { c.mu.Lock() defer c.mu.Unlock() + c.closeLocked() +} + +// +checklocks:c.mu +func (c *RemoteConnection) closeLocked() { if c.conn != nil { c.conn.Close() c.conn = nil } + c.connectedSince = time.Time{} + c.helloReceived = false } func (c *RemoteConnection) Close() error { @@ -260,15 +270,17 @@ func (c *RemoteConnection) Close() error { return nil } - if !c.closed.CompareAndSwap(false, true) { + if c.closeCtx.Err() != nil { // Already closed return nil } - c.closer.Close() + c.closeFunc() err1 := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{}) err2 := c.conn.Close() c.conn = nil + c.connectedSince = time.Time{} + c.helloReceived = false if err1 != nil { return err1 } @@ -296,7 +308,7 @@ func (c *RemoteConnection) SendMessage(msg *signaling.ProxyClientMessage) error c.mu.Lock() defer c.mu.Unlock() - return c.sendMessageLocked(context.Background(), msg) + return c.sendMessageLocked(c.closeCtx, msg) } // +checklocks:c.mu @@ -338,7 +350,7 @@ func (c *RemoteConnection) sendMessageLocked(ctx context.Context, msg *signaling func (c *RemoteConnection) readPump(conn *websocket.Conn) { defer func() { - if !c.closed.Load() { + if c.closeCtx.Err() == nil { c.scheduleReconnect() } }() @@ -353,7 +365,7 @@ func (c *RemoteConnection) readPump(conn *websocket.Conn) { websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { - if !errors.Is(err, net.ErrClosed) || !c.closed.Load() { + if !errors.Is(err, net.ErrClosed) || c.closeCtx.Err() == nil { log.Printf("Error reading from %s: %v", c, err) } } @@ -415,7 +427,7 @@ func (c *RemoteConnection) writePump() { c.reconnect() case <-ticker.C: c.sendPing() - case <-c.closer.C: + case <-c.closeCtx.Done(): return } } @@ -431,7 +443,7 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { if msg.Error.Code == "no_such_session" { log.Printf("Session %s could not be resumed on %s, registering new", c.sessionId, c) c.sessionId = "" - if err := c.sendHello(context.Background()); err != nil { + if err := c.sendHello(c.closeCtx); err != nil { log.Printf("Could not send hello request to %s: %s", c, err) c.scheduleReconnectLocked() } @@ -443,6 +455,7 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { case "hello": resumed := c.sessionId == msg.Hello.SessionId c.sessionId = msg.Hello.SessionId + c.helloReceived = true country := "" if msg.Hello.Server != nil { if country = msg.Hello.Server.Country; country != "" && !signaling.IsValidCountry(country) { @@ -465,7 +478,7 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { continue } - if err := c.sendMessageLocked(context.Background(), m); err != nil { + if err := c.sendMessageLocked(c.closeCtx, m); err != nil { log.Printf("Could not send pending message %+v to %s: %s", m, c, err) } } @@ -566,3 +579,15 @@ func (c *RemoteConnection) RequestMessage(ctx context.Context, msg *signaling.Pr return response, nil } } + +func (c *RemoteConnection) SendBye() error { + c.mu.Lock() + defer c.mu.Unlock() + if c.conn == nil { + return nil + } + + return c.sendMessageLocked(c.closeCtx, &signaling.ProxyClientMessage{ + Type: "bye", + }) +} diff --git a/proxy/proxy_remote_test.go b/proxy/proxy_remote_test.go new file mode 100644 index 0000000..7702f76 --- /dev/null +++ b/proxy/proxy_remote_test.go @@ -0,0 +1,176 @@ +/** + * 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 main + +import ( + "context" + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func (c *RemoteConnection) WaitForConnection(ctx context.Context) error { + c.mu.Lock() + defer c.mu.Unlock() + + // Only used in tests, so a busy-loop should be fine. + for c.conn == nil || c.connectedSince.IsZero() || !c.helloReceived { + if err := ctx.Err(); err != nil { + return err + } + + c.mu.Unlock() + time.Sleep(time.Nanosecond) + c.mu.Lock() + } + + return nil +} + +func (c *RemoteConnection) WaitForDisconnect(ctx context.Context) error { + c.mu.Lock() + defer c.mu.Unlock() + + initial := c.conn + if initial == nil { + return nil + } + + // Only used in tests, so a busy-loop should be fine. + for c.conn == initial { + if err := ctx.Err(); err != nil { + return err + } + + c.mu.Unlock() + time.Sleep(time.Nanosecond) + c.mu.Lock() + } + return nil +} + +func Test_ProxyRemoteConnectionReconnect(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + server, key, httpserver := newProxyServerForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + conn, err := NewRemoteConnection(server, httpserver.URL, TokenIdForTest, key, nil) + require.NoError(err) + t.Cleanup(func() { + assert.NoError(conn.SendBye()) + assert.NoError(conn.Close()) + }) + + assert.NoError(conn.WaitForConnection(ctx)) + + // Closing the connection will reconnect automatically + conn.mu.Lock() + c := conn.conn + conn.mu.Unlock() + assert.NoError(c.Close()) + assert.NoError(conn.WaitForDisconnect(ctx)) + assert.NoError(conn.WaitForConnection(ctx)) +} + +func Test_ProxyRemoteConnectionReconnectUnknownSession(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + server, key, httpserver := newProxyServerForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + conn, err := NewRemoteConnection(server, httpserver.URL, TokenIdForTest, key, nil) + require.NoError(err) + t.Cleanup(func() { + assert.NoError(conn.SendBye()) + assert.NoError(conn.Close()) + }) + + assert.NoError(conn.WaitForConnection(ctx)) + + // Closing the connection will reconnect automatically + conn.mu.Lock() + c := conn.conn + sessionId := conn.sessionId + conn.mu.Unlock() + var sid uint64 + server.IterateSessions(func(session *ProxySession) { + if session.PublicId() == sessionId { + sid = session.Sid() + } + }) + require.NotEqualValues(0, sid) + server.DeleteSession(sid) + if err := c.Close(); err != nil { + // If an error occurs while closing, it may only be "use of closed network + // connection" because the "DeleteSession" might have already closed the + // socket. + assert.ErrorIs(err, net.ErrClosed) + } + assert.NoError(conn.WaitForDisconnect(ctx)) + assert.NoError(conn.WaitForConnection(ctx)) + assert.NotEqual(sessionId, conn.SessionId()) +} + +func Test_ProxyRemoteConnectionReconnectExpiredSession(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + server, key, httpserver := newProxyServerForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + conn, err := NewRemoteConnection(server, httpserver.URL, TokenIdForTest, key, nil) + require.NoError(err) + t.Cleanup(func() { + assert.NoError(conn.SendBye()) + assert.NoError(conn.Close()) + }) + + assert.NoError(conn.WaitForConnection(ctx)) + + // Closing the connection will reconnect automatically + conn.mu.Lock() + sessionId := conn.sessionId + conn.mu.Unlock() + var session *ProxySession + server.IterateSessions(func(sess *ProxySession) { + if sess.PublicId() == sessionId { + session = sess + } + }) + require.NotNil(session) + session.Close() + assert.NoError(conn.WaitForDisconnect(ctx)) + assert.NoError(conn.WaitForConnection(ctx)) + assert.NotEqual(sessionId, conn.SessionId()) +} From 9dfcf3c75a291ec048e5beaf7f696f88e0d49212 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 1 Oct 2025 10:27:08 +0200 Subject: [PATCH 238/549] Add more proxy tests. --- proxy/proxy_remote_test.go | 36 ++++++++++ proxy/proxy_server.go | 7 ++ proxy/proxy_server_test.go | 141 +++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) diff --git a/proxy/proxy_remote_test.go b/proxy/proxy_remote_test.go index 7702f76..75515f1 100644 --- a/proxy/proxy_remote_test.go +++ b/proxy/proxy_remote_test.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + signaling "github.com/strukturag/nextcloud-spreed-signaling" ) func (c *RemoteConnection) WaitForConnection(ctx context.Context) error { @@ -174,3 +176,37 @@ func Test_ProxyRemoteConnectionReconnectExpiredSession(t *testing.T) { assert.NoError(conn.WaitForConnection(ctx)) assert.NotEqual(sessionId, conn.SessionId()) } + +func Test_ProxyRemoteConnectionCreatePublisher(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + server, key, httpserver := newProxyServerForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + conn, err := NewRemoteConnection(server, httpserver.URL, TokenIdForTest, key, nil) + require.NoError(err) + t.Cleanup(func() { + assert.NoError(conn.SendBye()) + assert.NoError(conn.Close()) + }) + + publisherId := "the-publisher" + hostname := "the-hostname" + port := 1234 + rtcpPort := 2345 + + _, err = conn.RequestMessage(ctx, &signaling.ProxyClientMessage{ + Type: "command", + Command: &signaling.CommandProxyClientMessage{ + Type: "publish-remote", + ClientId: publisherId, + Hostname: hostname, + Port: port, + RtcpPort: rtcpPort, + }, + }) + assert.ErrorContains(err, UnknownClient.Error()) +} diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index c5b6a21..7ee7f47 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -923,6 +923,13 @@ func (s *ProxyServer) addRemotePublisher(publisher *proxyRemotePublisher) { log.Printf("Add remote publisher to %s", publisher.remoteUrl) } +func (s *ProxyServer) hasRemotePublishers() bool { + s.remoteConnectionsLock.Lock() + defer s.remoteConnectionsLock.Unlock() + + return len(s.remotePublishers) > 0 +} + func (s *ProxyServer) removeRemotePublisher(publisher *proxyRemotePublisher) { s.remoteConnectionsLock.Lock() defer s.remoteConnectionsLock.Unlock() diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 3d70c97..25b4311 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -626,6 +626,121 @@ func TestProxyCodecs(t *testing.T) { } } +type StreamTestMCU struct { + TestMCU + + streams []signaling.PublisherStream +} + +type StreamsTestPublisher struct { + TestMCUPublisher + + streams []signaling.PublisherStream +} + +func (m *StreamTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { + return &StreamsTestPublisher{ + TestMCUPublisher: TestMCUPublisher{ + id: id, + sid: sid, + streamType: streamType, + }, + + streams: m.streams, + }, nil +} + +func (p *StreamsTestPublisher) GetStreams(ctx context.Context) ([]signaling.PublisherStream, error) { + return p.streams, nil +} + +func NewStreamTestMCU(t *testing.T, streams []signaling.PublisherStream) *StreamTestMCU { + return &StreamTestMCU{ + TestMCU: TestMCU{ + t: t, + }, + + streams: streams, + } +} + +func TestProxyStreams(t *testing.T) { + signaling.CatchLogForTest(t) + assert := assert.New(t) + require := require.New(t) + proxy, key, server := newProxyServerForTest(t) + + streams := []signaling.PublisherStream{ + { + Mid: "0", + Mindex: 0, + Type: "audio", + Codec: "opus", + }, + { + Mid: "1", + Mindex: 1, + Type: "video", + Codec: "vp8", + }, + } + + mcu := NewStreamTestMCU(t, streams) + proxy.mcu = mcu + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := NewProxyTestClient(ctx, t, server.URL) + defer client.CloseWithBye() + + require.NoError(client.SendHello(key)) + + if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) + } + + _, err := client.RunUntilLoad(ctx, 0) + assert.NoError(err) + + require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + Id: "2345", + Type: "command", + Command: &signaling.CommandProxyClientMessage{ + Type: "create-publisher", + StreamType: signaling.StreamTypeVideo, + }, + })) + + var clientId string + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("2345", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + if assert.NotEmpty(message.Command.Id) { + clientId = message.Command.Id + } + } + } + + require.NotEmpty(clientId, "should have received publisher id") + + require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + Id: "3456", + Type: "command", + Command: &signaling.CommandProxyClientMessage{ + Type: "get-publisher-streams", + ClientId: clientId, + }, + })) + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("3456", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + assert.Equal(clientId, message.Command.Id) + assert.Equal(streams, message.Command.Streams) + } + } +} + type RemoteSubscriberTestMCU struct { TestMCU @@ -648,12 +763,18 @@ type TestRemotePublisher struct { refcnt atomic.Int32 closed context.Context closeFunc context.CancelFunc + listener signaling.McuListener + controller signaling.RemotePublisherController } func (p *TestRemotePublisher) Id() string { return "id" } +func (p *TestRemotePublisher) PublisherId() signaling.PublicSessionId { + return "id" +} + func (p *TestRemotePublisher) Sid() string { return "sid" } @@ -669,6 +790,13 @@ func (p *TestRemotePublisher) MaxBitrate() int { func (p *TestRemotePublisher) Close(ctx context.Context) { if count := p.refcnt.Add(-1); assert.True(p.t, count >= 0) && count == 0 { p.closeFunc() + shortCtx, cancel := context.WithTimeout(ctx, time.Millisecond) + defer cancel() + // Won't be able to preform remote call to actually stop publishing. + if err := p.controller.StopPublishing(shortCtx, p); !errors.Is(err, context.DeadlineExceeded) { + assert.NoError(p.t, err) + } + p.listener.PublisherClosed(p) } } @@ -684,6 +812,13 @@ func (p *TestRemotePublisher) RtcpPort() int { return 2 } +func (p *TestRemotePublisher) SetMedia(mediaType signaling.MediaType) { +} + +func (p *TestRemotePublisher) HasMedia(mediaType signaling.MediaType) bool { + return false +} + func (m *RemoteSubscriberTestMCU) NewRemotePublisher(ctx context.Context, listener signaling.McuListener, controller signaling.RemotePublisherController, streamType signaling.StreamType) (signaling.McuRemotePublisher, error) { require.Nil(m.t, m.publisher) assert.EqualValues(m.t, "video", streamType) @@ -694,6 +829,8 @@ func (m *RemoteSubscriberTestMCU) NewRemotePublisher(ctx context.Context, listen streamType: streamType, closed: closeCtx, closeFunc: closeFunc, + listener: listener, + controller: controller, } m.publisher.refcnt.Add(1) return m.publisher, nil @@ -813,6 +950,8 @@ func TestProxyRemoteSubscriber(t *testing.T) { } } + assert.True(proxy.hasRemotePublishers()) + require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ Id: "3456", Type: "command", @@ -841,6 +980,8 @@ func TestProxyRemoteSubscriber(t *testing.T) { assert.Fail("publisher was not closed") } } + + assert.False(proxy.hasRemotePublishers()) } func TestProxyCloseRemoteOnSessionClose(t *testing.T) { From d295ebdf8153112eaafe9ef67b2a63e74add723a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 1 Oct 2025 16:39:25 +0200 Subject: [PATCH 239/549] Add test for remote publishers. --- mcu_janus_publisher_test.go | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/mcu_janus_publisher_test.go b/mcu_janus_publisher_test.go index 37aa890..98ae23b 100644 --- a/mcu_janus_publisher_test.go +++ b/mcu_janus_publisher_test.go @@ -22,9 +22,15 @@ package signaling import ( + "context" + "sync/atomic" "testing" + "github.com/notedit/janus-go" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func TestGetFmtpValueH264(t *testing.T) { @@ -94,3 +100,78 @@ func TestGetFmtpValueVP9(t *testing.T) { } } } + +func TestJanusPublisherRemote(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + var remotePublishId atomic.Value + + remoteId := PublicSessionId("the-remote-id") + hostname := "remote.server" + port := 12345 + rtcpPort := 23456 + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "publish_remotely": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + if value, found := api.GetStringMapString[string](body, "host"); assert.True(found) { + assert.Equal(hostname, value) + } + if value, found := api.GetStringMapEntry[float64](body, "port"); assert.True(found) { + assert.EqualValues(port, value) + } + if value, found := api.GetStringMapEntry[float64](body, "rtcp_port"); assert.True(found) { + assert.EqualValues(rtcpPort, value) + } + if value, found := api.GetStringMapString[string](body, "remote_id"); assert.True(found) { + prev := remotePublishId.Swap(value) + assert.Nil(prev, "should not have previous value") + } + + return &janus.SuccessMsg{ + Data: janus.SuccessData{ + ID: 1, + }, + }, nil + }, + "unpublish_remotely": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + if value, found := api.GetStringMapString[string](body, "remote_id"); assert.True(found) { + if prev := remotePublishId.Load(); assert.NotNil(prev, "should have previous value") { + assert.Equal(prev, value) + } + } + return &janus.SuccessMsg{ + Data: janus.SuccessData{ + ID: 1, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + require.Implements((*McuRemoteAwarePublisher)(nil), pub) + remotePub, _ := pub.(McuRemoteAwarePublisher) + + if assert.NoError(remotePub.PublishRemote(ctx, remoteId, hostname, port, rtcpPort)) { + assert.NoError(remotePub.UnpublishRemote(ctx, remoteId, hostname, port, rtcpPort)) + } +} From d80b9072a7095b5f187619d519de2dd512334492 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 1 Oct 2025 17:09:19 +0200 Subject: [PATCH 240/549] Test subscriber closing on all-inactive streams. --- mcu_janus_test.go | 174 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 157 insertions(+), 17 deletions(-) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index aaa33eb..db8b8b2 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -150,6 +150,18 @@ func (g *TestJanusGateway) Close() error { return nil } +func (g *TestJanusGateway) simulateEvent(delay time.Duration, session *JanusSession, handle *TestJanusHandle, event any) { + go func() { + time.Sleep(delay) + session.Lock() + h, found := session.Handles[handle.id] + session.Unlock() + if found { + h.Events <- event + } + }() +} + // +checklocks:g.mu func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body api.StringMap, jsep api.StringMap) any { request := body["request"].(string) @@ -248,6 +260,33 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan } sdp := publisher.sdp.Load() + + // Simulate "connected" event for subscriber. + g.simulateEvent(15*time.Millisecond, session, handle, &janus.WebRTCUpMsg{ + Session: session.Id, + Handle: handle.id, + }) + + if strings.Contains(g.t.Name(), "CloseEmptyStreams") { + // Simulate stream update event with no active streams. + g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "videoroom": "updated", + "streams": []any{ + api.StringMap{ + "type": "audio", + "active": false, + }, + }, + }, + }, + }) + } + return &janus.EventMsg{ Jsep: api.StringMap{ "type": "offer", @@ -317,23 +356,13 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan case "configure": if sdp, found := jsep["sdp"]; found { handle.sdp.Store(sdp.(string)) - // Simulate "connected" event. - go func() { - if strings.Contains(g.t.Name(), "SubscriberTimeout") { - return - } - - time.Sleep(10 * time.Millisecond) - session.Lock() - h, found := session.Handles[handle.id] - session.Unlock() - if found { - h.Events <- &janus.WebRTCUpMsg{ - Session: session.Id, - Handle: h.Id, - } - } - }() + if !strings.Contains(g.t.Name(), "SubscriberTimeout") { + // Simulate "connected" event for publisher. + g.simulateEvent(10*time.Millisecond, session, handle, &janus.WebRTCUpMsg{ + Session: session.Id, + Handle: handle.id, + }) + } } } } @@ -1594,3 +1623,114 @@ func Test_JanusSubscriberTimeout(t *testing.T) { client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } + +func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { + ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) + t.Cleanup(func() { + if !t.Failed() { + checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + } + }) + + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + + sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) + require.NotNil(sess2) + session2 := sess2.(*ClientSession) + + sub := session2.GetSubscriber(hello1.Hello.SessionId, StreamTypeVideo) + require.NotNil(sub) + + subscriber := sub.(*mcuJanusSubscriber) + handle := subscriber.handle.Load() + require.NotNil(handle) + + for ctx.Err() == nil { + if handle = subscriber.handle.Load(); handle == nil { + break + } + + time.Sleep(time.Millisecond) + } + + assert.Nil(handle, "subscriber should have been closed") +} From a697c4068698ecfeadaa22537b91d738c106d1cf Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 2 Oct 2025 16:06:28 +0200 Subject: [PATCH 241/549] Add more subscriber tests. --- mcu_janus_subscriber.go | 3 + mcu_janus_test.go | 237 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 28dd31d..dfb21fe 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -71,7 +71,10 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { // substream / temporal layer. if getPluginStringValue(event.Plugindata, pluginVideoRoom, "configured") == "ok" && event.Jsep != nil && event.Jsep["type"] == "offer" && event.Jsep["sdp"] != nil { + log.Printf("Subscriber %d: received updated offer", p.handleId.Load()) p.listener.OnUpdateOffer(p, event.Jsep) + } else { + log.Printf("Subscriber %d: received unsupported event %+v", p.handleId.Load(), event) } case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. diff --git a/mcu_janus_test.go b/mcu_janus_test.go index db8b8b2..641a0f5 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -287,6 +287,39 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan }) } + if strings.Contains(g.t.Name(), "SubscriberRoomDestroyed") { + // Simulate event that subscriber room has been destroyed. + g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "videoroom": "destroyed", + }, + }, + }) + } + + if strings.Contains(g.t.Name(), "SubscriberUpdateOffer") { + // Simulate event that subscriber receives new offer. + g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "videoroom": "event", + "configured": "ok", + }, + }, + Jsep: map[string]any{ + "type": "offer", + "sdp": MockSdpOfferAudioOnly, + }, + }) + } + return &janus.EventMsg{ Jsep: api.StringMap{ "type": "offer", @@ -1734,3 +1767,207 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { assert.Nil(handle, "subscriber should have been closed") } + +func Test_JanusSubscriberRoomDestroyed(t *testing.T) { + ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) + t.Cleanup(func() { + if !t.Failed() { + checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + } + }) + + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + + sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) + require.NotNil(sess2) + session2 := sess2.(*ClientSession) + + sub := session2.GetSubscriber(hello1.Hello.SessionId, StreamTypeVideo) + require.NotNil(sub) + + subscriber := sub.(*mcuJanusSubscriber) + handle := subscriber.handle.Load() + require.NotNil(handle) + + for ctx.Err() == nil { + if handle = subscriber.handle.Load(); handle == nil { + break + } + + time.Sleep(time.Millisecond) + } + + assert.Nil(handle, "subscriber should have been closed") +} + +func Test_JanusSubscriberUpdateOffer(t *testing.T) { + ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) + t.Cleanup(func() { + if !t.Failed() { + checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) + } + }) + + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.registerHandlers(map[string]TestJanusHandler{ + "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.id) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + + // Test MCU will trigger an updated offer. + client2.RunUntilOffer(ctx, MockSdpOfferAudioOnly) +} From d83eece45c8b4696a5baaa19fe598ce172bade06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:01:55 +0000 Subject: [PATCH 242/549] Bump peter-evans/create-or-update-comment from 4 to 5 Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 4 to 5. - [Release notes](https://github.com/peter-evans/create-or-update-comment/releases) - [Commits](https://github.com/peter-evans/create-or-update-comment/compare/v4...v5) --- updated-dependencies: - dependency-name: peter-evans/create-or-update-comment dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/command-rebase.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/command-rebase.yml b/.github/workflows/command-rebase.yml index 4e58a56..be2d0ed 100644 --- a/.github/workflows/command-rebase.yml +++ b/.github/workflows/command-rebase.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Add reaction on start - uses: peter-evans/create-or-update-comment@v4 + uses: peter-evans/create-or-update-comment@v5 with: token: ${{ secrets.COMMAND_BOT_PAT }} repository: ${{ github.event.repository.full_name }} @@ -42,7 +42,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }} - name: Add reaction on failure - uses: peter-evans/create-or-update-comment@v4 + uses: peter-evans/create-or-update-comment@v5 if: failure() with: token: ${{ secrets.COMMAND_BOT_PAT }} From c2d5facce213910f9080b72c27aba561ae89b8cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:01:42 +0000 Subject: [PATCH 243/549] Bump google.golang.org/grpc from 1.75.1 to 1.76.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.1 to 1.76.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.75.1...v1.76.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.76.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f449dfd..c1ad051 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.5 go.etcd.io/etcd/server/v3 v3.6.5 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.75.1 + google.golang.org/grpc v1.76.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.9 ) @@ -94,8 +94,8 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.13.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index cc0b771..13323ce 100644 --- a/go.sum +++ b/go.sum @@ -225,12 +225,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= From 4f5b4e807f58669cfab8467910834a4ae50fd954 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:01:51 +0000 Subject: [PATCH 244/549] Bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f1b2c4d..5f93431 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,12 +39,12 @@ jobs: uses: actions/checkout@v5 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From 28dfc3f53247b5eddfe6f8a936188eae110aea43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 06:01:48 +0000 Subject: [PATCH 245/549] Bump google.golang.org/protobuf from 1.36.9 to 1.36.10 Bumps google.golang.org/protobuf from 1.36.9 to 1.36.10. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c1ad051..ad70e75 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/grpc v1.76.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.36.9 + google.golang.org/protobuf v1.36.10 ) require ( diff --git a/go.sum b/go.sum index 13323ce..38c3a43 100644 --- a/go.sum +++ b/go.sum @@ -233,8 +233,8 @@ google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From ac2063d4847d17867dbeee09f00ff6b9d154b9d9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 8 Oct 2025 10:43:32 +0200 Subject: [PATCH 246/549] Also allow access from ::1 by default. --- allowed_ips.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/allowed_ips.go b/allowed_ips.go index 6693680..9325211 100644 --- a/allowed_ips.go +++ b/allowed_ips.go @@ -103,6 +103,10 @@ func DefaultAllowedIps() *AllowedIps { IP: net.ParseIP("127.0.0.1"), Mask: net.CIDRMask(32, 32), }, + { + IP: net.ParseIP("::1"), + Mask: net.CIDRMask(128, 128), + }, } result := &AllowedIps{ @@ -115,6 +119,7 @@ var ( privateIpNets = []string{ // Loopback addresses. "127.0.0.0/8", + "::1", // Private addresses. "10.0.0.0/8", "172.16.0.0/12", From 41c18d253138a832b0f33f6b862924ba2a23d03f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 8 Oct 2025 10:46:06 +0200 Subject: [PATCH 247/549] Protect access to the debug pprof handlers. --- backend_server.go | 44 ++++++++++++++++++++++++++++++++++--------- proxy.conf.in | 9 ++++++--- proxy/proxy_server.go | 23 ++++++++++++++-------- server.conf.in | 9 ++++++--- server/main.go | 14 -------------- 5 files changed, 62 insertions(+), 37 deletions(-) diff --git a/backend_server.go b/backend_server.go index 53672c7..ed6ee3c 100644 --- a/backend_server.go +++ b/backend_server.go @@ -34,9 +34,11 @@ import ( "log" "net" "net/http" + "net/http/pprof" "net/url" "reflect" "regexp" + runtimepprof "runtime/pprof" "slices" "strings" "sync" @@ -66,6 +68,7 @@ type BackendServer struct { roomSessions RoomSessions version string + debug bool welcomeMessage string turnapikey string @@ -111,8 +114,8 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac if !statsAllowedIps.Empty() { log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1") statsAllowedIps = DefaultAllowedIps() + log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } invalidSecret := make([]byte, 32) @@ -120,11 +123,14 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac return nil, err } + debug, _ := config.GetBool("app", "debug") + result := &BackendServer{ hub: hub, events: hub.events, roomSessions: hub.roomSessions, version: version, + debug: debug, turnapikey: turnapikey, turnsecret: []byte(turnsecret), @@ -145,8 +151,8 @@ func (b *BackendServer) Reload(config *goconf.ConfigFile) { if !statsAllowedIps.Empty() { log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1") statsAllowedIps = DefaultAllowedIps() + log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } b.statsAllowedIps.Store(statsAllowedIps) } else { @@ -167,22 +173,42 @@ func (b *BackendServer) Start(r *mux.Router) error { b.welcomeMessage = string(welcomeMessage) + "\n" + if b.debug { + log.Println("Installing debug handlers in \"/debug/pprof\"") + s := r.PathPrefix("/debug/pprof").Subrouter() + s.HandleFunc("", b.setCommonHeaders(b.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/debug/pprof/", http.StatusTemporaryRedirect) + }))) + s.HandleFunc("/", b.setCommonHeaders(b.validateStatsRequest(pprof.Index))) + s.HandleFunc("/cmdline", b.setCommonHeaders(b.validateStatsRequest(pprof.Cmdline))) + s.HandleFunc("/profile", b.setCommonHeaders(b.validateStatsRequest(pprof.Profile))) + s.HandleFunc("/symbol", b.setCommonHeaders(b.validateStatsRequest(pprof.Symbol))) + s.HandleFunc("/trace", b.setCommonHeaders(b.validateStatsRequest(pprof.Trace))) + for _, profile := range runtimepprof.Profiles() { + name := profile.Name() + handler := pprof.Handler(name) + s.HandleFunc("/"+name, b.setCommonHeaders(b.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) { + handler.ServeHTTP(w, r) + }))) + } + } + s := r.PathPrefix("/api/v1").Subrouter() - s.HandleFunc("/welcome", b.setComonHeaders(b.welcomeFunc)).Methods("GET") - s.HandleFunc("/room/{roomid}", b.setComonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST") - s.HandleFunc("/stats", b.setComonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET") - s.HandleFunc("/serverinfo", b.setComonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET") + s.HandleFunc("/welcome", b.setCommonHeaders(b.welcomeFunc)).Methods("GET") + s.HandleFunc("/room/{roomid}", b.setCommonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST") + s.HandleFunc("/stats", b.setCommonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET") + s.HandleFunc("/serverinfo", b.setCommonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET") // Expose prometheus metrics at "/metrics". - r.HandleFunc("/metrics", b.setComonHeaders(b.validateStatsRequest(b.metricsHandler))).Methods("GET") + r.HandleFunc("/metrics", b.setCommonHeaders(b.validateStatsRequest(b.metricsHandler))).Methods("GET") // Provide a REST service to get TURN credentials. // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 - r.HandleFunc("/turn/credentials", b.setComonHeaders(b.getTurnCredentials)).Methods("GET") + r.HandleFunc("/turn/credentials", b.setCommonHeaders(b.getTurnCredentials)).Methods("GET") return nil } -func (b *BackendServer) setComonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { +func (b *BackendServer) setCommonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Server", "nextcloud-spreed-signaling/"+b.version) w.Header().Set("X-Spreed-Signaling-Features", strings.Join(b.hub.info.Features, ", ")) diff --git a/proxy.conf.in b/proxy.conf.in index 22174ac..ac3482b 100644 --- a/proxy.conf.in +++ b/proxy.conf.in @@ -4,7 +4,9 @@ #listen = 127.0.0.1:9090 [app] -# Set to "true" to install pprof debug handlers. +# Set to "true" to install pprof debug handlers. Access will only be possible +# from IPs allowed through the "allowed_ips" option below. +# # See "https://golang.org/pkg/net/http/pprof/" for further information. #debug = false @@ -90,8 +92,9 @@ url = ws://localhost:8188/ #blockedcandidates = 1.2.3.0/24 [stats] -# Comma-separated list of IP addresses that are allowed to access the stats -# endpoint. Leave empty (or commented) to only allow access from "127.0.0.1". +# Comma-separated list of IP addresses that are allowed to access the debug, +# stats and metrics endpoints. +# Leave empty (or commented) to only allow access from localhost. #allowed_ips = [etcd] diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index b7179c1..89bbd9a 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -246,8 +246,8 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* if !statsAllowedIps.Empty() { log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1") statsAllowedIps = signaling.DefaultAllowedIps() + log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } trustedProxies, _ := config.GetString("app", "trustedproxies") @@ -377,14 +377,21 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* if debug, _ := config.GetBool("app", "debug"); debug { log.Println("Installing debug handlers in \"/debug/pprof\"") - r.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) - r.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) - r.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) - r.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) - r.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) + s := r.PathPrefix("/debug/pprof").Subrouter() + s.HandleFunc("", result.setCommonHeaders(result.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/debug/pprof/", http.StatusTemporaryRedirect) + }))) + s.HandleFunc("/", result.setCommonHeaders(result.validateStatsRequest(pprof.Index))) + s.HandleFunc("/cmdline", result.setCommonHeaders(result.validateStatsRequest(pprof.Cmdline))) + s.HandleFunc("/profile", result.setCommonHeaders(result.validateStatsRequest(pprof.Profile))) + s.HandleFunc("/symbol", result.setCommonHeaders(result.validateStatsRequest(pprof.Symbol))) + s.HandleFunc("/trace", result.setCommonHeaders(result.validateStatsRequest(pprof.Trace))) for _, profile := range runtimepprof.Profiles() { name := profile.Name() - r.Handle("/debug/pprof/"+name, pprof.Handler(name)) + handler := pprof.Handler(name) + s.HandleFunc("/"+name, result.setCommonHeaders(result.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) { + handler.ServeHTTP(w, r) + }))) } } @@ -594,8 +601,8 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { if !statsAllowedIps.Empty() { log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1") statsAllowedIps = signaling.DefaultAllowedIps() + log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } s.statsAllowedIps.Store(statsAllowedIps) } else { diff --git a/server.conf.in b/server.conf.in index 8f437e3..2e0a7cf 100644 --- a/server.conf.in +++ b/server.conf.in @@ -25,7 +25,9 @@ certificate = /etc/nginx/ssl/server.crt key = /etc/nginx/ssl/server.key [app] -# Set to "true" to install pprof debug handlers. +# Set to "true" to install pprof debug handlers. Access will only be possible +# from IPs allowed through the "allowed_ips" option below. +# # See "https://golang.org/pkg/net/http/pprof/" for further information. debug = false @@ -270,8 +272,9 @@ connectionsperhost = 8 #SA = NA [stats] -# Comma-separated list of IP addresses that are allowed to access the stats -# endpoint. Leave empty (or commented) to only allow access from "127.0.0.1". +# Comma-separated list of IP addresses that are allowed to access the debug, +# stats and metrics endpoints. +# Leave empty (or commented) to only allow access from localhost. #allowed_ips = [etcd] diff --git a/server/main.go b/server/main.go index eedf316..80474a8 100644 --- a/server/main.go +++ b/server/main.go @@ -30,7 +30,6 @@ import ( "log" "net" "net/http" - "net/http/pprof" "os" "os/signal" "runtime" @@ -310,19 +309,6 @@ func main() { log.Fatal("Could not start backend server: ", err) } - if debug, _ := config.GetBool("app", "debug"); debug { - log.Println("Installing debug handlers in \"/debug/pprof\"") - r.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) - r.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) - r.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) - r.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) - r.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) - for _, profile := range runtimepprof.Profiles() { - name := profile.Name() - r.Handle("/debug/pprof/"+name, pprof.Handler(name)) - } - } - var listeners Listeners if saddr, _ := signaling.GetStringOptionWithEnv(config, "https", "listen"); saddr != "" { From 9429119198b45f6ef2bff6e0fac3931684706709 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 20:02:33 +0000 Subject: [PATCH 248/549] Bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.1 to 1.47.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.46.1...v1.47.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ad70e75..485dac9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.12.0 - github.com/nats-io/nats.go v1.46.1 + github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.0.10 diff --git a/go.sum b/go.sum index 38c3a43..abb1308 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74= github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww= -github.com/nats-io/nats.go v1.46.1 h1:bqQ2ZcxVd2lpYI97xYASeRTY3I5boe/IVmuUDPitHfo= -github.com/nats-io/nats.go v1.46.1/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM= +github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From 2d3cb91833afb80e2b82c9b51df4bd7edb099c6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:33:45 +0000 Subject: [PATCH 249/549] Bump github.com/nats-io/nats-server/v2 from 2.12.0 to 2.12.1 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.0 to 2.12.1. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.12.0...v2.12.1) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 485dac9..b6f3283 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 - github.com/nats-io/nats-server/v2 v2.12.0 + github.com/nats-io/nats-server/v2 v2.12.1 github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -45,7 +45,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-tpm v0.9.5 // indirect + github.com/google/go-tpm v0.9.6 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect @@ -89,11 +89,11 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/time v0.13.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index abb1308..e3abd53 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= -github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA= +github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -78,8 +78,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74= -github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww= +github.com/nats-io/nats-server/v2 v2.12.1 h1:0tRrc9bzyXEdBLcHr2XEjDzVpUxWx64aZBm7Rl1QDrA= +github.com/nats-io/nats-server/v2 v2.12.1/go.mod h1:OEaOLmu/2e6J9LzUt2OuGjgNem4EpYApO5Rpf26HDs8= github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM= github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -186,8 +186,8 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -195,8 +195,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -207,14 +207,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From 6956df67c8c90bceacedbb1a25ead5221e5877cd Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 23 Oct 2025 14:28:50 +0200 Subject: [PATCH 250/549] Reconnect proxy connection even if shutdown was scheduled before. --- mcu_proxy.go | 10 ---------- mcu_proxy_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/mcu_proxy.go b/mcu_proxy.go index 167a2af..2daf592 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -759,11 +759,6 @@ func (c *mcuProxyConnection) scheduleReconnect() { } c.close() - if c.IsShutdownScheduled() { - c.proxy.removeConnection(c) - return - } - interval := c.reconnectInterval.Load() // Prevent all servers from reconnecting at the same time in case of an // interrupted connection to the proxy or a restart. @@ -812,11 +807,6 @@ func (c *mcuProxyConnection) reconnect() { return } - if c.IsShutdownScheduled() { - c.proxy.removeConnection(c) - return - } - log.Printf("Connected to %s", c) c.closed.Store(false) c.helloProcessed.Store(false) diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 5f3e64e..3531468 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -2501,6 +2501,46 @@ func Test_ProxyReconnectAfter(t *testing.T) { } } +func Test_ProxyReconnectAfterShutdown(t *testing.T) { + CatchLogForTest(t) + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ + servers: []*TestProxyServerHandler{server}, + }, 0) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + + client.sendMessage(&ProxyServerMessage{ + Type: "event", + Event: &EventProxyServerMessage{ + Type: "shutdown-scheduled", + }, + }) + + // Force reconnect. + client.close() + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect and resume the session. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.Equal(sessionId, connections[0].SessionId()) + } +} + func Test_ProxyResume(t *testing.T) { CatchLogForTest(t) t.Parallel() From 01f9fb934ffb1c7c45035295c65f9253d076ee73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:01:43 +0000 Subject: [PATCH 251/549] Bump the artifacts group with 2 updates Bumps the artifacts group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) Updates `actions/download-artifact` from 5 to 6 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts ... Signed-off-by: dependabot[bot] --- .github/workflows/tarball.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index c5e2f54..4693927 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -39,7 +39,7 @@ jobs: make tarball - name: Upload tarball - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: tarball-${{ matrix.go-version }} path: nextcloud-spreed-signaling*.tar.gz @@ -58,7 +58,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Download tarball - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: tarball-${{ matrix.go-version }} From 4b1511789429aa9c2caff1d483e7a16b4198bf21 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 23 Oct 2025 13:53:27 +0200 Subject: [PATCH 252/549] Support maximum bandwidths for compatibility backend. --- backend_storage_static.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/backend_storage_static.go b/backend_storage_static.go index f6e32a4..8a1d6a3 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -56,6 +56,14 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) numBackends := 0 if allowAll { log.Println("WARNING: All backend hostnames are allowed, only use for development!") + maxStreamBitrate, err := config.GetInt("backend", "maxstreambitrate") + if err != nil || maxStreamBitrate < 0 { + maxStreamBitrate = 0 + } + maxScreenBitrate, err := config.GetInt("backend", "maxscreenbitrate") + if err != nil || maxScreenBitrate < 0 { + maxScreenBitrate = 0 + } compatBackend = &Backend{ id: "compat", secret: []byte(commonSecret), @@ -64,6 +72,9 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) sessionLimit: uint64(sessionLimit), counted: true, + + maxStreamBitrate: maxStreamBitrate, + maxScreenBitrate: maxScreenBitrate, } if sessionLimit > 0 { log.Printf("Allow a maximum of %d sessions", sessionLimit) @@ -103,6 +114,14 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) if len(allowMap) == 0 { log.Println("WARNING: No backend hostnames are allowed, check your configuration!") } else { + maxStreamBitrate, err := config.GetInt("backend", "maxstreambitrate") + if err != nil || maxStreamBitrate < 0 { + maxStreamBitrate = 0 + } + maxScreenBitrate, err := config.GetInt("backend", "maxscreenbitrate") + if err != nil || maxScreenBitrate < 0 { + maxScreenBitrate = 0 + } compatBackend = &Backend{ id: "compat", secret: []byte(commonSecret), @@ -111,6 +130,9 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) sessionLimit: uint64(sessionLimit), counted: true, + + maxStreamBitrate: maxStreamBitrate, + maxScreenBitrate: maxScreenBitrate, } hosts := make([]string, 0, len(allowMap)) for host := range allowMap { From 05589f5b0469691cef1283e659da9a3a1d7d8eaa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 23 Oct 2025 13:59:51 +0200 Subject: [PATCH 253/549] Document the "bandwidth" field in room join responses. --- docs/standalone-signaling-api-v1.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index c080194..80be66a 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -466,6 +466,10 @@ Message format (Server -> Client): "roomid": "the-room-id", "properties": { ...additional room properties... + }, + "bandwidth": { + "maxstreambitrate": 1048576, + "maxscreenbitrate": 2097152 } } } @@ -474,6 +478,10 @@ Message format (Server -> Client): - The `roomid` will be empty if the client is no longer in a room. - Can be sent without a request if the server moves a client to a room / out of the current room or the properties of a room change. +- The optional `bandwidth` field contains information on the expected bandwidth + limits (in bits per second) for publishing in this room. If publishing is done + through signaling proxies or there already are other publishers in the room, + the actual limits might be lower than what is returned here. Message format (Server -> Client if already joined before): From c7dcfa765c85e4ea4472ed3fa2d155f433598aa9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 23 Oct 2025 14:08:22 +0200 Subject: [PATCH 254/549] Return bandwidth information in room responses. --- api_signaling.go | 6 ++ hub.go | 37 +++++++++++++ hub_test.go | 110 +++++++++++++++++++++++++++++++++++++ mcu_common.go | 1 + mcu_janus.go | 4 ++ mcu_proxy.go | 4 ++ mcu_test.go | 12 ++++ proxy/proxy_server_test.go | 4 ++ 8 files changed, 178 insertions(+) diff --git a/api_signaling.go b/api_signaling.go index d1b305d..e884a05 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -689,6 +689,12 @@ func (m *RoomFederationMessage) CheckValid() error { type RoomServerMessage struct { RoomId string `json:"roomid"` Properties json.RawMessage `json:"properties,omitempty"` + Bandwidth *RoomBandwidth `json:"bandwidth,omitempty"` +} + +type RoomBandwidth struct { + MaxStreamBitrate int `json:"maxstreambitrate"` + MaxScreenBitrate int `json:"maxscreenbitrate"` } type RoomErrorDetails struct { diff --git a/hub.go b/hub.go index 90abc74..c0458e8 100644 --- a/hub.go +++ b/hub.go @@ -1647,6 +1647,43 @@ func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Roo RoomId: room.id, Properties: room.Properties(), } + var mcuStreamBitrate int + var mcuScreenBitrate int + if mcu := h.mcu; mcu != nil { + mcuStreamBitrate, mcuScreenBitrate = mcu.GetBandwidthLimits() + } + + var backendStreamBitrate int + var backendScreenBitrate int + if backend := room.Backend(); backend != nil { + backendStreamBitrate = backend.maxStreamBitrate + backendScreenBitrate = backend.maxScreenBitrate + } + + var maxStreamBitrate int + if mcuStreamBitrate != 0 && backendStreamBitrate != 0 { + maxStreamBitrate = min(mcuStreamBitrate, backendStreamBitrate) + } else if mcuStreamBitrate != 0 { + maxStreamBitrate = mcuStreamBitrate + } else { + maxStreamBitrate = backendStreamBitrate + } + + var maxScreenBitrate int + if mcuScreenBitrate != 0 && backendScreenBitrate != 0 { + maxScreenBitrate = min(mcuScreenBitrate, backendScreenBitrate) + } else if mcuScreenBitrate != 0 { + maxScreenBitrate = mcuScreenBitrate + } else { + maxScreenBitrate = backendScreenBitrate + } + + if maxStreamBitrate != 0 || maxScreenBitrate != 0 { + response.Room.Bandwidth = &RoomBandwidth{ + MaxStreamBitrate: maxStreamBitrate, + MaxScreenBitrate: maxScreenBitrate, + } + } } return session.SendMessage(response) } diff --git a/hub_test.go b/hub_test.go index fe88af6..d69012c 100644 --- a/hub_test.go +++ b/hub_test.go @@ -2520,6 +2520,7 @@ func TestJoinRoom(t *testing.T) { t.Parallel() CatchLogForTest(t) require := require.New(t) + assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -2531,6 +2532,7 @@ func TestJoinRoom(t *testing.T) { roomId := "test-room" roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) + assert.Nil(roomMsg.Room.Bandwidth) // We will receive a "joined" event. client.RunUntilJoined(ctx, hello.Hello) @@ -2540,6 +2542,114 @@ func TestJoinRoom(t *testing.T) { require.Equal("", roomMsg.Room.RoomId) } +func TestJoinRoomBackendBandwidth(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTestWithConfig(t, func(server *httptest.Server) (*goconf.ConfigFile, error) { + config, err := getTestConfig(server) + if err != nil { + return nil, err + } + + config.AddOption("backend", "maxstreambitrate", "1000") + config.AddOption("backend", "maxscreenbitrate", "2000") + return config, nil + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + if bw := roomMsg.Room.Bandwidth; assert.NotNil(bw) { + assert.EqualValues(1000, bw.MaxStreamBitrate) + assert.EqualValues(2000, bw.MaxScreenBitrate) + } + + // We will receive a "joined" event. + client.RunUntilJoined(ctx, hello.Hello) +} + +func TestJoinRoomMcuBandwidth(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + mcu, err := NewTestMCU() + require.NoError(err) + hub.SetMcu(mcu) + + mcu.SetBandwidthLimits(1000, 2000) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + if bw := roomMsg.Room.Bandwidth; assert.NotNil(bw) { + assert.EqualValues(1000, bw.MaxStreamBitrate) + assert.EqualValues(2000, bw.MaxScreenBitrate) + } + + // We will receive a "joined" event. + client.RunUntilJoined(ctx, hello.Hello) +} + +func TestJoinRoomPreferMcuBandwidth(t *testing.T) { + t.Parallel() + CatchLogForTest(t) + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTestWithConfig(t, func(server *httptest.Server) (*goconf.ConfigFile, error) { + config, err := getTestConfig(server) + if err != nil { + return nil, err + } + + config.AddOption("backend", "maxstreambitrate", "1000") + config.AddOption("backend", "maxscreenbitrate", "2000") + return config, nil + }) + + mcu, err := NewTestMCU() + require.NoError(err) + hub.SetMcu(mcu) + + // The MCU bandwidth limits overwrite any backend limits. + mcu.SetBandwidthLimits(100, 200) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + if bw := roomMsg.Room.Bandwidth; assert.NotNil(bw) { + assert.EqualValues(100, bw.MaxStreamBitrate) + assert.EqualValues(200, bw.MaxScreenBitrate) + } + + // We will receive a "joined" event. + client.RunUntilJoined(ctx, hello.Hello) +} + func TestJoinInvalidRoom(t *testing.T) { t.Parallel() CatchLogForTest(t) diff --git a/mcu_common.go b/mcu_common.go index 5f77f7e..41bb4d1 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -131,6 +131,7 @@ type Mcu interface { GetStats() any GetServerInfoSfu() *BackendServerInfoSfu + GetBandwidthLimits() (int, int) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) diff --git a/mcu_janus.go b/mcu_janus.go index 72c8472..e1b54fb 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -300,6 +300,10 @@ func (m *mcuJanus) disconnect() { } } +func (m *mcuJanus) GetBandwidthLimits() (int, int) { + return int(m.settings.MaxStreamBitrate()), int(m.settings.MaxScreenBitrate()) +} + func (m *mcuJanus) reconnect(ctx context.Context) error { m.disconnect() gw, err := m.createJanusGateway(ctx, m.url, m) diff --git a/mcu_proxy.go b/mcu_proxy.go index 2daf592..9ee8d91 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1544,6 +1544,10 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * return mcu, nil } +func (m *mcuProxy) GetBandwidthLimits() (int, int) { + return int(m.settings.MaxStreamBitrate()), int(m.settings.MaxScreenBitrate()) +} + func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { options, err := GetStringOptions(config, "continent-overrides", false) if err != nil { diff --git a/mcu_test.go b/mcu_test.go index 9f26eb5..0a25406 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -46,6 +46,9 @@ type TestMCU struct { publishers map[PublicSessionId]*TestMCUPublisher // +checklocks:mu subscribers map[string]*TestMCUSubscriber + + maxStreamBitrate atomic.Int32 + maxScreenBitrate atomic.Int32 } func NewTestMCU() (*TestMCU, error) { @@ -55,6 +58,15 @@ func NewTestMCU() (*TestMCU, error) { }, nil } +func (m *TestMCU) GetBandwidthLimits() (int, int) { + return int(m.maxStreamBitrate.Load()), int(m.maxScreenBitrate.Load()) +} + +func (m *TestMCU) SetBandwidthLimits(maxStreamBitrate int, maxScreenBitrate int) { + m.maxStreamBitrate.Store(int32(maxStreamBitrate)) + m.maxScreenBitrate.Store(int32(maxScreenBitrate)) +} + func (m *TestMCU) Start(ctx context.Context) error { return nil } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 25b4311..7f6a2d7 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -358,6 +358,10 @@ type TestMCU struct { t *testing.T } +func (m *TestMCU) GetBandwidthLimits() (int, int) { + return 0, 0 +} + func (m *TestMCU) Start(ctx context.Context) error { return nil } From b9ccb419e6a90aa5b5c0eaf1da9e83362a5fad93 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 28 Oct 2025 08:25:44 +0100 Subject: [PATCH 255/549] Update generated files. --- api_signaling_easyjson.go | 503 ++++++++++++++++++++++---------------- 1 file changed, 299 insertions(+), 204 deletions(-) diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 7dbc9f0..34aae51 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -846,6 +846,20 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex in.AddError((out.Properties).UnmarshalJSON(data)) } } + case "bandwidth": + if in.IsNull() { + in.Skip() + out.Bandwidth = nil + } else { + if out.Bandwidth == nil { + out.Bandwidth = new(RoomBandwidth) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Bandwidth).UnmarshalEasyJSON(in) + } + } default: in.SkipRecursive() } @@ -870,6 +884,11 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwr out.RawString(prefix) out.Raw((in.Properties).MarshalJSON()) } + if in.Bandwidth != nil { + const prefix string = ",\"bandwidth\":" + out.RawString(prefix) + (*in.Bandwidth).MarshalEasyJSON(out) + } out.RawByte('}') } @@ -2046,7 +2065,83 @@ func (v *RoomClientMessage) UnmarshalJSON(data []byte) error { func (v *RoomClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *RoomBandwidth) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "maxstreambitrate": + if in.IsNull() { + in.Skip() + } else { + out.MaxStreamBitrate = int(in.Int()) + } + case "maxscreenbitrate": + if in.IsNull() { + in.Skip() + } else { + out.MaxScreenBitrate = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in RoomBandwidth) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"maxstreambitrate\":" + out.RawString(prefix[1:]) + out.Int(int(in.MaxStreamBitrate)) + } + { + const prefix string = ",\"maxscreenbitrate\":" + out.RawString(prefix) + out.Int(int(in.MaxScreenBitrate)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v RoomBandwidth) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v RoomBandwidth) MarshalEasyJSON(w *jwriter.Writer) { + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *RoomBandwidth) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *RoomBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) +} +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2088,7 +2183,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -2119,27 +2214,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v RemoveSessionInternalClientMessage) 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 RemoveSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) 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 *RemoveSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *MessageServerMessageSender) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *MessageServerMessageSender) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2181,7 +2276,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in MessageServerMessageSender) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in MessageServerMessageSender) { out.RawByte('{') first := true _ = first @@ -2206,27 +2301,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageSender) 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 MessageServerMessageSender) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageSender) 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 *MessageServerMessageSender) 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 { @@ -2256,7 +2351,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 @@ -2271,27 +2366,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 { @@ -2351,7 +2446,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 @@ -2380,27 +2475,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 { @@ -2442,7 +2537,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 @@ -2467,27 +2562,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 { @@ -2581,7 +2676,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 @@ -2658,27 +2753,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 { @@ -2716,7 +2811,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 @@ -2736,27 +2831,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 { @@ -2800,7 +2895,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 @@ -2820,27 +2915,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 { @@ -2890,7 +2985,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 @@ -2919,27 +3014,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 { @@ -3039,7 +3134,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 @@ -3079,27 +3174,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 { @@ -3129,7 +3224,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 @@ -3144,27 +3239,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 { @@ -3270,7 +3365,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 @@ -3356,27 +3451,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 { @@ -3406,7 +3501,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 @@ -3421,27 +3516,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 { @@ -3503,7 +3598,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 @@ -3538,27 +3633,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 { @@ -3602,7 +3697,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 @@ -3633,27 +3728,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 { @@ -3730,7 +3825,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 @@ -3769,27 +3864,27 @@ 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 easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *FederationTokenClaims) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *FederationTokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3895,7 +3990,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in FederationTokenClaims) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in FederationTokenClaims) { out.RawByte('{') first := true _ = first @@ -3981,27 +4076,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v FederationTokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationTokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *FederationAuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *FederationAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4031,7 +4126,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in FederationAuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in FederationAuthParams) { out.RawByte('{') first := true _ = first @@ -4046,27 +4141,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v FederationAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4104,7 +4199,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in EventServerMessageSwitchTo) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in EventServerMessageSwitchTo) { out.RawByte('{') first := true _ = first @@ -4124,27 +4219,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSwitchTo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSwitchTo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4227,7 +4322,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in EventServerMessageSessionEntry) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in EventServerMessageSessionEntry) { out.RawByte('{') first := true _ = first @@ -4276,27 +4371,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSessionEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSessionEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *EventServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4527,7 +4622,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in EventServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -4632,27 +4727,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *Error) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *Error) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4696,7 +4791,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in Error) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in Error) { out.RawByte('{') first := true _ = first @@ -4721,27 +4816,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw // MarshalJSON supports json.Marshaler interface func (v Error) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v Error) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *Error) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4795,7 +4890,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4830,27 +4925,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *DialoutInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *DialoutInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4914,7 +5009,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in DialoutInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in DialoutInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4944,27 +5039,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *ControlServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ControlServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5024,7 +5119,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in ControlServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ControlServerMessage) { out.RawByte('{') first := true _ = first @@ -5053,27 +5148,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ControlClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *ControlClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5111,7 +5206,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ControlClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in ControlClientMessage) { out.RawByte('{') first := true _ = first @@ -5131,27 +5226,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5187,7 +5282,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in CommonSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in CommonSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5207,27 +5302,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jw // MarshalJSON supports json.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5269,7 +5364,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in ClientTypeInternalAuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ClientTypeInternalAuthParams) { out.RawByte('{') first := true _ = first @@ -5294,27 +5389,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *ClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5448,7 +5543,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in ClientMessage) { out.RawByte('{') first := true _ = first @@ -5509,27 +5604,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *ByeServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5559,7 +5654,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in ByeServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -5574,27 +5669,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *ByeClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5618,7 +5713,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in ByeClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -5628,27 +5723,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *AnswerOfferMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jlexer.Lexer, out *AnswerOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5724,7 +5819,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in AnswerOfferMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jwriter.Writer, in AnswerOfferMessage) { out.RawByte('{') first := true _ = first @@ -5786,27 +5881,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jw // MarshalJSON supports json.Marshaler interface func (v AnswerOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AnswerOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jlexer.Lexer, out *AddSessionOptions) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jlexer.Lexer, out *AddSessionOptions) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5842,7 +5937,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jwriter.Writer, in AddSessionOptions) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jwriter.Writer, in AddSessionOptions) { out.RawByte('{') first := true _ = first @@ -5868,27 +5963,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionOptions) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionOptions) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionOptions) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionOptions) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5972,7 +6067,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jwriter.Writer, in AddSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(out *jwriter.Writer, in AddSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -6043,23 +6138,23 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(l, v) } From c54133ffb7252b8064105e3b727d07e820408b48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:03:54 +0000 Subject: [PATCH 256/549] Bump markdown from 3.9 to 3.10 in /docs Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.9 to 3.10. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.9.0...3.10.0) --- updated-dependencies: - dependency-name: markdown dependency-version: '3.10' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index d11b0ff..d927513 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ jinja2==3.1.6 -markdown==3.9 +markdown==3.10 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==8.2.3 From 91b29d4103f355db5699a5be40ce21df3045902b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 4 Nov 2025 14:12:59 +0100 Subject: [PATCH 257/549] Remove federated session from internal map on cleanup. --- hub.go | 1 + hub_test.go | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hub.go b/hub.go index c0458e8..03838e1 100644 --- a/hub.go +++ b/hub.go @@ -824,6 +824,7 @@ func (h *Hub) removeSession(session Session) (removed bool) { } delete(h.expiredSessions, session) if session, ok := session.(*ClientSession); ok { + delete(h.federatedSessions, session) delete(h.anonymousSessions, session) delete(h.dialoutSessions, session) } diff --git a/hub_test.go b/hub_test.go index d69012c..7fe12eb 100644 --- a/hub_test.go +++ b/hub_test.go @@ -286,6 +286,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { clients := len(h.clients) sessions := len(h.sessions) remoteSessions := len(h.remoteSessions) + federatedSessions := len(h.federatedSessions) h.mu.Unlock() h.ru.Lock() rooms := len(h.rooms) @@ -296,6 +297,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { rooms == 0 && sessions == 0 && remoteSessions == 0 && + federatedSessions == 0 && readActive == 0 && writeActive == 0 { break @@ -306,7 +308,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { h.mu.Lock() h.ru.Lock() dumpGoroutines("", os.Stderr) - assert.Fail(t, "Error waiting for hub to terminate", "clients %+v / rooms %+v / sessions %+v / remoteSessions %v / %d read / %d write: %s", h.clients, h.rooms, h.sessions, h.remoteSessions, readActive, writeActive, ctx.Err()) + assert.Fail(t, "Error waiting for hub to terminate", "clients %+v / rooms %+v / sessions %+v / remoteSessions %v / federatedSessions %v / %d read / %d write: %s", h.clients, h.rooms, h.sessions, remoteSessions, federatedSessions, readActive, writeActive, ctx.Err()) h.ru.Unlock() h.mu.Unlock() return From 3fd89f7113e6b89f986a1ac3e5290c9a639524fa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 4 Nov 2025 15:46:19 +0100 Subject: [PATCH 258/549] Fix race condition where closeOnLeave could be set after leave was received. With that also add checklocks annotations to federation client. --- federation.go | 57 +++++++++++++++++++++++++++++++++------------- federation_test.go | 18 ++++++++------- hub.go | 13 ++++++++++- hub_test.go | 14 +++++++++++- 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/federation.go b/federation.go index f3bcf35..afed664 100644 --- a/federation.go +++ b/federation.go @@ -87,20 +87,26 @@ type FederationClient struct { changeRoomId atomic.Bool federation atomic.Pointer[RoomFederationMessage] - mu sync.Mutex - dialer *websocket.Dialer - url string - conn *websocket.Conn - closer *Closer + mu sync.Mutex + dialer *websocket.Dialer + url string + // +checklocks:mu + conn *websocket.Conn + closer *Closer + // +checklocks:mu reconnectDelay time.Duration - reconnecting bool - reconnectFunc *time.Timer + reconnecting atomic.Bool + // +checklocks:mu + reconnectFunc *time.Timer - helloMu sync.Mutex + helloMu sync.Mutex + // +checklocks:helloMu helloMsgId string - helloAuth *FederationAuthParams - resumeId PrivateSessionId - hello atomic.Pointer[HelloServerMessage] + // +checklocks:helloMu + helloAuth *FederationAuthParams + // +checklocks:helloMu + resumeId PrivateSessionId + hello atomic.Pointer[HelloServerMessage] // +checklocks:helloMu pendingMessages []*ClientMessage @@ -167,6 +173,17 @@ func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, return result, nil } +func (c *FederationClient) LocalAddr() net.Addr { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn == nil { + return nil + } + + return c.conn.LocalAddr() +} + func (c *FederationClient) URL() string { return c.federation.Load().parsedSignalingUrl.String() } @@ -255,16 +272,20 @@ func (c *FederationClient) Leave(message *ClientMessage) error { } } - if err := c.sendMessageLocked(message); err != nil && !errors.Is(err, websocket.ErrCloseSent) { - return err + c.closeOnLeave.Store(true) + if err := c.sendMessageLocked(message); err != nil { + c.closeOnLeave.Store(false) + if !errors.Is(err, websocket.ErrCloseSent) { + return err + } } - c.closeOnLeave.Store(true) return nil } func (c *FederationClient) Close() { c.closer.Close() + c.hub.removeFederationClient(c) c.mu.Lock() defer c.mu.Unlock() @@ -272,6 +293,7 @@ func (c *FederationClient) Close() { c.closeConnection(true) } +// +checklocks:c.mu func (c *FederationClient) closeConnection(withBye bool) { if c.conn == nil { return @@ -311,8 +333,9 @@ func (c *FederationClient) scheduleReconnect() { c.scheduleReconnectLocked() } +// +checklocks:c.mu func (c *FederationClient) scheduleReconnectLocked() { - c.reconnecting = true + c.reconnecting.Store(true) if c.hello.Swap(nil) != nil { c.session.SendMessage(&ServerMessage{ Type: "event", @@ -454,6 +477,7 @@ func (c *FederationClient) sendHello(auth *FederationAuthParams) error { return c.sendHelloLocked(auth) } +// +checklocks:c.helloMu func (c *FederationClient) sendHelloLocked(auth *FederationAuthParams) error { c.helloMsgId = newRandomString(8) @@ -539,7 +563,7 @@ func (c *FederationClient) processHello(msg *ServerMessage) { c.hello.Store(msg.Hello) if c.resumeId == "" { c.resumeId = msg.Hello.ResumeId - if c.reconnecting { + if c.reconnecting.Load() { c.session.SendMessage(&ServerMessage{ Type: "event", Event: &EventServerMessage{ @@ -941,6 +965,7 @@ func (c *FederationClient) deferMessage(message *ClientMessage) { } } +// +checklocks:c.mu func (c *FederationClient) sendMessageLocked(message *ClientMessage) error { if c.conn == nil { if message.Type != "room" { diff --git a/federation_test.go b/federation_test.go index 726b998..cdfc720 100644 --- a/federation_test.go +++ b/federation_test.go @@ -250,12 +250,14 @@ func Test_Federation(t *testing.T) { // Client1 will receive the updated "remoteSessionId" if message, ok := client1.RunUntilMessage(ctx); ok { 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.Equal(features2, evt.Features) + if assert.Len(message.Event.Join, 1, "invalid message received: %+v", 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.Equal(features2, evt.Features) + } } client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) @@ -659,7 +661,7 @@ func Test_FederationChangeRoom(t *testing.T) { session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) fed := session2.GetFederationClient() require.NotNil(fed) - localAddr := fed.conn.LocalAddr() + localAddr := fed.LocalAddr() // The client1 will see the remote session id for client2. var remoteSessionId PublicSessionId @@ -701,7 +703,7 @@ func Test_FederationChangeRoom(t *testing.T) { fed2 := session2.GetFederationClient() require.NotNil(fed2) - localAddr2 := fed2.conn.LocalAddr() + localAddr2 := fed2.LocalAddr() assert.Equal(localAddr, localAddr2) } diff --git a/hub.go b/hub.go index 03838e1..2eb976b 100644 --- a/hub.go +++ b/hub.go @@ -195,6 +195,8 @@ type Hub struct { remoteSessions map[*RemoteSession]bool // +checklocks:mu federatedSessions map[*ClientSession]bool + // +checklocks:mu + federationClients map[*FederationClient]bool backendTimeout time.Duration backend *BackendClient @@ -387,6 +389,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer dialoutSessions: make(map[*ClientSession]bool), remoteSessions: make(map[*RemoteSession]bool), federatedSessions: make(map[*ClientSession]bool), + federationClients: make(map[*FederationClient]bool), backendTimeout: backendTimeout, backend: backend, @@ -835,6 +838,13 @@ func (h *Hub) removeSession(session Session) (removed bool) { return } +func (h *Hub) removeFederationClient(client *FederationClient) { + h.mu.Lock() + defer h.mu.Unlock() + + delete(h.federationClients, client) +} + // +checklocksread:h.mu func (h *Hub) hasSessionsLocked(withInternal bool) bool { if withInternal { @@ -1797,8 +1807,9 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { } h.mu.Lock() + defer h.mu.Unlock() h.federatedSessions[session] = true - h.mu.Unlock() + h.federationClients[client] = true return } diff --git a/hub_test.go b/hub_test.go index 7fe12eb..0c0a4a9 100644 --- a/hub_test.go +++ b/hub_test.go @@ -287,6 +287,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { sessions := len(h.sessions) remoteSessions := len(h.remoteSessions) federatedSessions := len(h.federatedSessions) + federationClients := len(h.federatedSessions) h.mu.Unlock() h.ru.Lock() rooms := len(h.rooms) @@ -298,6 +299,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { sessions == 0 && remoteSessions == 0 && federatedSessions == 0 && + federationClients == 0 && readActive == 0 && writeActive == 0 { break @@ -308,7 +310,17 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { h.mu.Lock() h.ru.Lock() dumpGoroutines("", os.Stderr) - assert.Fail(t, "Error waiting for hub to terminate", "clients %+v / rooms %+v / sessions %+v / remoteSessions %v / federatedSessions %v / %d read / %d write: %s", h.clients, h.rooms, h.sessions, remoteSessions, federatedSessions, readActive, writeActive, ctx.Err()) + assert.Fail(t, "Error waiting for hub to terminate", "clients %+v / rooms %+v / sessions %+v / remoteSessions %v / federatedSessions %v / federationClients %v / %d read / %d write: %s", + h.clients, + h.rooms, + h.sessions, + remoteSessions, + federatedSessions, + federationClients, + readActive, + writeActive, + ctx.Err(), + ) h.ru.Unlock() h.mu.Unlock() return From 7494bb631829fe4a95074ed88c28f74022f63c75 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 4 Nov 2025 16:11:35 +0100 Subject: [PATCH 259/549] Don't use environment to keep per-test properties. --- hub_test.go | 36 +++++++++++++++++++++++++----------- testutils_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/hub_test.go b/hub_test.go index 0c0a4a9..12c6615 100644 --- a/hub_test.go +++ b/hub_test.go @@ -579,13 +579,20 @@ func processPingRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re return response } +type testAuthToken struct { + PrivateKey string + PublicKey string +} + +var ( + authTokens testStorage[testAuthToken] +) + func ensureAuthTokens(t *testing.T) (string, string) { require := require.New(t) - if privateKey := os.Getenv("PRIVATE_AUTH_TOKEN_" + t.Name()); privateKey != "" { - publicKey := os.Getenv("PUBLIC_AUTH_TOKEN_" + t.Name()) - // should not happen, always both keys are created - require.NotEmpty(publicKey, "public key is empty") - return privateKey, publicKey + + if tokens, found := authTokens.Get(t); found { + return tokens.PrivateKey, tokens.PublicKey } var private []byte @@ -643,9 +650,12 @@ func ensureAuthTokens(t *testing.T) (string, string) { } privateKey := base64.StdEncoding.EncodeToString(private) - t.Setenv("PRIVATE_AUTH_TOKEN_"+t.Name(), privateKey) publicKey := base64.StdEncoding.EncodeToString(public) - t.Setenv("PUBLIC_AUTH_TOKEN_"+t.Name(), publicKey) + + authTokens.Set(t, testAuthToken{ + PrivateKey: privateKey, + PublicKey: publicKey, + }) return privateKey, publicKey } @@ -683,6 +693,10 @@ func registerBackendHandler(t *testing.T, router *mux.Router) { registerBackendHandlerUrl(t, router, "/") } +var ( + skipV2Capabilities testStorage[bool] +) + func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { handleFunc := validateBackendChecksum(t, func(w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { assert.Regexp(t, "/ocs/v2\\.php/apps/spreed/api/v[\\d]/signaling/backend$", r.URL.Path, "invalid url for backend request %+v", request) @@ -728,8 +742,8 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "MultiRoom") { signaling[ConfigKeySessionPingLimit] = 2 } - useV2 := os.Getenv("SKIP_V2_CAPABILITIES") == "" - if (strings.Contains(t.Name(), "V2") && useV2) || strings.Contains(t.Name(), "Federation") { + skipV2, _ := skipV2Capabilities.Get(t) + if (strings.Contains(t.Name(), "V2") && !skipV2) || strings.Contains(t.Name(), "Federation") { key := getPublicAuthToken(t) public, err := x509.MarshalPKIXPublicKey(key) require.NoError(t, err) @@ -1087,7 +1101,7 @@ func TestClientHelloV2_CachedCapabilities(t *testing.T) { defer cancel() // Simulate old-style Nextcloud without capabilities for Hello V2. - t.Setenv("SKIP_V2_CAPABILITIES", "1") + skipV2Capabilities.Set(t, true) client1 := NewTestClient(t, server, hub) defer client1.CloseWithBye() @@ -1099,7 +1113,7 @@ func TestClientHelloV2_CachedCapabilities(t *testing.T) { assert.NotEmpty(hello1.Hello.SessionId, "%+v", hello1.Hello) // Simulate updated Nextcloud with capabilities for Hello V2. - t.Setenv("SKIP_V2_CAPABILITIES", "") + skipV2Capabilities.Set(t, false) client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() diff --git a/testutils_test.go b/testutils_test.go index 60932af..e18146d 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -150,3 +150,49 @@ func AssertEqualSerialized(t *testing.T, expected any, actual any, msgAndArgs .. return assert.Equal(t, string(a), string(e), msgAndArgs...) } + +type testStorage[T any] struct { + mu sync.Mutex + // +checklocks:mu + entries map[string]T // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 +} + +func (s *testStorage[T]) cleanup(key string) { + s.mu.Lock() + defer s.mu.Unlock() + + delete(s.entries, key) + if len(s.entries) == 0 { + s.entries = nil + } +} + +func (s *testStorage[T]) Set(t *testing.T, value T) { + s.mu.Lock() + defer s.mu.Unlock() + + key := t.Name() + if _, found := s.entries[key]; !found { + t.Cleanup(func() { + s.cleanup(key) + }) + } + + if s.entries == nil { + s.entries = make(map[string]T) + } + s.entries[key] = value +} + +func (s *testStorage[T]) Get(t *testing.T) (T, bool) { + s.mu.Lock() + defer s.mu.Unlock() + + key := t.Name() + if value, found := s.entries[key]; found { + return value, true + } + + var defaultValue T + return defaultValue, false +} From 395f2a951b9e8f3141fd2b3bbbd533049ffc1483 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 28 Oct 2025 08:24:31 +0100 Subject: [PATCH 260/549] Expose real bandwidth usage through metrics. Data is fetched from Janus websocket events handler that sends messages for various events like media stats. --- api_backend.go | 2 +- api_proxy.go | 2 +- hub.go | 19 + internal/ips.go | 44 ++ internal/ips_test.go | 78 ++++ janus_client.go | 12 + mcu_common.go | 14 + mcu_janus.go | 121 +++++- mcu_janus_client.go | 43 +- mcu_janus_events_handler.go | 699 ++++++++++++++++++++++++++++++++ mcu_janus_test.go | 56 ++- mcu_proxy.go | 25 +- mcu_proxy_test.go | 11 +- mcu_stats_prometheus.go | 20 + proxy/proxy_server.go | 59 ++- proxy/proxy_server_test.go | 99 +++++ proxy/proxy_stats_prometheus.go | 7 + proxy/proxy_testclient_test.go | 2 +- 18 files changed, 1271 insertions(+), 42 deletions(-) create mode 100644 internal/ips.go create mode 100644 internal/ips_test.go create mode 100644 mcu_janus_events_handler.go diff --git a/api_backend.go b/api_backend.go index 8aeb826..9b01999 100644 --- a/api_backend.go +++ b/api_backend.go @@ -546,7 +546,7 @@ type BackendServerInfoSfuProxy struct { Features []string `json:"features,omitempty"` Country string `json:"country,omitempty"` - Load *int64 `json:"load,omitempty"` + Load *uint64 `json:"load,omitempty"` Bandwidth *EventProxyServerBandwidth `json:"bandwidth,omitempty"` } diff --git a/api_proxy.go b/api_proxy.go index ac51760..8f3bd6e 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -347,7 +347,7 @@ type EventProxyServerMessage struct { Type string `json:"type"` ClientId string `json:"clientId,omitempty"` - Load int64 `json:"load,omitempty"` + Load uint64 `json:"load,omitempty"` Sid string `json:"sid,omitempty"` Bandwidth *EventProxyServerBandwidth `json:"bandwidth,omitempty"` diff --git a/hub.go b/hub.go index 2eb976b..d202958 100644 --- a/hub.go +++ b/hub.go @@ -355,6 +355,9 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer ReadBufferSize: websocketReadBufferSize, WriteBufferSize: websocketWriteBufferSize, WriteBufferPool: websocketWriteBufferPool, + Subprotocols: []string{ + JanusEventsSubprotocol, + }, }, cookie: NewSessionIdCodec([]byte(hashKey), blockBytes), info: NewWelcomeServerMessage(version, DefaultFeatures...), @@ -3094,6 +3097,22 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { return } + if conn.Subprotocol() == JanusEventsSubprotocol { + if h.mcu == nil { + log.Printf("Could not create Janus events handler for %s: no MCU configured", addr) + return + } + + client, err := NewJanusEventsHandler(r.Context(), h.mcu, conn, addr, agent) + if err != nil { + log.Printf("Could not create Janus events handler for %s: %s", addr, err) + return + } + + client.Run() + return + } + client, err := NewClient(r.Context(), conn, addr, agent, h) if err != nil { log.Printf("Could not create client for %s: %s", addr, err) diff --git a/internal/ips.go b/internal/ips.go new file mode 100644 index 0000000..94f4484 --- /dev/null +++ b/internal/ips.go @@ -0,0 +1,44 @@ +/** + * 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 internal + +import ( + "net" +) + +func IsLoopbackIP(addr string) bool { + ip := net.ParseIP(addr) + if len(ip) == 0 { + return false + } + + return ip.IsLoopback() +} + +func IsPrivateIP(addr string) bool { + ip := net.ParseIP(addr) + if len(ip) == 0 { + return false + } + + return ip.IsPrivate() +} diff --git a/internal/ips_test.go b/internal/ips_test.go new file mode 100644 index 0000000..94516ea --- /dev/null +++ b/internal/ips_test.go @@ -0,0 +1,78 @@ +/** + * 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 internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsLoopbackIP(t *testing.T) { + loopback := []string{ + "127.0.0.1", + "127.1.0.1", + "::1", + "::ffff:127.0.0.1", + } + nonLoopback := []string{ + "", + "invalid", + "1.2.3.4", + "::0", + "::2", + } + assert := assert.New(t) + for _, ip := range loopback { + assert.True(IsLoopbackIP(ip), "should be loopback: %s", ip) + } + for _, ip := range nonLoopback { + assert.False(IsLoopbackIP(ip), "should not be loopback: %s", ip) + } +} + +func TestIsPrivateIP(t *testing.T) { + private := []string{ + "10.1.2.3", + "172.20.21.22", + "192.168.10.20", + "fdea:aef9:06e3:bb24:1234:1234:1234:1234", + "fd12:3456:789a:1::1", + } + nonPrivate := []string{ + "", + "invalid", + "127.0.0.1", + "1.2.3.4", + "::0", + "::1", + "::2", + "1234:3456:789a:1::1", + } + assert := assert.New(t) + for _, ip := range private { + assert.True(IsPrivateIP(ip), "should be private: %s", ip) + } + for _, ip := range nonPrivate { + assert.False(IsPrivateIP(ip), "should not be private: %s", ip) + } +} diff --git a/janus_client.go b/janus_client.go index 8686dea..6ae6b88 100644 --- a/janus_client.go +++ b/janus_client.go @@ -142,18 +142,30 @@ var msgtypes = map[string]func() any{ "trickle": func() any { return &TrickleMsg{} }, } +type InfoDependencies struct { + Glib2 string `json:"glib2"` + Jansson string `json:"jansson"` + Libnice string `json:"libnice"` + Libsrtp string `json:"libsrtp"` + Libcurl string `json:"libcurl,omitempty"` + Crypto string `json:"crypto"` +} + type InfoMsg struct { Name string Version int VersionString string `json:"version_string"` Author string DataChannels bool `json:"data_channels"` + EventHandlers bool `json:"event_handlers"` IPv6 bool `json:"ipv6"` LocalIP string `json:"local-ip"` ICE_TCP bool `json:"ice-tcp"` FullTrickle bool `json:"full-trickle"` Transports map[string]janus.PluginInfo Plugins map[string]janus.PluginInfo + Events map[string]janus.PluginInfo + Dependencies InfoDependencies } type TrickleMsg struct { diff --git a/mcu_common.go b/mcu_common.go index 41bb4d1..a30c5da 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -200,10 +200,18 @@ func IsValidStreamType(s string) bool { } } +type McuClientBandwidthInfo struct { + // Sent is the outgoing bandwidth in bytes per second. + Sent uint64 + // Received is the incoming bandwidth in bytes per second. + Received uint64 +} + type McuClient interface { Id() string Sid() string StreamType() StreamType + // MaxBitrate is the maximum allowed bitrate in bits per second. MaxBitrate() int Close(ctx context.Context) @@ -211,6 +219,12 @@ type McuClient interface { SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) } +type McuClientWithBandwidth interface { + McuClient + + Bandwidth() *McuClientBandwidthInfo +} + type McuPublisher interface { McuClient diff --git a/mcu_janus.go b/mcu_janus.go index e1b54fb..d8d8e58 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -41,8 +41,10 @@ import ( const ( pluginVideoRoom = "janus.plugin.videoroom" + eventWebsocket = "janus.eventhandler.wsevh" keepaliveInterval = 30 * time.Second + bandwidthInterval = time.Second videoPublisherUserId = 1 screenPublisherUserId = 2 @@ -137,7 +139,12 @@ func getPluginStringValue(data janus.PluginData, pluginName string, key string) // TODO(jojo): Lots of error handling still missing. type clientInterface interface { + Handle() uint64 + NotifyReconnected() + + Bandwidth() *McuClientBandwidthInfo + UpdateBandwidth(media string, sent uint32, received uint32) } type mcuJanusSettings struct { @@ -220,9 +227,9 @@ type mcuJanus struct { closeChan chan struct{} - muClients sync.Mutex + muClients sync.RWMutex // +checklocks:muClients - clients map[clientInterface]bool + clients map[uint64]clientInterface clientId atomic.Uint64 // +checklocks:mu @@ -253,7 +260,7 @@ func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mc url: url, settings: settings, closeChan: make(chan struct{}, 1), - clients: make(map[clientInterface]bool), + clients: make(map[uint64]clientInterface), publishers: make(map[StreamId]*mcuJanusPublisher), remotePublishers: make(map[StreamId]*mcuJanusRemotePublisher), @@ -304,6 +311,44 @@ func (m *mcuJanus) GetBandwidthLimits() (int, int) { return int(m.settings.MaxStreamBitrate()), int(m.settings.MaxScreenBitrate()) } +func (m *mcuJanus) Bandwidth() (result *McuClientBandwidthInfo) { + m.muClients.RLock() + defer m.muClients.RUnlock() + + for _, client := range m.clients { + if bandwidth := client.Bandwidth(); bandwidth != nil { + if result == nil { + result = &McuClientBandwidthInfo{} + } + result.Received += bandwidth.Received + result.Sent += bandwidth.Sent + } + } + return +} + +func (m *mcuJanus) updateBandwidthStats() { + if info := m.info.Load(); info != nil { + if !info.EventHandlers { + // Event handlers are disabled, no stats will be available. + return + } + + if _, found := info.Events[eventWebsocket]; !found { + // Event handler plugin not found, no stats will be available. + return + } + } + + if bandwidth := m.Bandwidth(); bandwidth != nil { + statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(float64(bandwidth.Received)) + statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(float64(bandwidth.Sent)) + } else { + statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(0) + statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(0) + } +} + func (m *mcuJanus) reconnect(ctx context.Context) error { m.disconnect() gw, err := m.createJanusGateway(ctx, m.url, m) @@ -334,11 +379,27 @@ func (m *mcuJanus) doReconnect(ctx context.Context) { m.reconnectInterval = initialReconnectInterval m.mu.Unlock() - m.muClients.Lock() - for client := range m.clients { - go client.NotifyReconnected() + m.notifyClientsReconnected() +} + +func (m *mcuJanus) notifyClientsReconnected() { + m.muClients.RLock() + defer m.muClients.RUnlock() + + for oldHandle, client := range m.clients { + go func(oldHandle uint64, client clientInterface) { + client.NotifyReconnected() + newHandle := client.Handle() + + if oldHandle != newHandle { + m.muClients.Lock() + defer m.muClients.Unlock() + + delete(m.clients, oldHandle) + m.clients[newHandle] = client + } + }(oldHandle, client) } - m.muClients.Unlock() } func (m *mcuJanus) scheduleReconnect(err error) { @@ -379,14 +440,25 @@ func (m *mcuJanus) Start(ctx context.Context) error { } log.Printf("Connected to %s %s by %s", info.Name, info.VersionString, info.Author) - plugin, found := info.Plugins[pluginVideoRoom] - if !found { + m.version = info.Version + + if plugin, found := info.Plugins[pluginVideoRoom]; found { + log.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) + } else { return fmt.Errorf("plugin %s is not supported", pluginVideoRoom) } - m.version = info.Version + if plugin, found := info.Events[eventWebsocket]; found { + if !info.EventHandlers { + log.Printf("Found %s %s by %s but event handlers are disabled, realtime usage will not be available", plugin.Name, plugin.VersionString, plugin.Author) + } else { + log.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) + } + } else { + log.Printf("Plugin %s not found, realtime usage will not be available", eventWebsocket) + } - log.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) + log.Printf("Used dependencies: %+v", info.Dependencies) if !info.DataChannels { return fmt.Errorf("data channels are not supported") } @@ -421,25 +493,32 @@ func (m *mcuJanus) Start(ctx context.Context) error { func (m *mcuJanus) registerClient(client clientInterface) { m.muClients.Lock() - m.clients[client] = true - m.muClients.Unlock() + defer m.muClients.Unlock() + + m.clients[client.Handle()] = client } func (m *mcuJanus) unregisterClient(client clientInterface) { m.muClients.Lock() - delete(m.clients, client) - m.muClients.Unlock() + defer m.muClients.Unlock() + + delete(m.clients, client.Handle()) } func (m *mcuJanus) run() { ticker := time.NewTicker(keepaliveInterval) defer ticker.Stop() + bandwidthTicker := time.NewTicker(bandwidthInterval) + defer bandwidthTicker.Stop() + loop: for { select { case <-ticker.C: m.sendKeepalive(context.Background()) + case <-bandwidthTicker.C: + m.updateBandwidthStats() case <-m.closeChan: break loop } @@ -975,3 +1054,15 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener statsSubscribersTotal.WithLabelValues(string(publisher.StreamType())).Inc() return client, nil } + +func (m *mcuJanus) UpdateBandwidth(handle uint64, media string, sent uint32, received uint32) { + m.muClients.RLock() + defer m.muClients.RUnlock() + + client, found := m.clients[handle] + if !found { + return + } + + client.UpdateBandwidth(media, sent, received) +} diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 88d4688..66cca69 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -37,7 +37,7 @@ import ( type mcuJanusClient struct { mcu *mcuJanus listener McuListener - mu sync.Mutex // nolint + mu sync.Mutex id uint64 session uint64 @@ -46,6 +46,9 @@ type mcuJanusClient struct { streamType StreamType maxBitrate int + // +checklocks:mu + bandwidth map[string]*McuClientBandwidthInfo + handle atomic.Pointer[JanusHandle] handleId atomic.Uint64 closeChan chan struct{} @@ -67,6 +70,10 @@ func (c *mcuJanusClient) Sid() string { return c.sid } +func (c *mcuJanusClient) Handle() uint64 { + return c.handleId.Load() +} + func (c *mcuJanusClient) StreamType() StreamType { return c.streamType } @@ -81,6 +88,40 @@ func (c *mcuJanusClient) Close(ctx context.Context) { func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { } +func (c *mcuJanusClient) UpdateBandwidth(media string, sent uint32, received uint32) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.bandwidth == nil { + c.bandwidth = make(map[string]*McuClientBandwidthInfo) + } + + info, found := c.bandwidth[media] + if !found { + info = &McuClientBandwidthInfo{} + c.bandwidth[media] = info + } + + info.Sent = uint64(sent) + info.Received = uint64(received) +} + +func (c *mcuJanusClient) Bandwidth() *McuClientBandwidthInfo { + c.mu.Lock() + defer c.mu.Unlock() + + if c.bandwidth == nil { + return nil + } + + result := &McuClientBandwidthInfo{} + for _, info := range c.bandwidth { + result.Received += info.Received + result.Sent += info.Sent + } + return result +} + func (c *mcuJanusClient) closeClient(ctx context.Context) bool { if handle := c.handle.Swap(nil); handle != nil { close(c.closeChan) diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go new file mode 100644 index 0000000..ddf83f1 --- /dev/null +++ b/mcu_janus_events_handler.go @@ -0,0 +1,699 @@ +/** + * 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 signaling + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" +) + +const ( + JanusEventsSubprotocol = "janus-events" + + JanusEventTypeSession = 1 + + JanusEventTypeHandle = 2 + + JanusEventTypeExternal = 4 + + JanusEventTypeJSEP = 8 + + JanusEventTypeWebRTC = 16 + JanusEventSubTypeWebRTCICE = 1 + JanusEventSubTypeWebRTCLocalCandidate = 2 + JanusEventSubTypeWebRTCRemoteCandidate = 3 + JanusEventSubTypeWebRTCSelectedPair = 4 + JanusEventSubTypeWebRTCDTLS = 5 + JanusEventSubTypeWebRTCPeerConnection = 6 + + JanusEventTypeMedia = 32 + JanusEventSubTypeMediaState = 1 + JanusEventSubTypeMediaSlowLink = 2 + JanusEventSubTypeMediaStats = 3 + + JanusEventTypePlugin = 64 + + JanusEventTypeTransport = 128 + + JanusEventTypeCore = 256 + JanusEventSubTypeCoreStatusStartup = 1 + JanusEventSubTypeCoreStatusShutdown = 2 +) + +func unmarshalEvent[T any](data json.RawMessage) (*T, error) { + var e T + if err := json.Unmarshal(data, &e); err != nil { + return nil, err + } + + return &e, nil +} + +func marshalEvent[T any](e T) string { + data, err := json.Marshal(e) + if err != nil { + return fmt.Sprintf("Could not serialize %#v: %s", e, err) + } + + return string(data) +} + +type JanusEvent struct { + Emitter string `json:"emitter"` + Type int `json:"type"` + SubType int `json:"subtype,omitempty"` + Timestamp uint64 `json:"timestamp"` + SessionId uint64 `json:"session_id,omitempty"` + HandleId uint64 `json:"handle_id,omitempty"` + OpaqueId uint64 `json:"opaque_id,omitempty"` + Event json.RawMessage `json:"event"` +} + +func (e JanusEvent) String() string { + return marshalEvent(e) +} + +func (e JanusEvent) Decode() (any, error) { + switch e.Type { + case JanusEventTypeSession: + return unmarshalEvent[JanusEventSession](e.Event) + case JanusEventTypeHandle: + return unmarshalEvent[JanusEventHandle](e.Event) + case JanusEventTypeExternal: + return unmarshalEvent[JanusEventExternal](e.Event) + case JanusEventTypeJSEP: + return unmarshalEvent[JanusEventJSEP](e.Event) + case JanusEventTypeWebRTC: + return unmarshalEvent[JanusEventWebRTC](e.Event) + case JanusEventTypeMedia: + switch e.SubType { + case JanusEventSubTypeMediaState: + return unmarshalEvent[JanusEventMediaState](e.Event) + case JanusEventSubTypeMediaSlowLink: + return unmarshalEvent[JanusEventMediaSlowLink](e.Event) + case JanusEventSubTypeMediaStats: + return unmarshalEvent[JanusEventMediaStats](e.Event) + } + case JanusEventTypePlugin: + return unmarshalEvent[JanusEventPlugin](e.Event) + case JanusEventTypeTransport: + return unmarshalEvent[JanusEventTransport](e.Event) + case JanusEventTypeCore: + switch e.SubType { + case JanusEventSubTypeCoreStatusStartup: + event, err := unmarshalEvent[JanusEventCoreStartup](e.Event) + if err != nil { + return nil, err + } + + switch event.Status { + case "started": + return unmarshalEvent[JanusEventStatusStartupInfo](event.Info) + case "update": + return unmarshalEvent[JanusEventStatusUpdateInfo](event.Info) + } + + return event, nil + case JanusEventSubTypeCoreStatusShutdown: + return unmarshalEvent[JanusEventCoreShutdown](e.Event) + } + } + + return nil, fmt.Errorf("unsupported event type %d", e.Type) +} + +type JanusEventSessionTransport struct { + Transport string `json:"transport"` + ID string `json:"id"` +} + +// type=1 +type JanusEventSession struct { + Name string `json:"name"` // "created", "destroyed", "timeout" + + Transport *JanusEventSessionTransport `json:"transport,omitempty"` +} + +func (e JanusEventSession) String() string { + return marshalEvent(e) +} + +// type=2 +type JanusEventHandle struct { + Name string `json:"name"` // "attached", "detached" + Plugin string `json:"plugin"` + Token string `json:"token,omitempty"` + // Deprecated + OpaqueId string `json:"opaque_id,omitempty"` +} + +func (e JanusEventHandle) String() string { + return marshalEvent(e) +} + +// type=4 +type JanusEventExternal struct { + Schema string `json:"schema"` + Data json.RawMessage `json:"data"` +} + +func (e JanusEventExternal) String() string { + return marshalEvent(e) +} + +// type=8 +type JanusEventJSEP struct { + Owner string `json:"owner"` + Jsep struct { + Type string `json:"type"` + SDP string `json:"sdp"` + } `json:"jsep"` +} + +func (e JanusEventJSEP) String() string { + return marshalEvent(e) +} + +// type=16, subtype=1 +type JanusEventWebRTCICE struct { + ICE string `json:"ice"` // "gathering", "connecting", "connected", "ready" + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` +} + +func (e JanusEventWebRTCICE) String() string { + return marshalEvent(e) +} + +// type=16, subtype=2 +type JanusEventWebRTCLocalCandidate struct { + LocalCandidate string `json:"local-candidate"` + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` +} + +func (e JanusEventWebRTCLocalCandidate) String() string { + return marshalEvent(e) +} + +// type=16, subtype=3 +type JanusEventWebRTCRemoteCandidate struct { + RemoteCandidate string `json:"remote-candidate"` + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` +} + +func (e JanusEventWebRTCRemoteCandidate) String() string { + return marshalEvent(e) +} + +type JanusEventCandidate struct { + Address string `json:"address"` + Port int `json:"port"` + Type string `json:"type"` + Transport string `json:"transport"` + Family int `json:"family"` +} + +func (e JanusEventCandidate) String() string { + return marshalEvent(e) +} + +type JanusEventCandidates struct { + Local JanusEventCandidate `json:"local"` + Remote JanusEventCandidate `json:"remote"` +} + +func (e JanusEventCandidates) String() string { + return marshalEvent(e) +} + +// type=16, subtype=4 +type JanusEventWebRTCSelectedPair struct { + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` + + SelectedPair string `json:"selected-pair"` + Candidates JanusEventCandidates `json:"candidates"` +} + +func (e JanusEventWebRTCSelectedPair) String() string { + return marshalEvent(e) +} + +// type=16, subtype=5 +type JanusEventWebRTCDTLS struct { + DTLS string `json:"dtls"` // "trying", "connected" + + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` + + Retransmissions int `json:"retransmissions"` +} + +func (e JanusEventWebRTCDTLS) String() string { + return marshalEvent(e) +} + +// type=16, subtype=6 +type JanusEventWebRTCPeerConnection struct { + Connection string `json:"connection"` // "webrtcup" +} + +func (e JanusEventWebRTCPeerConnection) String() string { + return marshalEvent(e) +} + +// type=16 +type JanusEventWebRTC struct { + Owner string `json:"owner"` + Jsep json.RawMessage `json:"jsep"` +} + +func (e JanusEventWebRTC) String() string { + return marshalEvent(e) +} + +// type=32, subtype=1 +type JanusEventMediaState struct { + Media string `json:"media"` // "audio", "video" + MID string `json:"mid"` + SubStream *int `json:"substream,omitempty"` + Receiving bool `json:"receiving"` + Seconds int `json:"seconds"` +} + +func (e JanusEventMediaState) String() string { + return marshalEvent(e) +} + +// type=32, subtype=2 +type JanusEventMediaSlowLink struct { + Media string `json:"media"` // "audio", "video" + MID string `json:"mid"` + SlowLink string `json:"slow_link"` // "uplink", "downlink" + LostLastSec int `json:"lost_lastsec"` +} + +func (e JanusEventMediaSlowLink) String() string { + return marshalEvent(e) +} + +type JanusMediaStatsRTTValues struct { + NTP uint32 `json:"ntp"` + LSR uint32 `json:"lsr"` + DLSR uint32 `json:"dlsr"` +} + +func (e JanusMediaStatsRTTValues) String() string { + return marshalEvent(e) +} + +// type=32, subtype=3 +type JanusEventMediaStats struct { + MID string `json:"mid"` + MIndex int `json:"mindex"` + Media string `json:"media"` // "audio", "video", "video-sim1", "video-sim2" + + // Audio / video only + Codec string `json:"codec,omitempty"` + Base uint32 `json:"base"` + Lost int32 `json:"lost"` + LostByRemote int32 `json:"lost-by-remote"` + JitterLocal uint32 `json:"jitter-local"` + JitterRemote uint32 `json:"jitter-remote"` + InLinkQuality uint32 `json:"in-link-quality"` + InMediaLinkQuality uint32 `json:"in-media-link-quality"` + OutLinkQuality uint32 `json:"out-link-quality"` + OutMediaLinkQuality uint32 `json:"out-media-link-quality"` + BytesReceivedLastSec uint32 `json:"bytes-received-lastsec"` + BytesSentLastSec uint32 `json:"bytes-sent-lastsec"` + NacksReceived uint32 `json:"nacks-received"` + NacksSent uint32 `json:"nacks-sent"` + RetransmissionsReceived uint32 `json:"retransmissions-received"` + + // Only for audio / video on layer 0 + RTT uint32 `json:"rtt,omitempty"` + // Only for audio / video on layer 0 if RTCP is active + RTTValues *JanusMediaStatsRTTValues `json:"rtt-values,omitempty"` + + // For all media on all layers + PacketsReceived int32 `json:"packets-received"` + PacketsSent int32 `json:"packets-sent"` + BytesReceived int64 `json:"bytes-received"` + BytesSent int64 `json:"bytes-sent"` + + // For layer 0 if REMB is enabled + REMBBitrate uint32 `json:"remb-bitrate"` +} + +func (e JanusEventMediaStats) String() string { + return marshalEvent(e) +} + +// type=64 +type JanusEventPlugin struct { + Plugin string `json:"plugin"` + Data json.RawMessage `json:"data"` +} + +func (e JanusEventPlugin) String() string { + return marshalEvent(e) +} + +type JanusEventTransportWebsocket struct { + Event string `json:"event"` + AdminApi bool `json:"admin_api,omitempty"` + IP string `json:"ip,omitempty"` +} + +// type=128 +type JanusEventTransport struct { + Transport string `json:"transport"` + Id string `json:"id"` + Data JanusEventTransportWebsocket `json:"data"` +} + +func (e JanusEventTransport) String() string { + return marshalEvent(e) +} + +type JanusEventDependenciesInfo struct { + Glib2 string `json:"glib2"` + Jansson string `json:"jansson"` + Libnice string `json:"libnice"` + Libsrtp string `json:"libsrtp"` + Libcurl string `json:"libcurl,omitempty"` + Crypto string `json:"crypto"` +} + +func (e JanusEventDependenciesInfo) String() string { + return marshalEvent(e) +} + +type JanusEventPluginInfo struct { + Name string `json:"name"` + Author string `json:"author"` + Description string `json:"description"` + VersionString string `json:"version_string"` + Version int `json:"version"` +} + +func (e JanusEventPluginInfo) String() string { + return marshalEvent(e) +} + +// type=256, subtype=1, status="startup" +type JanusEventStatusStartupInfo struct { + Janus string `json:"janus"` + Version int `json:"version"` + VersionString string `json:"version_string"` + Author string `json:"author"` + CommitHash string `json:"commit-hash"` + CompileTime string `json:"compile-time"` + LogToStdout bool `json:"log-to-stdout"` + LogToFile bool `json:"log-to-file"` + LogPath string `json:"log-path,omitempty"` + DataChannels bool `json:"data_channels"` + AcceptingNewSessions bool `json:"accepting-new-sessions"` + SessionTimeout int `json:"session-timeout"` + ReclaimSessionTimeout int `json:"reclaim-session-timeout"` + CandidatesTimeout int `json:"candidates-timeout"` + ServerName string `json:"server-name"` + LocalIP string `json:"local-ip"` + PublicIP string `json:"public-ip,omitempty"` + PublicIPs []string `json:"public-ips,omitempty"` + IPv6 bool `json:"ipv6"` + IPv6LinkLocal bool `json:"ipv6-link-local,omitempty"` + ICELite bool `json:"ice-lite"` + ICETCP bool `json:"ice-tcp"` + ICENomination string `json:"ice-nomination,omitempty"` + ICEConsentFreshness bool `json:"ice-consent-freshness"` + ICEKeepaliveConncheck bool `json:"ice-keepalive-conncheck"` + HangupOnFailed bool `json:"hangup-on-failed"` + FullTrickle bool `json:"full-trickle"` + MDNSEnabled bool `json:"mdns-enabled"` + MinNACKQueue int `json:"min-nack-queue"` + NACKOptimizations bool `json:"nack-optimizations"` + TWCCPeriod int `json:"twcc-period"` + DSCP int `json:"dscp,omitempty"` + DTLSMCU int `json:"dtls-mcu"` + STUNServer string `json:"stun-server,omitempty"` + TURNServer string `json:"turn-server,omitempty"` + AllowForceRelay bool `json:"allow-force-relay,omitempty"` + StaticEventLoops int `json:"static-event-loops"` + LoopIndication bool `json:"loop-indication,omitempty"` + APISecret bool `json:"api_secret"` + AuthToken bool `json:"auth_token"` + EventHandlers bool `json:"event_handlers"` + OpaqueIdInAPI bool `json:"opaqueid_in_api"` + WebRTCEncryption bool `json:"webrtc_encryption"` + + Dependencies *JanusEventDependenciesInfo `json:"dependencies,omitempty"` + Transports map[string]JanusEventPluginInfo `json:"transports,omitempty"` + Events map[string]JanusEventPluginInfo `json:"events,omitempty"` + Loggers map[string]JanusEventPluginInfo `json:"loggers,omitempty"` + Plugins map[string]JanusEventPluginInfo `json:"plugins,omitempty"` +} + +func (e JanusEventStatusStartupInfo) String() string { + return marshalEvent(e) +} + +// type=256, subtype=1, status="update" +type JanusEventStatusUpdateInfo struct { + Sessions int `json:"sessions"` + Handles int `json:"handles"` + PeerConnections int `json:"peerconnections"` + StatsPeriod int `json:"stats-period"` +} + +func (e JanusEventStatusUpdateInfo) String() string { + return marshalEvent(e) +} + +// type=256, subtype=1 +type JanusEventCoreStartup struct { + Status string `json:"status"` + Info json.RawMessage `json:"info"` +} + +func (e JanusEventCoreStartup) String() string { + return marshalEvent(e) +} + +// type=256, subtype=2 +type JanusEventCoreShutdown struct { + Status string `json:"status"` + Signum int `json:"signum"` +} + +func (e JanusEventCoreShutdown) String() string { + return marshalEvent(e) +} + +type JanusEventsHandler struct { + mu sync.Mutex + + ctx context.Context + mcu *mcuJanus + // +checklocks:mu + conn *websocket.Conn + addr string + agent string + + events chan JanusEvent +} + +func NewJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, addr string, agent string) (*JanusEventsHandler, error) { + if !internal.IsLoopbackIP(addr) && !internal.IsPrivateIP(addr) { + return nil, errors.New("only loopback and private connections allowed") + } + + m, ok := mcu.(*mcuJanus) + if !ok { + return nil, errors.New("need a Janus MCU") + } + + handler := &JanusEventsHandler{ + ctx: ctx, + mcu: m, + conn: conn, + addr: addr, + agent: agent, + + events: make(chan JanusEvent, 1), + } + + return handler, nil +} + +func (h *JanusEventsHandler) Run() { + log.Printf("Processing Janus events from %s", h.addr) + go h.writePump() + go h.processEvents() + + h.readPump() +} + +func (h *JanusEventsHandler) close() { + h.mu.Lock() + conn := h.conn + h.conn = nil + h.mu.Unlock() + + if conn != nil { + if err := conn.Close(); err != nil { + log.Printf("Error closing %s", err) + } + } +} + +func (h *JanusEventsHandler) readPump() { + h.mu.Lock() + conn := h.conn + h.mu.Unlock() + if conn == nil { + log.Printf("Connection from %s closed while starting readPump", h.addr) + return + } + + 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 + 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) || 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, + websocket.CloseGoingAway, + websocket.CloseNoStatusReceived) { + log.Printf("Error reading from %s: %v", h.addr, err) + } + break + } + + if messageType != websocket.TextMessage { + log.Printf("Unsupported message type %v from %s", messageType, h.addr) + continue + } + + decodeBuffer, err := bufferPool.ReadAll(reader) + if err != nil { + log.Printf("Error reading message from %s: %v", h.addr, err) + break + } + + var events []JanusEvent + if err := json.Unmarshal(decodeBuffer.Bytes(), &events); err != nil { + log.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) + bufferPool.Put(decodeBuffer) + break + } + + bufferPool.Put(decodeBuffer) + for _, e := range events { + h.events <- e + } + } +} + +func (h *JanusEventsHandler) sendPing() bool { + h.mu.Lock() + defer h.mu.Unlock() + if h.conn == nil { + return false + } + + now := time.Now().UnixNano() + msg := strconv.FormatInt(now, 10) + h.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint + if err := h.conn.WriteMessage(websocket.PingMessage, []byte(msg)); err != nil { + log.Printf("Could not send ping to %s: %v", h.addr, err) + return false + } + + return true +} + +func (h *JanusEventsHandler) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + h.close() + }() + + for { + select { + case <-ticker.C: + if !h.sendPing() { + return + } + case <-h.ctx.Done(): + return + } + } +} + +func (h *JanusEventsHandler) processEvents() { + for { + select { + case event := <-h.events: + h.processEvent(event) + case <-h.ctx.Done(): + return + } + } +} + +func (h *JanusEventsHandler) processEvent(event JanusEvent) { + evt, err := event.Decode() + if err != nil { + log.Printf("Error decoding event %s (%s)", event, err) + return + } + + switch evt := evt.(type) { + case *JanusEventMediaStats: + h.mcu.UpdateBandwidth(event.HandleId, evt.Media, evt.BytesSentLastSec, evt.BytesReceivedLastSec) + } +} diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 641a0f5..74632ed 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -122,6 +122,7 @@ func (g *TestJanusGateway) Info(ctx context.Context) (*InfoMsg, error) { VersionString: "1.4.0", Author: "struktur AG", DataChannels: true, + EventHandlers: true, FullTrickle: true, Plugins: map[string]janus.PluginInfo{ pluginVideoRoom: { @@ -130,6 +131,13 @@ func (g *TestJanusGateway) Info(ctx context.Context) (*InfoMsg, error) { Author: "struktur AG", }, }, + Events: map[string]janus.PluginInfo{ + eventWebsocket: { + Name: "Test Websocket events", + VersionString: "0.0.0", + Author: "struktur AG", + }, + }, }, nil } @@ -1071,9 +1079,12 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { } func Test_JanusPublisherSubscriber(t *testing.T) { + ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming")) + ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing")) + CatchLogForTest(t) - t.Parallel() require := require.New(t) + assert := assert.New(t) mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{}) @@ -1081,6 +1092,12 @@ func Test_JanusPublisherSubscriber(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() + // Bandwidth for unknown handles is ignored. + mcu.UpdateBandwidth(1234, "video", 100, 200) + mcu.updateBandwidthStats() + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 0) + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 0) + pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, @@ -1095,6 +1112,24 @@ func Test_JanusPublisherSubscriber(t *testing.T) { require.NoError(err) defer pub.Close(context.Background()) + janusPub, ok := pub.(*mcuJanusPublisher) + require.True(ok) + + assert.Nil(mcu.Bandwidth()) + assert.Nil(janusPub.Bandwidth()) + mcu.UpdateBandwidth(janusPub.Handle(), "video", 1000, 2000) + if bw := janusPub.Bandwidth(); assert.NotNil(bw) { + assert.EqualValues(1000, bw.Sent) + assert.EqualValues(2000, bw.Received) + } + if bw := mcu.Bandwidth(); assert.NotNil(bw) { + assert.EqualValues(1000, bw.Sent) + assert.EqualValues(2000, bw.Received) + } + mcu.updateBandwidthStats() + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 1000) + listener2 := &TestMcuListener{ id: pubId, } @@ -1105,6 +1140,25 @@ func Test_JanusPublisherSubscriber(t *testing.T) { sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) require.NoError(err) defer sub.Close(context.Background()) + + janusSub, ok := sub.(*mcuJanusSubscriber) + require.True(ok) + + assert.Nil(janusSub.Bandwidth()) + mcu.UpdateBandwidth(janusSub.Handle(), "video", 3000, 4000) + if bw := janusSub.Bandwidth(); assert.NotNil(bw) { + assert.EqualValues(3000, bw.Sent) + assert.EqualValues(4000, bw.Received) + } + if bw := mcu.Bandwidth(); assert.NotNil(bw) { + assert.EqualValues(4000, bw.Sent) + assert.EqualValues(6000, bw.Received) + } + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 1000) + mcu.updateBandwidthStats() + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 6000) + checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 4000) } func Test_JanusSubscriberPublisher(t *testing.T) { diff --git a/mcu_proxy.go b/mcu_proxy.go index 9ee8d91..461be23 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -345,7 +345,7 @@ type mcuProxyConnection struct { ip net.IP connectToken string - load atomic.Int64 + load atomic.Uint64 bandwidth atomic.Pointer[EventProxyServerBandwidth] mu sync.Mutex closer *Closer @@ -413,6 +413,9 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str conn.country.Store("") conn.version.Store("") conn.features.Store([]string{}) + statsProxyBackendLoadCurrent.WithLabelValues(conn.url.String()).Set(0) + statsProxyUsageCurrent.WithLabelValues(conn.url.String(), "incoming").Set(0) + statsProxyUsageCurrent.WithLabelValues(conn.url.String(), "outgoing").Set(0) return conn, nil } @@ -478,7 +481,7 @@ type mcuProxyConnectionStats struct { Connected bool `json:"connected"` Publishers int64 `json:"publishers"` Clients int64 `json:"clients"` - Load *int64 `json:"load,omitempty"` + Load *uint64 `json:"load,omitempty"` Shutdown *bool `json:"shutdown,omitempty"` Temporary *bool `json:"temporary,omitempty"` Uptime *time.Time `json:"uptime,omitempty"` @@ -514,7 +517,7 @@ func (c *mcuProxyConnection) GetStats() *mcuProxyConnectionStats { return result } -func (c *mcuProxyConnection) Load() int64 { +func (c *mcuProxyConnection) Load() uint64 { return c.load.Load() } @@ -748,6 +751,10 @@ func (c *mcuProxyConnection) closeIfEmpty() bool { log.Printf("All clients disconnected, closing connection to %s", c) c.stop(ctx) + statsProxyBackendLoadCurrent.DeleteLabelValues(c.url.String()) + statsProxyUsageCurrent.DeleteLabelValues(c.url.String(), "incoming") + statsProxyUsageCurrent.DeleteLabelValues(c.url.String(), "outgoing") + c.proxy.removeConnection(c) }() return true @@ -1082,6 +1089,18 @@ func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { c.load.Store(event.Load) c.bandwidth.Store(event.Bandwidth) statsProxyBackendLoadCurrent.WithLabelValues(c.url.String()).Set(float64(event.Load)) + if bw := event.Bandwidth; bw != nil { + if bw.Incoming != nil { + statsProxyUsageCurrent.WithLabelValues(c.url.String(), "incoming").Set(*bw.Incoming) + } else { + statsProxyUsageCurrent.WithLabelValues(c.url.String(), "incoming").Set(0) + } + if bw.Outgoing != nil { + statsProxyUsageCurrent.WithLabelValues(c.url.String(), "outgoing").Set(*bw.Outgoing) + } else { + statsProxyUsageCurrent.WithLabelValues(c.url.String(), "outgoing").Set(0) + } + } return case "shutdown-scheduled": log.Printf("Proxy %s is scheduled to shutdown", c) diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 3531468..268eaa7 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -551,7 +551,7 @@ type TestProxyServerHandler struct { country string mu sync.Mutex - load atomic.Int64 + load atomic.Uint64 incoming atomic.Pointer[float64] outgoing atomic.Pointer[float64] // +checklocks:mu @@ -666,7 +666,7 @@ func (h *TestProxyServerHandler) Clear(incoming bool, outgoing bool) { } } -func (h *TestProxyServerHandler) getLoadMessage(load int64) *ProxyServerMessage { +func (h *TestProxyServerHandler) getLoadMessage(load uint64) *ProxyServerMessage { msg := &ProxyServerMessage{ Type: "event", Event: &EventProxyServerMessage{ @@ -691,7 +691,12 @@ func (h *TestProxyServerHandler) updateLoad(delta int64) { return } - load := h.load.Add(delta) + var load uint64 + if delta > 0 { + load = h.load.Add(uint64(delta)) + } else { + load = h.load.Add(^uint64(delta - 1)) + } h.mu.Lock() defer h.mu.Unlock() diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index 0d0e9ca..d1df618 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -86,6 +86,17 @@ var ( statsMcuPublisherStreamTypesCurrent, } + statsJanusBandwidthCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "bandwidth", + Help: "The current bandwidth in bytes per second", + }, []string{"direction"}) + + janusMcuStats = []prometheus.Collector{ + statsJanusBandwidthCurrent, + } + statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "signaling", Subsystem: "mcu", @@ -98,6 +109,12 @@ var ( Name: "backend_load", Help: "Current load of signaling proxy backends", }, []string{"url"}) + statsProxyUsageCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "backend_usage", + Help: "The current usage of signaling proxy backends in percent", + }, []string{"url", "direction"}) statsProxyNobackendAvailableTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "signaling", Subsystem: "mcu", @@ -108,16 +125,19 @@ var ( proxyMcuStats = []prometheus.Collector{ statsConnectedProxyBackendsCurrent, statsProxyBackendLoadCurrent, + statsProxyUsageCurrent, statsProxyNobackendAvailableTotal, } ) func RegisterJanusMcuStats() { registerAll(commonMcuStats...) + registerAll(janusMcuStats...) } func UnregisterJanusMcuStats() { unregisterAll(commonMcuStats...) + unregisterAll(janusMcuStats...) } func RegisterProxyMcuStats() { diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 6190275..08d6046 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -115,12 +115,12 @@ type ProxyServer struct { url string mcu signaling.Mcu stopped atomic.Bool - load atomic.Int64 + load atomic.Uint64 - maxIncoming atomic.Int64 - currentIncoming atomic.Int64 - maxOutgoing atomic.Int64 - currentOutgoing atomic.Int64 + maxIncoming atomic.Uint64 + currentIncoming atomic.Uint64 + maxOutgoing atomic.Uint64 + currentOutgoing atomic.Uint64 shutdownChannel chan struct{} shutdownScheduled atomic.Bool @@ -356,6 +356,9 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* upgrader: websocket.Upgrader{ ReadBufferSize: websocketReadBufferSize, WriteBufferSize: websocketWriteBufferSize, + Subprotocols: []string{ + signaling.JanusEventsSubprotocol, + }, }, tokens: tokens, @@ -374,12 +377,14 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* remotePublishers: make(map[string]map[*proxyRemotePublisher]bool), } - result.maxIncoming.Store(int64(maxIncoming) * 1024 * 1024) - result.maxOutgoing.Store(int64(maxOutgoing) * 1024 * 1024) + result.maxIncoming.Store(uint64(maxIncoming) * 1024 * 1024) + result.maxOutgoing.Store(uint64(maxOutgoing) * 1024 * 1024) result.statsAllowedIps.Store(statsAllowedIps) result.trustedProxies.Store(trustedProxiesIps) result.upgrader.CheckOrigin = result.checkOrigin + statsLoadCurrent.Set(0) + if debug, _ := config.GetBool("app", "debug"); debug { log.Println("Installing debug handlers in \"/debug/pprof\"") s := r.PathPrefix("/debug/pprof").Subrouter() @@ -488,7 +493,7 @@ loop: } } -func (s *ProxyServer) newLoadEvent(load int64, incoming int64, outgoing int64) *signaling.ProxyServerMessage { +func (s *ProxyServer) newLoadEvent(load uint64, incoming uint64, outgoing uint64) *signaling.ProxyServerMessage { msg := &signaling.ProxyServerMessage{ Type: "event", Event: &signaling.EventProxyServerMessage{ @@ -521,10 +526,11 @@ func (s *ProxyServer) updateLoad() { return } + statsLoadCurrent.Set(float64(load)) s.sendLoadToAll(load, incoming, outgoing) } -func (s *ProxyServer) sendLoadToAll(load int64, incoming int64, outgoing int64) { +func (s *ProxyServer) sendLoadToAll(load uint64, incoming uint64, outgoing uint64) { if s.shutdownScheduled.Load() { // Server is scheduled to shutdown, no need to update clients with current load. return @@ -628,9 +634,9 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { } maxIncoming, maxOutgoing := getTargetBandwidths(config) - oldIncoming := s.maxIncoming.Swap(int64(maxIncoming)) - oldOutgoing := s.maxOutgoing.Swap(int64(maxOutgoing)) - if oldIncoming != int64(maxIncoming) || oldOutgoing != int64(maxOutgoing) { + oldIncoming := s.maxIncoming.Swap(uint64(maxIncoming)) + oldOutgoing := s.maxOutgoing.Swap(uint64(maxOutgoing)) + if oldIncoming != uint64(maxIncoming) || oldOutgoing != uint64(maxOutgoing) { // Notify sessions about updated load / bandwidth usage. go s.sendLoadToAll(s.load.Load(), s.currentIncoming.Load(), s.currentOutgoing.Load()) } @@ -664,6 +670,18 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { return } + if conn.Subprotocol() == signaling.JanusEventsSubprotocol { + agent := r.Header.Get("User-Agent") + client, err := signaling.NewJanusEventsHandler(r.Context(), s.mcu, conn, addr, agent) + if err != nil { + log.Printf("Could not create Janus events handler for %s: %s", addr, err) + return + } + + client.Run() + return + } + client, err := NewProxyClient(r.Context(), s, conn, addr) if err != nil { log.Printf("Could not create client for %s: %s", addr, err) @@ -1562,20 +1580,29 @@ func (s *ProxyServer) HasClients() bool { return len(s.clients) > 0 } -func (s *ProxyServer) GetClientsLoad() (load int64, incoming int64, outgoing int64) { +func (s *ProxyServer) GetClientsLoad() (load uint64, incoming uint64, outgoing uint64) { s.clientsLock.RLock() defer s.clientsLock.RUnlock() for _, c := range s.clients { - bitrate := int64(c.MaxBitrate()) - load += bitrate + // Use "current" bandwidth usage if supported. + if bw, ok := c.(signaling.McuClientWithBandwidth); ok { + if bandwidth := bw.Bandwidth(); bandwidth != nil { + incoming += bandwidth.Received * 8 + outgoing += bandwidth.Sent * 8 + continue + } + } + + bitrate := uint64(c.MaxBitrate()) if _, ok := c.(signaling.McuPublisher); ok { incoming += bitrate } else if _, ok := c.(signaling.McuSubscriber); ok { outgoing += bitrate } } - load = load / 1024 + load = incoming + outgoing + load = min(uint64(len(s.clients)), load/1024) return } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 7f6a2d7..850de2b 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -446,6 +446,105 @@ func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId signali return errors.New("not implemented") } +type PublisherTestMCU struct { + TestMCU +} + +type TestPublisherWithBandwidth struct { + TestMCUPublisher + + bandwidth *signaling.McuClientBandwidthInfo +} + +func (p *TestPublisherWithBandwidth) Bandwidth() *signaling.McuClientBandwidthInfo { + return p.bandwidth +} + +func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { + publisher := &TestPublisherWithBandwidth{ + TestMCUPublisher: TestMCUPublisher{ + id: id, + sid: sid, + streamType: streamType, + }, + + bandwidth: &signaling.McuClientBandwidthInfo{ + Sent: 1000, + Received: 2000, + }, + } + return publisher, nil +} + +func NewPublisherTestMCU(t *testing.T) *PublisherTestMCU { + return &PublisherTestMCU{ + TestMCU: TestMCU{ + t: t, + }, + } +} + +func TestProxyPublisherBandwidth(t *testing.T) { + signaling.CatchLogForTest(t) + assert := assert.New(t) + require := require.New(t) + proxy, key, server := newProxyServerForTest(t) + + // Values are in bits per second. + proxy.maxIncoming.Store(10000 * 8) + proxy.maxOutgoing.Store(10000 * 8) + + mcu := NewPublisherTestMCU(t) + proxy.mcu = mcu + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := NewProxyTestClient(ctx, t, server.URL) + defer client.CloseWithBye() + + require.NoError(client.SendHello(key)) + + if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) + } + + _, err := client.RunUntilLoad(ctx, 0) + assert.NoError(err) + + require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + Id: "2345", + Type: "command", + Command: &signaling.CommandProxyClientMessage{ + Type: "create-publisher", + StreamType: signaling.StreamTypeVideo, + }, + })) + + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("2345", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + assert.NotEmpty(message.Command.Id) + } + } + + proxy.updateLoad() + + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + if err := checkMessageType(message, "event"); assert.NoError(err) && assert.Equal("update-load", message.Event.Type) { + assert.EqualValues(1, message.Event.Load) + if bw := message.Event.Bandwidth; assert.NotNil(bw) { + if assert.NotNil(bw.Incoming) { + assert.EqualValues(20, *bw.Incoming) + } + if assert.NotNil(bw.Outgoing) { + assert.EqualValues(10, *bw.Outgoing) + } + } + } + } +} + type HangingTestMCU struct { TestMCU ctx context.Context diff --git a/proxy/proxy_stats_prometheus.go b/proxy/proxy_stats_prometheus.go index 054ec2b..26e317c 100644 --- a/proxy/proxy_stats_prometheus.go +++ b/proxy/proxy_stats_prometheus.go @@ -86,6 +86,12 @@ var ( Name: "token_errors_total", Help: "The total number of token errors", }, []string{"reason"}) + statsLoadCurrent = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "proxy", + Name: "load", + Help: "The current load of the signaling proxy", + }) ) func init() { @@ -99,4 +105,5 @@ func init() { prometheus.MustRegister(statsCommandMessagesTotal) prometheus.MustRegister(statsPayloadMessagesTotal) prometheus.MustRegister(statsTokenErrorsTotal) + prometheus.MustRegister(statsLoadCurrent) } diff --git a/proxy/proxy_testclient_test.go b/proxy/proxy_testclient_test.go index 6b983b2..0c91bdc 100644 --- a/proxy/proxy_testclient_test.go +++ b/proxy/proxy_testclient_test.go @@ -226,7 +226,7 @@ func (c *ProxyTestClient) RunUntilHello(ctx context.Context) (message *signaling return message, nil } -func (c *ProxyTestClient) RunUntilLoad(ctx context.Context, load int64) (message *signaling.ProxyServerMessage, err error) { +func (c *ProxyTestClient) RunUntilLoad(ctx context.Context, load uint64) (message *signaling.ProxyServerMessage, err error) { if message, err = c.RunUntilMessage(ctx); err != nil { return nil, err } From 8e784b9616be0a023bf05a82018f8206f56356f0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 29 Oct 2025 14:50:29 +0100 Subject: [PATCH 261/549] Document new bandwidth / usage related metrics. --- docs/prometheus-metrics.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index b8c5257..5027c40 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -55,3 +55,6 @@ The following metrics are available: | `signaling_backend_client_requests_total` | Counter | 2.0.3 | The total number of backend client requests | `backend` | | `signaling_backend_client_requests_duration` | Histogram | 2.0.3 | The duration of backend client requests in seconds | `backend` | | `signaling_backend_client_requests_errors_total` | Counter | 2.0.3 | The total number of backend client requests that had an error | `backend`, `error` | +| `signaling_mcu_bandwidth` | Gauge | 2.0.5 | The current bandwidth in bytes per second | `direction` | +| `signaling_mcu_backend_usage` | Gauge | 2.0.5 | The current usage of signaling proxy backends in percent | `url`, `direction` | +| `signaling_proxy_load` | Gauge | 2.0.5 | The current load of the signaling proxy | | From 184a41ae4f6526964ff082795657f11858a619d4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 29 Oct 2025 14:51:16 +0100 Subject: [PATCH 262/549] Update generated files. --- api_backend_easyjson.go | 6 +++--- api_proxy_easyjson.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index fe51e3b..3cf4ca9 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -1018,12 +1018,12 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex out.Load = nil } else { if out.Load == nil { - out.Load = new(int64) + out.Load = new(uint64) } if in.IsNull() { in.Skip() } else { - *out.Load = int64(in.Int64()) + *out.Load = uint64(in.Uint64()) } } case "bandwidth": @@ -1111,7 +1111,7 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr if in.Load != nil { const prefix string = ",\"load\":" out.RawString(prefix) - out.Int64(int64(*in.Load)) + out.Uint64(uint64(*in.Load)) } if in.Bandwidth != nil { const prefix string = ",\"bandwidth\":" diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index 2eb6996..95894a7 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -1294,7 +1294,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex if in.IsNull() { in.Skip() } else { - out.Load = int64(in.Int64()) + out.Load = uint64(in.Uint64()) } case "sid": if in.IsNull() { @@ -1343,7 +1343,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr if in.Load != 0 { const prefix string = ",\"load\":" out.RawString(prefix) - out.Int64(int64(in.Load)) + out.Uint64(uint64(in.Load)) } if in.Sid != "" { const prefix string = ",\"sid\":" From f2ef566acc973f415ddebc7800adf7bdab87004c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 29 Oct 2025 15:00:54 +0100 Subject: [PATCH 263/549] Add note on how to configure Janus events. --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48df0c9..2c23600 100644 --- a/README.md +++ b/README.md @@ -139,14 +139,23 @@ server. A Janus server (from https://github.com/meetecho/janus-gateway) can be used to act as a WebRTC gateway. See the documentation of Janus on how to configure and -run the server. At least the `VideoRoom` plugin and the websocket transport of -Janus must be enabled. +run the server. At least the `VideoRoom` plugin, the websocket transport and the +websocket events handler of Janus must be enabled. Also broadcasting of events +must be enabled. The signaling server uses the `VideoRoom` plugin of Janus to manage sessions. All gateway details are hidden from the clients, all messages are sent through the signaling server. Only WebRTC media is exchanged directly between the gateway and the clients. +To enable sending of events from Janus, the option `broadcast` must be set to +`true` in the block `events` of `janus.jcfg`. In the configuration of the +websocket events handler (`janus.eventhandler.wsevh.jcfg`), the module must be +enabled by setting `enabled` to `true`, the `backend` must be set to the +websocket url of the signaling server (or signaling proxy) and `subprotocol` +must be set to `janus-events`. +At least events of type `media` must be subscribed. + Edit the `server.conf` and enter the URL to the websocket endpoint of Janus in the section `[mcu]` and key `url`. During startup, the signaling server will connect to Janus and log information of the gateway. From 9b79aac1cf2d6b7dde40e10c8178bed168720ebe Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Nov 2025 10:29:48 +0100 Subject: [PATCH 264/549] Add tests for Janus events handler. --- hub.go | 13 +- mcu_janus_events_handler.go | 60 ++++- mcu_janus_events_handler_test.go | 366 +++++++++++++++++++++++++++++++ proxy/proxy_server.go | 8 +- 4 files changed, 418 insertions(+), 29 deletions(-) create mode 100644 mcu_janus_events_handler_test.go diff --git a/hub.go b/hub.go index d202958..21620ca 100644 --- a/hub.go +++ b/hub.go @@ -3098,18 +3098,7 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { } if conn.Subprotocol() == JanusEventsSubprotocol { - if h.mcu == nil { - log.Printf("Could not create Janus events handler for %s: no MCU configured", addr) - return - } - - client, err := NewJanusEventsHandler(r.Context(), h.mcu, conn, addr, agent) - if err != nil { - log.Printf("Could not create Janus events handler for %s: %s", addr, err) - return - } - - client.Run() + RunJanusEventsHandler(r.Context(), h.mcu, conn, addr, agent) return } diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index ddf83f1..0c548bb 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -523,11 +523,15 @@ func (e JanusEventCoreShutdown) String() string { return marshalEvent(e) } +type McuEventHandler interface { + UpdateBandwidth(handle uint64, media string, sent uint32, received uint32) +} + type JanusEventsHandler struct { mu sync.Mutex ctx context.Context - mcu *mcuJanus + mcu McuEventHandler // +checklocks:mu conn *websocket.Conn addr string @@ -536,19 +540,38 @@ type JanusEventsHandler struct { events chan JanusEvent } -func NewJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, addr string, agent string) (*JanusEventsHandler, error) { - if !internal.IsLoopbackIP(addr) && !internal.IsPrivateIP(addr) { - return nil, errors.New("only loopback and private connections allowed") +func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, addr string, agent string) { + deadline := time.Now().Add(time.Second) + if mcu == nil { + conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "no mcu configured"), deadline) // nolint + return } - m, ok := mcu.(*mcuJanus) + m, ok := mcu.(McuEventHandler) if !ok { - return nil, errors.New("need a Janus MCU") + conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "mcu does not support events"), deadline) // nolint + return } + if !internal.IsLoopbackIP(addr) && !internal.IsPrivateIP(addr) { + conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "only loopback and private connections allowed"), deadline) // nolint + return + } + + client, err := NewJanusEventsHandler(ctx, m, conn, addr, agent) + if err != nil { + log.Printf("Could not create Janus events handler for %s: %s", addr, err) + conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "error creating handler"), deadline) // nolint + return + } + + client.Run() +} + +func NewJanusEventsHandler(ctx context.Context, mcu McuEventHandler, conn *websocket.Conn, addr string, agent string) (*JanusEventsHandler, error) { handler := &JanusEventsHandler{ ctx: ctx, - mcu: m, + mcu: mcu, conn: conn, addr: addr, agent: agent, @@ -623,13 +646,30 @@ func (h *JanusEventsHandler) readPump() { break } - var events []JanusEvent - if err := json.Unmarshal(decodeBuffer.Bytes(), &events); err != nil { - log.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) + if decodeBuffer.Len() == 0 { + log.Printf("Received empty message from %s", h.addr) bufferPool.Put(decodeBuffer) break } + var events []JanusEvent + if data := decodeBuffer.Bytes(); data[0] != '[' { + var event JanusEvent + if err := json.Unmarshal(data, &event); err != nil { + log.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) + bufferPool.Put(decodeBuffer) + break + } + + events = append(events, event) + } else { + if err := json.Unmarshal(data, &events); err != nil { + log.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) + bufferPool.Put(decodeBuffer) + break + } + } + bufferPool.Put(decodeBuffer) for _, e := range events { h.events <- e diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go new file mode 100644 index 0000000..a2d4975 --- /dev/null +++ b/mcu_janus_events_handler_test.go @@ -0,0 +1,366 @@ +/** + * 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 signaling + +import ( + "context" + "encoding/json" + "net" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + "time" + + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type TestJanusEventsServerHandler struct { + t *testing.T + upgrader websocket.Upgrader + + mcu Mcu + addr string +} + +func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.t.Helper() + require := require.New(h.t) + conn, err := h.upgrader.Upgrade(w, r, nil) + require.NoError(err) + + if conn.Subprotocol() == JanusEventsSubprotocol { + addr := h.addr + if addr == "" { + addr = r.RemoteAddr + } + if host, _, err := net.SplitHostPort(addr); err == nil { + addr = host + } + RunJanusEventsHandler(r.Context(), h.mcu, conn, addr, r.Header.Get("User-Agent")) + return + } + + deadline := time.Now().Add(time.Second) + require.NoError(conn.SetWriteDeadline(deadline)) + require.NoError(conn.WriteJSON(map[string]string{"error": "invalid_subprotocol"})) + require.NoError(conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseProtocolError, "invalid_subprotocol"), deadline)) + require.NoError(conn.Close()) +} + +func NewTestJanusEventsHandlerServer(t *testing.T) (*httptest.Server, string, *TestJanusEventsServerHandler) { + t.Helper() + + handler := &TestJanusEventsServerHandler{ + t: t, + upgrader: websocket.Upgrader{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + }, + } + server := httptest.NewServer(handler) + t.Cleanup(func() { + server.Close() + }) + url := strings.ReplaceAll(server.URL, "http://", "ws://") + url = strings.ReplaceAll(url, "https://", "wss://") + return server, url, handler +} + +func TestJanusEventsHandlerNoMcu(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + _, url, _ := NewTestJanusEventsHandlerServer(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + dialer := websocket.Dialer{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + } + conn, response, err := dialer.DialContext(ctx, url, nil) + require.NoError(err) + + assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + + var ce *websocket.CloseError + require.NoError(conn.SetReadDeadline(time.Now().Add(testTimeout))) + if mt, msg, err := conn.ReadMessage(); err == nil { + assert.Fail("connection was not closed", "expected close error, got message %s with type %d", string(msg), mt) + } else if assert.ErrorAs(err, &ce) { + assert.EqualValues(websocket.CloseInternalServerErr, ce.Code) + assert.Equal("no mcu configured", ce.Text) + } +} + +func TestJanusEventsHandlerInvalidMcu(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + _, url, handler := NewTestJanusEventsHandlerServer(t) + + handler.mcu = &mcuProxy{} + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + dialer := websocket.Dialer{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + } + conn, response, err := dialer.DialContext(ctx, url, nil) + require.NoError(err) + + assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + + var ce *websocket.CloseError + require.NoError(conn.SetReadDeadline(time.Now().Add(testTimeout))) + if mt, msg, err := conn.ReadMessage(); err == nil { + assert.Fail("connection was not closed", "expected close error, got message %s with type %d", string(msg), mt) + } else if assert.ErrorAs(err, &ce) { + assert.EqualValues(websocket.CloseInternalServerErr, ce.Code) + assert.Equal("mcu does not support events", ce.Text) + } +} + +func TestJanusEventsHandlerPublicIP(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + _, url, handler := NewTestJanusEventsHandlerServer(t) + + handler.mcu = &mcuJanus{} + handler.addr = "1.2.3.4" + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + dialer := websocket.Dialer{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + } + conn, response, err := dialer.DialContext(ctx, url, nil) + require.NoError(err) + + assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + + var ce *websocket.CloseError + require.NoError(conn.SetReadDeadline(time.Now().Add(testTimeout))) + if mt, msg, err := conn.ReadMessage(); err == nil { + assert.Fail("connection was not closed", "expected close error, got message %s with type %d", string(msg), mt) + } else if assert.ErrorAs(err, &ce) { + assert.EqualValues(websocket.ClosePolicyViolation, ce.Code) + assert.Equal("only loopback and private connections allowed", ce.Text) + } +} + +type TestMcuWithEvents struct { + TestMCU + + t *testing.T + mu sync.Mutex + // +checklocks:mu + idx int +} + +func (m *TestMcuWithEvents) UpdateBandwidth(handle uint64, media string, sent uint32, received uint32) { + assert := assert.New(m.t) + + m.mu.Lock() + defer m.mu.Unlock() + + m.idx++ + switch m.idx { + case 1: + assert.EqualValues(1, handle) + assert.EqualValues("audio", media) + assert.EqualValues(100, sent) + assert.EqualValues(200, received) + case 2: + assert.EqualValues(1, handle) + assert.EqualValues("video", media) + assert.EqualValues(200, sent) + assert.EqualValues(300, received) + default: + assert.Fail("too many updates", "received update %d (handle=%d, media=%s, sent=%d, received=%d)", m.idx, handle, media, sent, received) + } +} + +func (m *TestMcuWithEvents) WaitForUpdates(ctx context.Context, waitForIdx int) error { + for { + if err := ctx.Err(); err != nil { + return err + } + + m.mu.Lock() + idx := m.idx + m.mu.Unlock() + if idx == waitForIdx { + return nil + } + + time.Sleep(time.Millisecond) + } +} + +type janusEventSender struct { + events []JanusEvent +} + +func (s *janusEventSender) SendSingle(t *testing.T, conn *websocket.Conn) { + t.Helper() + + require := require.New(t) + require.Len(s.events, 1) + + require.NoError(conn.WriteJSON(s.events[0])) +} + +func (s *janusEventSender) Send(t *testing.T, conn *websocket.Conn) { + t.Helper() + + require := require.New(t) + require.NoError(conn.WriteJSON(s.events)) +} + +func (s *janusEventSender) AddEvent(t *testing.T, eventType int, eventSubtype int, handleId uint64, event any) { + t.Helper() + + require := require.New(t) + data, err := json.Marshal(event) + require.NoError(err) + + message := JanusEvent{ + Type: eventType, + SubType: eventSubtype, + HandleId: handleId, + Event: data, + } + + s.events = append(s.events, message) +} + +func TestJanusEventsHandlerNotGrouped(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + _, url, handler := NewTestJanusEventsHandlerServer(t) + + mcu := &TestMcuWithEvents{ + t: t, + } + handler.mcu = mcu + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + dialer := websocket.Dialer{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + } + conn, response, err := dialer.DialContext(ctx, url, nil) + require.NoError(err) + + assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + + var sender janusEventSender + sender.AddEvent( + t, + JanusEventTypeMedia, + JanusEventSubTypeMediaStats, + 1, + JanusEventMediaStats{ + Media: "audio", + BytesSentLastSec: 100, + BytesReceivedLastSec: 200, + }, + ) + sender.SendSingle(t, conn) + assert.NoError(mcu.WaitForUpdates(ctx, 1)) +} + +func TestJanusEventsHandlerGrouped(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + _, url, handler := NewTestJanusEventsHandlerServer(t) + + mcu := &TestMcuWithEvents{ + t: t, + } + handler.mcu = mcu + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + dialer := websocket.Dialer{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + } + conn, response, err := dialer.DialContext(ctx, url, nil) + require.NoError(err) + + assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + + var sender janusEventSender + sender.AddEvent( + t, + JanusEventTypeMedia, + JanusEventSubTypeMediaStats, + 1, + JanusEventMediaStats{ + Media: "audio", + BytesSentLastSec: 100, + BytesReceivedLastSec: 200, + }, + ) + sender.AddEvent( + t, + JanusEventTypeMedia, + JanusEventSubTypeMediaStats, + 1, + JanusEventMediaStats{ + Media: "video", + BytesSentLastSec: 200, + BytesReceivedLastSec: 300, + }, + ) + sender.Send(t, conn) + + assert.NoError(mcu.WaitForUpdates(ctx, 2)) +} diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 08d6046..b38b190 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -672,13 +672,7 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { if conn.Subprotocol() == signaling.JanusEventsSubprotocol { agent := r.Header.Get("User-Agent") - client, err := signaling.NewJanusEventsHandler(r.Context(), s.mcu, conn, addr, agent) - if err != nil { - log.Printf("Could not create Janus events handler for %s: %s", addr, err) - return - } - - client.Run() + signaling.RunJanusEventsHandler(r.Context(), s.mcu, conn, addr, agent) return } From 6d0d31725210d6077c2fa77df8648c96f68a290a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Nov 2025 11:40:23 +0100 Subject: [PATCH 265/549] Add tests for different event types. --- mcu_janus_events_handler.go | 25 ++-- mcu_janus_events_handler_test.go | 219 +++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 11 deletions(-) diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index 0c548bb..04be231 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -115,7 +115,20 @@ func (e JanusEvent) Decode() (any, error) { case JanusEventTypeJSEP: return unmarshalEvent[JanusEventJSEP](e.Event) case JanusEventTypeWebRTC: - return unmarshalEvent[JanusEventWebRTC](e.Event) + switch e.SubType { + case JanusEventSubTypeWebRTCICE: + return unmarshalEvent[JanusEventWebRTCICE](e.Event) + case JanusEventSubTypeWebRTCLocalCandidate: + return unmarshalEvent[JanusEventWebRTCLocalCandidate](e.Event) + case JanusEventSubTypeWebRTCRemoteCandidate: + return unmarshalEvent[JanusEventWebRTCRemoteCandidate](e.Event) + case JanusEventSubTypeWebRTCSelectedPair: + return unmarshalEvent[JanusEventWebRTCSelectedPair](e.Event) + case JanusEventSubTypeWebRTCDTLS: + return unmarshalEvent[JanusEventWebRTCDTLS](e.Event) + case JanusEventSubTypeWebRTCPeerConnection: + return unmarshalEvent[JanusEventWebRTCPeerConnection](e.Event) + } case JanusEventTypeMedia: switch e.SubType { case JanusEventSubTypeMediaState: @@ -295,16 +308,6 @@ func (e JanusEventWebRTCPeerConnection) String() string { return marshalEvent(e) } -// type=16 -type JanusEventWebRTC struct { - Owner string `json:"owner"` - Jsep json.RawMessage `json:"jsep"` -} - -func (e JanusEventWebRTC) String() string { - return marshalEvent(e) -} - // type=32, subtype=1 type JanusEventMediaState struct { Media string `json:"media"` // "audio", "video" diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index a2d4975..bf078fe 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "fmt" "net" "net/http" "net/http/httptest" @@ -258,8 +259,12 @@ func (s *janusEventSender) AddEvent(t *testing.T, eventType int, eventSubtype in t.Helper() require := require.New(t) + assert := assert.New(t) data, err := json.Marshal(event) require.NoError(err) + if s, ok := event.(fmt.Stringer); assert.True(ok, "%T should implement fmt.Stringer", event) { + assert.Equal(s.String(), string(data)) + } message := JanusEvent{ Type: eventType, @@ -271,6 +276,220 @@ func (s *janusEventSender) AddEvent(t *testing.T, eventType int, eventSubtype in s.events = append(s.events, message) } +func TestJanusEventsHandlerDifferentTypes(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + _, url, handler := NewTestJanusEventsHandlerServer(t) + + mcu := &TestMcuWithEvents{ + t: t, + } + handler.mcu = mcu + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + dialer := websocket.Dialer{ + Subprotocols: []string{ + JanusEventsSubprotocol, + }, + } + conn, response, err := dialer.DialContext(ctx, url, nil) + require.NoError(err) + + assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + + var sender janusEventSender + + sender.AddEvent( + t, + JanusEventTypeSession, + 0, + 1, + JanusEventSession{ + Name: "created", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeHandle, + 0, + 1, + JanusEventHandle{ + Name: "attached", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeExternal, + 0, + 0, + JanusEventExternal{ + Schema: "test-external", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeJSEP, + 0, + 1, + JanusEventJSEP{ + Owner: "testing", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeWebRTC, + JanusEventSubTypeWebRTCICE, + 1, + JanusEventWebRTCICE{ + ICE: "gathering", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeWebRTC, + JanusEventSubTypeWebRTCLocalCandidate, + 1, + JanusEventWebRTCLocalCandidate{ + LocalCandidate: "invalid-candidate", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeWebRTC, + JanusEventSubTypeWebRTCRemoteCandidate, + 1, + JanusEventWebRTCRemoteCandidate{ + RemoteCandidate: "invalid-candidate", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeWebRTC, + JanusEventSubTypeWebRTCSelectedPair, + 1, + JanusEventWebRTCSelectedPair{ + SelectedPair: "invalid-pair", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeWebRTC, + JanusEventSubTypeWebRTCDTLS, + 1, + JanusEventWebRTCDTLS{ + DTLS: "trying", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeWebRTC, + JanusEventSubTypeWebRTCPeerConnection, + 1, + JanusEventWebRTCPeerConnection{ + Connection: "webrtcup", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeMedia, + JanusEventSubTypeMediaState, + 1, + JanusEventMediaState{ + Media: "audio", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeMedia, + JanusEventSubTypeMediaSlowLink, + 1, + JanusEventMediaSlowLink{ + Media: "audio", + }, + ) + + sender.AddEvent( + t, + JanusEventTypePlugin, + 0, + 1, + JanusEventPlugin{ + Plugin: "test-plugin", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeTransport, + 0, + 1, + JanusEventTransport{ + Transport: "test-transport", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeCore, + JanusEventSubTypeCoreStatusStartup, + 0, + JanusEventCoreStartup{ + Status: "started", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeCore, + JanusEventSubTypeCoreStatusStartup, + 0, + JanusEventCoreStartup{ + Status: "update", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeCore, + JanusEventSubTypeCoreStatusShutdown, + 0, + JanusEventCoreShutdown{ + Status: "shutdown", + }, + ) + + sender.AddEvent( + t, + JanusEventTypeMedia, + JanusEventSubTypeMediaStats, + 1, + JanusEventMediaStats{ + Media: "audio", + BytesSentLastSec: 100, + BytesReceivedLastSec: 200, + }, + ) + sender.Send(t, conn) + + // Wait until all events are processed. + assert.NoError(mcu.WaitForUpdates(ctx, 1)) +} + func TestJanusEventsHandlerNotGrouped(t *testing.T) { t.Parallel() require := require.New(t) From 889ec056f21e6f8f5dc01f15783fa4986f314995 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Nov 2025 12:11:13 +0100 Subject: [PATCH 266/549] Also expose bandwidth usage of backend servers through metrics. --- api_proxy.go | 5 +++++ docs/prometheus-metrics.md | 1 + mcu_proxy.go | 6 ++++++ mcu_stats_prometheus.go | 7 +++++++ proxy/proxy_server.go | 8 ++++++-- 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api_proxy.go b/api_proxy.go index 8f3bd6e..7b1a5e7 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -321,6 +321,11 @@ type EventProxyServerBandwidth struct { Incoming *float64 `json:"incoming,omitempty"` // Outgoing is the bandwidth utilization for subscribers in percent. Outgoing *float64 `json:"outgoing,omitempty"` + + // Received is the incoming bandwidth in bytes per second. + Received uint64 `json:"received,omitempty"` + // Sent is the outgoing bandwidth in bytes per second. + Sent uint64 `json:"sent,omitempty"` } func (b *EventProxyServerBandwidth) String() string { diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 5027c40..1f1c155 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -57,4 +57,5 @@ The following metrics are available: | `signaling_backend_client_requests_errors_total` | Counter | 2.0.3 | The total number of backend client requests that had an error | `backend`, `error` | | `signaling_mcu_bandwidth` | Gauge | 2.0.5 | The current bandwidth in bytes per second | `direction` | | `signaling_mcu_backend_usage` | Gauge | 2.0.5 | The current usage of signaling proxy backends in percent | `url`, `direction` | +| `signaling_mcu_backend_bandwidth` | Gauge | 2.0.5 | The current bandwidth of signaling proxy backends in bytes per second | `url`, `direction` | | `signaling_proxy_load` | Gauge | 2.0.5 | The current load of the signaling proxy | | diff --git a/mcu_proxy.go b/mcu_proxy.go index 461be23..b46d703 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -416,6 +416,8 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str statsProxyBackendLoadCurrent.WithLabelValues(conn.url.String()).Set(0) statsProxyUsageCurrent.WithLabelValues(conn.url.String(), "incoming").Set(0) statsProxyUsageCurrent.WithLabelValues(conn.url.String(), "outgoing").Set(0) + statsProxyBandwidthCurrent.WithLabelValues(conn.url.String(), "incoming").Set(0) + statsProxyBandwidthCurrent.WithLabelValues(conn.url.String(), "outgoing").Set(0) return conn, nil } @@ -754,6 +756,8 @@ func (c *mcuProxyConnection) closeIfEmpty() bool { statsProxyBackendLoadCurrent.DeleteLabelValues(c.url.String()) statsProxyUsageCurrent.DeleteLabelValues(c.url.String(), "incoming") statsProxyUsageCurrent.DeleteLabelValues(c.url.String(), "outgoing") + statsProxyBandwidthCurrent.DeleteLabelValues(c.url.String(), "incoming") + statsProxyBandwidthCurrent.DeleteLabelValues(c.url.String(), "outgoing") c.proxy.removeConnection(c) }() @@ -1090,6 +1094,8 @@ func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { c.bandwidth.Store(event.Bandwidth) statsProxyBackendLoadCurrent.WithLabelValues(c.url.String()).Set(float64(event.Load)) if bw := event.Bandwidth; bw != nil { + statsProxyBandwidthCurrent.WithLabelValues(c.url.String(), "incoming").Set(float64(bw.Received)) + statsProxyBandwidthCurrent.WithLabelValues(c.url.String(), "outgoing").Set(float64(bw.Sent)) if bw.Incoming != nil { statsProxyUsageCurrent.WithLabelValues(c.url.String(), "incoming").Set(*bw.Incoming) } else { diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index d1df618..cd063ea 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -115,6 +115,12 @@ var ( Name: "backend_usage", Help: "The current usage of signaling proxy backends in percent", }, []string{"url", "direction"}) + statsProxyBandwidthCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "backend_bandwidth", + Help: "The current bandwidth of signaling proxy backends in bytes per second", + }, []string{"url", "direction"}) statsProxyNobackendAvailableTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "signaling", Subsystem: "mcu", @@ -126,6 +132,7 @@ var ( statsConnectedProxyBackendsCurrent, statsProxyBackendLoadCurrent, statsProxyUsageCurrent, + statsProxyBandwidthCurrent, statsProxyNobackendAvailableTotal, } ) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index b38b190..93f1f42 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -503,8 +503,12 @@ func (s *ProxyServer) newLoadEvent(load uint64, incoming uint64, outgoing uint64 } maxIncoming := s.maxIncoming.Load() maxOutgoing := s.maxOutgoing.Load() - if maxIncoming > 0 || maxOutgoing > 0 { - msg.Event.Bandwidth = &signaling.EventProxyServerBandwidth{} + if maxIncoming > 0 || maxOutgoing > 0 || incoming != 0 || outgoing != 0 { + // Values should be sent in bytes per second. + msg.Event.Bandwidth = &signaling.EventProxyServerBandwidth{ + Received: incoming / 8, + Sent: outgoing / 8, + } if maxIncoming > 0 { value := float64(incoming) / float64(maxIncoming) * 100 msg.Event.Bandwidth.Incoming = &value From f11fc4008af8c6ede53ff7fb69caf9a9640e434a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 5 Nov 2025 12:11:20 +0100 Subject: [PATCH 267/549] Update generated files. --- api_proxy_easyjson.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index 95894a7..209edc1 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -1423,6 +1423,18 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle *out.Outgoing = float64(in.Float64()) } } + case "received": + if in.IsNull() { + in.Skip() + } else { + out.Received = uint64(in.Uint64()) + } + case "sent": + if in.IsNull() { + in.Skip() + } else { + out.Sent = uint64(in.Uint64()) + } default: in.SkipRecursive() } @@ -1453,6 +1465,26 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw } out.Float64(float64(*in.Outgoing)) } + if in.Received != 0 { + const prefix string = ",\"received\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Uint64(uint64(in.Received)) + } + if in.Sent != 0 { + const prefix string = ",\"sent\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Uint64(uint64(in.Sent)) + } out.RawByte('}') } From dc1c166fd18b846942d41d63fdc8a5aa04e319ec Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Nov 2025 09:14:30 +0100 Subject: [PATCH 268/549] Add type to store bandwidths. --- api/bandwidth.go | 79 +++++++++++++++++++++++++++++++++++++++++++ api/bandwidth_test.go | 58 +++++++++++++++++++++++++++++++ internal/nocopy.go | 35 +++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 api/bandwidth.go create mode 100644 api/bandwidth_test.go create mode 100644 internal/nocopy.go diff --git a/api/bandwidth.go b/api/bandwidth.go new file mode 100644 index 0000000..9434a6f --- /dev/null +++ b/api/bandwidth.go @@ -0,0 +1,79 @@ +/** + * 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 api + +import ( + "sync/atomic" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" +) + +// Bandwidth stores a bandwidth in bits per second. +type Bandwidth uint64 + +// Bits returns the bandwidth in bits per second. +func (b Bandwidth) Bits() uint64 { + return uint64(b) +} + +// Bytes returns the bandwidth in bytes per second. +func (b Bandwidth) Bytes() uint64 { + return b.Bits() / 8 +} + +// BandwidthFromBits creates a bandwidth from bits per second. +func BandwidthFromBits(b uint64) Bandwidth { + return Bandwidth(b) +} + +// BandwithFromBits creates a bandwidth from megabits per second. +func BandwidthFromMegabits(b uint64) Bandwidth { + return Bandwidth(b * 1024 * 1024) +} + +// BandwidthFromBytes creates a bandwidth from bytes per second. +func BandwidthFromBytes(b uint64) Bandwidth { + return Bandwidth(b * 8) +} + +// AtomicBandwidth is an atomic Bandwidth. The zero value is zero. +// AtomicBandwidth must not be copied after first use. +type AtomicBandwidth struct { + // 64-bit members that are accessed atomically must be 64-bit aligned. + v uint64 + _ internal.NoCopy +} + +// Load atomically loads and returns the value stored in b. +func (b *AtomicBandwidth) Load() Bandwidth { + return Bandwidth(atomic.LoadUint64(&b.v)) // +checklocksignore +} + +// Store atomically stores v into b. +func (b *AtomicBandwidth) Store(v Bandwidth) { + atomic.StoreUint64(&b.v, uint64(v)) // +checklocksignore +} + +// Swap atomically stores v into b and returns the previous value. +func (b *AtomicBandwidth) Swap(v Bandwidth) Bandwidth { + return Bandwidth(atomic.SwapUint64(&b.v, uint64(v))) // +checklocksignore +} diff --git a/api/bandwidth_test.go b/api/bandwidth_test.go new file mode 100644 index 0000000..935c8f1 --- /dev/null +++ b/api/bandwidth_test.go @@ -0,0 +1,58 @@ +/** + * 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 api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBandwidth(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + var b Bandwidth + assert.EqualValues(0, b.Bits()) + assert.EqualValues(0, b.Bytes()) + + b = BandwidthFromBits(8000) + assert.EqualValues(8000, b.Bits()) + assert.EqualValues(1000, b.Bytes()) + + b = BandwidthFromBytes(1000) + assert.EqualValues(8000, b.Bits()) + assert.EqualValues(1000, b.Bytes()) + + b = BandwidthFromMegabits(2) + assert.EqualValues(2*1024*1024, b.Bits()) + assert.EqualValues(2*1024*1024/8, b.Bytes()) + + var a AtomicBandwidth + assert.EqualValues(0, a.Load()) + a.Store(1000) + assert.EqualValues(1000, a.Load()) + old := a.Swap(2000) + assert.EqualValues(1000, old) + assert.EqualValues(2000, a.Load()) +} diff --git a/internal/nocopy.go b/internal/nocopy.go new file mode 100644 index 0000000..cc81cc3 --- /dev/null +++ b/internal/nocopy.go @@ -0,0 +1,35 @@ +/** + * 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 internal + +// NoCopy may be added to structs which must not be copied +// after the first use. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +// +// Note that it must not be embedded, due to the Lock and Unlock methods. +type NoCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*NoCopy) Lock() {} +func (*NoCopy) Unlock() {} From 3aacca1ff706dfbcf864bc24c46cbdce20b79694 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Nov 2025 10:00:50 +0100 Subject: [PATCH 269/549] Switch to new Bandwidth type. --- api_backend.go | 4 +-- api_proxy.go | 16 ++++++------ api_signaling.go | 14 +++++------ backend_configuration.go | 5 ++-- backend_storage_static.go | 13 +++++----- clientsession.go | 2 +- clientsession_test.go | 6 ++--- hub.go | 12 ++++----- mcu_common.go | 40 ++++++++++++++--------------- mcu_janus.go | 24 +++++++++--------- mcu_janus_client.go | 10 ++++---- mcu_janus_events_handler.go | 5 ++-- mcu_janus_events_handler_test.go | 12 +++++---- mcu_janus_test.go | 22 ++++++++-------- mcu_proxy.go | 22 ++++++++-------- mcu_test.go | 24 +++++++++--------- proxy/proxy_server.go | 43 ++++++++++++++++---------------- proxy/proxy_server_test.go | 17 ++++++------- 18 files changed, 147 insertions(+), 144 deletions(-) diff --git a/api_backend.go b/api_backend.go index 9b01999..5929f7c 100644 --- a/api_backend.go +++ b/api_backend.go @@ -453,8 +453,8 @@ type BackendInformationEtcd struct { parsedUrls []*url.URL Secret string `json:"secret"` - MaxStreamBitrate int `json:"maxstreambitrate,omitempty"` - MaxScreenBitrate int `json:"maxscreenbitrate,omitempty"` + MaxStreamBitrate api.Bandwidth `json:"maxstreambitrate,omitempty"` + MaxScreenBitrate api.Bandwidth `json:"maxscreenbitrate,omitempty"` SessionLimit uint64 `json:"sessionlimit,omitempty"` } diff --git a/api_proxy.go b/api_proxy.go index 7b1a5e7..1c332a5 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -196,8 +196,8 @@ type ByeProxyServerMessage struct { // Type "command" type NewPublisherSettings struct { - Bitrate int `json:"bitrate,omitempty"` - MediaTypes MediaType `json:"mediatypes,omitempty"` + Bitrate api.Bandwidth `json:"bitrate,omitempty"` + MediaTypes MediaType `json:"mediatypes,omitempty"` AudioCodec string `json:"audiocodec,omitempty"` VideoCodec string `json:"videocodec,omitempty"` @@ -214,7 +214,7 @@ type CommandProxyClientMessage struct { ClientId string `json:"clientId,omitempty"` // Deprecated: use PublisherSettings instead. - Bitrate int `json:"bitrate,omitempty"` + Bitrate api.Bandwidth `json:"bitrate,omitempty"` // Deprecated: use PublisherSettings instead. MediaTypes MediaType `json:"mediatypes,omitempty"` @@ -269,7 +269,7 @@ type CommandProxyServerMessage struct { Id string `json:"id,omitempty"` Sid string `json:"sid,omitempty"` - Bitrate int `json:"bitrate,omitempty"` + Bitrate api.Bandwidth `json:"bitrate,omitempty"` Streams []PublisherStream `json:"streams,omitempty"` } @@ -322,10 +322,10 @@ type EventProxyServerBandwidth struct { // Outgoing is the bandwidth utilization for subscribers in percent. Outgoing *float64 `json:"outgoing,omitempty"` - // Received is the incoming bandwidth in bytes per second. - Received uint64 `json:"received,omitempty"` - // Sent is the outgoing bandwidth in bytes per second. - Sent uint64 `json:"sent,omitempty"` + // Received is the incoming bandwidth. + Received api.Bandwidth `json:"received,omitempty"` + // Sent is the outgoing bandwidth. + Sent api.Bandwidth `json:"sent,omitempty"` } func (b *EventProxyServerBandwidth) String() string { diff --git a/api_signaling.go b/api_signaling.go index e884a05..4af7c28 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -693,8 +693,8 @@ type RoomServerMessage struct { } type RoomBandwidth struct { - MaxStreamBitrate int `json:"maxstreambitrate"` - MaxScreenBitrate int `json:"maxscreenbitrate"` + MaxStreamBitrate api.Bandwidth `json:"maxstreambitrate"` + MaxScreenBitrate api.Bandwidth `json:"maxscreenbitrate"` } type RoomErrorDetails struct { @@ -730,11 +730,11 @@ type MessageClientMessageData struct { Payload api.StringMap `json:"payload"` // Only supported if Type == "offer" - Bitrate int `json:"bitrate,omitempty"` - AudioCodec string `json:"audiocodec,omitempty"` - VideoCodec string `json:"videocodec,omitempty"` - VP9Profile string `json:"vp9profile,omitempty"` - H264Profile string `json:"h264profile,omitempty"` + Bitrate api.Bandwidth `json:"bitrate,omitempty"` + AudioCodec string `json:"audiocodec,omitempty"` + VideoCodec string `json:"videocodec,omitempty"` + VP9Profile string `json:"vp9profile,omitempty"` + H264Profile string `json:"h264profile,omitempty"` offerSdp *sdp.SessionDescription // Only set if Type == "offer" answerSdp *sdp.SessionDescription // Only set if Type == "answer" diff --git a/backend_configuration.go b/backend_configuration.go index bc604e6..d79bd7e 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -30,6 +30,7 @@ import ( "sync" "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) @@ -51,8 +52,8 @@ type Backend struct { allowHttp bool - maxStreamBitrate int - maxScreenBitrate int + maxStreamBitrate api.Bandwidth + maxScreenBitrate api.Bandwidth sessionLimit uint64 sessionsLock sync.Mutex diff --git a/backend_storage_static.go b/backend_storage_static.go index 8a1d6a3..601bf4e 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) @@ -73,8 +74,8 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) sessionLimit: uint64(sessionLimit), counted: true, - maxStreamBitrate: maxStreamBitrate, - maxScreenBitrate: maxScreenBitrate, + maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), + maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), } if sessionLimit > 0 { log.Printf("Allow a maximum of %d sessions", sessionLimit) @@ -131,8 +132,8 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) sessionLimit: uint64(sessionLimit), counted: true, - maxStreamBitrate: maxStreamBitrate, - maxScreenBitrate: maxScreenBitrate, + maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), + maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), } hosts := make([]string, 0, len(allowMap)) for host := range allowMap { @@ -342,8 +343,8 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr id: id, secret: []byte(secret), - maxStreamBitrate: maxStreamBitrate, - maxScreenBitrate: maxScreenBitrate, + maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), + maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), sessionLimit: uint64(sessionLimit), } diff --git a/clientsession.go b/clientsession.go index 2450b68..832bf15 100644 --- a/clientsession.go +++ b/clientsession.go @@ -910,7 +910,7 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea H264Profile: data.H264Profile, } if backend := s.Backend(); backend != nil { - var maxBitrate int + var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { maxBitrate = backend.maxScreenBitrate } else { diff --git a/clientsession_test.go b/clientsession_test.go index c455f6e..815c65f 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -63,7 +63,7 @@ func TestBandwidth_Client(t *testing.T) { client.RunUntilJoined(ctx, hello.Hello) // Client may not send an offer with audio and video. - bitrate := 10000 + bitrate := api.BandwidthFromBits(10000) require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, @@ -135,7 +135,7 @@ func TestBandwidth_Backend(t *testing.T) { require.True(client.RunUntilJoined(ctx, hello.Hello)) // Client may not send an offer with audio and video. - bitrate := 10000 + bitrate := api.BandwidthFromBits(10000) require.NoError(client.SendMessage(MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, @@ -154,7 +154,7 @@ func TestBandwidth_Backend(t *testing.T) { pub := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(pub, "Could not find publisher") - var expectBitrate int + var expectBitrate api.Bandwidth if streamType == StreamTypeVideo { expectBitrate = backend.maxStreamBitrate } else { diff --git a/hub.go b/hub.go index 21620ca..b70eded 100644 --- a/hub.go +++ b/hub.go @@ -1661,20 +1661,20 @@ func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Roo RoomId: room.id, Properties: room.Properties(), } - var mcuStreamBitrate int - var mcuScreenBitrate int + var mcuStreamBitrate api.Bandwidth + var mcuScreenBitrate api.Bandwidth if mcu := h.mcu; mcu != nil { mcuStreamBitrate, mcuScreenBitrate = mcu.GetBandwidthLimits() } - var backendStreamBitrate int - var backendScreenBitrate int + var backendStreamBitrate api.Bandwidth + var backendScreenBitrate api.Bandwidth if backend := room.Backend(); backend != nil { backendStreamBitrate = backend.maxStreamBitrate backendScreenBitrate = backend.maxScreenBitrate } - var maxStreamBitrate int + var maxStreamBitrate api.Bandwidth if mcuStreamBitrate != 0 && backendStreamBitrate != 0 { maxStreamBitrate = min(mcuStreamBitrate, backendStreamBitrate) } else if mcuStreamBitrate != 0 { @@ -1683,7 +1683,7 @@ func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Roo maxStreamBitrate = backendStreamBitrate } - var maxScreenBitrate int + var maxScreenBitrate api.Bandwidth if mcuScreenBitrate != 0 && backendScreenBitrate != 0 { maxScreenBitrate = min(mcuScreenBitrate, backendScreenBitrate) } else if mcuScreenBitrate != 0 { diff --git a/mcu_common.go b/mcu_common.go index a30c5da..bf3bff7 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -38,12 +38,12 @@ const ( McuTypeProxy = "proxy" McuTypeDefault = McuTypeJanus - - defaultMaxStreamBitrate = 1024 * 1024 - defaultMaxScreenBitrate = 2048 * 1024 ) var ( + defaultMaxStreamBitrate = api.BandwidthFromMegabits(1) + defaultMaxScreenBitrate = api.BandwidthFromMegabits(2) + ErrNotConnected = fmt.Errorf("not connected") ) @@ -74,25 +74,25 @@ type McuInitiator interface { } type McuSettings interface { - MaxStreamBitrate() int32 - MaxScreenBitrate() int32 + MaxStreamBitrate() api.Bandwidth + MaxScreenBitrate() api.Bandwidth Timeout() time.Duration Reload(config *goconf.ConfigFile) } type mcuCommonSettings struct { - maxStreamBitrate atomic.Int32 - maxScreenBitrate atomic.Int32 + maxStreamBitrate api.AtomicBandwidth + maxScreenBitrate api.AtomicBandwidth timeout atomic.Int64 } -func (s *mcuCommonSettings) MaxStreamBitrate() int32 { +func (s *mcuCommonSettings) MaxStreamBitrate() api.Bandwidth { return s.maxStreamBitrate.Load() } -func (s *mcuCommonSettings) MaxScreenBitrate() int32 { +func (s *mcuCommonSettings) MaxScreenBitrate() api.Bandwidth { return s.maxScreenBitrate.Load() } @@ -107,17 +107,17 @@ func (s *mcuCommonSettings) setTimeout(timeout time.Duration) { func (s *mcuCommonSettings) load(config *goconf.ConfigFile) error { maxStreamBitrate, _ := config.GetInt("mcu", "maxstreambitrate") if maxStreamBitrate <= 0 { - maxStreamBitrate = defaultMaxStreamBitrate + maxStreamBitrate = int(defaultMaxStreamBitrate.Bits()) } log.Printf("Maximum bandwidth %d bits/sec per publishing stream", maxStreamBitrate) - s.maxStreamBitrate.Store(int32(maxStreamBitrate)) + s.maxStreamBitrate.Store(api.BandwidthFromBits(uint64(maxStreamBitrate))) maxScreenBitrate, _ := config.GetInt("mcu", "maxscreenbitrate") if maxScreenBitrate <= 0 { - maxScreenBitrate = defaultMaxScreenBitrate + maxScreenBitrate = int(defaultMaxScreenBitrate.Bits()) } log.Printf("Maximum bandwidth %d bits/sec per screensharing stream", maxScreenBitrate) - s.maxScreenBitrate.Store(int32(maxScreenBitrate)) + s.maxScreenBitrate.Store(api.BandwidthFromBits(uint64(maxScreenBitrate))) return nil } @@ -131,7 +131,7 @@ type Mcu interface { GetStats() any GetServerInfoSfu() *BackendServerInfoSfu - GetBandwidthLimits() (int, int) + GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) @@ -201,18 +201,18 @@ func IsValidStreamType(s string) bool { } type McuClientBandwidthInfo struct { - // Sent is the outgoing bandwidth in bytes per second. - Sent uint64 - // Received is the incoming bandwidth in bytes per second. - Received uint64 + // Sent is the outgoing bandwidth. + Sent api.Bandwidth + // Received is the incoming bandwidth. + Received api.Bandwidth } type McuClient interface { Id() string Sid() string StreamType() StreamType - // MaxBitrate is the maximum allowed bitrate in bits per second. - MaxBitrate() int + // MaxBitrate is the maximum allowed bitrate. + MaxBitrate() api.Bandwidth Close(ctx context.Context) diff --git a/mcu_janus.go b/mcu_janus.go index d8d8e58..d4558d6 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -144,7 +144,7 @@ type clientInterface interface { NotifyReconnected() Bandwidth() *McuClientBandwidthInfo - UpdateBandwidth(media string, sent uint32, received uint32) + UpdateBandwidth(media string, sent api.Bandwidth, received api.Bandwidth) } type mcuJanusSettings struct { @@ -307,8 +307,8 @@ func (m *mcuJanus) disconnect() { } } -func (m *mcuJanus) GetBandwidthLimits() (int, int) { - return int(m.settings.MaxStreamBitrate()), int(m.settings.MaxScreenBitrate()) +func (m *mcuJanus) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { + return m.settings.MaxStreamBitrate(), m.settings.MaxScreenBitrate() } func (m *mcuJanus) Bandwidth() (result *McuClientBandwidthInfo) { @@ -341,8 +341,8 @@ func (m *mcuJanus) updateBandwidthStats() { } if bandwidth := m.Bandwidth(); bandwidth != nil { - statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(float64(bandwidth.Received)) - statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(float64(bandwidth.Sent)) + statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(float64(bandwidth.Received.Bytes())) + statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(float64(bandwidth.Sent.Bytes())) } else { statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(0) statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(0) @@ -655,7 +655,7 @@ func (m *mcuJanus) SubscriberDisconnected(id string, publisher PublicSessionId, } } -func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, int, error) { +func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, api.Bandwidth, error) { create_msg := api.StringMap{ "request": "create", "description": getStreamId(id, streamType), @@ -677,11 +677,11 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, if profile := settings.H264Profile; profile != "" { create_msg["h264_profile"] = profile } - var maxBitrate int + var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { - maxBitrate = int(m.settings.MaxScreenBitrate()) + maxBitrate = m.settings.MaxScreenBitrate() } else { - maxBitrate = int(m.settings.MaxStreamBitrate()) + maxBitrate = m.settings.MaxStreamBitrate() } bitrate := settings.Bitrate if bitrate <= 0 { @@ -689,7 +689,7 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, } else { bitrate = min(bitrate, maxBitrate) } - create_msg["bitrate"] = bitrate + create_msg["bitrate"] = bitrate.Bits() create_response, err := handle.Request(ctx, create_msg) if err != nil { if _, err2 := handle.Detach(ctx); err2 != nil { @@ -710,7 +710,7 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, return roomId, bitrate, nil } -func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, int, error) { +func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, api.Bandwidth, error) { session := m.session if session == nil { return nil, 0, 0, 0, ErrNotConnected @@ -1055,7 +1055,7 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener return client, nil } -func (m *mcuJanus) UpdateBandwidth(handle uint64, media string, sent uint32, received uint32) { +func (m *mcuJanus) UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) { m.muClients.RLock() defer m.muClients.RUnlock() diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 66cca69..8945199 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -44,7 +44,7 @@ type mcuJanusClient struct { roomId uint64 sid string streamType StreamType - maxBitrate int + maxBitrate api.Bandwidth // +checklocks:mu bandwidth map[string]*McuClientBandwidthInfo @@ -78,7 +78,7 @@ func (c *mcuJanusClient) StreamType() StreamType { return c.streamType } -func (c *mcuJanusClient) MaxBitrate() int { +func (c *mcuJanusClient) MaxBitrate() api.Bandwidth { return c.maxBitrate } @@ -88,7 +88,7 @@ func (c *mcuJanusClient) Close(ctx context.Context) { func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { } -func (c *mcuJanusClient) UpdateBandwidth(media string, sent uint32, received uint32) { +func (c *mcuJanusClient) UpdateBandwidth(media string, sent api.Bandwidth, received api.Bandwidth) { c.mu.Lock() defer c.mu.Unlock() @@ -102,8 +102,8 @@ func (c *mcuJanusClient) UpdateBandwidth(media string, sent uint32, received uin c.bandwidth[media] = info } - info.Sent = uint64(sent) - info.Received = uint64(received) + info.Sent = sent + info.Received = received } func (c *mcuJanusClient) Bandwidth() *McuClientBandwidthInfo { diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index 04be231..0bb97c5 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -35,6 +35,7 @@ import ( "github.com/gorilla/websocket" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) @@ -527,7 +528,7 @@ func (e JanusEventCoreShutdown) String() string { } type McuEventHandler interface { - UpdateBandwidth(handle uint64, media string, sent uint32, received uint32) + UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) } type JanusEventsHandler struct { @@ -737,6 +738,6 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { switch evt := evt.(type) { case *JanusEventMediaStats: - h.mcu.UpdateBandwidth(event.HandleId, evt.Media, evt.BytesSentLastSec, evt.BytesReceivedLastSec) + h.mcu.UpdateBandwidth(event.HandleId, evt.Media, api.BandwidthFromBytes(uint64(evt.BytesSentLastSec)), api.BandwidthFromBytes(uint64(evt.BytesReceivedLastSec))) } } diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index bf078fe..ace254f 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -36,6 +36,8 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type TestJanusEventsServerHandler struct { @@ -195,7 +197,7 @@ type TestMcuWithEvents struct { idx int } -func (m *TestMcuWithEvents) UpdateBandwidth(handle uint64, media string, sent uint32, received uint32) { +func (m *TestMcuWithEvents) UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) { assert := assert.New(m.t) m.mu.Lock() @@ -206,13 +208,13 @@ func (m *TestMcuWithEvents) UpdateBandwidth(handle uint64, media string, sent ui case 1: assert.EqualValues(1, handle) assert.EqualValues("audio", media) - assert.EqualValues(100, sent) - assert.EqualValues(200, received) + assert.EqualValues(api.BandwidthFromBytes(100), sent) + assert.EqualValues(api.BandwidthFromBytes(200), received) case 2: assert.EqualValues(1, handle) assert.EqualValues("video", media) - assert.EqualValues(200, sent) - assert.EqualValues(300, received) + assert.EqualValues(api.BandwidthFromBytes(200), sent) + assert.EqualValues(api.BandwidthFromBytes(300), received) default: assert.Fail("too many updates", "received update %d (handle=%d, media=%s, sent=%d, received=%d)", m.idx, handle, media, sent, received) } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 74632ed..fb3e3ef 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -1093,7 +1093,7 @@ func Test_JanusPublisherSubscriber(t *testing.T) { defer cancel() // Bandwidth for unknown handles is ignored. - mcu.UpdateBandwidth(1234, "video", 100, 200) + mcu.UpdateBandwidth(1234, "video", api.BandwidthFromBytes(100), api.BandwidthFromBytes(200)) mcu.updateBandwidthStats() checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 0) checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 0) @@ -1117,14 +1117,14 @@ func Test_JanusPublisherSubscriber(t *testing.T) { assert.Nil(mcu.Bandwidth()) assert.Nil(janusPub.Bandwidth()) - mcu.UpdateBandwidth(janusPub.Handle(), "video", 1000, 2000) + mcu.UpdateBandwidth(janusPub.Handle(), "video", api.BandwidthFromBytes(1000), api.BandwidthFromBytes(2000)) if bw := janusPub.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(1000, bw.Sent) - assert.EqualValues(2000, bw.Received) + assert.EqualValues(api.BandwidthFromBytes(1000), bw.Sent) + assert.EqualValues(api.BandwidthFromBytes(2000), bw.Received) } if bw := mcu.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(1000, bw.Sent) - assert.EqualValues(2000, bw.Received) + assert.EqualValues(api.BandwidthFromBytes(1000), bw.Sent) + assert.EqualValues(api.BandwidthFromBytes(2000), bw.Received) } mcu.updateBandwidthStats() checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) @@ -1145,14 +1145,14 @@ func Test_JanusPublisherSubscriber(t *testing.T) { require.True(ok) assert.Nil(janusSub.Bandwidth()) - mcu.UpdateBandwidth(janusSub.Handle(), "video", 3000, 4000) + mcu.UpdateBandwidth(janusSub.Handle(), "video", api.BandwidthFromBytes(3000), api.BandwidthFromBytes(4000)) if bw := janusSub.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(3000, bw.Sent) - assert.EqualValues(4000, bw.Received) + assert.EqualValues(api.BandwidthFromBytes(3000), bw.Sent) + assert.EqualValues(api.BandwidthFromBytes(4000), bw.Received) } if bw := mcu.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(4000, bw.Sent) - assert.EqualValues(6000, bw.Received) + assert.EqualValues(api.BandwidthFromBytes(4000), bw.Sent) + assert.EqualValues(api.BandwidthFromBytes(6000), bw.Received) } checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 1000) diff --git a/mcu_proxy.go b/mcu_proxy.go index b46d703..85ce622 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -82,7 +82,7 @@ type McuProxy interface { type mcuProxyPubSubCommon struct { sid string streamType StreamType - maxBitrate int + maxBitrate api.Bandwidth proxyId string conn *mcuProxyConnection listener McuListener @@ -100,7 +100,7 @@ func (c *mcuProxyPubSubCommon) StreamType() StreamType { return c.streamType } -func (c *mcuProxyPubSubCommon) MaxBitrate() int { +func (c *mcuProxyPubSubCommon) MaxBitrate() api.Bandwidth { return c.maxBitrate } @@ -148,7 +148,7 @@ type mcuProxyPublisher struct { settings NewPublisherSettings } -func newMcuProxyPublisher(id PublicSessionId, sid string, streamType StreamType, maxBitrate int, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { +func newMcuProxyPublisher(id PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { return &mcuProxyPublisher{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ sid: sid, @@ -240,7 +240,7 @@ type mcuProxySubscriber struct { publisherConn *mcuProxyConnection } -func newMcuProxySubscriber(publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate int, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { +func newMcuProxySubscriber(publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { return &mcuProxySubscriber{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ sid: sid, @@ -1094,8 +1094,8 @@ func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { c.bandwidth.Store(event.Bandwidth) statsProxyBackendLoadCurrent.WithLabelValues(c.url.String()).Set(float64(event.Load)) if bw := event.Bandwidth; bw != nil { - statsProxyBandwidthCurrent.WithLabelValues(c.url.String(), "incoming").Set(float64(bw.Received)) - statsProxyBandwidthCurrent.WithLabelValues(c.url.String(), "outgoing").Set(float64(bw.Sent)) + statsProxyBandwidthCurrent.WithLabelValues(c.url.String(), "incoming").Set(float64(bw.Received.Bytes())) + statsProxyBandwidthCurrent.WithLabelValues(c.url.String(), "outgoing").Set(float64(bw.Sent.Bytes())) if bw.Incoming != nil { statsProxyUsageCurrent.WithLabelValues(c.url.String(), "incoming").Set(*bw.Incoming) } else { @@ -1569,8 +1569,8 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * return mcu, nil } -func (m *mcuProxy) GetBandwidthLimits() (int, int) { - return int(m.settings.MaxStreamBitrate()), int(m.settings.MaxScreenBitrate()) +func (m *mcuProxy) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { + return m.settings.MaxStreamBitrate(), m.settings.MaxScreenBitrate() } func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { @@ -2009,11 +2009,11 @@ func (m *mcuProxy) removePublisher(publisher *mcuProxyPublisher) { } func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuPublisher { - var maxBitrate int + var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { - maxBitrate = int(m.settings.MaxScreenBitrate()) + maxBitrate = m.settings.MaxScreenBitrate() } else { - maxBitrate = int(m.settings.MaxStreamBitrate()) + maxBitrate = m.settings.MaxStreamBitrate() } publisherSettings := settings diff --git a/mcu_test.go b/mcu_test.go index 0a25406..4023a90 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -35,9 +35,9 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" ) -const ( - TestMaxBitrateScreen = 12345678 - TestMaxBitrateVideo = 23456789 +var ( + TestMaxBitrateScreen = api.BandwidthFromBits(12345678) + TestMaxBitrateVideo = api.BandwidthFromBits(23456789) ) type TestMCU struct { @@ -47,8 +47,8 @@ type TestMCU struct { // +checklocks:mu subscribers map[string]*TestMCUSubscriber - maxStreamBitrate atomic.Int32 - maxScreenBitrate atomic.Int32 + maxStreamBitrate api.AtomicBandwidth + maxScreenBitrate api.AtomicBandwidth } func NewTestMCU() (*TestMCU, error) { @@ -58,13 +58,13 @@ func NewTestMCU() (*TestMCU, error) { }, nil } -func (m *TestMCU) GetBandwidthLimits() (int, int) { - return int(m.maxStreamBitrate.Load()), int(m.maxScreenBitrate.Load()) +func (m *TestMCU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { + return m.maxStreamBitrate.Load(), m.maxScreenBitrate.Load() } -func (m *TestMCU) SetBandwidthLimits(maxStreamBitrate int, maxScreenBitrate int) { - m.maxStreamBitrate.Store(int32(maxStreamBitrate)) - m.maxScreenBitrate.Store(int32(maxScreenBitrate)) +func (m *TestMCU) SetBandwidthLimits(maxStreamBitrate api.Bandwidth, maxScreenBitrate api.Bandwidth) { + m.maxStreamBitrate.Store(maxStreamBitrate) + m.maxScreenBitrate.Store(maxScreenBitrate) } func (m *TestMCU) Start(ctx context.Context) error { @@ -92,7 +92,7 @@ func (m *TestMCU) GetServerInfoSfu() *BackendServerInfoSfu { } func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { - var maxBitrate int + var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { maxBitrate = TestMaxBitrateScreen } else { @@ -176,7 +176,7 @@ func (c *TestMCUClient) StreamType() StreamType { return c.streamType } -func (c *TestMCUClient) MaxBitrate() int { +func (c *TestMCUClient) MaxBitrate() api.Bandwidth { return 0 } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 93f1f42..53bb797 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -117,10 +117,10 @@ type ProxyServer struct { stopped atomic.Bool load atomic.Uint64 - maxIncoming atomic.Uint64 - currentIncoming atomic.Uint64 - maxOutgoing atomic.Uint64 - currentOutgoing atomic.Uint64 + maxIncoming api.AtomicBandwidth + currentIncoming api.AtomicBandwidth + maxOutgoing api.AtomicBandwidth + currentOutgoing api.AtomicBandwidth shutdownChannel chan struct{} shutdownScheduled atomic.Bool @@ -189,7 +189,7 @@ func GetLocalIP() (string, error) { return "", nil } -func getTargetBandwidths(config *goconf.ConfigFile) (int, int) { +func getTargetBandwidths(config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidth) { maxIncoming, _ := config.GetInt("bandwidth", "incoming") if maxIncoming < 0 { maxIncoming = 0 @@ -209,7 +209,7 @@ func getTargetBandwidths(config *goconf.ConfigFile) (int, int) { log.Printf("Target bandwidth for outgoing streams: unlimited") } - return maxIncoming, maxOutgoing + return api.BandwidthFromMegabits(uint64(maxIncoming)), api.BandwidthFromMegabits(uint64(maxOutgoing)) } func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*ProxyServer, error) { @@ -377,8 +377,8 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* remotePublishers: make(map[string]map[*proxyRemotePublisher]bool), } - result.maxIncoming.Store(uint64(maxIncoming) * 1024 * 1024) - result.maxOutgoing.Store(uint64(maxOutgoing) * 1024 * 1024) + result.maxIncoming.Store(maxIncoming) + result.maxOutgoing.Store(maxOutgoing) result.statsAllowedIps.Store(statsAllowedIps) result.trustedProxies.Store(trustedProxiesIps) result.upgrader.CheckOrigin = result.checkOrigin @@ -493,7 +493,7 @@ loop: } } -func (s *ProxyServer) newLoadEvent(load uint64, incoming uint64, outgoing uint64) *signaling.ProxyServerMessage { +func (s *ProxyServer) newLoadEvent(load uint64, incoming api.Bandwidth, outgoing api.Bandwidth) *signaling.ProxyServerMessage { msg := &signaling.ProxyServerMessage{ Type: "event", Event: &signaling.EventProxyServerMessage{ @@ -504,10 +504,9 @@ func (s *ProxyServer) newLoadEvent(load uint64, incoming uint64, outgoing uint64 maxIncoming := s.maxIncoming.Load() maxOutgoing := s.maxOutgoing.Load() if maxIncoming > 0 || maxOutgoing > 0 || incoming != 0 || outgoing != 0 { - // Values should be sent in bytes per second. msg.Event.Bandwidth = &signaling.EventProxyServerBandwidth{ - Received: incoming / 8, - Sent: outgoing / 8, + Received: incoming, + Sent: outgoing, } if maxIncoming > 0 { value := float64(incoming) / float64(maxIncoming) * 100 @@ -534,7 +533,7 @@ func (s *ProxyServer) updateLoad() { s.sendLoadToAll(load, incoming, outgoing) } -func (s *ProxyServer) sendLoadToAll(load uint64, incoming uint64, outgoing uint64) { +func (s *ProxyServer) sendLoadToAll(load uint64, incoming api.Bandwidth, outgoing api.Bandwidth) { if s.shutdownScheduled.Load() { // Server is scheduled to shutdown, no need to update clients with current load. return @@ -638,9 +637,9 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { } maxIncoming, maxOutgoing := getTargetBandwidths(config) - oldIncoming := s.maxIncoming.Swap(uint64(maxIncoming)) - oldOutgoing := s.maxOutgoing.Swap(uint64(maxOutgoing)) - if oldIncoming != uint64(maxIncoming) || oldOutgoing != uint64(maxOutgoing) { + oldIncoming := s.maxIncoming.Swap(maxIncoming) + oldOutgoing := s.maxOutgoing.Swap(maxOutgoing) + if oldIncoming != maxIncoming || oldOutgoing != maxOutgoing { // Notify sessions about updated load / bandwidth usage. go s.sendLoadToAll(s.load.Load(), s.currentIncoming.Load(), s.currentOutgoing.Load()) } @@ -1022,7 +1021,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s Type: "command", Command: &signaling.CommandProxyServerMessage{ Id: id, - Bitrate: int(publisher.MaxBitrate()), + Bitrate: publisher.MaxBitrate(), }, } session.sendMessage(response) @@ -1578,7 +1577,7 @@ func (s *ProxyServer) HasClients() bool { return len(s.clients) > 0 } -func (s *ProxyServer) GetClientsLoad() (load uint64, incoming uint64, outgoing uint64) { +func (s *ProxyServer) GetClientsLoad() (load uint64, incoming api.Bandwidth, outgoing api.Bandwidth) { s.clientsLock.RLock() defer s.clientsLock.RUnlock() @@ -1586,20 +1585,20 @@ func (s *ProxyServer) GetClientsLoad() (load uint64, incoming uint64, outgoing u // Use "current" bandwidth usage if supported. if bw, ok := c.(signaling.McuClientWithBandwidth); ok { if bandwidth := bw.Bandwidth(); bandwidth != nil { - incoming += bandwidth.Received * 8 - outgoing += bandwidth.Sent * 8 + incoming += bandwidth.Received + outgoing += bandwidth.Sent continue } } - bitrate := uint64(c.MaxBitrate()) + bitrate := c.MaxBitrate() if _, ok := c.(signaling.McuPublisher); ok { incoming += bitrate } else if _, ok := c.(signaling.McuSubscriber); ok { outgoing += bitrate } } - load = incoming + outgoing + load = incoming.Bits() + outgoing.Bits() load = min(uint64(len(s.clients)), load/1024) return } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 850de2b..39b9324 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -358,7 +358,7 @@ type TestMCU struct { t *testing.T } -func (m *TestMCU) GetBandwidthLimits() (int, int) { +func (m *TestMCU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { return 0, 0 } @@ -416,7 +416,7 @@ func (p *TestMCUPublisher) StreamType() signaling.StreamType { return p.streamType } -func (p *TestMCUPublisher) MaxBitrate() int { +func (p *TestMCUPublisher) MaxBitrate() api.Bandwidth { return 0 } @@ -469,8 +469,8 @@ func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener signaling. }, bandwidth: &signaling.McuClientBandwidthInfo{ - Sent: 1000, - Received: 2000, + Sent: api.BandwidthFromBytes(1000), + Received: api.BandwidthFromBytes(2000), }, } return publisher, nil @@ -490,9 +490,8 @@ func TestProxyPublisherBandwidth(t *testing.T) { require := require.New(t) proxy, key, server := newProxyServerForTest(t) - // Values are in bits per second. - proxy.maxIncoming.Store(10000 * 8) - proxy.maxOutgoing.Store(10000 * 8) + proxy.maxIncoming.Store(api.BandwidthFromBytes(10000)) + proxy.maxOutgoing.Store(api.BandwidthFromBytes(10000)) mcu := NewPublisherTestMCU(t) proxy.mcu = mcu @@ -886,7 +885,7 @@ func (p *TestRemotePublisher) StreamType() signaling.StreamType { return p.streamType } -func (p *TestRemotePublisher) MaxBitrate() int { +func (p *TestRemotePublisher) MaxBitrate() api.Bandwidth { return 0 } @@ -959,7 +958,7 @@ func (s *TestRemoteSubscriber) StreamType() signaling.StreamType { return s.publisher.StreamType() } -func (s *TestRemoteSubscriber) MaxBitrate() int { +func (s *TestRemoteSubscriber) MaxBitrate() api.Bandwidth { return 0 } From 315fba975b57464a1cde89fb602c5046a4f7d5e7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Nov 2025 10:01:53 +0100 Subject: [PATCH 270/549] Update generated files. --- api_backend_easyjson.go | 8 ++++---- api_proxy_easyjson.go | 16 ++++++++-------- api_signaling_easyjson.go | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 3cf4ca9..5952647 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -4022,13 +4022,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle if in.IsNull() { in.Skip() } else { - out.MaxStreamBitrate = int(in.Int()) + out.MaxStreamBitrate = api.Bandwidth(in.Uint64()) } case "maxscreenbitrate": if in.IsNull() { in.Skip() } else { - out.MaxScreenBitrate = int(in.Int()) + out.MaxScreenBitrate = api.Bandwidth(in.Uint64()) } case "sessionlimit": if in.IsNull() { @@ -4088,12 +4088,12 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw if in.MaxStreamBitrate != 0 { const prefix string = ",\"maxstreambitrate\":" out.RawString(prefix) - out.Int(int(in.MaxStreamBitrate)) + out.Uint64(uint64(in.MaxStreamBitrate)) } if in.MaxScreenBitrate != 0 { const prefix string = ",\"maxscreenbitrate\":" out.RawString(prefix) - out.Int(int(in.MaxScreenBitrate)) + out.Uint64(uint64(in.MaxScreenBitrate)) } if in.SessionLimit != 0 { const prefix string = ",\"sessionlimit\":" diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index 209edc1..b527f17 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -913,7 +913,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex if in.IsNull() { in.Skip() } else { - out.Bitrate = int(in.Int()) + out.Bitrate = api.Bandwidth(in.Uint64()) } case "mediatypes": if in.IsNull() { @@ -963,7 +963,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwr const prefix string = ",\"bitrate\":" first = false out.RawString(prefix[1:]) - out.Int(int(in.Bitrate)) + out.Uint64(uint64(in.Bitrate)) } if in.MediaTypes != 0 { const prefix string = ",\"mediatypes\":" @@ -1427,13 +1427,13 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle if in.IsNull() { in.Skip() } else { - out.Received = uint64(in.Uint64()) + out.Received = api.Bandwidth(in.Uint64()) } case "sent": if in.IsNull() { in.Skip() } else { - out.Sent = uint64(in.Uint64()) + out.Sent = api.Bandwidth(in.Uint64()) } default: in.SkipRecursive() @@ -1541,7 +1541,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle if in.IsNull() { in.Skip() } else { - out.Bitrate = int(in.Int()) + out.Bitrate = api.Bandwidth(in.Uint64()) } case "streams": if in.IsNull() { @@ -1604,7 +1604,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw } else { out.RawString(prefix) } - out.Int(int(in.Bitrate)) + out.Uint64(uint64(in.Bitrate)) } if len(in.Streams) != 0 { const prefix string = ",\"streams\":" @@ -1894,7 +1894,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle if in.IsNull() { in.Skip() } else { - out.Bitrate = int(in.Int()) + out.Bitrate = api.Bandwidth(in.Uint64()) } case "mediatypes": if in.IsNull() { @@ -1988,7 +1988,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw if in.Bitrate != 0 { const prefix string = ",\"bitrate\":" out.RawString(prefix) - out.Int(int(in.Bitrate)) + out.Uint64(uint64(in.Bitrate)) } if in.MediaTypes != 0 { const prefix string = ",\"mediatypes\":" diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 34aae51..67ecbdb 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -2083,13 +2083,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle if in.IsNull() { in.Skip() } else { - out.MaxStreamBitrate = int(in.Int()) + out.MaxStreamBitrate = api.Bandwidth(in.Uint64()) } case "maxscreenbitrate": if in.IsNull() { in.Skip() } else { - out.MaxScreenBitrate = int(in.Int()) + out.MaxScreenBitrate = api.Bandwidth(in.Uint64()) } default: in.SkipRecursive() @@ -2108,12 +2108,12 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw { const prefix string = ",\"maxstreambitrate\":" out.RawString(prefix[1:]) - out.Int(int(in.MaxStreamBitrate)) + out.Uint64(uint64(in.MaxStreamBitrate)) } { const prefix string = ",\"maxscreenbitrate\":" out.RawString(prefix) - out.Int(int(in.MaxScreenBitrate)) + out.Uint64(uint64(in.MaxScreenBitrate)) } out.RawByte('}') } @@ -2640,7 +2640,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle if in.IsNull() { in.Skip() } else { - out.Bitrate = int(in.Int()) + out.Bitrate = api.Bandwidth(in.Uint64()) } case "audiocodec": if in.IsNull() { @@ -2725,7 +2725,7 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw if in.Bitrate != 0 { const prefix string = ",\"bitrate\":" out.RawString(prefix) - out.Int(int(in.Bitrate)) + out.Uint64(uint64(in.Bitrate)) } if in.AudioCodec != "" { const prefix string = ",\"audiocodec\":" From 71fda2f258024ea79d5b4d94f6d77eab76f6ba06 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Nov 2025 16:35:54 +0100 Subject: [PATCH 271/549] Add metric for RTT of WebSocket ping messages. --- client.go | 1 + client_stats_prometheus.go | 8 ++++++++ docs/prometheus-metrics.md | 1 + 3 files changed, 10 insertions(+) diff --git a/client.go b/client.go index 53f8967..b7d7d38 100644 --- a/client.go +++ b/client.go @@ -353,6 +353,7 @@ func (c *Client) ReadPump() { log.Printf("Client from %s has RTT of %d ms (%s)", addr, rtt_ms, rtt) } } + statsClientRTT.Observe(float64(rtt.Milliseconds())) c.getHandler().OnRTTReceived(c, rtt) } return nil diff --git a/client_stats_prometheus.go b/client_stats_prometheus.go index e20447e..6a6d140 100644 --- a/client_stats_prometheus.go +++ b/client_stats_prometheus.go @@ -32,9 +32,17 @@ var ( Name: "countries_total", Help: "The total number of connections by country", }, []string{"country"}) + statsClientRTT = prometheus.NewHistogram(prometheus.HistogramOpts{ + Namespace: "signaling", + Subsystem: "client", + Name: "rtt", + Help: "The roundtrip time of WebSocket ping messages in milliseconds", + Buckets: prometheus.ExponentialBucketsRange(1, 30000, 50), + }) clientStats = []prometheus.Collector{ statsClientCountries, + statsClientRTT, } ) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 1f1c155..013c601 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -59,3 +59,4 @@ The following metrics are available: | `signaling_mcu_backend_usage` | Gauge | 2.0.5 | The current usage of signaling proxy backends in percent | `url`, `direction` | | `signaling_mcu_backend_bandwidth` | Gauge | 2.0.5 | The current bandwidth of signaling proxy backends in bytes per second | `url`, `direction` | | `signaling_proxy_load` | Gauge | 2.0.5 | The current load of the signaling proxy | | +| `signaling_client_rtt` | Histogram | 2.0.5 | The roundtrip time of WebSocket ping messages in milliseconds | | From fa900132b43faef966f11a99483dd0565e774846 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Nov 2025 20:41:43 +0100 Subject: [PATCH 272/549] Add metrics for candidates and ICE, DTLS and PeerConnection states. --- docs/prometheus-metrics.md | 5 +++++ mcu_janus_events_handler.go | 12 +++++++++++- mcu_stats_prometheus.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 013c601..3ff405d 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -60,3 +60,8 @@ The following metrics are available: | `signaling_mcu_backend_bandwidth` | Gauge | 2.0.5 | The current bandwidth of signaling proxy backends in bytes per second | `url`, `direction` | | `signaling_proxy_load` | Gauge | 2.0.5 | The current load of the signaling proxy | | | `signaling_client_rtt` | Histogram | 2.0.5 | The roundtrip time of WebSocket ping messages in milliseconds | | +| `signaling_mcu_selected_local_candidate_total` | Counter | 2.0.5 | Total number of selected local candidates | `type`, `transport`, `family` | +| `signaling_mcu_selected_remote_candidate_total` | Counter | 2.0.5 | Total number of selected local candidates | `type`, `transport`, `family` | +| `signaling_mcu_peerconnection_state_total` | Counter | 2.0.5 | Total number PeerConnection states | `state`, `reason` | +| `signaling_mcu_ice_state_total` | Counter | 2.0.5 | Total number of ICE connection states | `state` | +| `signaling_mcu_dtls_state_total` | Counter | 2.0.5 | Total number of DTLS connection states | `state` | diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index 0bb97c5..e9d6706 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -302,7 +302,8 @@ func (e JanusEventWebRTCDTLS) String() string { // type=16, subtype=6 type JanusEventWebRTCPeerConnection struct { - Connection string `json:"connection"` // "webrtcup" + Connection string `json:"connection"` // "webrtcup", "hangup" + Reason string `json:"reason,omitempty"` // Only if "connection" == "hangup" } func (e JanusEventWebRTCPeerConnection) String() string { @@ -737,6 +738,15 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { } switch evt := evt.(type) { + case *JanusEventWebRTCICE: + statsJanusICEStateTotal.WithLabelValues(evt.ICE).Inc() + case *JanusEventWebRTCDTLS: + statsJanusDTLSStateTotal.WithLabelValues(evt.DTLS).Inc() + case *JanusEventWebRTCPeerConnection: + statsJanusPeerConnectionStateTotal.WithLabelValues(evt.Connection, evt.Reason).Inc() + case *JanusEventWebRTCSelectedPair: + statsJanusSelectedLocalCandidateTotal.WithLabelValues(evt.Candidates.Local.Type, evt.Candidates.Local.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Local.Family)).Inc() + statsJanusSelectedRemoteCandidateTotal.WithLabelValues(evt.Candidates.Remote.Type, evt.Candidates.Remote.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Remote.Family)).Inc() case *JanusEventMediaStats: h.mcu.UpdateBandwidth(event.HandleId, evt.Media, api.BandwidthFromBytes(uint64(evt.BytesSentLastSec)), api.BandwidthFromBytes(uint64(evt.BytesReceivedLastSec))) } diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index cd063ea..a821a89 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -92,9 +92,44 @@ var ( Name: "bandwidth", Help: "The current bandwidth in bytes per second", }, []string{"direction"}) + statsJanusSelectedLocalCandidateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "selected_local_candidate_total", + Help: "Total number of selected local candidates", + }, []string{"type", "transport", "family"}) + statsJanusSelectedRemoteCandidateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "selected_remote_candidate_total", + Help: "Total number of selected remote candidates", + }, []string{"type", "transport", "family"}) + statsJanusPeerConnectionStateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "peerconnection_state_total", + Help: "Total number of PeerConnections states", + }, []string{"state", "reason"}) + statsJanusICEStateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "ice_state_total", + Help: "Total number of ICE connection states", + }, []string{"state"}) + statsJanusDTLSStateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "dtls_state_total", + Help: "Total number of DTLS connection states", + }, []string{"state"}) janusMcuStats = []prometheus.Collector{ statsJanusBandwidthCurrent, + statsJanusSelectedLocalCandidateTotal, + statsJanusSelectedRemoteCandidateTotal, + statsJanusPeerConnectionStateTotal, + statsJanusICEStateTotal, + statsJanusDTLSStateTotal, } statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ From a5c7ad272fb163dc9be37b0c32fb9074d25a84f3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 6 Nov 2025 20:44:48 +0100 Subject: [PATCH 273/549] Need events of type "webrtc". --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c23600..7cc8b83 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ websocket events handler (`janus.eventhandler.wsevh.jcfg`), the module must be enabled by setting `enabled` to `true`, the `backend` must be set to the websocket url of the signaling server (or signaling proxy) and `subprotocol` must be set to `janus-events`. -At least events of type `media` must be subscribed. +At least events of type `media` and `webrtc` must be subscribed. Edit the `server.conf` and enter the URL to the websocket endpoint of Janus in the section `[mcu]` and key `url`. During startup, the signaling server will From 42d691ce4a18b956d9c2cda45400cf3bb6780d49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:01:47 +0000 Subject: [PATCH 274/549] Bump golangci/golangci-lint-action from 8.0.0 to 9.0.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 8.0.0 to 9.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 25ab73b..73d350c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,7 +33,7 @@ jobs: go-version: "1.24" - name: lint - uses: golangci/golangci-lint-action@v8.0.0 + uses: golangci/golangci-lint-action@v9.0.0 with: version: latest args: --timeout=2m0s From 2a17128743d6cbbf67aa6c19dd696a77c93ec949 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:02:15 +0000 Subject: [PATCH 275/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.5 to 3.6.6 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.5...v3.6.6) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.5 to 3.6.6 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.5...v3.6.6) Updates `go.etcd.io/etcd/client/v3` from 3.6.5 to 3.6.6 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.5...v3.6.6) Updates `go.etcd.io/etcd/server/v3` from 3.6.5 to 3.6.6 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.5...v3.6.6) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b6f3283..1d64bf9 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/api/v3 v3.6.5 - go.etcd.io/etcd/client/pkg/v3 v3.6.5 - go.etcd.io/etcd/client/v3 v3.6.5 - go.etcd.io/etcd/server/v3 v3.6.5 + go.etcd.io/etcd/api/v3 v3.6.6 + go.etcd.io/etcd/client/pkg/v3 v3.6.6 + go.etcd.io/etcd/client/v3 v3.6.6 + go.etcd.io/etcd/server/v3 v3.6.6 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.76.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 @@ -76,7 +76,7 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.5 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.6 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index e3abd53..86b2f95 100644 --- a/go.sum +++ b/go.sum @@ -143,16 +143,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= -go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= -go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= -go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= -go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= -go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= -go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM= -go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU= -go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0= -go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= +go.etcd.io/etcd/pkg/v3 v3.6.6 h1:wylOivS/UxXTZ0Le5fOdxCjatW5ql9dcWEggQQHSorw= +go.etcd.io/etcd/pkg/v3 v3.6.6/go.mod h1:9TKZL7WUEVHXYM3srP3ESZfIms34s1G72eNtWA9YKg4= +go.etcd.io/etcd/server/v3 v3.6.6 h1:YSRWGJPzU+lIREwUQI4MfyLZrkUyzjJOVpMxJvZePaY= +go.etcd.io/etcd/server/v3 v3.6.6/go.mod h1:A1OQ1x3PaiENDLywMjCiMwV1pwJSpb0h9Z5ORP2dv6I= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From 1785e7b42e658aa9097bf13699bb16d4aa0d8e24 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 13 Nov 2025 13:20:10 +0100 Subject: [PATCH 276/549] Also rewrite token in comment for federated chat relay. --- clientsession_test.go | 3 +++ federation.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/clientsession_test.go b/clientsession_test.go index 815c65f..a9e2c72 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -204,6 +204,7 @@ func TestFeatureChatRelay(t *testing.T) { "lala": map[string]any{ "one": "eins", }, + "token": roomId, } message := api.StringMap{ "type": "chat", @@ -340,6 +341,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { require.NotNil(room) chatComment := map[string]any{ + "token": roomId, "actorId": hello1.Hello.UserId, "actorType": "users", "lastEditActorId": hello1.Hello.UserId, @@ -370,6 +372,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { }, } federatedChatComment := map[string]any{ + "token": federatedRoomId, "actorId": hello1.Hello.UserId + "@" + getCloudUrl(server1.URL), "actorType": "federated_users", "lastEditActorId": hello1.Hello.UserId + "@" + getCloudUrl(server1.URL), diff --git a/federation.go b/federation.go index afed664..218b09a 100644 --- a/federation.go +++ b/federation.go @@ -660,6 +660,11 @@ func (c *FederationClient) updateComment(comment api.StringMap, localCloudUrl st changed = true } + if token, found := api.GetStringMapString[string](comment, "token"); found && c.changeRoomId.Load() && token == c.RemoteRoomId() { + comment["token"] = c.RoomId() + changed = true + } + if params, found := api.GetStringMapEntry[map[string]any](comment, "messageParameters"); found { localUrl := getCloudUrlWithoutPath(c.session.BackendUrl()) remoteUrl := getCloudUrlWithoutPath(c.federation.Load().NextcloudUrl) From 1f0ed8005aa72e0a82a1977c8a53e7af9eb704a0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 13 Nov 2025 14:20:28 +0100 Subject: [PATCH 277/549] Add metrics for Janus slow link events. --- docs/prometheus-metrics.md | 1 + mcu_janus_events_handler.go | 9 +++++++++ mcu_stats_prometheus.go | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 3ff405d..6784592 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -65,3 +65,4 @@ The following metrics are available: | `signaling_mcu_peerconnection_state_total` | Counter | 2.0.5 | Total number PeerConnection states | `state`, `reason` | | `signaling_mcu_ice_state_total` | Counter | 2.0.5 | Total number of ICE connection states | `state` | | `signaling_mcu_dtls_state_total` | Counter | 2.0.5 | Total number of DTLS connection states | `state` | +| `signaling_mcu_slow_link_total` | Counter | 2.0.5 | Total number of slow link events | `media`, `direction` | diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index e9d6706..b1bae25 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -747,6 +747,15 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { case *JanusEventWebRTCSelectedPair: statsJanusSelectedLocalCandidateTotal.WithLabelValues(evt.Candidates.Local.Type, evt.Candidates.Local.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Local.Family)).Inc() statsJanusSelectedRemoteCandidateTotal.WithLabelValues(evt.Candidates.Remote.Type, evt.Candidates.Remote.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Remote.Family)).Inc() + case *JanusEventMediaSlowLink: + var direction string + // "uplink" is Janus -> client, "downlink" is client -> Janus. + if evt.SlowLink == "uplink" { + direction = "outgoing" + } else { + direction = "incoming" + } + statsJanusSlowLinkTotal.WithLabelValues(evt.Media, direction).Inc() case *JanusEventMediaStats: h.mcu.UpdateBandwidth(event.HandleId, evt.Media, api.BandwidthFromBytes(uint64(evt.BytesSentLastSec)), api.BandwidthFromBytes(uint64(evt.BytesReceivedLastSec))) } diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index a821a89..239249a 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -122,6 +122,12 @@ var ( Name: "dtls_state_total", Help: "Total number of DTLS connection states", }, []string{"state"}) + statsJanusSlowLinkTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "slow_link_total", + Help: "Total number of slow link events", + }, []string{"media", "direction"}) janusMcuStats = []prometheus.Collector{ statsJanusBandwidthCurrent, @@ -130,6 +136,7 @@ var ( statsJanusPeerConnectionStateTotal, statsJanusICEStateTotal, statsJanusDTLSStateTotal, + statsJanusSlowLinkTotal, } statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ From 1ad460cee61a2a5724f20d8ca834107f4bf4f6f3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 13 Nov 2025 14:39:57 +0100 Subject: [PATCH 278/549] Add metrics for media RTT / jitter. --- docs/prometheus-metrics.md | 2 ++ mcu_janus_events_handler.go | 9 +++++++++ mcu_stats_prometheus.go | 16 ++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 6784592..ac4a3c2 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -66,3 +66,5 @@ The following metrics are available: | `signaling_mcu_ice_state_total` | Counter | 2.0.5 | Total number of ICE connection states | `state` | | `signaling_mcu_dtls_state_total` | Counter | 2.0.5 | Total number of DTLS connection states | `state` | | `signaling_mcu_slow_link_total` | Counter | 2.0.5 | Total number of slow link events | `media`, `direction` | +| `signaling_mcu_media_rtt` | Histogram | 2.0.5 | The roundtrip time of WebRTC media in milliseconds | `media` | +| `signaling_mcu_media_jitter` | Histogram | 2.0.5 | The jitter of WebRTC media in milliseconds | `media`, `origin` | diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index b1bae25..3467102 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -757,6 +757,15 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { } statsJanusSlowLinkTotal.WithLabelValues(evt.Media, direction).Inc() case *JanusEventMediaStats: + if rtt := evt.RTT; rtt > 0 { + statsJanusMediaRTT.WithLabelValues(evt.Media).Observe(float64(rtt)) + } + if jitter := evt.JitterLocal; jitter > 0 { + statsJanusMediaJitter.WithLabelValues(evt.Media, "local").Observe(float64(jitter)) + } + if jitter := evt.JitterRemote; jitter > 0 { + statsJanusMediaJitter.WithLabelValues(evt.Media, "remote").Observe(float64(jitter)) + } h.mcu.UpdateBandwidth(event.HandleId, evt.Media, api.BandwidthFromBytes(uint64(evt.BytesSentLastSec)), api.BandwidthFromBytes(uint64(evt.BytesReceivedLastSec))) } } diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index 239249a..a13ef1b 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -128,6 +128,20 @@ var ( Name: "slow_link_total", Help: "Total number of slow link events", }, []string{"media", "direction"}) + statsJanusMediaRTT = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_rtt", + Help: "The roundtrip time of WebRTC media in milliseconds", + Buckets: prometheus.ExponentialBucketsRange(1, 10000, 25), + }, []string{"media"}) + statsJanusMediaJitter = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_jitter", + Help: "The jitter of WebRTC media in milliseconds", + Buckets: prometheus.ExponentialBucketsRange(1, 2000, 20), + }, []string{"media", "origin"}) janusMcuStats = []prometheus.Collector{ statsJanusBandwidthCurrent, @@ -137,6 +151,8 @@ var ( statsJanusICEStateTotal, statsJanusDTLSStateTotal, statsJanusSlowLinkTotal, + statsJanusMediaRTT, + statsJanusMediaJitter, } statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ From 826d6244f3f808973b6e1574cde1f9807717ba58 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 13 Nov 2025 14:43:15 +0100 Subject: [PATCH 279/549] Use single metrics for selected candidates. --- docs/prometheus-metrics.md | 3 +-- mcu_janus_events_handler.go | 4 ++-- mcu_stats_prometheus.go | 17 +++++------------ 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index ac4a3c2..c8443df 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -60,8 +60,7 @@ The following metrics are available: | `signaling_mcu_backend_bandwidth` | Gauge | 2.0.5 | The current bandwidth of signaling proxy backends in bytes per second | `url`, `direction` | | `signaling_proxy_load` | Gauge | 2.0.5 | The current load of the signaling proxy | | | `signaling_client_rtt` | Histogram | 2.0.5 | The roundtrip time of WebSocket ping messages in milliseconds | | -| `signaling_mcu_selected_local_candidate_total` | Counter | 2.0.5 | Total number of selected local candidates | `type`, `transport`, `family` | -| `signaling_mcu_selected_remote_candidate_total` | Counter | 2.0.5 | Total number of selected local candidates | `type`, `transport`, `family` | +| `signaling_mcu_selected_candidate_total` | Counter | 2.0.5 | Total number of selected candidates | `origin`, `type`, `transport`, `family` | | `signaling_mcu_peerconnection_state_total` | Counter | 2.0.5 | Total number PeerConnection states | `state`, `reason` | | `signaling_mcu_ice_state_total` | Counter | 2.0.5 | Total number of ICE connection states | `state` | | `signaling_mcu_dtls_state_total` | Counter | 2.0.5 | Total number of DTLS connection states | `state` | diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index 3467102..b488548 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -745,8 +745,8 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { case *JanusEventWebRTCPeerConnection: statsJanusPeerConnectionStateTotal.WithLabelValues(evt.Connection, evt.Reason).Inc() case *JanusEventWebRTCSelectedPair: - statsJanusSelectedLocalCandidateTotal.WithLabelValues(evt.Candidates.Local.Type, evt.Candidates.Local.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Local.Family)).Inc() - statsJanusSelectedRemoteCandidateTotal.WithLabelValues(evt.Candidates.Remote.Type, evt.Candidates.Remote.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Remote.Family)).Inc() + statsJanusSelectedCandidateTotal.WithLabelValues("local", evt.Candidates.Local.Type, evt.Candidates.Local.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Local.Family)).Inc() + statsJanusSelectedCandidateTotal.WithLabelValues("remote", evt.Candidates.Remote.Type, evt.Candidates.Remote.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Remote.Family)).Inc() case *JanusEventMediaSlowLink: var direction string // "uplink" is Janus -> client, "downlink" is client -> Janus. diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index a13ef1b..2e08092 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -92,18 +92,12 @@ var ( Name: "bandwidth", Help: "The current bandwidth in bytes per second", }, []string{"direction"}) - statsJanusSelectedLocalCandidateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + statsJanusSelectedCandidateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "signaling", Subsystem: "mcu", - Name: "selected_local_candidate_total", - Help: "Total number of selected local candidates", - }, []string{"type", "transport", "family"}) - statsJanusSelectedRemoteCandidateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "selected_remote_candidate_total", - Help: "Total number of selected remote candidates", - }, []string{"type", "transport", "family"}) + Name: "selected_candidate_total", + Help: "Total number of selected candidates", + }, []string{"origin", "type", "transport", "family"}) statsJanusPeerConnectionStateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "signaling", Subsystem: "mcu", @@ -145,8 +139,7 @@ var ( janusMcuStats = []prometheus.Collector{ statsJanusBandwidthCurrent, - statsJanusSelectedLocalCandidateTotal, - statsJanusSelectedRemoteCandidateTotal, + statsJanusSelectedCandidateTotal, statsJanusPeerConnectionStateTotal, statsJanusICEStateTotal, statsJanusDTLSStateTotal, From 09850d2ce7955c59cbed694cce3376970ac47109 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:02:00 +0000 Subject: [PATCH 280/549] Bump github.com/nats-io/nats-server/v2 from 2.12.1 to 2.12.2 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.1 to 2.12.2. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.12.1...v2.12.2) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1d64bf9..d1d778d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 - github.com/nats-io/nats-server/v2 v2.12.1 + github.com/nats-io/nats-server/v2 v2.12.2 github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -51,9 +51,9 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/minio/highwayhash v1.0.3 // indirect + github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.11 // indirect @@ -91,7 +91,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/net v0.45.0 // indirect - golang.org/x/sys v0.37.0 // indirect + golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect diff --git a/go.sum b/go.sum index 86b2f95..66ef877 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -72,14 +72,14 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk= +github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.1 h1:0tRrc9bzyXEdBLcHr2XEjDzVpUxWx64aZBm7Rl1QDrA= -github.com/nats-io/nats-server/v2 v2.12.1/go.mod h1:OEaOLmu/2e6J9LzUt2OuGjgNem4EpYApO5Rpf26HDs8= +github.com/nats-io/nats-server/v2 v2.12.2 h1:4TEQd0Y4zvcW0IsVxjlXnRso1hBkQl3TS0BI+SxgPhE= +github.com/nats-io/nats-server/v2 v2.12.2/go.mod h1:j1AAttYeu7WnvD8HLJ+WWKNMSyxsqmZ160pNtCQRMyE= github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM= github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= @@ -207,8 +207,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= From 2d5379b61d9267974da13acd323da37f44eb4f24 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 13 Nov 2025 16:51:12 +0100 Subject: [PATCH 281/549] Add more media-related metrics. --- README.md | 2 +- docs/prometheus-metrics.md | 5 + mcu_janus_events_handler.go | 166 ++++++++++++++++++++++++++++++- mcu_janus_events_handler_test.go | 61 +++++++++++- mcu_stats_prometheus.go | 35 +++++++ stats_prometheus_test.go | 16 +++ 6 files changed, 276 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7cc8b83..7398c48 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ websocket events handler (`janus.eventhandler.wsevh.jcfg`), the module must be enabled by setting `enabled` to `true`, the `backend` must be set to the websocket url of the signaling server (or signaling proxy) and `subprotocol` must be set to `janus-events`. -At least events of type `media` and `webrtc` must be subscribed. +At least events of type `handles`, `media` and `webrtc` must be subscribed. Edit the `server.conf` and enter the URL to the websocket endpoint of Janus in the section `[mcu]` and key `url`. During startup, the signaling server will diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index c8443df..3016d6a 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -67,3 +67,8 @@ The following metrics are available: | `signaling_mcu_slow_link_total` | Counter | 2.0.5 | Total number of slow link events | `media`, `direction` | | `signaling_mcu_media_rtt` | Histogram | 2.0.5 | The roundtrip time of WebRTC media in milliseconds | `media` | | `signaling_mcu_media_jitter` | Histogram | 2.0.5 | The jitter of WebRTC media in milliseconds | `media`, `origin` | +| `signaling_mcu_media_codecs_total` | Counter | 2.0.5 | The total number of codecs | `media`, `codec` | +| `signaling_mcu_media_nacks_total` | Counter | 2.0.5 | The total number of NACKs | `media`, `direction` | +| `signaling_mcu_media_retransmissions_total` | Counter | 2.0.5 | The total number of received retransmissions | `media` | +| `signaling_mcu_media_bytes_total` | Counter | 2.0.5 | The total number of media bytes sent / received | `media`, `direction` | +| `signaling_mcu_media_lost_total` | Counter | 2.0.5 | The total number of lost media packets | `media`, `origin` | diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index b488548..7d0ddc1 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -27,6 +27,7 @@ import ( "errors" "fmt" "log" + "math" "net" "strconv" "strings" @@ -374,10 +375,10 @@ type JanusEventMediaStats struct { RTTValues *JanusMediaStatsRTTValues `json:"rtt-values,omitempty"` // For all media on all layers - PacketsReceived int32 `json:"packets-received"` - PacketsSent int32 `json:"packets-sent"` - BytesReceived int64 `json:"bytes-received"` - BytesSent int64 `json:"bytes-sent"` + PacketsReceived uint32 `json:"packets-received"` + PacketsSent uint32 `json:"packets-sent"` + BytesReceived uint64 `json:"bytes-received"` + BytesSent uint64 `json:"bytes-sent"` // For layer 0 if REMB is enabled REMBBitrate uint32 `json:"remb-bitrate"` @@ -532,6 +533,91 @@ type McuEventHandler interface { UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) } +type ValueCounter struct { + values map[string]uint64 +} + +func (c *ValueCounter) Update(key string, value uint64) uint64 { + if c.values == nil { + c.values = make(map[string]uint64) + } + + var delta uint64 + prev := c.values[key] + if value == prev { + return 0 + } else if value < prev { + // Wrap around + c.values[key] = 0 + delta = math.MaxUint64 - prev + value + } else { + delta = value - prev + } + + c.values[key] += delta + return delta +} + +type handleStats struct { + codecs map[string]string + + bytesReceived ValueCounter + bytesSent ValueCounter + + nacksReceived ValueCounter + nacksSent ValueCounter + + lostLocal ValueCounter + lostRemote ValueCounter + + retransmissionsReceived ValueCounter +} + +func (h *handleStats) Codec(media string, codec string) { + if h.codecs == nil { + h.codecs = make(map[string]string) + } + if h.codecs[media] != codec { + statsJanusMediaCodecsTotal.WithLabelValues(media, codec).Inc() + h.codecs[media] = codec + } +} + +func (h *handleStats) BytesReceived(media string, bytes uint64) { + delta := h.bytesReceived.Update(media, bytes) + statsJanusMediaBytesTotal.WithLabelValues(media, "incoming").Add(float64(delta)) +} + +func (h *handleStats) BytesSent(media string, bytes uint64) { + delta := h.bytesSent.Update(media, bytes) + statsJanusMediaBytesTotal.WithLabelValues(media, "outgoing").Add(float64(delta)) +} + +func (h *handleStats) NacksReceived(media string, nacks uint64) { + delta := h.nacksReceived.Update(media, nacks) + statsJanusMediaNACKTotal.WithLabelValues(media, "incoming").Add(float64(delta)) +} + +func (h *handleStats) NacksSent(media string, nacks uint64) { + delta := h.nacksSent.Update(media, nacks) + statsJanusMediaNACKTotal.WithLabelValues(media, "outgoing").Add(float64(delta)) +} + +func (h *handleStats) RetransmissionsReceived(media string, retransmissions uint64) { + delta := h.retransmissionsReceived.Update(media, retransmissions) + statsJanusMediaRetransmissionsTotal.WithLabelValues(media).Add(float64(delta)) +} + +func (h *handleStats) LostLocal(media string, lost uint64) { + delta := h.lostLocal.Update(media, lost) + statsJanusMediaLostTotal.WithLabelValues(media, "local").Add(float64(delta)) +} + +func (h *handleStats) LostRemote(media string, lost uint64) { + delta := h.lostRemote.Update(media, lost) + statsJanusMediaLostTotal.WithLabelValues(media, "remote").Add(float64(delta)) +} + type JanusEventsHandler struct { mu sync.Mutex @@ -542,6 +628,9 @@ type JanusEventsHandler struct { addr string agent string + supportsHandles bool + handleStats map[uint64]*handleStats + events chan JanusEvent } @@ -730,6 +819,32 @@ func (h *JanusEventsHandler) processEvents() { } } +func (h *JanusEventsHandler) deleteHandleStats(event JanusEvent) { + if event.HandleId != 0 { + delete(h.handleStats, event.HandleId) + } +} + +func (h *JanusEventsHandler) getHandleStats(event JanusEvent) *handleStats { + if !h.supportsHandles { + // Only create per-handle stats if enabled in Janus. Otherwise the + // handleStats map will never be cleaned up. + return nil + } else if event.HandleId == 0 { + return nil + } + + if h.handleStats == nil { + h.handleStats = make(map[uint64]*handleStats) + } + stats, found := h.handleStats[event.HandleId] + if !found { + stats = &handleStats{} + h.handleStats[event.HandleId] = stats + } + return stats +} + func (h *JanusEventsHandler) processEvent(event JanusEvent) { evt, err := event.Decode() if err != nil { @@ -738,6 +853,13 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { } switch evt := evt.(type) { + case *JanusEventHandle: + switch evt.Name { + case "attached": + h.supportsHandles = true + case "detached": + h.deleteHandleStats(event) + } case *JanusEventWebRTCICE: statsJanusICEStateTotal.WithLabelValues(evt.ICE).Inc() case *JanusEventWebRTCDTLS: @@ -766,6 +888,42 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { if jitter := evt.JitterRemote; jitter > 0 { statsJanusMediaJitter.WithLabelValues(evt.Media, "remote").Observe(float64(jitter)) } + if codec := evt.Codec; codec != "" { + if stats := h.getHandleStats(event); stats != nil { + stats.Codec(evt.Media, codec) + } + } + if stats := h.getHandleStats(event); stats != nil { + stats.BytesReceived(evt.Media, evt.BytesReceived) + } + if stats := h.getHandleStats(event); stats != nil { + stats.BytesSent(evt.Media, evt.BytesSent) + } + if nacks := evt.NacksReceived; nacks > 0 { + if stats := h.getHandleStats(event); stats != nil { + stats.NacksReceived(evt.Media, uint64(nacks)) + } + } + if nacks := evt.NacksSent; nacks > 0 { + if stats := h.getHandleStats(event); stats != nil { + stats.NacksSent(evt.Media, uint64(nacks)) + } + } + if retransmissions := evt.RetransmissionsReceived; retransmissions > 0 { + if stats := h.getHandleStats(event); stats != nil { + stats.RetransmissionsReceived(evt.Media, uint64(retransmissions)) + } + } + if lost := evt.Lost; lost > 0 { + if stats := h.getHandleStats(event); stats != nil { + stats.LostLocal(evt.Media, uint64(lost)) + } + } + if lost := evt.LostByRemote; lost > 0 { + if stats := h.getHandleStats(event); stats != nil { + stats.LostRemote(evt.Media, uint64(lost)) + } + } h.mcu.UpdateBandwidth(event.HandleId, evt.Media, api.BandwidthFromBytes(uint64(evt.BytesSentLastSec)), api.BandwidthFromBytes(uint64(evt.BytesReceivedLastSec))) } } diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index ace254f..b04d604 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -25,6 +25,7 @@ import ( "context" "encoding/json" "fmt" + "math" "net" "net/http" "net/http/httptest" @@ -248,6 +249,7 @@ func (s *janusEventSender) SendSingle(t *testing.T, conn *websocket.Conn) { require.Len(s.events, 1) require.NoError(conn.WriteJSON(s.events[0])) + s.events = nil } func (s *janusEventSender) Send(t *testing.T, conn *websocket.Conn) { @@ -255,6 +257,7 @@ func (s *janusEventSender) Send(t *testing.T, conn *websocket.Conn) { require := require.New(t) require.NoError(conn.WriteJSON(s.events)) + s.events = nil } func (s *janusEventSender) AddEvent(t *testing.T, eventType int, eventSubtype int, handleId uint64, event any) { @@ -493,7 +496,6 @@ func TestJanusEventsHandlerDifferentTypes(t *testing.T) { } func TestJanusEventsHandlerNotGrouped(t *testing.T) { - t.Parallel() require := require.New(t) assert := assert.New(t) @@ -517,16 +519,51 @@ func TestJanusEventsHandlerNotGrouped(t *testing.T) { assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assertCollectorChangeBy(t, statsJanusMediaNACKTotal.WithLabelValues("audio", "incoming"), 20) + assertCollectorChangeBy(t, statsJanusMediaNACKTotal.WithLabelValues("audio", "outgoing"), 30) + assertCollectorChangeBy(t, statsJanusMediaRetransmissionsTotal.WithLabelValues("audio"), 40) + assertCollectorChangeBy(t, statsJanusMediaLostTotal.WithLabelValues("audio", "local"), 50) + assertCollectorChangeBy(t, statsJanusMediaLostTotal.WithLabelValues("audio", "remote"), 60) + var sender janusEventSender + sender.AddEvent( + t, + JanusEventTypeHandle, + 0, + 1, + JanusEventHandle{ + Name: "attached", + }, + ) + sender.SendSingle(t, conn) sender.AddEvent( t, JanusEventTypeMedia, JanusEventSubTypeMediaStats, 1, JanusEventMediaStats{ - Media: "audio", - BytesSentLastSec: 100, - BytesReceivedLastSec: 200, + Media: "audio", + BytesSentLastSec: 100, + BytesReceivedLastSec: 200, + Codec: "opus", + RTT: 10, + JitterLocal: 11, + JitterRemote: 12, + NacksReceived: 20, + NacksSent: 30, + RetransmissionsReceived: 40, + Lost: 50, + LostByRemote: 60, + }, + ) + sender.SendSingle(t, conn) + sender.AddEvent( + t, + JanusEventTypeHandle, + 0, + 1, + JanusEventHandle{ + Name: "detached", }, ) sender.SendSingle(t, conn) @@ -585,3 +622,19 @@ func TestJanusEventsHandlerGrouped(t *testing.T) { assert.NoError(mcu.WaitForUpdates(ctx, 2)) } + +func TestValueCounter(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + var c ValueCounter + assert.EqualValues(0, c.Update("foo", 0)) + assert.EqualValues(10, c.Update("foo", 10)) + assert.EqualValues(0, c.Update("foo", 10)) + assert.EqualValues(1, c.Update("foo", 11)) + assert.EqualValues(10, c.Update("bar", 10)) + assert.EqualValues(1, c.Update("bar", 11)) + assert.EqualValues(uint64(math.MaxUint64-10), c.Update("baz", math.MaxUint64-10)) + assert.EqualValues(20, c.Update("baz", 10)) +} diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index 2e08092..cb5ecf3 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -136,6 +136,36 @@ var ( Help: "The jitter of WebRTC media in milliseconds", Buckets: prometheus.ExponentialBucketsRange(1, 2000, 20), }, []string{"media", "origin"}) + statsJanusMediaCodecsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_codecs_total", + Help: "The total number of codecs", + }, []string{"media", "codec"}) + statsJanusMediaNACKTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_nacks_total", + Help: "The total number of NACKs", + }, []string{"media", "direction"}) + statsJanusMediaRetransmissionsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_retransmissions_total", + Help: "The total number of received retransmissions", + }, []string{"media"}) + statsJanusMediaBytesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_bytes_total", + Help: "The total number of media bytes sent / received", + }, []string{"media", "direction"}) + statsJanusMediaLostTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "media_lost_total", + Help: "The total number of lost media packets", + }, []string{"media", "origin"}) janusMcuStats = []prometheus.Collector{ statsJanusBandwidthCurrent, @@ -146,6 +176,11 @@ var ( statsJanusSlowLinkTotal, statsJanusMediaRTT, statsJanusMediaJitter, + statsJanusMediaCodecsTotal, + statsJanusMediaNACKTotal, + statsJanusMediaRetransmissionsTotal, + statsJanusMediaBytesTotal, + statsJanusMediaLostTotal, } statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index 7783d26..0516de9 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -42,6 +42,22 @@ func ResetStatsValue[T prometheus.Gauge](t *testing.T, collector T) { }) } +func assertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta float64) { + t.Helper() + + ch := make(chan *prometheus.Desc, 1) + collector.Describe(ch) + desc := <-ch + + before := testutil.ToFloat64(collector) + t.Cleanup(func() { + t.Helper() + + after := testutil.ToFloat64(collector) + assert.EqualValues(t, delta, after-before, "failed for %s", desc) + }) +} + func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64) { // Make sure test is not executed with "t.Parallel()" t.Setenv("PARALLEL_CHECK", "1") From 10e55ff2410deb16ba58108a9a25f7662cf16551 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 13 Nov 2025 23:17:46 +0100 Subject: [PATCH 282/549] Lint Janus metrics. --- mcu_janus_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index fb3e3ef..a818f35 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -39,6 +39,10 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" ) +func TestMcuJanusStats(t *testing.T) { + collectAndLint(t, janusMcuStats...) +} + type TestJanusHandle struct { id uint64 From 4e87170f0e79112bc01f765a5aea9522be4053cf Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 18 Nov 2025 09:32:40 +0100 Subject: [PATCH 283/549] Add formatting to bandwidth values. --- api/bandwidth.go | 35 ++++++++++++++++++++++++++++- api/bandwidth_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/api/bandwidth.go b/api/bandwidth.go index 9434a6f..16e0950 100644 --- a/api/bandwidth.go +++ b/api/bandwidth.go @@ -22,14 +22,47 @@ package api import ( + "fmt" + "math" "sync/atomic" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) +var ( + Kilobit = BandwidthFromBits(1024) + Megabit = BandwidthFromBits(1024) * Kilobit + Gigabit = BandwidthFromBits(1024) * Megabit +) + // Bandwidth stores a bandwidth in bits per second. type Bandwidth uint64 +func formatWithRemainder(value uint64, divisor uint64, format string) string { + if value%divisor == 0 { + return fmt.Sprintf("%d %s", value/divisor, format) + } else { + v := float64(value) / float64(divisor) + v = math.Trunc(v*100) / 100 + return fmt.Sprintf("%.2f %s", v, format) + } +} + +// String returns the formatted bandwidth. +func (b Bandwidth) String() string { + if b >= Gigabit { + return formatWithRemainder(b.Bits(), Gigabit.Bits(), "Gbps") + } else if b >= Megabit { + return formatWithRemainder(b.Bits(), Megabit.Bits(), "Mbps") + } else if b >= Kilobit { + return formatWithRemainder(b.Bits(), Kilobit.Bits(), "Kbps") + } else if b > 0 { + return fmt.Sprintf("%d bps", b) + } else { + return "unlimited" + } +} + // Bits returns the bandwidth in bits per second. func (b Bandwidth) Bits() uint64 { return uint64(b) @@ -47,7 +80,7 @@ func BandwidthFromBits(b uint64) Bandwidth { // BandwithFromBits creates a bandwidth from megabits per second. func BandwidthFromMegabits(b uint64) Bandwidth { - return Bandwidth(b * 1024 * 1024) + return Bandwidth(b) * Megabit } // BandwidthFromBytes creates a bandwidth from bytes per second. diff --git a/api/bandwidth_test.go b/api/bandwidth_test.go index 935c8f1..a5023e8 100644 --- a/api/bandwidth_test.go +++ b/api/bandwidth_test.go @@ -56,3 +56,54 @@ func TestBandwidth(t *testing.T) { assert.EqualValues(1000, old) assert.EqualValues(2000, a.Load()) } + +func TestBandwidthString(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + testcases := []struct { + value Bandwidth + expected string + }{ + { + 0, + "unlimited", + }, + { + BandwidthFromBits(123), + "123 bps", + }, + { + BandwidthFromBits(1023), + "1023 bps", + }, + { + BandwidthFromBits(1024), + "1 Kbps", + }, + { + BandwidthFromBits(1024 + 512), + "1.50 Kbps", + }, + { + BandwidthFromBits(1024*1024 - 1), + "1023.99 Kbps", + }, + { + BandwidthFromBits(1024 * 1024), + "1 Mbps", + }, + { + BandwidthFromBits(1024*1024*1024 - 1), + "1023.99 Mbps", + }, + { + BandwidthFromBits(1024 * 1024 * 1024), + "1 Gbps", + }, + } + + for idx, tc := range testcases { + assert.Equal(tc.expected, tc.value.String(), "failed for testcase %d (%d)", idx, tc.value.Bits()) + } +} From 14d1c9bf598a618c9fdedd667e26910d933420b8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 18 Nov 2025 09:38:28 +0100 Subject: [PATCH 284/549] Log formatted bandwidths. --- mcu_common.go | 22 ++++++++++++---------- proxy/proxy_server.go | 31 +++++++++++++------------------ 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/mcu_common.go b/mcu_common.go index bf3bff7..75d5fb4 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -105,19 +105,21 @@ func (s *mcuCommonSettings) setTimeout(timeout time.Duration) { } func (s *mcuCommonSettings) load(config *goconf.ConfigFile) error { - maxStreamBitrate, _ := config.GetInt("mcu", "maxstreambitrate") - if maxStreamBitrate <= 0 { - maxStreamBitrate = int(defaultMaxStreamBitrate.Bits()) + maxStreamBitrateValue, _ := config.GetInt("mcu", "maxstreambitrate") + if maxStreamBitrateValue <= 0 { + maxStreamBitrateValue = int(defaultMaxStreamBitrate.Bits()) } - log.Printf("Maximum bandwidth %d bits/sec per publishing stream", maxStreamBitrate) - s.maxStreamBitrate.Store(api.BandwidthFromBits(uint64(maxStreamBitrate))) + maxStreamBitrate := api.BandwidthFromBits(uint64(maxStreamBitrateValue)) + log.Printf("Maximum bandwidth %s per publishing stream", maxStreamBitrate) + s.maxStreamBitrate.Store(maxStreamBitrate) - maxScreenBitrate, _ := config.GetInt("mcu", "maxscreenbitrate") - if maxScreenBitrate <= 0 { - maxScreenBitrate = int(defaultMaxScreenBitrate.Bits()) + maxScreenBitrateValue, _ := config.GetInt("mcu", "maxscreenbitrate") + if maxScreenBitrateValue <= 0 { + maxScreenBitrateValue = int(defaultMaxScreenBitrate.Bits()) } - log.Printf("Maximum bandwidth %d bits/sec per screensharing stream", maxScreenBitrate) - s.maxScreenBitrate.Store(api.BandwidthFromBits(uint64(maxScreenBitrate))) + maxScreenBitrate := api.BandwidthFromBits(uint64(maxScreenBitrateValue)) + log.Printf("Maximum bandwidth %s per screensharing stream", maxScreenBitrate) + s.maxScreenBitrate.Store(maxScreenBitrate) return nil } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 53bb797..e5ac742 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -190,26 +190,21 @@ func GetLocalIP() (string, error) { } func getTargetBandwidths(config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidth) { - maxIncoming, _ := config.GetInt("bandwidth", "incoming") - if maxIncoming < 0 { - maxIncoming = 0 - } - if maxIncoming > 0 { - log.Printf("Target bandwidth for incoming streams: %d MBit/s", maxIncoming) - } else { - log.Printf("Target bandwidth for incoming streams: unlimited") - } - maxOutgoing, _ := config.GetInt("bandwidth", "outgoing") - if maxOutgoing < 0 { - maxOutgoing = 0 - } - if maxIncoming > 0 { - log.Printf("Target bandwidth for outgoing streams: %d MBit/s", maxOutgoing) - } else { - log.Printf("Target bandwidth for outgoing streams: unlimited") + maxIncomingValue, _ := config.GetInt("bandwidth", "incoming") + if maxIncomingValue < 0 { + maxIncomingValue = 0 } + maxIncoming := api.BandwidthFromMegabits(uint64(maxIncomingValue)) + log.Printf("Target bandwidth for incoming streams: %s", maxIncoming) - return api.BandwidthFromMegabits(uint64(maxIncoming)), api.BandwidthFromMegabits(uint64(maxOutgoing)) + maxOutgoingValue, _ := config.GetInt("bandwidth", "outgoing") + if maxOutgoingValue < 0 { + maxOutgoingValue = 0 + } + maxOutgoing := api.BandwidthFromMegabits(uint64(maxOutgoingValue)) + log.Printf("Target bandwidth for outgoing streams: %s", maxOutgoing) + + return maxIncoming, maxOutgoing } func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*ProxyServer, error) { From dca35b46d474697fb12d4b17bafe556182788a1e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 18 Nov 2025 15:35:06 +0100 Subject: [PATCH 285/549] Don't format zero bandwidth as "unlimited". --- api/bandwidth.go | 4 +--- api/bandwidth_test.go | 2 +- proxy/proxy_server.go | 12 ++++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/bandwidth.go b/api/bandwidth.go index 16e0950..683dbac 100644 --- a/api/bandwidth.go +++ b/api/bandwidth.go @@ -56,10 +56,8 @@ func (b Bandwidth) String() string { return formatWithRemainder(b.Bits(), Megabit.Bits(), "Mbps") } else if b >= Kilobit { return formatWithRemainder(b.Bits(), Kilobit.Bits(), "Kbps") - } else if b > 0 { - return fmt.Sprintf("%d bps", b) } else { - return "unlimited" + return fmt.Sprintf("%d bps", b) } } diff --git a/api/bandwidth_test.go b/api/bandwidth_test.go index a5023e8..5cdb427 100644 --- a/api/bandwidth_test.go +++ b/api/bandwidth_test.go @@ -67,7 +67,7 @@ func TestBandwidthString(t *testing.T) { }{ { 0, - "unlimited", + "0 bps", }, { BandwidthFromBits(123), diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index e5ac742..ad932db 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -195,14 +195,22 @@ func getTargetBandwidths(config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidt maxIncomingValue = 0 } maxIncoming := api.BandwidthFromMegabits(uint64(maxIncomingValue)) - log.Printf("Target bandwidth for incoming streams: %s", maxIncoming) + if maxIncoming > 0 { + log.Printf("Target bandwidth for incoming streams: %s", maxIncoming) + } else { + log.Printf("Target bandwidth for incoming streams: unlimited") + } maxOutgoingValue, _ := config.GetInt("bandwidth", "outgoing") if maxOutgoingValue < 0 { maxOutgoingValue = 0 } maxOutgoing := api.BandwidthFromMegabits(uint64(maxOutgoingValue)) - log.Printf("Target bandwidth for outgoing streams: %s", maxOutgoing) + if maxOutgoing > 0 { + log.Printf("Target bandwidth for outgoing streams: %s", maxOutgoing) + } else { + log.Printf("Target bandwidth for outgoing streams: unlimited") + } return maxIncoming, maxOutgoing } From b14ec35679f133a9c97e854d6f6267bce9ca787d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:02:09 +0000 Subject: [PATCH 286/549] Bump google.golang.org/grpc from 1.76.0 to 1.77.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.76.0 to 1.77.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.76.0...v1.77.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++--------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index d1d778d..9353630 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.6 go.etcd.io/etcd/server/v3 v3.6.6 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.76.0 + google.golang.org/grpc v1.77.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.10 ) @@ -78,24 +78,24 @@ require ( go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/etcd/pkg/v3 v3.6.6 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.45.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index 66ef877..9bc4847 100644 --- a/go.sum +++ b/go.sum @@ -155,24 +155,24 @@ go.etcd.io/etcd/server/v3 v3.6.6 h1:YSRWGJPzU+lIREwUQI4MfyLZrkUyzjJOVpMxJvZePaY= go.etcd.io/etcd/server/v3 v3.6.6/go.mod h1:A1OQ1x3PaiENDLywMjCiMwV1pwJSpb0h9Z5ORP2dv6I= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -195,8 +195,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -225,12 +225,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= From c021de49572512476b84a5ee669310705a862e8b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 19 Nov 2025 20:13:45 +0100 Subject: [PATCH 287/549] CI: Split test jobs to speed up total actions time. --- .github/workflows/test.yml | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffdd8fa..95d4e07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,10 +22,7 @@ permissions: contents: read jobs: - go: - env: - MAXMIND_GEOLITE2_LICENSE: ${{ secrets.MAXMIND_GEOLITE2_LICENSE }} - USE_DB_IP_GEOIP_DATABASE: "1" + build: strategy: matrix: go-version: @@ -45,10 +42,42 @@ jobs: make proxy -j$(nproc) make server -j$(nproc) + go: + env: + MAXMIND_GEOLITE2_LICENSE: ${{ secrets.MAXMIND_GEOLITE2_LICENSE }} + USE_DB_IP_GEOIP_DATABASE: "1" + strategy: + matrix: + go-version: + - "1.24" + - "1.25" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + - name: Run tests run: | make test TIMEOUT=120s + coverage: + env: + MAXMIND_GEOLITE2_LICENSE: ${{ secrets.MAXMIND_GEOLITE2_LICENSE }} + USE_DB_IP_GEOIP_DATABASE: "1" + strategy: + matrix: + go-version: + - "1.24" + - "1.25" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + - name: Generate coverage report run: | make cover TIMEOUT=120s From e80dd3740d04a90595796f318cab6f3f930c7be4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 02:59:28 +0000 Subject: [PATCH 288/549] Bump golang.org/x/crypto from 0.43.0 to 0.45.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.43.0 to 0.45.0. - [Commits](https://github.com/golang/crypto/compare/v0.43.0...v0.45.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.45.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 9353630..98d5362 100644 --- a/go.mod +++ b/go.mod @@ -89,10 +89,10 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect diff --git a/go.sum b/go.sum index 9bc4847..32d6fc4 100644 --- a/go.sum +++ b/go.sum @@ -186,8 +186,8 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -195,13 +195,13 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= -golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -211,8 +211,8 @@ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From ec5a34f9265b55650abfc24282ad0627dccc620c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 19 Nov 2025 16:00:35 +0100 Subject: [PATCH 289/549] Support storing loggers in contexts. --- logging.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 logging.go diff --git a/logging.go b/logging.go new file mode 100644 index 0000000..df87432 --- /dev/null +++ b/logging.go @@ -0,0 +1,60 @@ +/** + * 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 signaling + +import ( + "context" + "log" + "testing" +) + +type loggerKey struct{} + +var ( + ctxLogger loggerKey = struct{}{} +) + +type Logger interface { + Printf(format string, v ...any) + Println(...any) +} + +// NewLoggerContext returns a derieved context that stores the passed logger. +func NewLoggerContext(ctx context.Context, logger Logger) context.Context { + if logger == nil { + panic("logger is nil") + } + return context.WithValue(ctx, ctxLogger, logger) +} + +// LoggerFromContext returns the logger to use for the passed context. +func LoggerFromContext(ctx context.Context) Logger { + logger := ctx.Value(ctxLogger) + if logger == nil { + if testing.Testing() { + panic("accessed global logger") + } + return log.Default() + } + + return logger.(Logger) +} From 0e4c4c775b6439b5ad0949d065f5ead48ca95a80 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 19 Nov 2025 16:01:52 +0100 Subject: [PATCH 290/549] Add method to create new test logger. --- test_helpers.go | 49 +++++++++++++++++++++++++++++++++++++++++----- test_helpers_24.go | 34 ++++++++++++++++++++++++++++++++ test_helpers_25.go | 33 +++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 test_helpers_24.go create mode 100644 test_helpers_25.go diff --git a/test_helpers.go b/test_helpers.go index b7f0bdd..9c1a7f7 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -22,8 +22,11 @@ package signaling import ( + "bytes" + "fmt" "io" "log" + "sync" "testing" ) @@ -38,13 +41,18 @@ func init() { } type testLogWriter struct { - t testing.TB + mu sync.Mutex + t testing.TB } func (w *testLogWriter) Write(b []byte) (int, error) { w.t.Helper() - w.t.Logf("%s", string(b)) - return len(b), nil + if !bytes.HasSuffix(b, []byte("\n")) { + b = append(b, '\n') + } + w.mu.Lock() + defer w.mu.Unlock() + return writeTestOutput(w.t, b) } func CatchLogForTest(t testing.TB) { @@ -53,6 +61,37 @@ func CatchLogForTest(t testing.TB) { log.SetFlags(prevFlags) }) - log.SetOutput(&testLogWriter{t}) - log.SetFlags(prevFlags | log.Lshortfile) + log.SetOutput(&testLogWriter{ + t: t, + }) + log.SetFlags(prevFlags | log.Lmicroseconds | log.Lshortfile) +} + +var ( + // +checklocks:testLoggersLock + testLoggers = map[testing.TB]Logger{} + testLoggersLock sync.Mutex +) + +func NewLoggerForTest(t testing.TB) Logger { + t.Helper() + testLoggersLock.Lock() + defer testLoggersLock.Unlock() + + logger, found := testLoggers[t] + if !found { + logger = log.New(&testLogWriter{ + t: t, + }, fmt.Sprintf("%s: ", t.Name()), log.LstdFlags|log.Lmicroseconds|log.Lshortfile) + + t.Cleanup(func() { + testLoggersLock.Lock() + defer testLoggersLock.Unlock() + + delete(testLoggers, t) + }) + + testLoggers[t] = logger + } + return logger } diff --git a/test_helpers_24.go b/test_helpers_24.go new file mode 100644 index 0000000..f51c259 --- /dev/null +++ b/test_helpers_24.go @@ -0,0 +1,34 @@ +//go:build !go1.25 + +/** + * 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 signaling + +import ( + "testing" +) + +func writeTestOutput(t testing.TB, p []byte) (int, error) { + t.Helper() + t.Logf("%s", string(p)) + return len(p), nil +} diff --git a/test_helpers_25.go b/test_helpers_25.go new file mode 100644 index 0000000..06b0a82 --- /dev/null +++ b/test_helpers_25.go @@ -0,0 +1,33 @@ +//go:build go1.25 + +/** + * 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 signaling + +import ( + "testing" +) + +func writeTestOutput(t testing.TB, p []byte) (int, error) { + t.Helper() + return t.Output().Write(p) +} From 6ca41dee618ddade93cf07d59601df63ef054032 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 19 Nov 2025 16:03:05 +0100 Subject: [PATCH 291/549] Don't use global logger. --- async_events.go | 11 +- async_events_nats.go | 43 +++--- async_events_test.go | 8 +- backend_client.go | 31 ++-- backend_client_test.go | 25 ++-- backend_configuration.go | 6 +- backend_configuration_test.go | 73 +++++---- backend_server.go | 109 +++++++------- backend_server_test.go | 113 +++++++------- backend_storage_etcd.go | 27 ++-- backend_storage_etcd_test.go | 4 +- backend_storage_static.go | 51 +++---- capabilities.go | 46 +++--- capabilities_test.go | 45 +++--- certificate_reloader.go | 25 ++-- client.go | 37 ++--- clientsession.go | 74 ++++----- clientsession_test.go | 13 +- deferred_executor.go | 11 +- deferred_executor_test.go | 19 ++- dns_monitor.go | 7 +- dns_monitor_test.go | 3 +- etcd_client.go | 27 ++-- etcd_client_test.go | 32 ++-- federation.go | 42 +++--- federation_test.go | 20 +-- file_watcher.go | 11 +- file_watcher_test.go | 27 ++-- geoip.go | 30 ++-- geoip_test.go | 16 +- grpc_client.go | 83 +++++----- grpc_client_test.go | 29 ++-- grpc_common.go | 11 +- grpc_remote_client.go | 9 +- grpc_server.go | 24 +-- grpc_server_test.go | 11 +- hub.go | 250 ++++++++++++++++--------------- hub_test.go | 134 ++++------------- mcu_common.go | 7 +- mcu_janus.go | 102 +++++++------ mcu_janus_client.go | 9 +- mcu_janus_events_handler.go | 42 +++--- mcu_janus_events_handler_test.go | 4 +- mcu_janus_publisher.go | 43 +++--- mcu_janus_publisher_test.go | 1 - mcu_janus_remote_publisher.go | 33 ++-- mcu_janus_remote_subscriber.go | 23 ++- mcu_janus_subscriber.go | 43 +++--- mcu_janus_test.go | 20 +-- mcu_proxy.go | 216 +++++++++++++------------- mcu_proxy_test.go | 86 ++++------- mcu_test.go | 15 +- natsclient.go | 29 ++-- natsclient_loopback.go | 9 +- natsclient_loopback_test.go | 3 +- natsclient_test.go | 9 +- proxy/main.go | 40 ++--- proxy/proxy_remote.go | 53 +++---- proxy/proxy_server.go | 153 +++++++++---------- proxy/proxy_server_test.go | 20 +-- proxy/proxy_session.go | 15 +- proxy/proxy_tokens_etcd.go | 17 ++- proxy/proxy_tokens_etcd_test.go | 4 +- proxy/proxy_tokens_static.go | 20 +-- proxy_config_etcd.go | 31 ++-- proxy_config_etcd_test.go | 6 +- proxy_config_static.go | 15 +- proxy_config_static_test.go | 5 +- remotesession.go | 7 +- room.go | 67 +++++---- room_ping.go | 27 ++-- room_ping_test.go | 40 ++--- room_test.go | 35 +++-- roomsessions_builtin.go | 6 +- server/main.go | 126 ++++++++-------- test_helpers.go | 23 --- throttle.go | 7 +- throttle_test.go | 21 ++- transient_data_test.go | 1 - virtualsession.go | 18 ++- virtualsession_test.go | 4 - 81 files changed, 1494 insertions(+), 1498 deletions(-) diff --git a/async_events.go b/async_events.go index d8bb0c9..6598cd3 100644 --- a/async_events.go +++ b/async_events.go @@ -21,7 +21,10 @@ */ package signaling -import "sync" +import ( + "context" + "sync" +) type AsyncBackendRoomEventListener interface { ProcessBackendRoomRequest(message *AsyncMessage) @@ -60,13 +63,13 @@ type AsyncEvents interface { PublishSessionMessage(sessionId PublicSessionId, backend *Backend, message *AsyncMessage) error } -func NewAsyncEvents(url string) (AsyncEvents, error) { - client, err := NewNatsClient(url) +func NewAsyncEvents(ctx context.Context, url string) (AsyncEvents, error) { + client, err := NewNatsClient(ctx, url) if err != nil { return nil, err } - return NewAsyncEventsNats(client) + return NewAsyncEventsNats(LoggerFromContext(ctx), client) } type asyncBackendRoomSubscriber struct { diff --git a/async_events_nats.go b/async_events_nats.go index b5c3ae5..ab9fe13 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -23,7 +23,6 @@ package signaling import ( "fmt" - "log" "sync" "time" @@ -61,6 +60,7 @@ func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string type asyncSubscriberNats struct { key string client NatsClient + logger Logger receiver chan *nats.Msg closeChan chan struct{} @@ -69,7 +69,7 @@ type asyncSubscriberNats struct { processMessage func(*nats.Msg) } -func newAsyncSubscriberNats(key string, client NatsClient) (*asyncSubscriberNats, error) { +func newAsyncSubscriberNats(logger Logger, key string, client NatsClient) (*asyncSubscriberNats, error) { receiver := make(chan *nats.Msg, 64) sub, err := client.Subscribe(key, receiver) if err != nil { @@ -79,6 +79,7 @@ func newAsyncSubscriberNats(key string, client NatsClient) (*asyncSubscriberNats result := &asyncSubscriberNats{ key: key, client: client, + logger: logger, receiver: receiver, closeChan: make(chan struct{}), @@ -90,7 +91,7 @@ func newAsyncSubscriberNats(key string, client NatsClient) (*asyncSubscriberNats func (s *asyncSubscriberNats) run() { defer func() { if err := s.subscription.Unsubscribe(); err != nil { - log.Printf("Error unsubscribing %s: %s", s.key, err) + s.logger.Printf("Error unsubscribing %s: %s", s.key, err) } }() @@ -116,8 +117,8 @@ type asyncBackendRoomSubscriberNats struct { asyncBackendRoomSubscriber } -func newAsyncBackendRoomSubscriberNats(key string, client NatsClient) (*asyncBackendRoomSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(key, client) +func newAsyncBackendRoomSubscriberNats(logger Logger, key string, client NatsClient) (*asyncBackendRoomSubscriberNats, error) { + sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err } @@ -133,7 +134,7 @@ func newAsyncBackendRoomSubscriberNats(key string, client NatsClient) (*asyncBac func (s *asyncBackendRoomSubscriberNats) doProcessMessage(msg *nats.Msg) { var message AsyncMessage if err := s.client.Decode(msg, &message); err != nil { - log.Printf("Could not decode NATS message %+v, %s", msg, err) + s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) return } @@ -145,8 +146,8 @@ type asyncRoomSubscriberNats struct { *asyncSubscriberNats } -func newAsyncRoomSubscriberNats(key string, client NatsClient) (*asyncRoomSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(key, client) +func newAsyncRoomSubscriberNats(logger Logger, key string, client NatsClient) (*asyncRoomSubscriberNats, error) { + sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err } @@ -162,7 +163,7 @@ func newAsyncRoomSubscriberNats(key string, client NatsClient) (*asyncRoomSubscr func (s *asyncRoomSubscriberNats) doProcessMessage(msg *nats.Msg) { var message AsyncMessage if err := s.client.Decode(msg, &message); err != nil { - log.Printf("Could not decode nats message %+v, %s", msg, err) + s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) return } @@ -174,8 +175,8 @@ type asyncUserSubscriberNats struct { asyncUserSubscriber } -func newAsyncUserSubscriberNats(key string, client NatsClient) (*asyncUserSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(key, client) +func newAsyncUserSubscriberNats(logger Logger, key string, client NatsClient) (*asyncUserSubscriberNats, error) { + sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err } @@ -191,7 +192,7 @@ func newAsyncUserSubscriberNats(key string, client NatsClient) (*asyncUserSubscr func (s *asyncUserSubscriberNats) doProcessMessage(msg *nats.Msg) { var message AsyncMessage if err := s.client.Decode(msg, &message); err != nil { - log.Printf("Could not decode nats message %+v, %s", msg, err) + s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) return } @@ -203,8 +204,8 @@ type asyncSessionSubscriberNats struct { asyncSessionSubscriber } -func newAsyncSessionSubscriberNats(key string, client NatsClient) (*asyncSessionSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(key, client) +func newAsyncSessionSubscriberNats(logger Logger, key string, client NatsClient) (*asyncSessionSubscriberNats, error) { + sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err } @@ -220,7 +221,7 @@ func newAsyncSessionSubscriberNats(key string, client NatsClient) (*asyncSession func (s *asyncSessionSubscriberNats) doProcessMessage(msg *nats.Msg) { var message AsyncMessage if err := s.client.Decode(msg, &message); err != nil { - log.Printf("Could not decode nats message %+v, %s", msg, err) + s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) return } @@ -230,6 +231,7 @@ func (s *asyncSessionSubscriberNats) doProcessMessage(msg *nats.Msg) { type asyncEventsNats struct { mu sync.Mutex client NatsClient + logger Logger // +checklocksignore // +checklocks:mu backendRoomSubscriptions map[string]*asyncBackendRoomSubscriberNats @@ -241,9 +243,10 @@ type asyncEventsNats struct { sessionSubscriptions map[string]*asyncSessionSubscriberNats } -func NewAsyncEventsNats(client NatsClient) (AsyncEvents, error) { +func NewAsyncEventsNats(logger Logger, client NatsClient) (AsyncEvents, error) { events := &asyncEventsNats{ client: client, + logger: logger, backendRoomSubscriptions: make(map[string]*asyncBackendRoomSubscriberNats), roomSubscriptions: make(map[string]*asyncRoomSubscriberNats), @@ -328,7 +331,7 @@ func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Ba sub, found := e.backendRoomSubscriptions[key] if !found { var err error - if sub, err = newAsyncBackendRoomSubscriberNats(key, e.client); err != nil { + if sub, err = newAsyncBackendRoomSubscriberNats(e.logger, key, e.client); err != nil { return err } @@ -362,7 +365,7 @@ func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *Backend, sub, found := e.roomSubscriptions[key] if !found { var err error - if sub, err = newAsyncRoomSubscriberNats(key, e.client); err != nil { + if sub, err = newAsyncRoomSubscriberNats(e.logger, key, e.client); err != nil { return err } @@ -396,7 +399,7 @@ func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *Backend, sub, found := e.userSubscriptions[key] if !found { var err error - if sub, err = newAsyncUserSubscriberNats(key, e.client); err != nil { + if sub, err = newAsyncUserSubscriberNats(e.logger, key, e.client); err != nil { return err } @@ -430,7 +433,7 @@ func (e *asyncEventsNats) RegisterSessionListener(sessionId PublicSessionId, bac sub, found := e.sessionSubscriptions[key] if !found { var err error - if sub, err = newAsyncSessionSubscriberNats(key, e.client); err != nil { + if sub, err = newAsyncSessionSubscriberNats(e.logger, key, e.client); err != nil { return err } diff --git a/async_events_test.go b/async_events_test.go index 3ded5a0..02d6145 100644 --- a/async_events_test.go +++ b/async_events_test.go @@ -50,8 +50,10 @@ func getAsyncEventsForTest(t *testing.T) AsyncEvents { } func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) server, _ := startLocalNatsServer(t) - events, err := NewAsyncEvents(server.ClientURL()) + events, err := NewAsyncEvents(ctx, server.ClientURL()) if err != nil { require.NoError(t, err) } @@ -59,7 +61,9 @@ func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { } func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { - events, err := NewAsyncEvents(NatsLoopbackUrl) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + events, err := NewAsyncEvents(ctx, NatsLoopbackUrl) if err != nil { require.NoError(t, err) } diff --git a/backend_client.go b/backend_client.go index 769d02c..54ee18f 100644 --- a/backend_client.go +++ b/backend_client.go @@ -26,7 +26,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net/http" "net/url" "strings" @@ -57,15 +56,16 @@ type BackendClient struct { buffers BufferPool } -func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) { - backends, err := NewBackendConfiguration(config, etcdClient) +func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) { + logger := LoggerFromContext(ctx) + backends, err := NewBackendConfiguration(logger, config, etcdClient) if err != nil { return nil, err } skipverify, _ := config.GetBool("backend", "skipverify") if skipverify { - log.Println("WARNING: Backend verification is disabled!") + logger.Println("WARNING: Backend verification is disabled!") } pool, err := NewHttpClientPool(maxConcurrentRequestsPerHost, skipverify) @@ -118,6 +118,7 @@ func isOcsRequest(u *url.URL) bool { // 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 { + logger := LoggerFromContext(ctx) if u == nil { return fmt.Errorf("no url passed to perform JSON request %+v", request) } @@ -139,21 +140,21 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ c, pool, err := b.pool.Get(ctx, u) if err != nil { - log.Printf("Could not get client for host %s: %s", u.Host, err) + logger.Printf("Could not get client for host %s: %s", u.Host, err) return err } defer pool.Put(c) data, err := b.buffers.MarshalAsJSON(request) if err != nil { - log.Printf("Could not marshal request %+v: %s", request, err) + logger.Printf("Could not marshal request %+v: %s", request, err) return err } defer b.buffers.Put(data) req, err := http.NewRequestWithContext(ctx, "POST", requestUrl.String(), data) if err != nil { - log.Printf("Could not create request to %s: %s", requestUrl, err) + logger.Printf("Could not create request to %s: %s", requestUrl, err) return err } req.Header.Set("Content-Type", "application/json") @@ -181,21 +182,21 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } else { statsBackendClientError.WithLabelValues(backend.Id(), "unknown").Inc() } - log.Printf("Could not send request %s to %s: %s", data.String(), req.URL, err) + logger.Printf("Could not send request %s to %s: %s", data.String(), req.URL, err) return err } defer resp.Body.Close() ct := resp.Header.Get("Content-Type") if !strings.HasPrefix(ct, "application/json") { - log.Printf("Received unsupported content-type from %s for %s: %s (%s)", req.URL, data.String(), ct, resp.Status) + logger.Printf("Received unsupported content-type from %s for %s: %s (%s)", req.URL, data.String(), ct, resp.Status) statsBackendClientError.WithLabelValues(backend.Id(), "invalid_content_type").Inc() return ErrUnsupportedContentType } body, err := b.buffers.ReadAll(resp.Body) if err != nil { - log.Printf("Could not read response body from %s for %s: %s", req.URL, data.String(), err) + logger.Printf("Could not read response body from %s for %s: %s", req.URL, data.String(), err) statsBackendClientError.WithLabelValues(backend.Id(), "error_reading_body").Inc() return err } @@ -213,29 +214,29 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ // } var ocs OcsResponse if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { - log.Printf("Could not decode OCS response %s from %s: %s", body.String(), req.URL, err) + logger.Printf("Could not decode OCS response %s from %s: %s", body.String(), req.URL, err) statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_ocs").Inc() return err } else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 { - log.Printf("Incomplete OCS response %s from %s", body.String(), req.URL) + logger.Printf("Incomplete OCS response %s from %s", body.String(), req.URL) statsBackendClientError.WithLabelValues(backend.Id(), "error_incomplete_ocs").Inc() return ErrIncompleteResponse } switch ocs.Ocs.Meta.StatusCode { case http.StatusTooManyRequests: - log.Printf("Throttled OCS response %s from %s", body.String(), req.URL) + logger.Printf("Throttled OCS response %s from %s", body.String(), req.URL) statsBackendClientError.WithLabelValues(backend.Id(), "throttled").Inc() return ErrThrottledResponse } if err := json.Unmarshal(ocs.Ocs.Data, response); err != nil { - log.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), req.URL, err) + logger.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), req.URL, err) statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_ocs_data").Inc() return err } } else if err := json.Unmarshal(body.Bytes(), response); err != nil { - log.Printf("Could not decode response body %s from %s: %s", body.String(), req.URL, err) + logger.Printf("Could not decode response body %s from %s: %s", body.String(), req.URL, err) statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_body").Inc() return err } diff --git a/backend_client_test.go b/backend_client_test.go index d0a4d77..71c7824 100644 --- a/backend_client_test.go +++ b/backend_client_test.go @@ -22,7 +22,6 @@ package signaling import ( - "context" "encoding/json" "io" "net/http" @@ -67,7 +66,8 @@ func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { func TestPostOnRedirect(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() r.HandleFunc("/ocs/v2.php/one", func(w http.ResponseWriter, r *http.Request) { @@ -96,10 +96,9 @@ func TestPostOnRedirect(t *testing.T) { if u.Scheme == "http" { config.AddOption("backend", "allowhttp", "true") } - client, err := NewBackendClient(config, 1, "0.0", nil) + client, err := NewBackendClient(ctx, config, 1, "0.0", nil) require.NoError(err) - ctx := context.Background() request := map[string]string{ "foo": "bar", } @@ -114,7 +113,8 @@ func TestPostOnRedirect(t *testing.T) { func TestPostOnRedirectDifferentHost(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() r.HandleFunc("/ocs/v2.php/one", func(w http.ResponseWriter, r *http.Request) { @@ -132,10 +132,9 @@ func TestPostOnRedirectDifferentHost(t *testing.T) { if u.Scheme == "http" { config.AddOption("backend", "allowhttp", "true") } - client, err := NewBackendClient(config, 1, "0.0", nil) + client, err := NewBackendClient(ctx, config, 1, "0.0", nil) require.NoError(err) - ctx := context.Background() request := map[string]string{ "foo": "bar", } @@ -151,7 +150,8 @@ func TestPostOnRedirectDifferentHost(t *testing.T) { func TestPostOnRedirectStatusFound(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) r := mux.NewRouter() @@ -177,10 +177,9 @@ func TestPostOnRedirectStatusFound(t *testing.T) { if u.Scheme == "http" { config.AddOption("backend", "allowhttp", "true") } - client, err := NewBackendClient(config, 1, "0.0", nil) + client, err := NewBackendClient(ctx, config, 1, "0.0", nil) require.NoError(err) - ctx := context.Background() request := map[string]string{ "foo": "bar", } @@ -193,7 +192,8 @@ func TestPostOnRedirectStatusFound(t *testing.T) { func TestHandleThrottled(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) r := mux.NewRouter() @@ -212,10 +212,9 @@ func TestHandleThrottled(t *testing.T) { if u.Scheme == "http" { config.AddOption("backend", "allowhttp", "true") } - client, err := NewBackendClient(config, 1, "0.0", nil) + client, err := NewBackendClient(ctx, config, 1, "0.0", nil) require.NoError(err) - ctx := context.Background() request := map[string]string{ "foo": "bar", } diff --git a/backend_configuration.go b/backend_configuration.go index d79bd7e..d9be669 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -224,7 +224,7 @@ type BackendConfiguration struct { storage BackendStorage } -func NewBackendConfiguration(config *goconf.ConfigFile, etcdClient *EtcdClient) (*BackendConfiguration, error) { +func NewBackendConfiguration(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (*BackendConfiguration, error) { backendType, _ := config.GetString("backend", "backendtype") if backendType == "" { backendType = DefaultBackendType @@ -236,9 +236,9 @@ func NewBackendConfiguration(config *goconf.ConfigFile, etcdClient *EtcdClient) var err error switch backendType { case BackendTypeStatic: - storage, err = NewBackendStorageStatic(config) + storage, err = NewBackendStorageStatic(logger, config) case BackendTypeEtcd: - storage, err = NewBackendStorageEtcd(config, etcdClient) + storage, err = NewBackendStorageEtcd(logger, config, etcdClient) default: err = fmt.Errorf("unknown backend type: %s", backendType) } diff --git a/backend_configuration_test.go b/backend_configuration_test.go index d5d7bdd..e22d160 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -81,7 +81,7 @@ func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]str } func TestIsUrlAllowed_Compat(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) // Old-style configuration valid_urls := []string{ "http://domain.invalid", @@ -96,13 +96,13 @@ func TestIsUrlAllowed_Compat(t *testing.T) { config.AddOption("backend", "allowed", "domain.invalid") config.AddOption("backend", "allowhttp", "true") config.AddOption("backend", "secret", string(testBackendSecret)) - cfg, err := NewBackendConfiguration(config, nil) + cfg, err := NewBackendConfiguration(logger, config, nil) require.NoError(t, err) testUrls(t, cfg, valid_urls, invalid_urls) } func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) // Old-style configuration, force HTTPS valid_urls := []string{ "https://domain.invalid", @@ -116,13 +116,13 @@ func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { config := goconf.NewConfigFile() config.AddOption("backend", "allowed", "domain.invalid") config.AddOption("backend", "secret", string(testBackendSecret)) - cfg, err := NewBackendConfiguration(config, nil) + cfg, err := NewBackendConfiguration(logger, config, nil) require.NoError(t, err) testUrls(t, cfg, valid_urls, invalid_urls) } func TestIsUrlAllowed(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) valid_urls := [][]string{ {"https://domain.invalid/foo", string(testBackendSecret) + "-foo"}, {"https://domain.invalid/foo/", string(testBackendSecret) + "-foo"}, @@ -160,13 +160,13 @@ func TestIsUrlAllowed(t *testing.T) { config.AddOption("baz", "secret", string(testBackendSecret)+"-baz") config.AddOption("lala", "url", "https://otherdomain.invalid/") config.AddOption("lala", "secret", string(testBackendSecret)+"-lala") - cfg, err := NewBackendConfiguration(config, nil) + cfg, err := NewBackendConfiguration(logger, config, nil) require.NoError(t, err) testBackends(t, cfg, valid_urls, invalid_urls) } func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) valid_urls := []string{} invalid_urls := []string{ "http://domain.invalid", @@ -176,13 +176,13 @@ func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { config := goconf.NewConfigFile() config.AddOption("backend", "allowed", "") config.AddOption("backend", "secret", string(testBackendSecret)) - cfg, err := NewBackendConfiguration(config, nil) + cfg, err := NewBackendConfiguration(logger, config, nil) require.NoError(t, err) testUrls(t, cfg, valid_urls, invalid_urls) } func TestIsUrlAllowed_AllowAll(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) valid_urls := []string{ "http://domain.invalid", "https://domain.invalid", @@ -195,7 +195,7 @@ func TestIsUrlAllowed_AllowAll(t *testing.T) { config.AddOption("backend", "allowall", "true") config.AddOption("backend", "allowed", "") config.AddOption("backend", "secret", string(testBackendSecret)) - cfg, err := NewBackendConfiguration(config, nil) + cfg, err := NewBackendConfiguration(logger, config, nil) require.NoError(t, err) testUrls(t, cfg, valid_urls, invalid_urls) } @@ -206,7 +206,6 @@ type ParseBackendIdsTestcase struct { } func TestParseBackendIds(t *testing.T) { - CatchLogForTest(t) testcases := []ParseBackendIdsTestcase{ {"", nil}, {"backend1", []string{"backend1"}}, @@ -227,7 +226,7 @@ func TestParseBackendIds(t *testing.T) { func TestBackendReloadNoChange(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") @@ -236,7 +235,7 @@ func TestBackendReloadNoChange(t *testing.T) { original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(original_config, nil) + o_cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 2) @@ -247,7 +246,7 @@ func TestBackendReloadNoChange(t *testing.T) { new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - n_cfg, err := NewBackendConfiguration(new_config, nil) + n_cfg, err := NewBackendConfiguration(logger, new_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 4) @@ -261,7 +260,7 @@ func TestBackendReloadNoChange(t *testing.T) { func TestBackendReloadChangeExistingURL(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") @@ -270,7 +269,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(original_config, nil) + o_cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 2) @@ -282,7 +281,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { new_config.AddOption("backend1", "sessionlimit", "10") new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - n_cfg, err := NewBackendConfiguration(new_config, nil) + n_cfg, err := NewBackendConfiguration(logger, new_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 4) @@ -300,7 +299,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { func TestBackendReloadChangeSecret(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") @@ -309,7 +308,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(original_config, nil) + o_cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 2) @@ -320,7 +319,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3") new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - n_cfg, err := NewBackendConfiguration(new_config, nil) + n_cfg, err := NewBackendConfiguration(logger, new_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 4) @@ -335,14 +334,14 @@ func TestBackendReloadChangeSecret(t *testing.T) { func TestBackendReloadAddBackend(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend1", "url", "http://domain1.invalid") original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") - o_cfg, err := NewBackendConfiguration(original_config, nil) + o_cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 1) @@ -354,7 +353,7 @@ func TestBackendReloadAddBackend(t *testing.T) { new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") new_config.AddOption("backend2", "sessionlimit", "10") - n_cfg, err := NewBackendConfiguration(new_config, nil) + n_cfg, err := NewBackendConfiguration(logger, new_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 3) @@ -374,7 +373,7 @@ func TestBackendReloadAddBackend(t *testing.T) { func TestBackendReloadRemoveHost(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") @@ -383,7 +382,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(original_config, nil) + o_cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 2) @@ -392,7 +391,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend1", "url", "http://domain1.invalid") new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") - n_cfg, err := NewBackendConfiguration(new_config, nil) + n_cfg, err := NewBackendConfiguration(logger, new_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 3) @@ -410,7 +409,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") @@ -419,7 +418,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain1.invalid/bar/") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(original_config, nil) + o_cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 2) @@ -428,7 +427,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend1", "url", "http://domain1.invalid/foo/") new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") - n_cfg, err := NewBackendConfiguration(new_config, nil) + n_cfg, err := NewBackendConfiguration(logger, new_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 3) @@ -462,7 +461,7 @@ func mustParse(s string) *url.URL { func TestBackendConfiguration_EtcdCompat(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) @@ -479,7 +478,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { checkStatsValue(t, statsBackendsCurrent, 0) - cfg, err := NewBackendConfiguration(config, client) + cfg, err := NewBackendConfiguration(logger, config, client) require.NoError(err) defer cfg.Close() @@ -581,7 +580,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { func TestBackendCommonSecret(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) u1, err := url.Parse("http://domain1.invalid") @@ -594,7 +593,7 @@ func TestBackendCommonSecret(t *testing.T) { original_config.AddOption("backend1", "url", u1.String()) original_config.AddOption("backend2", "url", u2.String()) original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - cfg, err := NewBackendConfiguration(original_config, nil) + cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { @@ -623,7 +622,7 @@ func TestBackendCommonSecret(t *testing.T) { func TestBackendChangeUrls(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) u1, err := url.Parse("http://domain1.invalid/") @@ -638,7 +637,7 @@ func TestBackendChangeUrls(t *testing.T) { checkStatsValue(t, statsBackendsCurrent, 0) - cfg, err := NewBackendConfiguration(original_config, nil) + cfg, err := NewBackendConfiguration(logger, original_config, nil) require.NoError(err) checkStatsValue(t, statsBackendsCurrent, 2) @@ -714,7 +713,7 @@ func TestBackendChangeUrls(t *testing.T) { func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { ResetStatsValue(t, statsBackendsCurrent) - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) @@ -731,7 +730,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { checkStatsValue(t, statsBackendsCurrent, 0) - cfg, err := NewBackendConfiguration(config, client) + cfg, err := NewBackendConfiguration(logger, config, client) require.NoError(err) defer cfg.Close() diff --git a/backend_server.go b/backend_server.go index ed6ee3c..23975be 100644 --- a/backend_server.go +++ b/backend_server.go @@ -31,7 +31,6 @@ import ( "errors" "fmt" "io" - "log" "net" "net/http" "net/http/pprof" @@ -63,6 +62,7 @@ const ( ) type BackendServer struct { + logger Logger hub *Hub events AsyncEvents roomSessions RoomSessions @@ -82,7 +82,8 @@ type BackendServer struct { buffers BufferPool } -func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { +func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { + logger := LoggerFromContext(ctx) turnapikey, _ := GetStringOptionWithEnv(config, "turn", "apikey") turnsecret, _ := GetStringOptionWithEnv(config, "turn", "secret") turnservers, _ := config.GetString("turn", "servers") @@ -98,10 +99,10 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac return nil, fmt.Errorf("need a shared TURN secret if TURN servers are configured") } - log.Printf("Using configured TURN API key") - log.Printf("Using configured shared TURN secret") + logger.Printf("Using configured TURN API key") + logger.Printf("Using configured shared TURN secret") for _, s := range turnserverslist { - log.Printf("Adding \"%s\" as TURN server", s) + logger.Printf("Adding \"%s\" as TURN server", s) } } @@ -112,10 +113,10 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac } if !statsAllowedIps.Empty() { - log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) + logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { statsAllowedIps = DefaultAllowedIps() - log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) + logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } invalidSecret := make([]byte, 32) @@ -126,6 +127,7 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac debug, _ := config.GetBool("app", "debug") result := &BackendServer{ + logger: logger, hub: hub, events: hub.events, roomSessions: hub.roomSessions, @@ -149,14 +151,14 @@ func (b *BackendServer) Reload(config *goconf.ConfigFile) { statsAllowed, _ := config.GetString("stats", "allowed_ips") if statsAllowedIps, err := ParseAllowedIps(statsAllowed); err == nil { if !statsAllowedIps.Empty() { - log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) + b.logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { statsAllowedIps = DefaultAllowedIps() - log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) + b.logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } b.statsAllowedIps.Store(statsAllowedIps) } else { - log.Printf("Error parsing allowed stats ips from \"%s\": %s", statsAllowedIps, err) + b.logger.Printf("Error parsing allowed stats ips from \"%s\": %s", statsAllowedIps, err) } } @@ -174,7 +176,7 @@ func (b *BackendServer) Start(r *mux.Router) error { b.welcomeMessage = string(welcomeMessage) + "\n" if b.debug { - log.Println("Installing debug handlers in \"/debug/pprof\"") + b.logger.Println("Installing debug handlers in \"/debug/pprof\"") s := r.PathPrefix("/debug/pprof").Subrouter() s.HandleFunc("", b.setCommonHeaders(b.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/debug/pprof/", http.StatusTemporaryRedirect) @@ -273,7 +275,7 @@ func (b *BackendServer) getTurnCredentials(w http.ResponseWriter, r *http.Reques data, err := json.Marshal(result) if err != nil { - log.Printf("Could not serialize TURN credentials: %s", err) + b.logger.Printf("Could not serialize TURN credentials: %s", err) w.WriteHeader(http.StatusInternalServerError) io.WriteString(w, "Could not serialize credentials.") // nolint return @@ -288,7 +290,7 @@ func (b *BackendServer) getTurnCredentials(w http.ResponseWriter, r *http.Reques w.Write(data) // nolint } -func (b *BackendServer) parseRequestBody(f func(http.ResponseWriter, *http.Request, []byte)) func(http.ResponseWriter, *http.Request) { +func (b *BackendServer) parseRequestBody(f func(context.Context, http.ResponseWriter, *http.Request, []byte)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { // Sanity checks if r.ContentLength == -1 { @@ -300,7 +302,7 @@ func (b *BackendServer) parseRequestBody(f func(http.ResponseWriter, *http.Reque } ct := r.Header.Get("Content-Type") if !strings.HasPrefix(ct, "application/json") { - log.Printf("Received unsupported content-type: %s", ct) + b.logger.Printf("Received unsupported content-type: %s", ct) http.Error(w, "Unsupported Content-Type", http.StatusBadRequest) return } @@ -313,13 +315,14 @@ func (b *BackendServer) parseRequestBody(f func(http.ResponseWriter, *http.Reque body, err := b.buffers.ReadAll(r.Body) if err != nil { - log.Println("Error reading body: ", err) + b.logger.Println("Error reading body: ", err) http.Error(w, "Could not read body", http.StatusBadRequest) return } defer b.buffers.Put(body) - f(w, r, body.Bytes()) + ctx := NewLoggerContext(r.Context(), b.logger) + f(ctx, w, r, body.Bytes()) } } @@ -340,7 +343,7 @@ func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids } for _, userid := range userids { if err := b.events.PublishUserMessage(userid, backend, msg); err != nil { - log.Printf("Could not publish room invite for user %s in backend %s: %s", userid, backend.Id(), err) + b.logger.Printf("Could not publish room invite for user %s in backend %s: %s", userid, backend.Id(), err) } } } @@ -364,12 +367,13 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso } for _, userid := range userids { if err := b.events.PublishUserMessage(userid, backend, msg); err != nil { - log.Printf("Could not publish room disinvite for user %s in backend %s: %s", userid, backend.Id(), err) + b.logger.Printf("Could not publish room disinvite for user %s in backend %s: %s", userid, backend.Id(), err) } } timeout := time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx := NewLoggerContext(context.Background(), b.logger) + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var wg sync.WaitGroup for _, sessionid := range sessionids { @@ -382,10 +386,10 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso go func(sessionid RoomSessionId) { defer wg.Done() if sid, err := b.lookupByRoomSessionId(ctx, sessionid, nil); err != nil { - log.Printf("Could not lookup by room session %s: %s", sessionid, err) + b.logger.Printf("Could not lookup by room session %s: %s", sessionid, err) } else if sid != "" { if err := b.events.PublishSessionMessage(sid, backend, msg); err != nil { - log.Printf("Could not publish room disinvite for session %s: %s", sid, err) + b.logger.Printf("Could not publish room disinvite for session %s: %s", sid, err) } } }(sessionid) @@ -419,14 +423,14 @@ func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified } if err := b.events.PublishUserMessage(userid, backend, msg); err != nil { - log.Printf("Could not publish room update for user %s in backend %s: %s", userid, backend.Id(), err) + b.logger.Printf("Could not publish room update for user %s in backend %s: %s", userid, backend.Id(), err) } } } func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, cache *ConcurrentMap[RoomSessionId, PublicSessionId]) (PublicSessionId, error) { if roomSessionId == sessionIdNotInMeeting { - log.Printf("Trying to lookup empty room session id: %s", roomSessionId) + b.logger.Printf("Trying to lookup empty room session id: %s", roomSessionId) return "", nil } @@ -458,13 +462,13 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent for _, user := range users { roomSessionId, found := api.GetStringMapString[RoomSessionId](user, "sessionId") if !found { - log.Printf("User %+v has invalid room session id, ignoring", user) + b.logger.Printf("User %+v has invalid room session id, ignoring", user) delete(user, "sessionId") continue } if roomSessionId == sessionIdNotInMeeting { - log.Printf("User %+v is not in the meeting, ignoring", user) + b.logger.Printf("User %+v is not in the meeting, ignoring", user) delete(user, "sessionId") continue } @@ -473,7 +477,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *Concurrent go func(roomSessionId RoomSessionId, u api.StringMap) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { - log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) + b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) delete(u, "sessionId") } else if sessionId != "" { u["sessionId"] = sessionId @@ -498,7 +502,8 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request if !request.InCall.All { timeout := time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx := NewLoggerContext(context.Background(), b.logger) + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var cache ConcurrentMap[RoomSessionId, PublicSessionId] // Convert (Nextcloud) session ids to signaling session ids. @@ -518,11 +523,11 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomParticipantsUpdate(roomid string, backend *Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid string, backend *Backend, request *BackendServerRoomRequest) error { timeout := time.Second // Convert (Nextcloud) session ids to signaling session ids. - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var cache ConcurrentMap[RoomSessionId, PublicSessionId] request.Participants.Users = b.fixupUserSessions(ctx, &cache, request.Participants.Users) @@ -542,20 +547,20 @@ loop: sessionId, found := api.GetStringMapString[PublicSessionId](user, "sessionId") if !found { - log.Printf("User entry has no session id: %+v", user) + b.logger.Printf("User entry has no session id: %+v", user) continue } permissionsList, ok := permissionsInterface.([]any) if !ok { - log.Printf("Received invalid permissions %+v (%s) for session %s", permissionsInterface, reflect.TypeOf(permissionsInterface), sessionId) + b.logger.Printf("Received invalid permissions %+v (%s) for session %s", permissionsInterface, reflect.TypeOf(permissionsInterface), sessionId) continue } var permissions []Permission for idx, ob := range permissionsList { permission, ok := ob.(string) if !ok { - log.Printf("Received invalid permission at position %d %+v (%s) for session %s", idx, ob, reflect.TypeOf(ob), sessionId) + b.logger.Printf("Received invalid permission at position %d %+v (%s) for session %s", idx, ob, reflect.TypeOf(ob), sessionId) continue loop } permissions = append(permissions, Permission(permission)) @@ -569,7 +574,7 @@ loop: Permissions: permissions, } if err := b.events.PublishSessionMessage(sessionId, backend, message); err != nil { - log.Printf("Could not send permissions update (%+v) to session %s: %s", permissions, sessionId, err) + b.logger.Printf("Could not send permissions update (%+v) to session %s: %s", permissions, sessionId, err) } }(sessionId, permissions) } @@ -590,11 +595,11 @@ func (b *BackendServer) sendRoomMessage(roomid string, backend *Backend, request return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomSwitchTo(roomid string, backend *Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, backend *Backend, request *BackendServerRoomRequest) error { timeout := time.Second // Convert (Nextcloud) session ids to signaling session ids. - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var wg sync.WaitGroup @@ -621,7 +626,7 @@ func (b *BackendServer) sendRoomSwitchTo(roomid string, backend *Backend, reques go func(roomSessionId RoomSessionId) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { - log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) + b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) } else if sessionId != "" { mu.Lock() defer mu.Unlock() @@ -659,7 +664,7 @@ func (b *BackendServer) sendRoomSwitchTo(roomid string, backend *Backend, reques go func(roomSessionId RoomSessionId, details json.RawMessage) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { - log.Printf("Could not lookup by room session %s: %s", roomSessionId, err) + b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) } else if sessionId != "" { mu.Lock() defer mu.Unlock() @@ -819,7 +824,7 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend response, err := b.startDialoutInSession(ctx, session, roomid, backend, backendUrl, request) if err != nil { - log.Printf("Error starting dialout request %+v in session %s: %+v", request.Dialout, session.PublicId(), err) + b.logger.Printf("Error starting dialout request %+v in session %s: %+v", request.Dialout, session.PublicId(), err) var e *Error if sessionError == nil && errors.As(err, &e) { sessionError = e @@ -837,13 +842,13 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend return returnDialoutError(http.StatusNotFound, NewError("no_client_available", "No available client found to trigger dialout.")) } -func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body []byte) { - throttle, err := b.hub.throttler.CheckBruteforce(r.Context(), b.hub.getRealUserIP(r), "BackendRoomAuth") +func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, body []byte) { + throttle, err := b.hub.throttler.CheckBruteforce(ctx, b.hub.getRealUserIP(r), "BackendRoomAuth") if err == ErrBruteforceDetected { http.Error(w, "Too many requests", http.StatusTooManyRequests) return } else if err != nil { - log.Printf("Error checking for bruteforce: %s", err) + b.logger.Printf("Error checking for bruteforce: %s", err) http.Error(w, "Could not check for bruteforce", http.StatusInternalServerError) return } @@ -860,7 +865,7 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body if backend == nil { // Unknown backend URL passed, return immediately. - throttle(r.Context()) + throttle(ctx) http.Error(w, "Authentication check failed", http.StatusForbidden) return } @@ -882,21 +887,21 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body } if backend == nil { - throttle(r.Context()) + throttle(ctx) http.Error(w, "Authentication check failed", http.StatusForbidden) return } } if !ValidateBackendChecksum(r, body, backend.Secret()) { - throttle(r.Context()) + throttle(ctx) http.Error(w, "Authentication check failed", http.StatusForbidden) return } var request BackendServerRoomRequest if err := json.Unmarshal(body, &request); err != nil { - log.Printf("Error decoding body %s: %s", string(body), err) + b.logger.Printf("Error decoding body %s: %s", string(body), err) http.Error(w, "Could not read body", http.StatusBadRequest) return } @@ -928,20 +933,20 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body case "incall": err = b.sendRoomIncall(roomid, backend, &request) case "participants": - err = b.sendRoomParticipantsUpdate(roomid, backend, &request) + err = b.sendRoomParticipantsUpdate(ctx, roomid, backend, &request) case "message": err = b.sendRoomMessage(roomid, backend, &request) case "switchto": - err = b.sendRoomSwitchTo(roomid, backend, &request) + err = b.sendRoomSwitchTo(ctx, roomid, backend, &request) case "dialout": - response, err = b.startDialout(r.Context(), roomid, backend, backendUrl, &request) + response, err = b.startDialout(ctx, roomid, backend, backendUrl, &request) default: http.Error(w, "Unsupported request type: "+request.Type, http.StatusBadRequest) return } if err != nil { - log.Printf("Error processing %s for room %s: %s", string(body), roomid, err) + b.logger.Printf("Error processing %s for room %s: %s", string(body), roomid, err) http.Error(w, "Error while processing", http.StatusInternalServerError) return } @@ -957,7 +962,7 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body } responseData, err = json.Marshal(response) if err != nil { - log.Printf("Could not serialize backend response %+v: %s", response, err) + b.logger.Printf("Could not serialize backend response %+v: %s", response, err) responseStatus = http.StatusInternalServerError responseData = []byte("{\"error\":\"could_not_serialize\"}") } @@ -995,7 +1000,7 @@ func (b *BackendServer) statsHandler(w http.ResponseWriter, r *http.Request) { stats := b.hub.GetStats() statsData, err := json.MarshalIndent(stats, "", " ") if err != nil { - log.Printf("Could not serialize stats %+v: %s", stats, err) + b.logger.Printf("Could not serialize stats %+v: %s", stats, err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } @@ -1028,7 +1033,7 @@ func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request infoData, err := json.MarshalIndent(info, "", " ") if err != nil { - log.Printf("Could not serialize server info %+v: %s", info, err) + b.logger.Printf("Could not serialize server info %+v: %s", info, err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } diff --git a/backend_server_test.go b/backend_server_test.go index 5d9eb95..460bb7d 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -99,9 +99,11 @@ func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFil config.AddOption("clients", "internalsecret", string(testInternalSecret)) config.AddOption("geoip", "url", "none") events := getAsyncEventsForTest(t) - hub, err := NewHub(config, events, nil, nil, nil, r, "no-version") + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + hub, err := NewHub(ctx, config, events, nil, nil, nil, r, "no-version") require.NoError(err) - b, err := NewBackendServer(config, hub, "no-version") + b, err := NewBackendServer(ctx, config, hub, "no-version") require.NoError(err) require.NoError(b.Start(r)) @@ -158,13 +160,16 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config1.AddOption("clients", "internalsecret", string(testInternalSecret)) config1.AddOption("geoip", "url", "none") - events1, err := NewAsyncEvents(nats.ClientURL()) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + + events1, err := NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) t.Cleanup(func() { events1.Close() }) client1, _ := NewGrpcClientsForTest(t, addr2) - hub1, err := NewHub(config1, events1, grpcServer1, client1, nil, r1, "no-version") + hub1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") require.NoError(err) if config2 == nil { @@ -181,19 +186,19 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config2.AddOption("sessions", "blockkey", "09876543210987654321098765432109") config2.AddOption("clients", "internalsecret", string(testInternalSecret)) config2.AddOption("geoip", "url", "none") - events2, err := NewAsyncEvents(nats.ClientURL()) + events2, err := NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) t.Cleanup(func() { events2.Close() }) client2, _ := NewGrpcClientsForTest(t, addr1) - hub2, err := NewHub(config2, events2, grpcServer2, client2, nil, r2, "no-version") + hub2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") require.NoError(err) - b1, err := NewBackendServer(config1, hub1, "no-version") + b1, err := NewBackendServer(ctx, config1, hub1, "no-version") require.NoError(err) require.NoError(b1.Start(r1)) - b2, err := NewBackendServer(config2, hub2, "no-version") + b2, err := NewBackendServer(ctx, config2, hub2, "no-version") require.NoError(err) require.NoError(b2.Start(r2)) @@ -258,7 +263,6 @@ func expectRoomlistEvent(t *testing.T, ch chan *AsyncMessage, msgType string) (* func TestBackendServer_NoAuth(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTest(t) @@ -280,7 +284,6 @@ func TestBackendServer_NoAuth(t *testing.T) { func TestBackendServer_InvalidAuth(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTest(t) @@ -304,7 +307,6 @@ func TestBackendServer_InvalidAuth(t *testing.T) { func TestBackendServer_OldCompatAuth(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTest(t) @@ -347,7 +349,6 @@ func TestBackendServer_OldCompatAuth(t *testing.T) { func TestBackendServer_InvalidBody(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTest(t) @@ -364,7 +365,6 @@ func TestBackendServer_InvalidBody(t *testing.T) { func TestBackendServer_UnsupportedRequest(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTest(t) @@ -385,11 +385,12 @@ func TestBackendServer_UnsupportedRequest(t *testing.T) { } func TestBackendServer_RoomInvite(t *testing.T) { - CatchLogForTest(t) for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - RunTestBackendServer_RoomInvite(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + RunTestBackendServer_RoomInvite(ctx, t) }) } } @@ -402,7 +403,7 @@ func (l *channelEventListener) ProcessAsyncUserMessage(message *AsyncMessage) { l.ch <- message } -func RunTestBackendServer_RoomInvite(t *testing.T) { +func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) _, _, events, hub, _, server := CreateBackendServerForTest(t) @@ -451,16 +452,17 @@ func RunTestBackendServer_RoomInvite(t *testing.T) { } func TestBackendServer_RoomDisinvite(t *testing.T) { - CatchLogForTest(t) for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - RunTestBackendServer_RoomDisinvite(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + RunTestBackendServer_RoomDisinvite(ctx, t) }) } } -func RunTestBackendServer_RoomDisinvite(t *testing.T) { +func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) _, _, events, hub, _, server := CreateBackendServerForTest(t) @@ -470,7 +472,7 @@ func RunTestBackendServer_RoomDisinvite(t *testing.T) { backend := hub.backend.GetBackend(u) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) @@ -531,12 +533,13 @@ func RunTestBackendServer_RoomDisinvite(t *testing.T) { func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) @@ -607,16 +610,17 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { } func TestBackendServer_RoomUpdate(t *testing.T) { - CatchLogForTest(t) for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - RunTestBackendServer_RoomUpdate(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + RunTestBackendServer_RoomUpdate(ctx, t) }) } } -func RunTestBackendServer_RoomUpdate(t *testing.T) { +func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) _, _, events, hub, _, server := CreateBackendServerForTest(t) @@ -675,16 +679,17 @@ func RunTestBackendServer_RoomUpdate(t *testing.T) { } func TestBackendServer_RoomDelete(t *testing.T) { - CatchLogForTest(t) for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - RunTestBackendServer_RoomDelete(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + RunTestBackendServer_RoomDelete(ctx, t) }) } } -func RunTestBackendServer_RoomDelete(t *testing.T) { +func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) _, _, events, hub, _, server := CreateBackendServerForTest(t) @@ -740,10 +745,11 @@ func RunTestBackendServer_RoomDelete(t *testing.T) { } func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) var hub1 *Hub @@ -760,7 +766,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { _, _, hub1, hub2, server1, server2 = CreateBackendServerWithClusteringForTest(t) } - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") @@ -837,12 +843,13 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) @@ -900,12 +907,13 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") @@ -1056,10 +1064,11 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { } func TestBackendServer_InCallAll(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) var hub1 *Hub @@ -1076,7 +1085,7 @@ func TestBackendServer_InCallAll(t *testing.T) { _, _, hub1, hub2, server1, server2 = CreateBackendServerWithClusteringForTest(t) } - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") @@ -1228,12 +1237,13 @@ func TestBackendServer_InCallAll(t *testing.T) { func TestBackendServer_RoomMessage(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") @@ -1271,7 +1281,6 @@ func TestBackendServer_RoomMessage(t *testing.T) { func TestBackendServer_TurnCredentials(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTestWithTurn(t) @@ -1301,7 +1310,6 @@ func TestBackendServer_TurnCredentials(t *testing.T) { } func TestBackendServer_StatsAllowedIps(t *testing.T) { - CatchLogForTest(t) config := goconf.NewConfigFile() config.AddOption("app", "trustedproxies", "1.2.3.4") config.AddOption("stats", "allowed_ips", "127.0.0.1, 192.168.0.1, 192.168.1.1/24") @@ -1397,7 +1405,8 @@ func Test_IsNumeric(t *testing.T) { func TestBackendServer_DialoutNoSipBridge(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1406,7 +1415,7 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloInternal()) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() MustSucceed1(t, client.RunUntilHello, ctx) @@ -1440,7 +1449,8 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { func TestBackendServer_DialoutAccepted(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1449,7 +1459,7 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloInternalWithFeatures([]string{"start-dialout"})) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() MustSucceed1(t, client.RunUntilHello, ctx) @@ -1526,7 +1536,8 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1535,7 +1546,7 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloInternalWithFeatures([]string{"start-dialout"})) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() MustSucceed1(t, client.RunUntilHello, ctx) @@ -1612,7 +1623,8 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { func TestBackendServer_DialoutRejected(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1621,7 +1633,7 @@ func TestBackendServer_DialoutRejected(t *testing.T) { defer client.CloseWithBye() require.NoError(client.SendHelloInternalWithFeatures([]string{"start-dialout"})) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() MustSucceed1(t, client.RunUntilHello, ctx) @@ -1696,7 +1708,8 @@ func TestBackendServer_DialoutRejected(t *testing.T) { func TestBackendServer_DialoutFirstFailed(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1709,7 +1722,7 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { defer client2.CloseWithBye() require.NoError(client2.SendHelloInternalWithFeatures([]string{"start-dialout"})) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() MustSucceed1(t, client1.RunUntilHello, ctx) diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 654e725..769cb0b 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -26,7 +26,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net/url" "slices" "time" @@ -38,6 +37,7 @@ import ( type backendStorageEtcd struct { backendStorageCommon + logger Logger etcdClient *EtcdClient keyPrefix string keyInfos map[string]*BackendInformationEtcd @@ -50,7 +50,7 @@ type backendStorageEtcd struct { closeFunc context.CancelFunc } -func NewBackendStorageEtcd(config *goconf.ConfigFile, etcdClient *EtcdClient) (BackendStorage, error) { +func NewBackendStorageEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (BackendStorage, error) { if etcdClient == nil || !etcdClient.IsConfigured() { return nil, fmt.Errorf("no etcd endpoints configured") } @@ -66,6 +66,7 @@ func NewBackendStorageEtcd(config *goconf.ConfigFile, etcdClient *EtcdClient) (B backendStorageCommon: backendStorageCommon{ backends: make(map[string][]*Backend), }, + logger: logger, etcdClient: etcdClient, keyPrefix: keyPrefix, keyInfos: make(map[string]*BackendInformationEtcd), @@ -120,9 +121,9 @@ func (s *backendStorageEtcd) EtcdClientCreated(client *EtcdClient) { if errors.Is(err, context.Canceled) { return } else if errors.Is(err, context.DeadlineExceeded) { - log.Printf("Timeout getting initial list of backends, retry in %s", backoff.NextWait()) + s.logger.Printf("Timeout getting initial list of backends, retry in %s", backoff.NextWait()) } else { - log.Printf("Could not get initial list of backends, retry in %s: %s", backoff.NextWait(), err) + s.logger.Printf("Could not get initial list of backends, retry in %s: %s", backoff.NextWait(), err) } backoff.Wait(s.closeCtx) @@ -140,7 +141,7 @@ func (s *backendStorageEtcd) EtcdClientCreated(client *EtcdClient) { for s.closeCtx.Err() == nil { var err error if nextRevision, err = client.Watch(s.closeCtx, s.keyPrefix, nextRevision, s, clientv3.WithPrefix()); err != nil { - log.Printf("Error processing watch for %s (%s), retry in %s", s.keyPrefix, err, backoff.NextWait()) + s.logger.Printf("Error processing watch for %s (%s), retry in %s", s.keyPrefix, err, backoff.NextWait()) backoff.Wait(s.closeCtx) continue } @@ -149,7 +150,7 @@ func (s *backendStorageEtcd) EtcdClientCreated(client *EtcdClient) { backoff.Reset() prevRevision = nextRevision } else { - log.Printf("Processing watch for %s interrupted, retry in %s", s.keyPrefix, backoff.NextWait()) + s.logger.Printf("Processing watch for %s interrupted, retry in %s", s.keyPrefix, backoff.NextWait()) backoff.Wait(s.closeCtx) } } @@ -171,11 +172,11 @@ func (s *backendStorageEtcd) getBackends(ctx context.Context, client *EtcdClient func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { var info BackendInformationEtcd if err := json.Unmarshal(data, &info); err != nil { - log.Printf("Could not decode backend information %s: %s", string(data), err) + s.logger.Printf("Could not decode backend information %s: %s", string(data), err) return } if err := info.CheckValid(); err != nil { - log.Printf("Received invalid backend information %s: %s", string(data), err) + s.logger.Printf("Received invalid backend information %s: %s", string(data), err) return } @@ -205,7 +206,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data entries, found := s.backends[host] if !found { // Simple case, first backend for this host - log.Printf("Added backend %s (from %s)", info.Urls[idx], key) + s.logger.Printf("Added backend %s (from %s)", info.Urls[idx], key) s.backends[host] = []*Backend{backend} added = true continue @@ -215,7 +216,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data replaced := false for idx, entry := range entries { if entry.id == key { - log.Printf("Updated backend %s (from %s)", info.Urls[idx], key) + s.logger.Printf("Updated backend %s (from %s)", info.Urls[idx], key) entries[idx] = backend replaced = true break @@ -224,7 +225,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data if !replaced { // New backend, add to list. - log.Printf("Added backend %s (from %s)", info.Urls[idx], key) + s.logger.Printf("Added backend %s (from %s)", info.Urls[idx], key) s.backends[host] = append(entries, backend) added = true } @@ -256,13 +257,13 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev if slices.ContainsFunc(d, func(b *Backend) bool { return slices.Contains(b.urls, u.String()) }) { - log.Printf("Removing backend %s (from %s)", info.Urls[idx], key) + s.logger.Printf("Removing backend %s (from %s)", info.Urls[idx], key) } } continue } - log.Printf("Removing backend %s (from %s)", info.Urls[idx], key) + s.logger.Printf("Removing backend %s (from %s)", info.Urls[idx], key) newEntries := make([]*Backend, 0, len(entries)-1) for _, entry := range entries { if entry.id == key { diff --git a/backend_storage_etcd_test.go b/backend_storage_etcd_test.go index d9bd77c..2c62520 100644 --- a/backend_storage_etcd_test.go +++ b/backend_storage_etcd_test.go @@ -53,7 +53,7 @@ func (tl *testListener) EtcdClientCreated(client *EtcdClient) { } func Test_BackendStorageEtcdNoLeak(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { etcd, client := NewEtcdClientForTest(t) tl := &testListener{ @@ -67,7 +67,7 @@ func Test_BackendStorageEtcdNoLeak(t *testing.T) { config.AddOption("backend", "backendtype", "etcd") config.AddOption("backend", "backendprefix", "/backends") - cfg, err := NewBackendConfiguration(config, client) + cfg, err := NewBackendConfiguration(logger, config, client) require.NoError(t, err) <-tl.closed diff --git a/backend_storage_static.go b/backend_storage_static.go index 601bf4e..dc90dfa 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -22,7 +22,6 @@ package signaling import ( - "log" "net/url" "slices" "strings" @@ -35,6 +34,7 @@ import ( type backendStorageStatic struct { backendStorageCommon + logger Logger backendsById map[string]*Backend // Deprecated @@ -43,7 +43,7 @@ type backendStorageStatic struct { compatBackend *Backend } -func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) { +func NewBackendStorageStatic(logger Logger, config *goconf.ConfigFile) (BackendStorage, error) { allowAll, _ := config.GetBool("backend", "allowall") allowHttp, _ := config.GetBool("backend", "allowhttp") commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") @@ -56,7 +56,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) var compatBackend *Backend numBackends := 0 if allowAll { - log.Println("WARNING: All backend hostnames are allowed, only use for development!") + logger.Println("WARNING: All backend hostnames are allowed, only use for development!") maxStreamBitrate, err := config.GetInt("backend", "maxstreambitrate") if err != nil || maxStreamBitrate < 0 { maxStreamBitrate = 0 @@ -78,21 +78,21 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), } if sessionLimit > 0 { - log.Printf("Allow a maximum of %d sessions", sessionLimit) + logger.Printf("Allow a maximum of %d sessions", sessionLimit) } updateBackendStats(compatBackend) backendsById[compatBackend.id] = compatBackend numBackends++ } else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { added := make(map[string]*Backend) - for host, configuredBackends := range getConfiguredHosts(backendIds, config, commonSecret) { + for host, configuredBackends := range getConfiguredHosts(logger, backendIds, config, commonSecret) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { added[be.id] = be } } for _, be := range added { - log.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) + logger.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) backendsById[be.id] = be updateBackendStats(be) be.counted = true @@ -103,7 +103,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) allowMap := make(map[string]bool) for u := range SplitEntries(allowedUrls, ",") { if idx := strings.IndexByte(u, '/'); idx != -1 { - log.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u) + logger.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u) if u = u[:idx]; u == "" { continue } @@ -113,7 +113,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) } if len(allowMap) == 0 { - log.Println("WARNING: No backend hostnames are allowed, check your configuration!") + logger.Println("WARNING: No backend hostnames are allowed, check your configuration!") } else { maxStreamBitrate, err := config.GetInt("backend", "maxstreambitrate") if err != nil || maxStreamBitrate < 0 { @@ -141,11 +141,11 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) backends[host] = []*Backend{compatBackend} } if len(hosts) > 1 { - log.Println("WARNING: Using deprecated backend configuration. Please migrate the \"allowed\" setting to the new \"backends\" configuration.") + logger.Println("WARNING: Using deprecated backend configuration. Please migrate the \"allowed\" setting to the new \"backends\" configuration.") } - log.Printf("Allowed backend hostnames: %s", hosts) + logger.Printf("Allowed backend hostnames: %s", hosts) if sessionLimit > 0 { - log.Printf("Allow a maximum of %d sessions", sessionLimit) + logger.Printf("Allow a maximum of %d sessions", sessionLimit) } updateBackendStats(compatBackend) backendsById[compatBackend.id] = compatBackend @@ -154,7 +154,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) } if numBackends == 0 { - log.Printf("WARNING: No backends configured, client connections will not be possible.") + logger.Printf("WARNING: No backends configured, client connections will not be possible.") } statsBackendsCurrent.Add(float64(numBackends)) @@ -163,6 +163,7 @@ func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) backends: backends, }, + logger: logger, backendsById: backendsById, allowAll: allowAll, @@ -187,7 +188,7 @@ func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[strin urls := slices.DeleteFunc(backend.urls, func(s string) bool { return !strings.Contains(s, "://"+host) }) - log.Printf("Backend %s removed for %s", backend.id, strings.Join(urls, ", ")) + s.logger.Printf("Backend %s removed for %s", backend.id, strings.Join(urls, ", ")) if len(urls) == len(backend.urls) && backend.counted { deleteBackendStats(backend) delete(s.backendsById, backend.Id()) @@ -225,7 +226,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen backends = slices.Delete(backends, index, index+1) if seen[newBackend.id] != seenUpdated { seen[newBackend.id] = seenUpdated - log.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) + s.logger.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) updateBackendStats(newBackend) newBackend.counted = existingBackend.counted s.backendsById[newBackend.id] = newBackend @@ -242,7 +243,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen urls := slices.DeleteFunc(removed.urls, func(s string) bool { return !strings.Contains(s, "://"+host) }) - log.Printf("Backend %s removed for %s", removed.id, strings.Join(urls, ", ")) + s.logger.Printf("Backend %s removed for %s", removed.id, strings.Join(urls, ", ")) if len(urls) == len(removed.urls) && removed.counted { deleteBackendStats(removed) delete(s.backendsById, removed.Id()) @@ -268,7 +269,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen s.backendsById[added.id] = added } - log.Printf("Backend %s added for %s", added.id, strings.Join(added.urls, ", ")) + s.logger.Printf("Backend %s added for %s", added.id, strings.Join(added.urls, ", ")) if !added.counted { updateBackendStats(added) addedBackends++ @@ -293,17 +294,17 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } -func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { +func getConfiguredHosts(logger Logger, backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { hosts = make(map[string][]*Backend) seenUrls := make(map[string]string) for _, id := range getConfiguredBackendIDs(backendIds) { secret, _ := GetStringOptionWithEnv(config, id, "secret") if secret == "" && commonSecret != "" { - log.Printf("Backend %s has no own shared secret set, using common shared secret", id) + logger.Printf("Backend %s has no own shared secret set, using common shared secret", id) secret = commonSecret } if secret == "" { - log.Printf("Backend %s is missing or incomplete, skipping", id) + logger.Printf("Backend %s is missing or incomplete, skipping", id) continue } @@ -312,7 +313,7 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr sessionLimit = 0 } if sessionLimit > 0 { - log.Printf("Backend %s allows a maximum of %d sessions", id, sessionLimit) + logger.Printf("Backend %s allows a maximum of %d sessions", id, sessionLimit) } maxStreamBitrate, err := config.GetInt(id, "maxstreambitrate") @@ -335,7 +336,7 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr } if len(urls) == 0 { - log.Printf("Backend %s is missing or incomplete, skipping", id) + logger.Printf("Backend %s is missing or incomplete, skipping", id) continue } @@ -357,7 +358,7 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr parsed, err := url.Parse(u) if err != nil { - log.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err) + logger.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err) continue } @@ -367,7 +368,7 @@ func getConfiguredHosts(backendIds string, config *goconf.ConfigFile, commonSecr } if prev, found := seenUrls[u]; found { - log.Printf("Url %s in backend %s was already used in backend %s, skipping", u, id, prev) + logger.Printf("Url %s in backend %s was already used in backend %s, skipping", u, id, prev) continue } @@ -392,14 +393,14 @@ func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { defer s.mu.Unlock() if s.compatBackend != nil { - log.Println("Old-style configuration active, reload is not supported") + s.logger.Println("Old-style configuration active, reload is not supported") return } commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { - configuredHosts := getConfiguredHosts(backendIds, config, commonSecret) + configuredHosts := getConfiguredHosts(s.logger, backendIds, config, commonSecret) // remove backends that are no longer configured seen := make(map[string]seenState) diff --git a/capabilities.go b/capabilities.go index c5bb1e0..2f68bb9 100644 --- a/capabilities.go +++ b/capabilities.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "errors" - "log" "net/http" "net/url" "strings" @@ -118,6 +117,7 @@ func (e *capabilitiesEntry) errorIfMustRevalidate(err error) (bool, error) { } func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Time) (bool, error) { + logger := LoggerFromContext(ctx) e.mu.Lock() defer e.mu.Unlock() @@ -136,18 +136,18 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim capUrl.Path = capUrl.Path[:pos+11] + "/cloud/capabilities" } - log.Printf("Capabilities expired for %s, updating", capUrl.String()) + logger.Printf("Capabilities expired for %s, updating", capUrl.String()) client, pool, err := e.c.pool.Get(ctx, &capUrl) if err != nil { - log.Printf("Could not get client for host %s: %s", capUrl.Host, err) + logger.Printf("Could not get client for host %s: %s", capUrl.Host, err) return false, err } defer pool.Put(client) req, err := http.NewRequestWithContext(ctx, "GET", capUrl.String(), nil) if err != nil { - log.Printf("Could not create request to %s: %s", &capUrl, err) + logger.Printf("Could not create request to %s: %s", &capUrl, err) return false, err } req.Header.Set("Accept", "application/json") @@ -179,22 +179,22 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim e.nextUpdate = now.Add(maxAge) if response.StatusCode == http.StatusNotModified { - log.Printf("Capabilities %+v from %s have not changed", e.capabilities, url) + logger.Printf("Capabilities %+v from %s have not changed", e.capabilities, url) return false, nil } else if response.StatusCode != http.StatusOK { - log.Printf("Received unexpected HTTP status from %s: %s", url, response.Status) + logger.Printf("Received unexpected HTTP status from %s: %s", url, response.Status) return e.errorIfMustRevalidate(ErrUnexpectedHttpStatus) } ct := response.Header.Get("Content-Type") if !strings.HasPrefix(ct, "application/json") { - log.Printf("Received unsupported content-type from %s: %s (%s)", url, ct, response.Status) + logger.Printf("Received unsupported content-type from %s: %s (%s)", url, ct, response.Status) return e.errorIfMustRevalidate(ErrUnsupportedContentType) } body, err := e.c.buffers.ReadAll(response.Body) if err != nil { - log.Printf("Could not read response body from %s: %s", url, err) + logger.Printf("Could not read response body from %s: %s", url, err) return e.errorIfMustRevalidate(err) } @@ -202,34 +202,34 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim var ocs OcsResponse if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { - log.Printf("Could not decode OCS response %s from %s: %s", body.String(), url, err) + logger.Printf("Could not decode OCS response %s from %s: %s", body.String(), url, err) return e.errorIfMustRevalidate(err) } else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 { - log.Printf("Incomplete OCS response %s from %s", body.String(), url) + logger.Printf("Incomplete OCS response %s from %s", body.String(), url) return e.errorIfMustRevalidate(ErrIncompleteResponse) } var capaResponse CapabilitiesResponse if err := json.Unmarshal(ocs.Ocs.Data, &capaResponse); err != nil { - log.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), url, err) + logger.Printf("Could not decode OCS response body %s from %s: %s", string(ocs.Ocs.Data), url, err) return e.errorIfMustRevalidate(err) } capaObj, found := capaResponse.Capabilities[AppNameSpreed] if !found || len(capaObj) == 0 { - log.Printf("No capabilities received for app spreed from %s: %+v", url, capaResponse) + logger.Printf("No capabilities received for app spreed from %s: %+v", url, capaResponse) e.capabilities = nil return false, nil } var capa api.StringMap if err := json.Unmarshal(capaObj, &capa); err != nil { - log.Printf("Unsupported capabilities received for app spreed from %s: %+v", url, capaResponse) + logger.Printf("Unsupported capabilities received for app spreed from %s: %+v", url, capaResponse) e.capabilities = nil return false, nil } - log.Printf("Received capabilities %+v from %s", capa, url) + logger.Printf("Received capabilities %+v from %s", capa, url) e.capabilities = capa return true, nil } @@ -351,9 +351,10 @@ func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (api.St } func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, feature string) bool { + logger := LoggerFromContext(ctx) caps, _, err := c.loadCapabilities(ctx, u) if err != nil { - log.Printf("Could not get capabilities for %s: %s", u, err) + logger.Printf("Could not get capabilities for %s: %s", u, err) return false } @@ -364,7 +365,7 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea features, ok := featuresInterface.([]any) if !ok { - log.Printf("Invalid features list received for %s: %+v", u, featuresInterface) + logger.Printf("Invalid features list received for %s: %+v", u, featuresInterface) return false } @@ -377,9 +378,10 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea } func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (api.StringMap, bool, bool) { + logger := LoggerFromContext(ctx) caps, cached, err := c.loadCapabilities(ctx, u) if err != nil { - log.Printf("Could not get capabilities for %s: %s", u, err) + logger.Printf("Could not get capabilities for %s: %s", u, err) return nil, cached, false } @@ -390,7 +392,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str config, ok := api.ConvertStringMap(configInterface) if !ok { - log.Printf("Invalid config mapping received from %s: %+v", u, configInterface) + logger.Printf("Invalid config mapping received from %s: %+v", u, configInterface) return nil, cached, false } @@ -401,7 +403,7 @@ func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group str groupConfig, ok := api.ConvertStringMap(groupInterface) if !ok { - log.Printf("Invalid group mapping \"%s\" received from %s: %+v", group, u, groupInterface) + logger.Printf("Invalid group mapping \"%s\" received from %s: %+v", group, u, groupInterface) return nil, cached, false } @@ -427,7 +429,8 @@ func (c *Capabilities) GetIntegerConfig(ctx context.Context, u *url.URL, group, case float64: return int(value), cached, true default: - log.Printf("Invalid config value for \"%s\" received from %s: %+v", key, u, value) + logger := LoggerFromContext(ctx) + logger.Printf("Invalid config value for \"%s\" received from %s: %+v", key, u, value) } return 0, cached, false @@ -448,7 +451,8 @@ func (c *Capabilities) GetStringConfig(ctx context.Context, u *url.URL, group, k case string: return value, cached, true default: - log.Printf("Invalid config value for \"%s\" received from %s: %+v", key, u, value) + logger := LoggerFromContext(ctx) + logger.Printf("Invalid config value for \"%s\" received from %s: %+v", key, u, value) } return "", cached, false diff --git a/capabilities_test.go b/capabilities_test.go index 72a7f00..c4e7a7b 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -174,11 +174,12 @@ func SetCapabilitiesGetNow(t *testing.T, capabilities *Capabilities, f func() ti func TestCapabilities(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) url, capabilities := NewCapabilitiesForTest(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() assert.True(capabilities.HasCapabilityFeature(ctx, url, "foo")) @@ -217,7 +218,8 @@ func TestCapabilities(t *testing.T) { func TestInvalidateCapabilities(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -225,7 +227,7 @@ func TestInvalidateCapabilities(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -277,7 +279,8 @@ func TestInvalidateCapabilities(t *testing.T) { func TestCapabilitiesNoCache(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -285,7 +288,7 @@ func TestCapabilitiesNoCache(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -321,7 +324,8 @@ func TestCapabilitiesNoCache(t *testing.T) { func TestCapabilitiesShortCache(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -329,7 +333,7 @@ func TestCapabilitiesShortCache(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -375,7 +379,8 @@ func TestCapabilitiesShortCache(t *testing.T) { func TestCapabilitiesNoCacheETag(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -389,7 +394,7 @@ func TestCapabilitiesNoCacheETag(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -416,7 +421,8 @@ func TestCapabilitiesNoCacheETag(t *testing.T) { func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -427,7 +433,7 @@ func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -456,7 +462,8 @@ func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -467,7 +474,7 @@ func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -496,7 +503,8 @@ func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -507,7 +515,7 @@ func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" @@ -534,7 +542,8 @@ func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { func TestConcurrentExpired(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -542,7 +551,7 @@ func TestConcurrentExpired(t *testing.T) { return nil }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() expectedString := "bar" diff --git a/certificate_reloader.go b/certificate_reloader.go index 3e23c96..373b503 100644 --- a/certificate_reloader.go +++ b/certificate_reloader.go @@ -25,12 +25,13 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "log" "os" "sync/atomic" ) type CertificateReloader struct { + logger Logger + certFile string certWatcher *FileWatcher @@ -42,22 +43,23 @@ type CertificateReloader struct { reloadCounter atomic.Uint64 } -func NewCertificateReloader(certFile string, keyFile string) (*CertificateReloader, error) { +func NewCertificateReloader(logger Logger, certFile string, keyFile string) (*CertificateReloader, error) { pair, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, fmt.Errorf("could not load certificate / key: %w", err) } reloader := &CertificateReloader{ + logger: logger, certFile: certFile, keyFile: keyFile, } reloader.certificate.Store(&pair) - reloader.certWatcher, err = NewFileWatcher(certFile, reloader.reload) + reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload) if err != nil { return nil, err } - reloader.keyWatcher, err = NewFileWatcher(keyFile, reloader.reload) + reloader.keyWatcher, err = NewFileWatcher(reloader.logger, keyFile, reloader.reload) if err != nil { reloader.certWatcher.Close() // nolint return nil, err @@ -72,10 +74,10 @@ func (r *CertificateReloader) Close() { } func (r *CertificateReloader) reload(filename string) { - log.Printf("reloading certificate from %s with %s", r.certFile, r.keyFile) + r.logger.Printf("reloading certificate from %s with %s", r.certFile, r.keyFile) pair, err := tls.LoadX509KeyPair(r.certFile, r.keyFile) if err != nil { - log.Printf("could not load certificate / key: %s", err) + r.logger.Printf("could not load certificate / key: %s", err) return } @@ -100,6 +102,8 @@ func (r *CertificateReloader) GetReloadCounter() uint64 { } type CertPoolReloader struct { + logger Logger + certFile string certWatcher *FileWatcher @@ -122,17 +126,18 @@ func loadCertPool(filename string) (*x509.CertPool, error) { return pool, nil } -func NewCertPoolReloader(certFile string) (*CertPoolReloader, error) { +func NewCertPoolReloader(logger Logger, certFile string) (*CertPoolReloader, error) { pool, err := loadCertPool(certFile) if err != nil { return nil, err } reloader := &CertPoolReloader{ + logger: logger, certFile: certFile, } reloader.pool.Store(pool) - reloader.certWatcher, err = NewFileWatcher(certFile, reloader.reload) + reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload) if err != nil { return nil, err } @@ -145,10 +150,10 @@ func (r *CertPoolReloader) Close() { } func (r *CertPoolReloader) reload(filename string) { - log.Printf("reloading certificate pool from %s", r.certFile) + r.logger.Printf("reloading certificate pool from %s", r.certFile) pool, err := loadCertPool(r.certFile) if err != nil { - log.Printf("could not load certificate pool: %s", err) + r.logger.Printf("could not load certificate pool: %s", err) return } diff --git a/client.go b/client.go index b7d7d38..7ffff52 100644 --- a/client.go +++ b/client.go @@ -26,7 +26,6 @@ import ( "context" "encoding/json" "errors" - "log" "net" "strconv" "strings" @@ -121,6 +120,7 @@ type ClientGeoIpHandler interface { } type Client struct { + logger Logger ctx context.Context conn *websocket.Conn addr string @@ -163,6 +163,7 @@ func NewClient(ctx context.Context, conn *websocket.Conn, remoteAddress string, } func (c *Client) SetConn(ctx context.Context, conn *websocket.Conn, remoteAddress string, handler ClientHandler) { + c.logger = LoggerFromContext(ctx) c.ctx = ctx c.conn = conn c.addr = remoteAddress @@ -332,7 +333,7 @@ func (c *Client) ReadPump() { conn := c.conn c.mu.Unlock() if conn == nil { - log.Printf("Connection from %s closed while starting readPump", addr) + c.logger.Printf("Connection from %s closed while starting readPump", addr) return } @@ -348,9 +349,9 @@ func (c *Client) ReadPump() { if c.logRTT { rtt_ms := rtt.Nanoseconds() / time.Millisecond.Nanoseconds() if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Client %s has RTT of %d ms (%s)", sessionId, rtt_ms, rtt) + c.logger.Printf("Client %s has RTT of %d ms (%s)", sessionId, rtt_ms, rtt) } else { - log.Printf("Client from %s has RTT of %d ms (%s)", addr, rtt_ms, rtt) + c.logger.Printf("Client from %s has RTT of %d ms (%s)", addr, rtt_ms, rtt) } } statsClientRTT.Observe(float64(rtt.Milliseconds())) @@ -371,9 +372,9 @@ func (c *Client) ReadPump() { websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Error reading from client %s: %v", sessionId, err) + c.logger.Printf("Error reading from client %s: %v", sessionId, err) } else { - log.Printf("Error reading from %s: %v", addr, err) + c.logger.Printf("Error reading from %s: %v", addr, err) } } break @@ -381,9 +382,9 @@ func (c *Client) ReadPump() { if messageType != websocket.TextMessage { if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Unsupported message type %v from client %s", messageType, sessionId) + c.logger.Printf("Unsupported message type %v from client %s", messageType, sessionId) } else { - log.Printf("Unsupported message type %v from %s", messageType, addr) + c.logger.Printf("Unsupported message type %v from %s", messageType, addr) } c.SendError(InvalidFormat) continue @@ -392,9 +393,9 @@ func (c *Client) ReadPump() { decodeBuffer, err := bufferPool.ReadAll(reader) if err != nil { if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Error reading message from client %s: %v", sessionId, err) + c.logger.Printf("Error reading message from client %s: %v", sessionId, err) } else { - log.Printf("Error reading message from %s: %v", addr, err) + c.logger.Printf("Error reading message from %s: %v", addr, err) } break } @@ -446,9 +447,9 @@ func (c *Client) writeInternal(message json.Marshaler) bool { } if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Could not send message %+v to client %s: %v", message, sessionId, err) + c.logger.Printf("Could not send message %+v to client %s: %v", message, sessionId, err) } else { - log.Printf("Could not send message %+v to %s: %v", message, c.RemoteAddr(), err) + c.logger.Printf("Could not send message %+v to %s: %v", message, c.RemoteAddr(), err) } closeData = websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "") goto close @@ -459,9 +460,9 @@ close: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint if err := c.conn.WriteMessage(websocket.CloseMessage, closeData); err != nil { if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Could not send close message to client %s: %v", sessionId, err) + c.logger.Printf("Could not send close message to client %s: %v", sessionId, err) } else { - log.Printf("Could not send close message to %s: %v", c.RemoteAddr(), err) + c.logger.Printf("Could not send close message to %s: %v", c.RemoteAddr(), err) } } return false @@ -486,9 +487,9 @@ func (c *Client) writeError(e error) bool { // nolint c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint if err := c.conn.WriteMessage(websocket.CloseMessage, closeData); err != nil { if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Could not send close message to client %s: %v", sessionId, err) + c.logger.Printf("Could not send close message to client %s: %v", sessionId, err) } else { - log.Printf("Could not send close message to %s: %v", c.RemoteAddr(), err) + c.logger.Printf("Could not send close message to %s: %v", c.RemoteAddr(), err) } } return false @@ -534,9 +535,9 @@ func (c *Client) sendPing() bool { c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint if err := c.conn.WriteMessage(websocket.PingMessage, []byte(msg)); err != nil { if sessionId := c.GetSessionId(); sessionId != "" { - log.Printf("Could not send ping to client %s: %v", sessionId, err) + c.logger.Printf("Could not send ping to client %s: %v", sessionId, err) } else { - log.Printf("Could not send ping to %s: %v", c.RemoteAddr(), err) + c.logger.Printf("Could not send ping to %s: %v", c.RemoteAddr(), err) } return false } diff --git a/clientsession.go b/clientsession.go index 832bf15..a22af47 100644 --- a/clientsession.go +++ b/clientsession.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "fmt" - "log" "maps" "net/url" "slices" @@ -55,6 +54,7 @@ const ( type ResponseHandlerFunc func(message *ClientMessage) bool type ClientSession struct { + logger Logger hub *Hub events AsyncEvents privateId PrivateSessionId @@ -119,8 +119,10 @@ type ClientSession struct { } func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { - ctx, closeFunc := context.WithCancel(context.Background()) + ctx := NewLoggerContext(context.Background(), hub.logger) + ctx, closeFunc := context.WithCancel(ctx) s := &ClientSession{ + logger: hub.logger, hub: hub, events: hub.events, privateId: privateId, @@ -276,7 +278,7 @@ func (s *ClientSession) SetPermissions(permissions []Permission) { s.permissions = p s.supportsPermissions = true - log.Printf("Permissions of session %s changed: %s", s.PublicId(), permissions) + s.logger.Printf("Permissions of session %s changed: %s", s.PublicId(), permissions) } func (s *ClientSession) Backend() *Backend { @@ -443,19 +445,19 @@ func (s *ClientSession) UpdateRoomSessionId(roomSessionId RoomSessionId) error { if roomSessionId != "" { if room := s.GetRoom(); room != nil { - log.Printf("Session %s updated room session id to %s in room %s", s.PublicId(), roomSessionId, room.Id()) + s.logger.Printf("Session %s updated room session id to %s in room %s", s.PublicId(), roomSessionId, room.Id()) } else if client := s.GetFederationClient(); client != nil { - log.Printf("Session %s updated room session id to %s in federated room %s", s.PublicId(), roomSessionId, client.RemoteRoomId()) + s.logger.Printf("Session %s updated room session id to %s in federated room %s", s.PublicId(), roomSessionId, client.RemoteRoomId()) } else { - log.Printf("Session %s updated room session id to %s in unknown room", s.PublicId(), roomSessionId) + s.logger.Printf("Session %s updated room session id to %s in unknown room", s.PublicId(), roomSessionId) } } else { if room := s.GetRoom(); room != nil { - log.Printf("Session %s cleared room session id in room %s", s.PublicId(), room.Id()) + s.logger.Printf("Session %s cleared room session id in room %s", s.PublicId(), room.Id()) } else if client := s.GetFederationClient(); client != nil { - log.Printf("Session %s cleared room session id in federated room %s", s.PublicId(), client.RemoteRoomId()) + s.logger.Printf("Session %s cleared room session id in federated room %s", s.PublicId(), client.RemoteRoomId()) } else { - log.Printf("Session %s cleared room session id in unknown room", s.PublicId()) + s.logger.Printf("Session %s cleared room session id in unknown room", s.PublicId()) } } @@ -477,7 +479,7 @@ func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId RoomSes return err } } - log.Printf("Session %s joined room %s with room session id %s", s.PublicId(), roomid, roomSessionId) + s.logger.Printf("Session %s joined room %s with room session id %s", s.PublicId(), roomid, roomSessionId) s.roomSessionId = roomSessionId return nil } @@ -491,7 +493,7 @@ func (s *ClientSession) LeaveCall() { return } - log.Printf("Session %s left call %s", s.PublicId(), room.Id()) + s.logger.Printf("Session %s left call %s", s.PublicId(), room.Id()) s.releaseMcuObjects() } @@ -503,7 +505,7 @@ func (s *ClientSession) LeaveRoomWithMessage(notify bool, message *ClientMessage 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) + s.logger.Printf("Error leaving room for session %s on federation client %s: %s", s.PublicId(), prev.URL(), err) prev.Close() } return nil @@ -548,15 +550,15 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { if notify && room != nil && s.roomSessionId != "" && !s.roomSessionId.IsFederated() { // Notify go func(sid RoomSessionId) { - ctx := context.Background() + ctx := NewLoggerContext(context.Background(), s.logger) request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) request.Room.Action = "leave" var response api.StringMap if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { - log.Printf("Could not notify about room session %s left room %s: %s", sid, room.Id(), err) + s.logger.Printf("Could not notify about room session %s left room %s: %s", sid, room.Id(), err) } else { - log.Printf("Removed room session %s: %+v", sid, response) + s.logger.Printf("Removed room session %s: %+v", sid, response) } }(s.roomSessionId) } @@ -575,7 +577,7 @@ func (s *ClientSession) clearClientLocked(client HandlerClient) { if s.client == nil { return } else if client != nil && s.client != client { - log.Printf("Trying to clear other client in session %s", s.PublicId()) + s.logger.Printf("Trying to clear other client in session %s", s.PublicId()) return } @@ -630,7 +632,7 @@ func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, stre } offer_data, err := json.Marshal(offer_message) if err != nil { - log.Println("Could not serialize offer", offer_message, err) + s.logger.Println("Could not serialize offer", offer_message, err) return } response_message := &ServerMessage{ @@ -661,7 +663,7 @@ func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, } candidate_data, err := json.Marshal(candidate_message) if err != nil { - log.Println("Could not serialize candidate", candidate_message, err) + s.logger.Println("Could not serialize candidate", candidate_message, err) return } response_message := &ServerMessage{ @@ -750,7 +752,7 @@ func (s *ClientSession) OnIceCandidate(client McuClient, candidate any) { } } - log.Printf("Session %s received candidate %+v for unknown client %s", s.PublicId(), candidate, client.Id()) + s.logger.Printf("Session %s received candidate %+v for unknown client %s", s.PublicId(), candidate, client.Id()) } func (s *ClientSession) OnIceCompleted(client McuClient) { @@ -942,7 +944,7 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea } else { s.publishers[streamType] = publisher } - log.Printf("Publishing %s as %s for session %s", streamType, publisher.Id(), s.PublicId()) + s.logger.Printf("Publishing %s as %s for session %s", streamType, publisher.Id(), s.PublicId()) s.publisherWaiters.Wakeup() } else { publisher.SetMedia(mediaTypes) @@ -1021,7 +1023,7 @@ func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id P } else { s.subscribers[getStreamId(id, streamType)] = subscriber } - log.Printf("Subscribing %s from %s as %s in session %s", streamType, id, subscriber.Id(), s.PublicId()) + s.logger.Printf("Subscribing %s from %s as %s in session %s", streamType, id, subscriber.Id(), s.PublicId()) } return subscriber, nil @@ -1059,7 +1061,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { if (publisher.HasMedia(MediaTypeAudio) && !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_AUDIO)) || (publisher.HasMedia(MediaTypeVideo) && !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_VIDEO)) { delete(s.publishers, StreamTypeVideo) - log.Printf("Session %s is no longer allowed to publish media, closing publisher %s", s.PublicId(), publisher.Id()) + s.logger.Printf("Session %s is no longer allowed to publish media, closing publisher %s", s.PublicId(), publisher.Id()) go func() { publisher.Close(context.Background()) }() @@ -1070,7 +1072,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { if !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { if publisher, found := s.publishers[StreamTypeScreen]; found { delete(s.publishers, StreamTypeScreen) - log.Printf("Session %s is no longer allowed to publish screen, closing publisher %s", s.PublicId(), publisher.Id()) + s.logger.Printf("Session %s is no longer allowed to publish screen, closing publisher %s", s.PublicId(), publisher.Id()) go func() { publisher.Close(context.Background()) }() @@ -1081,7 +1083,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { return case "message": if message.Message.Type == "bye" && message.Message.Bye.Reason == "room_session_reconnected" { - log.Printf("Closing session %s because same room session %s connected", s.PublicId(), s.RoomSessionId()) + s.logger.Printf("Closing session %s because same room session %s connected", s.PublicId(), s.RoomSessionId()) s.LeaveRoom(false) defer s.closeAndWait(false) } @@ -1093,7 +1095,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { mc, err := s.GetOrCreateSubscriber(ctx, s.hub.mcu, message.SendOffer.SessionId, StreamType(message.SendOffer.Data.RoomType)) if err != nil { - log.Printf("Could not create MCU subscriber for session %s to process sendoffer in %s: %s", message.SendOffer.SessionId, s.PublicId(), err) + s.logger.Printf("Could not create MCU subscriber for session %s to process sendoffer in %s: %s", message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ Type: "message", Message: &ServerMessage{ @@ -1102,11 +1104,11 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { Error: NewError("client_not_found", "No MCU client found to send message to."), }, }); err != nil { - log.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) + s.logger.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) } return } else if mc == nil { - log.Printf("No MCU subscriber found for session %s to process sendoffer in %s", message.SendOffer.SessionId, s.PublicId()) + s.logger.Printf("No MCU subscriber found for session %s to process sendoffer in %s", message.SendOffer.SessionId, s.PublicId()) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ Type: "message", Message: &ServerMessage{ @@ -1115,14 +1117,14 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { Error: NewError("client_not_found", "No MCU client found to send message to."), }, }); err != nil { - log.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) + s.logger.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) } return } mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response api.StringMap) { if err != nil { - log.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) + s.logger.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ Type: "message", Message: &ServerMessage{ @@ -1131,7 +1133,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { Error: NewError("processing_failed", "Processing of the message failed, please check server logs."), }, }); err != nil { - log.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) + s.logger.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) } return } else if response == nil { @@ -1172,7 +1174,7 @@ func (s *ClientSession) storePendingMessage(message *ServerMessage) { } s.pendingClientMessages = append(s.pendingClientMessages, message) if len(s.pendingClientMessages) >= warnPendingMessagesCount { - log.Printf("Session %s has %d pending messages", s.PublicId(), len(s.pendingClientMessages)) + s.logger.Printf("Session %s has %d pending messages", s.PublicId(), len(s.pendingClientMessages)) } } @@ -1227,7 +1229,7 @@ func (s *ClientSession) filterDuplicateJoin(entries []*EventServerMessageSession result := make([]*EventServerMessageSessionEntry, 0, len(entries)) for _, e := range entries { if s.seenJoinedEvents[e.SessionId] { - log.Printf("Session %s got duplicate joined event for %s, ignoring", s.publicId, e.SessionId) + s.logger.Printf("Session %s got duplicate joined event for %s, ignoring", s.publicId, e.SessionId) continue } @@ -1383,7 +1385,7 @@ func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { switch msg.Type { case "message": if msg.Message == nil { - log.Printf("Received asynchronous message without payload: %+v", msg) + s.logger.Printf("Received asynchronous message without payload: %+v", msg) return nil } @@ -1423,7 +1425,7 @@ func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { // Can happen mostly during tests where an older room async message // could be received by a subscriber that joined after it was sent. if joined := s.getRoomJoinTime(); joined.IsZero() || msg.SendTime.Before(joined) { - log.Printf("Message %+v was sent on %s before room was joined on %s, ignoring", msg.Message, msg.SendTime, joined) + s.logger.Printf("Message %+v was sent on %s before room was joined on %s, ignoring", msg.Message, msg.SendTime, joined) return nil } } @@ -1431,7 +1433,7 @@ func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { return msg.Message default: - log.Printf("Received async message with unsupported type %s: %+v", msg.Type, msg) + s.logger.Printf("Received async message with unsupported type %s: %+v", msg.Type, msg) return nil } } @@ -1453,7 +1455,7 @@ func (s *ClientSession) NotifySessionResumed(client HandlerClient) { s.hasPendingParticipantsUpdate = false s.mu.Unlock() - log.Printf("Send %d pending messages to session %s", len(messages), s.PublicId()) + s.logger.Printf("Send %d pending messages to session %s", len(messages), s.PublicId()) // Send through session to handle connection interruptions. s.SendMessages(messages) diff --git a/clientsession_test.go b/clientsession_test.go index a9e2c72..b0f0cb4 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -37,7 +37,6 @@ import ( func TestBandwidth_Client(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -45,8 +44,7 @@ func TestBandwidth_Client(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -86,7 +84,6 @@ func TestBandwidth_Client(t *testing.T) { func TestBandwidth_Backend(t *testing.T) { t.Parallel() - CatchLogForTest(t) hub, _, _, server := CreateHubWithMultipleBackendsForTest(t) u, err := url.Parse(server.URL + "/one") @@ -100,8 +97,7 @@ func TestBandwidth_Backend(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(t, err) + mcu := NewTestMCU(t) require.NoError(t, mcu.Start(ctx)) defer mcu.Stop() @@ -167,7 +163,6 @@ func TestBandwidth_Backend(t *testing.T) { func TestFeatureChatRelay(t *testing.T) { t.Parallel() - CatchLogForTest(t) testFunc := func(feature bool) func(t *testing.T) { return func(t *testing.T) { @@ -253,11 +248,8 @@ func TestFeatureChatRelay(t *testing.T) { } func TestFeatureChatRelayFederation(t *testing.T) { - CatchLogForTest(t) - var testFunc = func(feature bool) func(t *testing.T) { return func(t *testing.T) { - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -466,7 +458,6 @@ func TestFeatureChatRelayFederation(t *testing.T) { func TestPermissionHideDisplayNames(t *testing.T) { t.Parallel() - CatchLogForTest(t) testFunc := func(permission bool) func(t *testing.T) { return func(t *testing.T) { diff --git a/deferred_executor.go b/deferred_executor.go index c193eee..3162058 100644 --- a/deferred_executor.go +++ b/deferred_executor.go @@ -22,7 +22,6 @@ package signaling import ( - "log" "reflect" "runtime" "runtime/debug" @@ -32,16 +31,18 @@ import ( // DeferredExecutor will asynchronously execute functions while maintaining // their order. type DeferredExecutor struct { + logger Logger queue chan func() closed chan struct{} closeOnce sync.Once } -func NewDeferredExecutor(queueSize int) *DeferredExecutor { +func NewDeferredExecutor(logger Logger, queueSize int) *DeferredExecutor { if queueSize < 0 { queueSize = 0 } result := &DeferredExecutor{ + logger: logger, queue: make(chan func(), queueSize), closed: make(chan struct{}), } @@ -68,9 +69,9 @@ func getFunctionName(i any) string { func (e *DeferredExecutor) Execute(f func()) { defer func() { - if e := recover(); e != nil { - log.Printf("Could not defer function %v: %+v", getFunctionName(f), e) - log.Printf("Called from %s", string(debug.Stack())) + if err := recover(); err != nil { + e.logger.Printf("Could not defer function %v: %+v", getFunctionName(f), err) + e.logger.Printf("Called from %s", string(debug.Stack())) } }() diff --git a/deferred_executor_test.go b/deferred_executor_test.go index ed71c0c..183b2ae 100644 --- a/deferred_executor_test.go +++ b/deferred_executor_test.go @@ -29,7 +29,8 @@ import ( ) func TestDeferredExecutor_MultiClose(t *testing.T) { - e := NewDeferredExecutor(0) + logger := NewLoggerForTest(t) + e := NewDeferredExecutor(logger, 0) defer e.waitForStop() e.Close() @@ -38,7 +39,8 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { func TestDeferredExecutor_QueueSize(t *testing.T) { SynctestTest(t, func(t *testing.T) { - e := NewDeferredExecutor(0) + logger := NewLoggerForTest(t) + e := NewDeferredExecutor(logger, 0) defer e.waitForStop() defer e.Close() @@ -59,7 +61,8 @@ func TestDeferredExecutor_QueueSize(t *testing.T) { } func TestDeferredExecutor_Order(t *testing.T) { - e := NewDeferredExecutor(64) + logger := NewLoggerForTest(t) + e := NewDeferredExecutor(logger, 64) defer e.waitForStop() defer e.Close() @@ -86,7 +89,8 @@ func TestDeferredExecutor_Order(t *testing.T) { } func TestDeferredExecutor_CloseFromFunc(t *testing.T) { - e := NewDeferredExecutor(64) + logger := NewLoggerForTest(t) + e := NewDeferredExecutor(logger, 64) defer e.waitForStop() done := make(chan struct{}) @@ -99,8 +103,8 @@ func TestDeferredExecutor_CloseFromFunc(t *testing.T) { } func TestDeferredExecutor_DeferAfterClose(t *testing.T) { - CatchLogForTest(t) - e := NewDeferredExecutor(64) + logger := NewLoggerForTest(t) + e := NewDeferredExecutor(logger, 64) defer e.waitForStop() e.Close() @@ -111,7 +115,8 @@ func TestDeferredExecutor_DeferAfterClose(t *testing.T) { } func TestDeferredExecutor_WaitForStopTwice(t *testing.T) { - e := NewDeferredExecutor(64) + logger := NewLoggerForTest(t) + e := NewDeferredExecutor(logger, 64) defer e.waitForStop() e.Close() diff --git a/dns_monitor.go b/dns_monitor.go index dcfba64..6906057 100644 --- a/dns_monitor.go +++ b/dns_monitor.go @@ -23,7 +23,6 @@ package signaling import ( "context" - "log" "net" "net/url" "slices" @@ -159,6 +158,7 @@ func (e *dnsMonitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP } type DnsMonitor struct { + logger Logger interval time.Duration stopCtx context.Context @@ -176,13 +176,14 @@ type DnsMonitor struct { checkHostnames func() } -func NewDnsMonitor(interval time.Duration) (*DnsMonitor, error) { +func NewDnsMonitor(logger Logger, interval time.Duration) (*DnsMonitor, error) { if interval < 0 { interval = defaultDnsMonitorInterval } stopCtx, stopFunc := context.WithCancel(context.Background()) monitor := &DnsMonitor{ + logger: logger, interval: interval, stopCtx: stopCtx, @@ -348,7 +349,7 @@ func (m *DnsMonitor) checkHostname(entry *dnsMonitorEntry) { ips, err := lookupDnsMonitorIP(entry.hostname) if err != nil { - log.Printf("Could not lookup %s: %s", entry.hostname, err) + m.logger.Printf("Could not lookup %s: %s", entry.hostname, err) return } diff --git a/dns_monitor_test.go b/dns_monitor_test.go index daa92ef..7d43dd7 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -90,7 +90,8 @@ func newDnsMonitorForTest(t *testing.T, interval time.Duration) *DnsMonitor { t.Helper() require := require.New(t) - monitor, err := NewDnsMonitor(interval) + logger := NewLoggerForTest(t) + monitor, err := NewDnsMonitor(logger, interval) require.NoError(err) t.Cleanup(func() { diff --git a/etcd_client.go b/etcd_client.go index 1780679..15ce75e 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -25,7 +25,6 @@ import ( "context" "errors" "fmt" - "log" "slices" "sync" "sync/atomic" @@ -53,6 +52,7 @@ type EtcdClientWatcher interface { } type EtcdClient struct { + logger Logger compatSection string mu sync.Mutex @@ -61,8 +61,9 @@ type EtcdClient struct { listeners map[EtcdClientListener]bool } -func NewEtcdClient(config *goconf.ConfigFile, compatSection string) (*EtcdClient, error) { +func NewEtcdClient(logger Logger, config *goconf.ConfigFile, compatSection string) (*EtcdClient, error) { result := &EtcdClient{ + logger: logger, compatSection: compatSection, } if err := result.load(config, false); err != nil { @@ -96,7 +97,7 @@ func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, opti if value == "" && c.compatSection != "" { value, _ = config.GetString(c.compatSection, option) if value != "" { - log.Printf("WARNING: Configuring etcd option \"%s\" in section \"%s\" is deprecated, use section \"etcd\" instead", option, c.compatSection) + c.logger.Printf("WARNING: Configuring etcd option \"%s\" in section \"%s\" is deprecated, use section \"etcd\" instead", option, c.compatSection) } } @@ -124,7 +125,7 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { return nil } - log.Printf("No etcd endpoints configured, not changing client") + c.logger.Printf("No etcd endpoints configured, not changing client") } else { cfg := clientv3.Config{ Endpoints: endpoints, @@ -159,7 +160,7 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { return fmt.Errorf("could not setup etcd TLS configuration: %w", err) } - log.Printf("Could not setup TLS configuration, will be disabled (%s)", err) + c.logger.Printf("Could not setup TLS configuration, will be disabled (%s)", err) } else { cfg.TLS = tlsConfig } @@ -171,14 +172,14 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { return err } - log.Printf("Could not create new client from etd endpoints %+v: %s", endpoints, err) + c.logger.Printf("Could not create new client from etd endpoints %+v: %s", endpoints, err) } else { prev := c.getEtcdClient() if prev != nil { prev.Close() } c.client.Store(client) - log.Printf("Using etcd endpoints %+v", endpoints) + c.logger.Printf("Using etcd endpoints %+v", endpoints) c.notifyListeners() } } @@ -259,16 +260,16 @@ func (c *EtcdClient) WaitForConnection(ctx context.Context) error { if errors.Is(err, context.Canceled) { return err } else if errors.Is(err, context.DeadlineExceeded) { - log.Printf("Timeout waiting for etcd client to connect to the cluster, retry in %s", backoff.NextWait()) + c.logger.Printf("Timeout waiting for etcd client to connect to the cluster, retry in %s", backoff.NextWait()) } else { - log.Printf("Could not sync etcd client with the cluster, retry in %s: %s", backoff.NextWait(), err) + c.logger.Printf("Could not sync etcd client with the cluster, retry in %s: %s", backoff.NextWait(), err) } backoff.Wait(ctx) continue } - log.Printf("Client synced, using endpoints %+v", c.getEtcdClient().Endpoints()) + c.logger.Printf("Client synced, using endpoints %+v", c.getEtcdClient().Endpoints()) return nil } } @@ -278,10 +279,10 @@ func (c *EtcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOpt } func (c *EtcdClient) Watch(ctx context.Context, key string, nextRevision int64, watcher EtcdClientWatcher, opts ...clientv3.OpOption) (int64, error) { - log.Printf("Wait for leader and start watching on %s (rev=%d)", key, nextRevision) + c.logger.Printf("Wait for leader and start watching on %s (rev=%d)", key, nextRevision) opts = append(opts, clientv3.WithRev(nextRevision), clientv3.WithPrevKV()) ch := c.getEtcdClient().Watch(clientv3.WithRequireLeader(ctx), key, opts...) - log.Printf("Watch created for %s", key) + c.logger.Printf("Watch created for %s", key) watcher.EtcdWatchCreated(c, key) for response := range ch { if err := response.Err(); err != nil { @@ -304,7 +305,7 @@ func (c *EtcdClient) Watch(ctx context.Context, key string, nextRevision int64, } watcher.EtcdKeyDeleted(c, string(ev.Kv.Key), prevValue) default: - log.Printf("Unsupported watch event %s %q -> %q", ev.Type, ev.Kv.Key, ev.Kv.Value) + c.logger.Printf("Unsupported watch event %s %q -> %q", ev.Type, ev.Kv.Key, ev.Kv.Value) } } } diff --git a/etcd_client_test.go b/etcd_client_test.go index c48e580..8c2e07d 100644 --- a/etcd_client_test.go +++ b/etcd_client_test.go @@ -154,7 +154,8 @@ func NewEtcdClientForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { config.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) config.AddOption("etcd", "loglevel", "error") - client, err := NewEtcdClient(config, "") + logger := NewLoggerForTest(t) + client, err := NewEtcdClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, client.Close()) @@ -172,7 +173,8 @@ func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { config.AddOption("etcd", "clientcert", certfile) config.AddOption("etcd", "cacert", certfile) - client, err := NewEtcdClient(config, "") + logger := NewLoggerForTest(t) + client, err := NewEtcdClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, client.Close()) @@ -196,12 +198,13 @@ func DeleteEtcdValue(etcd *embed.Etcd, key string) { func Test_EtcdClient_Get(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) etcd, client := NewEtcdClientForTest(t) - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() if info := client.GetServerInfoEtcd(); assert.NotNil(info) { @@ -226,13 +229,13 @@ func Test_EtcdClient_Get(t *testing.T) { } } - if response, err := client.Get(context.Background(), "foo"); assert.NoError(err) { + if response, err := client.Get(ctx, "foo"); assert.NoError(err) { assert.EqualValues(0, response.Count) } SetEtcdValue(etcd, "foo", []byte("bar")) - if response, err := client.Get(context.Background(), "foo"); assert.NoError(err) { + if response, err := client.Get(ctx, "foo"); assert.NoError(err) { if assert.EqualValues(1, response.Count) { assert.Equal("foo", string(response.Kvs[0].Key)) assert.Equal("bar", string(response.Kvs[0].Value)) @@ -242,12 +245,13 @@ func Test_EtcdClient_Get(t *testing.T) { func Test_EtcdClientTLS_Get(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) etcd, client := NewEtcdClientWithTLSForTest(t) - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() if info := client.GetServerInfoEtcd(); assert.NotNil(info) { @@ -288,11 +292,12 @@ func Test_EtcdClientTLS_Get(t *testing.T) { func Test_EtcdClient_GetPrefix(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) - if response, err := client.Get(context.Background(), "foo"); assert.NoError(err) { + if response, err := client.Get(ctx, "foo"); assert.NoError(err) { assert.EqualValues(0, response.Count) } @@ -300,7 +305,7 @@ func Test_EtcdClient_GetPrefix(t *testing.T) { SetEtcdValue(etcd, "foo/lala", []byte("2")) SetEtcdValue(etcd, "lala/foo", []byte("3")) - if response, err := client.Get(context.Background(), "foo", clientv3.WithPrefix()); assert.NoError(err) { + if response, err := client.Get(ctx, "foo", clientv3.WithPrefix()); assert.NoError(err) { if assert.EqualValues(2, response.Count) { assert.Equal("foo", string(response.Kvs[0].Key)) assert.Equal("1", string(response.Kvs[0].Value)) @@ -399,13 +404,14 @@ func (l *EtcdClientTestListener) EtcdKeyDeleted(client *EtcdClient, key string, func Test_EtcdClient_Watch(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) SetEtcdValue(etcd, "foo/a", []byte("1")) - listener := NewEtcdClientTestListener(context.Background(), t) + listener := NewEtcdClientTestListener(ctx, t) defer listener.Close() client.AddListener(listener) diff --git a/federation.go b/federation.go index 218b09a..5fa9ec1 100644 --- a/federation.go +++ b/federation.go @@ -27,7 +27,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net" "strconv" "strings" @@ -78,6 +77,7 @@ func getCloudUrl(s string) string { } type FederationClient struct { + logger Logger hub *Hub session *ClientSession message atomic.Pointer[ClientMessage] @@ -144,6 +144,7 @@ func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, } result := &FederationClient{ + logger: hub.logger, hub: hub, session: session, @@ -203,7 +204,7 @@ func (c *FederationClient) CanReuse(federation *RoomFederationMessage) bool { } func (c *FederationClient) connect(ctx context.Context) error { - log.Printf("Creating federation connection to %s for %s", c.URL(), c.session.PublicId()) + c.logger.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 @@ -220,13 +221,13 @@ func (c *FederationClient) connect(ctx context.Context) error { } if !supportsFederation { if err := conn.Close(); err != nil { - log.Printf("Error closing federation connection to %s: %s", c.URL(), err) + c.logger.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.logger.Printf("Federation connection established to %s for %s", c.URL(), c.session.PublicId()) c.mu.Lock() defer c.mu.Unlock() @@ -303,18 +304,18 @@ func (c *FederationClient) closeConnection(withBye bool) { 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) + c.logger.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) + c.logger.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.logger.Printf("Error closing federation connection to %s: %s", c.URL(), err) } c.conn = nil @@ -362,11 +363,12 @@ func (c *FederationClient) reconnect() { return } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.hub.federationTimeout)) + ctx := NewLoggerContext(context.Background(), c.logger) + ctx, cancel := context.WithTimeout(ctx, 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.logger.Printf("Error connecting to federation server %s for %s: %s", c.URL(), c.session.PublicId(), err) c.scheduleReconnect() return } @@ -390,7 +392,7 @@ func (c *FederationClient) readPump(conn *websocket.Conn) { } if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - log.Printf("Error reading from %s for %s: %s", c.URL(), c.session.PublicId(), err) + c.logger.Printf("Error reading from %s for %s: %s", c.URL(), c.session.PublicId(), err) } c.scheduleReconnect() @@ -403,7 +405,7 @@ func (c *FederationClient) readPump(conn *websocket.Conn) { var msg ServerMessage if err := json.Unmarshal(data, &msg); err != nil { - log.Printf("Error unmarshalling %s from %s: %s", string(data), c.URL(), err) + c.logger.Printf("Error unmarshalling %s from %s: %s", string(data), c.URL(), err) continue } @@ -432,7 +434,7 @@ func (c *FederationClient) sendPing() { 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.logger.Printf("Could not send ping to federated client %s for %s: %v", c.URL(), c.session.PublicId(), err) c.scheduleReconnectLocked() } } @@ -517,7 +519,7 @@ func (c *FederationClient) processWelcome(msg *ServerMessage) { 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.logger.Printf("Error sending hello message to %s for %s: %s", c.URL(), c.session.PublicId(), err) c.closeWithError(err) } } @@ -529,7 +531,7 @@ func (c *FederationClient) processHello(msg *ServerMessage) { defer c.helloMu.Unlock() if msg.Id != c.helloMsgId { - log.Printf("Received hello response %+v for unknown request, expected %s", msg, c.helloMsgId) + c.logger.Printf("Received hello response %+v for unknown request, expected %s", msg, c.helloMsgId) if err := c.sendHelloLocked(c.helloAuth); err != nil { c.closeWithError(err) } @@ -548,12 +550,12 @@ func (c *FederationClient) processHello(msg *ServerMessage) { c.closeWithError(err) } default: - log.Printf("Received hello error from federated client for %s to %s: %+v", c.session.PublicId(), c.URL(), msg) + c.logger.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) + c.logger.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) } @@ -594,7 +596,7 @@ func (c *FederationClient) processHello(msg *ServerMessage) { messages := c.pendingMessages c.pendingMessages = nil - log.Printf("Sending %d pending messages to %s for %s", count, c.URL(), c.session.PublicId()) + c.logger.Printf("Sending %d pending messages to %s for %s", count, c.URL(), c.session.PublicId()) c.helloMu.Unlock() defer c.helloMu.Lock() @@ -603,7 +605,7 @@ func (c *FederationClient) processHello(msg *ServerMessage) { 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) + c.logger.Printf("Error sending pending message %+v on federation connection to %s: %s", msg, c.URL(), err) break } } @@ -966,7 +968,7 @@ func (c *FederationClient) deferMessage(message *ClientMessage) { 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)) + c.logger.Printf("Session %s has %d pending federated messages", c.session.PublicId(), len(c.pendingMessages)) } } @@ -999,7 +1001,7 @@ func (c *FederationClient) sendMessageLocked(message *ClientMessage) error { return err } - log.Printf("Could not send message %+v for %s to federated client %s: %v", message, c.session.PublicId(), c.URL(), err) + c.logger.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() } diff --git a/federation_test.go b/federation_test.go index cdfc720..e97996b 100644 --- a/federation_test.go +++ b/federation_test.go @@ -36,8 +36,6 @@ import ( ) func Test_FederationInvalidToken(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) @@ -75,8 +73,6 @@ func Test_FederationInvalidToken(t *testing.T) { } func Test_Federation(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) @@ -492,8 +488,6 @@ func Test_Federation(t *testing.T) { } func Test_FederationJoinRoomTwice(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) @@ -599,8 +593,6 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } func Test_FederationChangeRoom(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) @@ -708,8 +700,6 @@ func Test_FederationChangeRoom(t *testing.T) { } func Test_FederationMedia(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) @@ -718,15 +708,13 @@ func Test_FederationMedia(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu1, err := NewTestMCU() - require.NoError(err) + mcu1 := NewTestMCU(t) require.NoError(mcu1.Start(ctx)) defer mcu1.Stop() hub1.SetMcu(mcu1) - mcu2, err := NewTestMCU() - require.NoError(err) + mcu2 := NewTestMCU(t) require.NoError(mcu2.Start(ctx)) defer mcu2.Stop() @@ -815,8 +803,6 @@ func Test_FederationMedia(t *testing.T) { } func Test_FederationResume(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) @@ -936,8 +922,6 @@ func Test_FederationResume(t *testing.T) { } func Test_FederationResumeNewSession(t *testing.T) { - CatchLogForTest(t) - assert := assert.New(t) require := require.New(t) diff --git a/file_watcher.go b/file_watcher.go index a26d0a7..489ed10 100644 --- a/file_watcher.go +++ b/file_watcher.go @@ -24,7 +24,6 @@ package signaling import ( "context" "errors" - "log" "os" "path" "path/filepath" @@ -51,6 +50,7 @@ func init() { type FileWatcherCallback func(filename string) type FileWatcher struct { + logger Logger filename string target string callback FileWatcherCallback @@ -60,7 +60,7 @@ type FileWatcher struct { closeFunc context.CancelFunc } -func NewFileWatcher(filename string, callback FileWatcherCallback) (*FileWatcher, error) { +func NewFileWatcher(logger Logger, filename string, callback FileWatcherCallback) (*FileWatcher, error) { watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err @@ -74,6 +74,7 @@ func NewFileWatcher(filename string, callback FileWatcherCallback) (*FileWatcher closeCtx, closeFunc := context.WithCancel(context.Background()) w := &FileWatcher{ + logger: logger, filename: filename, callback: callback, watcher: watcher, @@ -157,14 +158,14 @@ func (f *FileWatcher) run() { triggerEvent(event) if err := f.updateWatcher(); err != nil { - log.Printf("Error updating watcher after %s is deleted: %s", event.Name, err) + f.logger.Printf("Error updating watcher after %s is deleted: %s", event.Name, err) } continue } if stat, err := os.Lstat(event.Name); err != nil { if !errors.Is(err, os.ErrNotExist) { - log.Printf("Could not lstat %s: %s", event.Name, err) + f.logger.Printf("Could not lstat %s: %s", event.Name, err) } } else if stat.Mode()&os.ModeSymlink != 0 { target, err := filepath.EvalSymlinks(event.Name) @@ -183,7 +184,7 @@ func (f *FileWatcher) run() { return } - log.Printf("Error watching %s: %s", f.filename, err) + f.logger.Printf("Error watching %s: %s", f.filename, err) case <-f.closeCtx.Done(): return } diff --git a/file_watcher_test.go b/file_watcher_test.go index 8dfa48c..5a29a73 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -38,7 +38,8 @@ var ( func TestFileWatcher_NotExist(t *testing.T) { assert := assert.New(t) tmpdir := t.TempDir() - if w, err := NewFileWatcher(path.Join(tmpdir, "test.txt"), func(filename string) {}); !assert.ErrorIs(err, os.ErrNotExist) { + logger := NewLoggerForTest(t) + if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}); !assert.ErrorIs(err, os.ErrNotExist) { if w != nil { assert.NoError(w.Close()) } @@ -53,8 +54,9 @@ func TestFileWatcher_File(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -95,8 +97,9 @@ func TestFileWatcher_CurrentDir(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher("./"+path.Base(filename), func(filename string) { + w, err := NewFileWatcher(logger, "./"+path.Base(filename), func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -135,8 +138,9 @@ func TestFileWatcher_Rename(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -177,8 +181,9 @@ func TestFileWatcher_Symlink(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename, filename)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -210,8 +215,9 @@ func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename1, filename)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -245,8 +251,9 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename1, filename)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -274,8 +281,9 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.Symlink(sourceFilename1, filename)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) @@ -326,8 +334,9 @@ func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.Symlink("data/test.txt", filename)) + logger := NewLoggerForTest(t) modified := make(chan struct{}) - w, err := NewFileWatcher(filename, func(filename string) { + w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} }) require.NoError(err) diff --git a/geoip.go b/geoip.go index 1051324..f48092f 100644 --- a/geoip.go +++ b/geoip.go @@ -24,9 +24,9 @@ package signaling import ( "archive/tar" "compress/gzip" + "context" "fmt" "io" - "log" "net" "net/http" "net/url" @@ -56,6 +56,7 @@ func GetGeoIpDownloadUrl(license string) string { } type GeoLookup struct { + logger Logger url string isFile bool client http.Client @@ -66,15 +67,17 @@ type GeoLookup struct { reader atomic.Pointer[maxminddb.Reader] } -func NewGeoLookupFromUrl(url string) (*GeoLookup, error) { +func NewGeoLookupFromUrl(logger Logger, url string) (*GeoLookup, error) { geoip := &GeoLookup{ - url: url, + logger: logger, + url: url, } return geoip, nil } -func NewGeoLookupFromFile(filename string) (*GeoLookup, error) { +func NewGeoLookupFromFile(logger Logger, filename string) (*GeoLookup, error) { geoip := &GeoLookup{ + logger: logger, url: filename, isFile: true, } @@ -119,7 +122,7 @@ func (g *GeoLookup) updateFile() error { } metadata := reader.Metadata - log.Printf("Using %s GeoIP database from %s (built on %s)", metadata.DatabaseType, g.url, time.Unix(int64(metadata.BuildEpoch), 0).UTC()) + g.logger.Printf("Using %s GeoIP database from %s (built on %s)", metadata.DatabaseType, g.url, time.Unix(int64(metadata.BuildEpoch), 0).UTC()) if old := g.reader.Swap(reader); old != nil { old.Close() @@ -144,7 +147,7 @@ func (g *GeoLookup) updateUrl() error { defer response.Body.Close() if response.StatusCode == http.StatusNotModified { - log.Printf("GeoIP database at %s has not changed", g.url) + g.logger.Printf("GeoIP database at %s has not changed", g.url) return nil } else if response.StatusCode/100 != 2 { return fmt.Errorf("downloading %s returned an error: %s", g.url, response.Status) @@ -202,7 +205,7 @@ func (g *GeoLookup) updateUrl() error { } metadata := reader.Metadata - log.Printf("Using %s GeoIP database from %s (built on %s)", metadata.DatabaseType, g.url, time.Unix(int64(metadata.BuildEpoch), 0).UTC()) + g.logger.Printf("Using %s GeoIP database from %s (built on %s)", metadata.DatabaseType, g.url, time.Unix(int64(metadata.BuildEpoch), 0).UTC()) if old := g.reader.Swap(reader); old != nil { old.Close() @@ -268,7 +271,8 @@ func IsValidContinent(continent string) bool { } } -func LoadGeoIPOverrides(config *goconf.ConfigFile, ignoreErrors bool) (map[*net.IPNet]string, error) { +func LoadGeoIPOverrides(ctx context.Context, config *goconf.ConfigFile, ignoreErrors bool) (map[*net.IPNet]string, error) { + logger := LoggerFromContext(ctx) options, _ := GetStringOptions(config, "geoip-overrides", true) if len(options) == 0 { return nil, nil @@ -283,7 +287,7 @@ func LoadGeoIPOverrides(config *goconf.ConfigFile, ignoreErrors bool) (map[*net. _, ipNet, err = net.ParseCIDR(option) if err != nil { if ignoreErrors { - log.Printf("could not parse CIDR %s (%s), skipping", option, err) + logger.Printf("could not parse CIDR %s (%s), skipping", option, err) continue } @@ -293,7 +297,7 @@ func LoadGeoIPOverrides(config *goconf.ConfigFile, ignoreErrors bool) (map[*net. ip = net.ParseIP(option) if ip == nil { if ignoreErrors { - log.Printf("could not parse IP %s, skipping", option) + logger.Printf("could not parse IP %s, skipping", option) continue } @@ -314,14 +318,14 @@ func LoadGeoIPOverrides(config *goconf.ConfigFile, ignoreErrors bool) (map[*net. value = strings.ToUpper(strings.TrimSpace(value)) if value == "" { - log.Printf("IP %s doesn't have a country assigned, skipping", option) + logger.Printf("IP %s doesn't have a country assigned, skipping", option) continue } else if !IsValidCountry(value) { - log.Printf("Country %s for IP %s is invalid, skipping", value, option) + logger.Printf("Country %s for IP %s is invalid, skipping", value, option) continue } - log.Printf("Using country %s for %s", value, ipNet) + logger.Printf("Using country %s for %s", value, ipNet) geoipOverrides[ipNet] = value } diff --git a/geoip_test.go b/geoip_test.go index 0aa1aa7..885749a 100644 --- a/geoip_test.go +++ b/geoip_test.go @@ -76,9 +76,9 @@ func GetGeoIpUrlForTest(t *testing.T) string { } func TestGeoLookup(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) - reader, err := NewGeoLookupFromUrl(GetGeoIpUrlForTest(t)) + reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) require.NoError(err) defer reader.Close() @@ -88,9 +88,9 @@ func TestGeoLookup(t *testing.T) { } func TestGeoLookupCaching(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) - reader, err := NewGeoLookupFromUrl(GetGeoIpUrlForTest(t)) + reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) require.NoError(err) defer reader.Close() @@ -126,14 +126,14 @@ func TestGeoLookupContinent(t *testing.T) { } func TestGeoLookupCloseEmpty(t *testing.T) { - CatchLogForTest(t) - reader, err := NewGeoLookupFromUrl("ignore-url") + logger := NewLoggerForTest(t) + reader, err := NewGeoLookupFromUrl(logger, "ignore-url") require.NoError(t, err) reader.Close() } func TestGeoLookupFromFile(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) geoIpUrl := GetGeoIpUrlForTest(t) @@ -188,7 +188,7 @@ func TestGeoLookupFromFile(t *testing.T) { require.True(foundDatabase, "Did not find GeoIP database in download from %s", geoIpUrl) - reader, err := NewGeoLookupFromFile(tmpfile.Name()) + reader, err := NewGeoLookupFromFile(logger, tmpfile.Name()) require.NoError(err) defer reader.Close() diff --git a/grpc_client.go b/grpc_client.go index 38a4270..81b449e 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -27,7 +27,6 @@ import ( "errors" "fmt" "io" - "log" "net" "slices" "sync" @@ -79,6 +78,7 @@ func newGrpcClientImpl(conn grpc.ClientConnInterface) *grpcClientImpl { } type GrpcClient struct { + logger Logger ip net.IP rawTarget string target string @@ -127,7 +127,7 @@ func (r *customIpResolver) Close() { // Noop } -func NewGrpcClient(target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClient, error) { +func NewGrpcClient(logger Logger, target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClient, error) { var conn *grpc.ClientConn var err error if ip != nil { @@ -153,6 +153,7 @@ func NewGrpcClient(target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClie } result := &GrpcClient{ + logger: logger, ip: ip, rawTarget: target, target: target, @@ -200,7 +201,7 @@ func (c *GrpcClient) GetServerId(ctx context.Context) (string, string, error) { func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId PrivateSessionId) (*LookupResumeIdReply, error) { statsGrpcClientCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging - log.Printf("Lookup resume id %s on %s", resumeId, c.Target()) + c.logger.Printf("Lookup resume id %s on %s", resumeId, c.Target()) response, err := c.impl.LookupResumeId(ctx, &LookupResumeIdRequest{ ResumeId: string(resumeId), }, grpc.WaitForReady(true)) @@ -220,7 +221,7 @@ func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId PrivateSession func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) { statsGrpcClientCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging - log.Printf("Lookup room session %s on %s", roomSessionId, c.Target()) + c.logger.Printf("Lookup room session %s on %s", roomSessionId, c.Target()) response, err := c.impl.LookupSessionId(ctx, &LookupSessionIdRequest{ RoomSessionId: string(roomSessionId), DisconnectReason: disconnectReason, @@ -242,7 +243,7 @@ func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId RoomSess func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId PublicSessionId, room *Room, backendUrl string) (bool, error) { statsGrpcClientCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging - log.Printf("Check if session %s is in call %s on %s", sessionId, room.Id(), c.Target()) + c.logger.Printf("Check if session %s is in call %s on %s", sessionId, room.Id(), c.Target()) response, err := c.impl.IsSessionInCall(ctx, &IsSessionInCallRequest{ SessionId: string(sessionId), RoomId: room.Id(), @@ -260,7 +261,7 @@ func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId PublicSessio func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[PublicSessionId]*InternalSessionData, virtual map[PublicSessionId]*VirtualSessionData, err error) { statsGrpcClientCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging - log.Printf("Get internal sessions for %s on %s", roomId, c.Target()) + c.logger.Printf("Get internal sessions for %s on %s", roomId, c.Target()) var backendUrl string if len(backendUrls) > 0 { backendUrl = backendUrls[0] @@ -295,7 +296,7 @@ func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, bac func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId PublicSessionId, streamType StreamType) (PublicSessionId, string, net.IP, string, string, error) { statsGrpcClientCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging - log.Printf("Get %s publisher id %s on %s", streamType, sessionId, c.Target()) + c.logger.Printf("Get %s publisher id %s on %s", streamType, sessionId, c.Target()) response, err := c.impl.GetPublisherId(ctx, &GetPublisherIdRequest{ SessionId: string(sessionId), StreamType: string(streamType), @@ -312,7 +313,7 @@ func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId PublicSession func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, error) { statsGrpcClientCalls.WithLabelValues("GetSessionCount").Inc() // TODO: Remove debug logging - log.Printf("Get session count for %s on %s", url, c.Target()) + c.logger.Printf("Get session count for %s on %s", url, c.Target()) response, err := c.impl.GetSessionCount(ctx, &GetSessionCountRequest{ Url: url, }, grpc.WaitForReady(true)) @@ -335,6 +336,7 @@ type ProxySessionReceiver interface { } type SessionProxy struct { + logger Logger sessionId PublicSessionId receiver ProxySessionReceiver @@ -347,7 +349,7 @@ func (p *SessionProxy) recvPump() { defer func() { p.receiver.OnProxyClose(closeError) if err := p.Close(); err != nil { - log.Printf("Error closing proxy for session %s: %s", p.sessionId, err) + p.logger.Printf("Error closing proxy for session %s: %s", p.sessionId, err) } }() @@ -358,13 +360,13 @@ func (p *SessionProxy) recvPump() { break } - log.Printf("Error receiving message from proxy for session %s: %s", p.sessionId, err) + p.logger.Printf("Error receiving message from proxy for session %s: %s", p.sessionId, err) closeError = err break } if err := p.receiver.OnProxyMessage(msg); err != nil { - log.Printf("Error processing message %+v from proxy for session %s: %s", msg, p.sessionId, err) + p.logger.Printf("Error processing message %+v from proxy for session %s: %s", msg, p.sessionId, err) } } } @@ -395,6 +397,7 @@ func (c *GrpcClient) ProxySession(ctx context.Context, sessionId PublicSessionId } proxy := &SessionProxy{ + logger: c.logger, sessionId: sessionId, receiver: receiver, @@ -413,6 +416,7 @@ type grpcClientsList struct { type GrpcClients struct { mu sync.RWMutex version string + logger Logger // +checklocks:mu clientsMap map[string]*grpcClientsList @@ -439,11 +443,12 @@ type GrpcClients struct { closeFunc context.CancelFunc // +checklocksignore: No locking necessary. } -func NewGrpcClients(config *goconf.ConfigFile, etcdClient *EtcdClient, dnsMonitor *DnsMonitor, version string) (*GrpcClients, error) { +func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient *EtcdClient, dnsMonitor *DnsMonitor, version string) (*GrpcClients, error) { initializedCtx, initializedFunc := context.WithCancel(context.Background()) closeCtx, closeFunc := context.WithCancel(context.Background()) result := &GrpcClients{ version: version, + logger: LoggerFromContext(ctx), dnsMonitor: dnsMonitor, etcdClient: etcdClient, initializedCtx: initializedCtx, @@ -484,7 +489,7 @@ func (c *GrpcClients) GetServerInfoGrpc() (result []BackendServerInfoGrpc) { } func (c *GrpcClients) load(config *goconf.ConfigFile, fromReload bool) error { - creds, err := NewReloadableCredentials(config, false) + creds, err := NewReloadableCredentials(c.logger, config, false) if err != nil { return err } @@ -522,7 +527,7 @@ func (c *GrpcClients) closeClient(client *GrpcClient) { } if err := client.Close(); err != nil { - log.Printf("Error closing client to %s: %s", client.Target(), err) + c.logger.Printf("Error closing client to %s: %s", client.Target(), err) } } @@ -568,7 +573,7 @@ loop: } if status.Code(err) != codes.Canceled { - log.Printf("Error checking GRPC server id of %s, retrying in %s: %s", client.Target(), backoff.NextWait(), err) + c.logger.Printf("Error checking GRPC server id of %s, retrying in %s: %s", client.Target(), backoff.NextWait(), err) } backoff.Wait(ctx) continue @@ -576,13 +581,13 @@ loop: client.version.Store(version) if id == GrpcServerId { - log.Printf("GRPC target %s is this server, removing", client.Target()) + c.logger.Printf("GRPC target %s is this server, removing", client.Target()) c.closeClient(client) client.SetSelf(true) } else if version != c.version { - log.Printf("WARNING: Node %s is runing different version %s than local node (%s)", client.Target(), version, c.version) + c.logger.Printf("WARNING: Node %s is runing different version %s than local node (%s)", client.Target(), version, c.version) } else { - log.Printf("Checked GRPC server id of %s running version %s", client.Target(), version) + c.logger.Printf("Checked GRPC server id of %s running version %s", client.Target(), version) } break loop } @@ -648,7 +653,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo continue } - client, err := NewGrpcClient(target, nil, opts...) + client, err := NewGrpcClient(c.logger, target, nil, opts...) if err != nil { for _, entry := range clientsMap { for _, client := range entry.clients { @@ -666,7 +671,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo c.selfCheckWaitGroup.Add(1) go c.checkIsSelf(c.closeCtx, target, client) - log.Printf("Adding %s as GRPC target", client.Target()) + c.logger.Printf("Adding %s as GRPC target", client.Target()) entry, found := clientsMap[target] if !found { entry = &grpcClientsList{} @@ -679,7 +684,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo for target := range removeTargets { if entry, found := clientsMap[target]; found { for _, client := range entry.clients { - log.Printf("Deleting GRPC target %s", client.Target()) + c.logger.Printf("Deleting GRPC target %s", client.Target()) c.closeClient(client) } @@ -716,7 +721,7 @@ func (c *GrpcClients) onLookup(entry *DnsMonitorEntry, all []net.IP, added []net for _, client := range e.clients { if ip.Equal(client.ip) { mapModified = true - log.Printf("Removing connection to %s", client.Target()) + c.logger.Printf("Removing connection to %s", client.Target()) c.closeClient(client) c.wakeupForTesting() } @@ -732,16 +737,16 @@ func (c *GrpcClients) onLookup(entry *DnsMonitorEntry, all []net.IP, added []net } for _, ip := range added { - client, err := NewGrpcClient(target, ip, opts...) + client, err := NewGrpcClient(c.logger, target, ip, opts...) if err != nil { - log.Printf("Error creating client to %s with IP %s: %s", target, ip.String(), err) + c.logger.Printf("Error creating client to %s with IP %s: %s", target, ip.String(), err) continue } c.selfCheckWaitGroup.Add(1) go c.checkIsSelf(c.closeCtx, target, client) - log.Printf("Adding %s as GRPC target", client.Target()) + c.logger.Printf("Adding %s as GRPC target", client.Target()) newClients = append(newClients, client) mapModified = true c.wakeupForTesting() @@ -797,9 +802,9 @@ func (c *GrpcClients) EtcdClientCreated(client *EtcdClient) { if errors.Is(err, context.Canceled) { return } else if errors.Is(err, context.DeadlineExceeded) { - log.Printf("Timeout getting initial list of GRPC targets, retry in %s", backoff.NextWait()) + c.logger.Printf("Timeout getting initial list of GRPC targets, retry in %s", backoff.NextWait()) } else { - log.Printf("Could not get initial list of GRPC targets, retry in %s: %s", backoff.NextWait(), err) + c.logger.Printf("Could not get initial list of GRPC targets, retry in %s: %s", backoff.NextWait(), err) } backoff.Wait(c.closeCtx) @@ -819,7 +824,7 @@ func (c *GrpcClients) EtcdClientCreated(client *EtcdClient) { for c.closeCtx.Err() == nil { var err error if nextRevision, err = client.Watch(c.closeCtx, c.targetPrefix, nextRevision, c, clientv3.WithPrefix()); err != nil { - log.Printf("Error processing watch for %s (%s), retry in %s", c.targetPrefix, err, backoff.NextWait()) + c.logger.Printf("Error processing watch for %s (%s), retry in %s", c.targetPrefix, err, backoff.NextWait()) backoff.Wait(c.closeCtx) continue } @@ -828,7 +833,7 @@ func (c *GrpcClients) EtcdClientCreated(client *EtcdClient) { backoff.Reset() prevRevision = nextRevision } else { - log.Printf("Processing watch for %s interrupted, retry in %s", c.targetPrefix, backoff.NextWait()) + c.logger.Printf("Processing watch for %s interrupted, retry in %s", c.targetPrefix, backoff.NextWait()) backoff.Wait(c.closeCtx) } } @@ -848,11 +853,11 @@ func (c *GrpcClients) getGrpcTargets(ctx context.Context, client *EtcdClient, ta func (c *GrpcClients) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { var info GrpcTargetInformationEtcd if err := json.Unmarshal(data, &info); err != nil { - log.Printf("Could not decode GRPC target %s=%s: %s", key, string(data), err) + c.logger.Printf("Could not decode GRPC target %s=%s: %s", key, string(data), err) return } if err := info.CheckValid(); err != nil { - log.Printf("Received invalid GRPC target %s=%s: %s", key, string(data), err) + c.logger.Printf("Received invalid GRPC target %s=%s: %s", key, string(data), err) return } @@ -866,21 +871,21 @@ func (c *GrpcClients) EtcdKeyUpdated(client *EtcdClient, key string, data []byte } if _, found := c.clientsMap[info.Address]; found { - log.Printf("GRPC target %s already exists, ignoring %s", info.Address, key) + c.logger.Printf("GRPC target %s already exists, ignoring %s", info.Address, key) return } opts := c.dialOptions.Load().([]grpc.DialOption) - cl, err := NewGrpcClient(info.Address, nil, opts...) + cl, err := NewGrpcClient(c.logger, info.Address, nil, opts...) if err != nil { - log.Printf("Could not create GRPC client for target %s: %s", info.Address, err) + c.logger.Printf("Could not create GRPC client for target %s: %s", info.Address, err) return } c.selfCheckWaitGroup.Add(1) go c.checkIsSelf(c.closeCtx, info.Address, cl) - log.Printf("Adding %s as GRPC target", cl.Target()) + c.logger.Printf("Adding %s as GRPC target", cl.Target()) if c.clientsMap == nil { c.clientsMap = make(map[string]*grpcClientsList) @@ -905,7 +910,7 @@ func (c *GrpcClients) EtcdKeyDeleted(client *EtcdClient, key string, prevValue [ func (c *GrpcClients) removeEtcdClientLocked(key string) { info, found := c.targetInformation[key] if !found { - log.Printf("No connection found for %s, ignoring", key) + c.logger.Printf("No connection found for %s, ignoring", key) c.wakeupForTesting() return } @@ -917,7 +922,7 @@ func (c *GrpcClients) removeEtcdClientLocked(key string) { } for _, client := range entry.clients { - log.Printf("Removing connection to %s (from %s)", client.Target(), key) + c.logger.Printf("Removing connection to %s (from %s)", client.Target(), key) c.closeClient(client) } delete(c.clientsMap, info.Address) @@ -951,7 +956,7 @@ func (c *GrpcClients) wakeupForTesting() { func (c *GrpcClients) Reload(config *goconf.ConfigFile) { if err := c.load(config, true); err != nil { - log.Printf("Could not reload RPC clients: %s", err) + c.logger.Printf("Could not reload RPC clients: %s", err) } } @@ -962,7 +967,7 @@ func (c *GrpcClients) Close() { for _, entry := range c.clientsMap { for _, client := range entry.clients { if err := client.Close(); err != nil { - log.Printf("Error closing client to %s: %s", client.Target(), err) + c.logger.Printf("Error closing client to %s: %s", client.Target(), err) } } diff --git a/grpc_client_test.go b/grpc_client_test.go index 329cd37..031c1ba 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -53,7 +53,9 @@ func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient *EtcdClient) (*GrpcClients, *DnsMonitor) { dnsMonitor := newDnsMonitorForTest(t, time.Hour) // will be updated manually - client, err := NewGrpcClients(config, etcdClient, dnsMonitor, "0.0.0") + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + client, err := NewGrpcClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") require.NoError(t, err) t.Cleanup(func() { client.Close() @@ -77,7 +79,8 @@ func NewGrpcClientsWithEtcdForTest(t *testing.T, etcd *embed.Etcd) (*GrpcClients config.AddOption("grpc", "targettype", "etcd") config.AddOption("grpc", "targetprefix", "/grpctargets") - etcdClient, err := NewEtcdClient(config, "") + logger := NewLoggerForTest(t) + etcdClient, err := NewEtcdClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, etcdClient.Close()) @@ -108,7 +111,8 @@ func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { } func Test_GrpcClients_EtcdInitial(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) ensureNoGoroutinesLeak(t, func(t *testing.T) { _, addr1 := NewGrpcServerForTest(t) _, addr2 := NewGrpcServerForTest(t) @@ -119,7 +123,7 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) client, _ := NewGrpcClientsWithEtcdForTest(t, etcd) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() require.NoError(t, client.WaitForInitialized(ctx)) @@ -130,13 +134,14 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { func Test_GrpcClients_EtcdUpdate(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd := NewEtcdForTest(t) client, _ := NewGrpcClientsWithEtcdForTest(t, etcd) ch := client.getWakeupChannelForTesting() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() assert.Empty(client.GetClients()) @@ -176,13 +181,14 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd := NewEtcdForTest(t) client, _ := NewGrpcClientsWithEtcdForTest(t, etcd) ch := client.getWakeupChannelForTesting() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() assert.Empty(client.GetClients()) @@ -214,7 +220,8 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { } func Test_GrpcClients_DnsDiscovery(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) ensureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) @@ -228,7 +235,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { client, dnsMonitor := NewGrpcClientsForTest(t, target) ch := client.getWakeupChannelForTesting() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() // Wait for initial check to be done to make sure internal dnsmonitor goroutine is waiting. @@ -268,7 +275,6 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { } func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { - CatchLogForTest(t) assert := assert.New(t) lookup := newMockDnsLookupForTest(t) target := "testgrpc:12345" @@ -298,7 +304,6 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { } func Test_GrpcClients_Encryption(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) diff --git a/grpc_common.go b/grpc_common.go index b7df93e..dfea756 100644 --- a/grpc_common.go +++ b/grpc_common.go @@ -25,7 +25,6 @@ import ( "context" "crypto/tls" "fmt" - "log" "net" "github.com/dlintw/goconf" @@ -134,7 +133,7 @@ func (c *reloadableCredentials) Close() { } } -func NewReloadableCredentials(config *goconf.ConfigFile, server bool) (credentials.TransportCredentials, error) { +func NewReloadableCredentials(logger Logger, config *goconf.ConfigFile, server bool) (credentials.TransportCredentials, error) { var prefix string var caPrefix string if server { @@ -153,7 +152,7 @@ func NewReloadableCredentials(config *goconf.ConfigFile, server bool) (credentia var loader *CertificateReloader var err error if certificateFile != "" && keyFile != "" { - loader, err = NewCertificateReloader(certificateFile, keyFile) + loader, err = NewCertificateReloader(logger, certificateFile, keyFile) if err != nil { return nil, fmt.Errorf("invalid GRPC %s certificate / key in %s / %s: %w", prefix, certificateFile, keyFile, err) } @@ -161,7 +160,7 @@ func NewReloadableCredentials(config *goconf.ConfigFile, server bool) (credentia var pool *CertPoolReloader if caFile != "" { - pool, err = NewCertPoolReloader(caFile) + pool, err = NewCertPoolReloader(logger, caFile) if err != nil { return nil, err } @@ -173,9 +172,9 @@ func NewReloadableCredentials(config *goconf.ConfigFile, server bool) (credentia if loader == nil && pool == nil { if server { - log.Printf("WARNING: No GRPC server certificate and/or key configured, running unencrypted") + logger.Printf("WARNING: No GRPC server certificate and/or key configured, running unencrypted") } else { - log.Printf("WARNING: No GRPC CA configured, expecting unencrypted connections") + logger.Printf("WARNING: No GRPC CA configured, expecting unencrypted connections") } return insecure.NewCredentials(), nil } diff --git a/grpc_remote_client.go b/grpc_remote_client.go index 8940fde..02133ec 100644 --- a/grpc_remote_client.go +++ b/grpc_remote_client.go @@ -27,7 +27,6 @@ import ( "errors" "fmt" "io" - "log" "sync/atomic" "google.golang.org/grpc/codes" @@ -49,6 +48,7 @@ func getMD(md metadata.MD, key string) string { // remoteGrpcClient is a remote client connecting from a GRPC proxy to a Hub. type remoteGrpcClient struct { + logger Logger hub *Hub client RpcSessions_ProxySessionServer @@ -73,6 +73,7 @@ func newRemoteGrpcClient(hub *Hub, request RpcSessions_ProxySessionServer) (*rem closeCtx, closeFunc := context.WithCancelCause(context.Background()) result := &remoteGrpcClient{ + logger: hub.logger, hub: hub, client: request, @@ -105,7 +106,7 @@ func (c *remoteGrpcClient) readPump() { } if status.Code(err) != codes.Canceled { - log.Printf("Error reading from remote client for session %s: %s", c.sessionId, err) + c.logger.Printf("Error reading from remote client for session %s: %s", c.sessionId, err) closeError = err } break @@ -193,7 +194,7 @@ func (c *remoteGrpcClient) SendMessage(message WritableClientMessage) bool { case c.messages <- message: return true default: - log.Printf("Message queue for remote client of session %s is full, not sending %+v", c.sessionId, message) + c.logger.Printf("Message queue for remote client of session %s is full, not sending %+v", c.sessionId, message) return false } } @@ -215,7 +216,7 @@ func (c *remoteGrpcClient) run() error { case msg := <-c.messages: data, err := json.Marshal(msg) if err != nil { - log.Printf("Error marshalling %+v for remote client for session %s: %s", msg, c.sessionId, err) + c.logger.Printf("Error marshalling %+v for remote client for session %s: %s", msg, c.sessionId, err) continue } diff --git a/grpc_server.go b/grpc_server.go index 9639e16..c7863c4 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -27,7 +27,6 @@ import ( "encoding/hex" "errors" "fmt" - "log" "net" "net/url" "os" @@ -73,6 +72,7 @@ type GrpcServer struct { UnimplementedRpcMcuServer UnimplementedRpcSessionsServer + logger Logger version string creds credentials.TransportCredentials conn *grpc.Server @@ -82,7 +82,7 @@ type GrpcServer struct { hub GrpcServerHub } -func NewGrpcServer(config *goconf.ConfigFile, version string) (*GrpcServer, error) { +func NewGrpcServer(ctx context.Context, config *goconf.ConfigFile, version string) (*GrpcServer, error) { var listener net.Listener if addr, _ := GetStringOptionWithEnv(config, "grpc", "listen"); addr != "" { var err error @@ -92,13 +92,15 @@ func NewGrpcServer(config *goconf.ConfigFile, version string) (*GrpcServer, erro } } - creds, err := NewReloadableCredentials(config, true) + logger := LoggerFromContext(ctx) + creds, err := NewReloadableCredentials(logger, config, true) if err != nil { return nil, err } conn := grpc.NewServer(grpc.Creds(creds)) result := &GrpcServer{ + logger: logger, version: version, creds: creds, conn: conn, @@ -130,7 +132,7 @@ func (s *GrpcServer) Close() { func (s *GrpcServer) LookupResumeId(ctx context.Context, request *LookupResumeIdRequest) (*LookupResumeIdReply, error) { statsGrpcServerCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging - log.Printf("Lookup session for resume id %s", request.ResumeId) + s.logger.Printf("Lookup session for resume id %s", request.ResumeId) session := s.hub.GetSessionByResumeId(PrivateSessionId(request.ResumeId)) if session == nil { return nil, status.Error(codes.NotFound, "no such room session id") @@ -144,7 +146,7 @@ func (s *GrpcServer) LookupResumeId(ctx context.Context, request *LookupResumeId func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSessionIdRequest) (*LookupSessionIdReply, error) { statsGrpcServerCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging - log.Printf("Lookup session id for room session id %s", request.RoomSessionId) + s.logger.Printf("Lookup session id for room session id %s", request.RoomSessionId) sid, err := s.hub.GetSessionIdByRoomSessionId(RoomSessionId(request.RoomSessionId)) if errors.Is(err, ErrNoSuchRoomSession) { return nil, status.Error(codes.NotFound, "no such room session id") @@ -154,7 +156,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession if sid != "" && request.DisconnectReason != "" { if session := s.hub.GetSessionByPublicId(PublicSessionId(sid)); session != nil { - log.Printf("Closing session %s because same room session %s connected", session.PublicId(), request.RoomSessionId) + s.logger.Printf("Closing session %s because same room session %s connected", session.PublicId(), request.RoomSessionId) session.LeaveRoom(false) switch sess := session.(type) { case *ClientSession: @@ -173,7 +175,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCallRequest) (*IsSessionInCallReply, error) { statsGrpcServerCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging - log.Printf("Check if session %s is in call %s on %s", request.SessionId, request.RoomId, request.BackendUrl) + s.logger.Printf("Check if session %s is in call %s on %s", request.SessionId, request.RoomId, request.BackendUrl) session := s.hub.GetSessionByPublicId(PublicSessionId(request.SessionId)) if session == nil { return nil, status.Error(codes.NotFound, "no such session id") @@ -194,7 +196,7 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetInternalSessionsRequest) (*GetInternalSessionsReply, error) { statsGrpcServerCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging - log.Printf("Get internal sessions from %s on %v (fallback %s)", request.RoomId, request.BackendUrls, request.BackendUrl) + s.logger.Printf("Get internal sessions from %s on %v (fallback %s)", request.RoomId, request.BackendUrls, request.BackendUrl) var backendUrls []string if len(request.BackendUrls) > 0 { @@ -259,7 +261,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetIntern func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherIdRequest) (*GetPublisherIdReply, error) { statsGrpcServerCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging - log.Printf("Get %s publisher id for session %s", request.StreamType, request.SessionId) + s.logger.Printf("Get %s publisher id for session %s", request.StreamType, request.SessionId) session := s.hub.GetSessionByPublicId(PublicSessionId(request.SessionId)) if session == nil { return nil, status.Error(codes.NotFound, "no such session") @@ -281,11 +283,11 @@ func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherId } var err error if reply.ConnectToken, err = s.hub.CreateProxyToken(""); err != nil && !errors.Is(err, ErrNoProxyMcu) { - log.Printf("Error creating proxy token for connection: %s", err) + s.logger.Printf("Error creating proxy token for connection: %s", err) return nil, status.Error(codes.Internal, "error creating proxy connect token") } if reply.PublisherToken, err = s.hub.CreateProxyToken(publisher.Id()); err != nil && !errors.Is(err, ErrNoProxyMcu) { - log.Printf("Error creating proxy token for publisher %s: %s", publisher.Id(), err) + s.logger.Printf("Error creating proxy token for publisher %s: %s", publisher.Id(), err) return nil, status.Error(codes.Internal, "error creating proxy publisher token") } return reply, nil diff --git a/grpc_server_test.go b/grpc_server_test.go index 8ebdc7c..87a036a 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -62,11 +62,13 @@ func (s *GrpcServer) WaitForCertPoolReload(ctx context.Context, counter uint64) } func NewGrpcServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *GrpcServer, addr string) { + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) for port := 50000; port < 50100; port++ { addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) config.AddOption("grpc", "listen", addr) var err error - server, err = NewGrpcServer(config, "0.0.0") + server, err = NewGrpcServer(ctx, config, "0.0.0") if isErrorAddressAlreadyInUse(err) { continue } @@ -96,7 +98,6 @@ func NewGrpcServerForTest(t *testing.T) (server *GrpcServer, addr string) { } func Test_GrpcServer_ReloadCerts(t *testing.T) { - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) key, err := rsa.GenerateKey(rand.Reader, 1024) @@ -167,7 +168,7 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { } func Test_GrpcServer_ReloadCA(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) @@ -211,7 +212,7 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { RootCAs: pool, Certificates: []tls.Certificate{pair1}, } - client1, err := NewGrpcClient(addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg1))) + client1, err := NewGrpcClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg1))) require.NoError(err) defer client1.Close() // nolint @@ -237,7 +238,7 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { RootCAs: pool, Certificates: []tls.Certificate{pair2}, } - client2, err := NewGrpcClient(addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg2))) + client2, err := NewGrpcClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg2))) require.NoError(err) defer client2.Close() // nolint diff --git a/hub.go b/hub.go index b70eded..75a683a 100644 --- a/hub.go +++ b/hub.go @@ -36,7 +36,6 @@ import ( "errors" "fmt" "hash/fnv" - "log" "net" "net/http" "net/url" @@ -140,6 +139,7 @@ func init() { type Hub struct { version string + logger Logger events AsyncEvents upgrader websocket.Upgrader cookie *SessionIdCodec @@ -219,13 +219,14 @@ type Hub struct { blockedCandidates atomic.Pointer[AllowedIps] } -func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { +func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { + logger := LoggerFromContext(ctx) hashKey, _ := GetStringOptionWithEnv(config, "sessions", "hashkey") switch len(hashKey) { case 32: case 64: default: - log.Printf("WARNING: The sessions hash key should be 32 or 64 bytes but is %d bytes", len(hashKey)) + logger.Printf("WARNING: The sessions hash key should be 32 or 64 bytes but is %d bytes", len(hashKey)) } blockKey, _ := GetStringOptionWithEnv(config, "sessions", "blockkey") @@ -242,7 +243,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer internalClientsSecret, _ := GetStringOptionWithEnv(config, "clients", "internalsecret") if internalClientsSecret == "" { - log.Println("WARNING: No shared secret has been set for internal clients.") + logger.Println("WARNING: No shared secret has been set for internal clients.") } maxConcurrentRequestsPerHost, _ := config.GetInt("backend", "connectionsperhost") @@ -250,18 +251,18 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer maxConcurrentRequestsPerHost = defaultMaxConcurrentRequestsPerHost } - backend, err := NewBackendClient(config, maxConcurrentRequestsPerHost, version, etcdClient) + backend, err := NewBackendClient(ctx, config, maxConcurrentRequestsPerHost, version, etcdClient) if err != nil { return nil, err } - log.Printf("Using a maximum of %d concurrent backend connections per host", maxConcurrentRequestsPerHost) + logger.Printf("Using a maximum of %d concurrent backend connections per host", maxConcurrentRequestsPerHost) backendTimeoutSeconds, _ := config.GetInt("backend", "timeout") if backendTimeoutSeconds <= 0 { backendTimeoutSeconds = defaultBackendTimeoutSeconds } backendTimeout := time.Duration(backendTimeoutSeconds) * time.Second - log.Printf("Using a timeout of %s for backend connections", backendTimeout) + logger.Printf("Using a timeout of %s for backend connections", backendTimeout) mcuTimeoutSeconds, _ := config.GetInt("mcu", "timeout") if mcuTimeoutSeconds <= 0 { @@ -271,7 +272,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer allowSubscribeAnyStream, _ := config.GetBool("app", "allowsubscribeany") if allowSubscribeAnyStream { - log.Printf("WARNING: Allow subscribing any streams, this is insecure and should only be enabled for testing") + logger.Printf("WARNING: Allow subscribing any streams, this is insecure and should only be enabled for testing") } trustedProxies, _ := config.GetString("app", "trustedproxies") @@ -282,7 +283,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer skipFederationVerify, _ := config.GetBool("federation", "skipverify") if skipFederationVerify { - log.Println("WARNING: Federation target verification is disabled!") + logger.Println("WARNING: Federation target verification is disabled!") } federationTimeoutSeconds, _ := config.GetInt("federation", "timeout") if federationTimeoutSeconds <= 0 { @@ -291,10 +292,10 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer federationTimeout := time.Duration(federationTimeoutSeconds) * time.Second if !trustedProxiesIps.Empty() { - log.Printf("Trusted proxies: %s", trustedProxiesIps) + logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { trustedProxiesIps = DefaultTrustedProxies - log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) + logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) @@ -325,20 +326,20 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer var geoip *GeoLookup if geoipUrl != "" { if geoipUrl, found := strings.CutPrefix(geoipUrl, "file://"); found { - log.Printf("Using GeoIP database from %s", geoipUrl) - geoip, err = NewGeoLookupFromFile(geoipUrl) + logger.Printf("Using GeoIP database from %s", geoipUrl) + geoip, err = NewGeoLookupFromFile(logger, geoipUrl) } else { - log.Printf("Downloading GeoIP database from %s", geoipUrl) - geoip, err = NewGeoLookupFromUrl(geoipUrl) + logger.Printf("Downloading GeoIP database from %s", geoipUrl) + geoip, err = NewGeoLookupFromUrl(logger, geoipUrl) } if err != nil { return nil, err } } else { - log.Printf("Not using GeoIP database") + logger.Printf("Not using GeoIP database") } - geoipOverrides, err := LoadGeoIPOverrides(config, false) + geoipOverrides, err := LoadGeoIPOverrides(ctx, config, false) if err != nil { return nil, err } @@ -350,6 +351,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer hub := &Hub{ version: version, + logger: logger, events: events, upgrader: websocket.Upgrader{ ReadBufferSize: websocketReadBufferSize, @@ -414,10 +416,10 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer return nil, fmt.Errorf("invalid allowedcandidates: %w", err) } - log.Printf("Candidates allowlist: %s", allowed) + logger.Printf("Candidates allowlist: %s", allowed) hub.allowedCandidates.Store(allowed) } else { - log.Printf("No candidates allowlist") + logger.Printf("No candidates allowlist") } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { blocked, err := ParseAllowedIps(value) @@ -425,10 +427,10 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer return nil, fmt.Errorf("invalid blockedcandidates: %w", err) } - log.Printf("Candidates blocklist: %s", blocked) + logger.Printf("Candidates blocklist: %s", blocked) hub.blockedCandidates.Store(blocked) } else { - log.Printf("No candidates blocklist") + logger.Printf("No candidates blocklist") } hub.trustedProxies.Store(trustedProxiesIps) @@ -469,7 +471,7 @@ func (h *Hub) SetMcu(mcu Mcu) { welcome.Welcome.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) } else { - log.Printf("Using a timeout of %s for MCU requests", h.mcuTimeout) + h.logger.Printf("Using a timeout of %s for MCU requests", h.mcuTimeout) h.info.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) h.infoInternal.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) @@ -504,7 +506,7 @@ func (h *Hub) updateGeoDatabase() { defer h.geoipUpdating.Store(false) backoff, err := NewExponentialBackoff(time.Second, 5*time.Minute) if err != nil { - log.Printf("Could not create exponential backoff: %s", err) + h.logger.Printf("Could not create exponential backoff: %s", err) return } @@ -514,7 +516,7 @@ func (h *Hub) updateGeoDatabase() { break } - log.Printf("Could not update GeoIP database, will retry in %s (%s)", backoff.NextWait(), err) + h.logger.Printf("Could not update GeoIP database, will retry in %s (%s)", backoff.NextWait(), err) backoff.Wait(context.Background()) } } @@ -562,21 +564,21 @@ func (h *Hub) Stop() { h.throttler.Close() } -func (h *Hub) Reload(config *goconf.ConfigFile) { +func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { trustedProxies, _ := config.GetString("app", "trustedproxies") if trustedProxiesIps, err := ParseAllowedIps(trustedProxies); err == nil { if !trustedProxiesIps.Empty() { - log.Printf("Trusted proxies: %s", trustedProxiesIps) + h.logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { trustedProxiesIps = DefaultTrustedProxies - log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) + h.logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } h.trustedProxies.Store(trustedProxiesIps) } else { - log.Printf("Error parsing trusted proxies from \"%s\": %s", trustedProxies, err) + h.logger.Printf("Error parsing trusted proxies from \"%s\": %s", trustedProxies, err) } - geoipOverrides, _ := LoadGeoIPOverrides(config, true) + geoipOverrides, _ := LoadGeoIPOverrides(ctx, config, true) if len(geoipOverrides) > 0 { h.geoipOverrides.Store(&geoipOverrides) } else { @@ -585,24 +587,24 @@ func (h *Hub) Reload(config *goconf.ConfigFile) { if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { if allowed, err := ParseAllowedIps(value); err != nil { - log.Printf("invalid allowedcandidates: %s", err) + h.logger.Printf("invalid allowedcandidates: %s", err) } else { - log.Printf("Candidates allowlist: %s", allowed) + h.logger.Printf("Candidates allowlist: %s", allowed) h.allowedCandidates.Store(allowed) } } else { - log.Printf("No candidates allowlist") + h.logger.Printf("No candidates allowlist") h.allowedCandidates.Store(nil) } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { if blocked, err := ParseAllowedIps(value); err != nil { - log.Printf("invalid blockedcandidates: %s", err) + h.logger.Printf("invalid blockedcandidates: %s", err) } else { - log.Printf("Candidates blocklist: %s", blocked) + h.logger.Printf("Candidates blocklist: %s", blocked) h.blockedCandidates.Store(blocked) } } else { - log.Printf("No candidates blocklist") + h.logger.Printf("No candidates blocklist") h.blockedCandidates.Store(nil) } @@ -769,7 +771,7 @@ func (h *Hub) checkExpiredSessions(now time.Time) { for session, expires := range h.expiredSessions { if now.After(expires) { h.mu.Unlock() - log.Printf("Closing expired session %s (private=%s)", session.PublicId(), session.PrivateId()) + h.logger.Printf("Closing expired session %s (private=%s)", session.PublicId(), session.PrivateId()) session.Close() h.mu.Lock() // Should already be deleted by the close code, but better be sure. @@ -961,7 +963,7 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * client, ok := c.(*Client) if !ok { - log.Printf("Can't register non-client %T", c) + h.logger.Printf("Can't register non-client %T", c) client.SendMessage(message.NewWrappedErrorServerMessage(errors.New("can't register non-client"))) return } @@ -980,11 +982,11 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * userId := auth.Auth.UserId if userId != "" { - log.Printf("Register user %s@%s from %s in %s (%s) %s (private=%s)", userId, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) + h.logger.Printf("Register user %s@%s from %s in %s (%s) %s (private=%s)", userId, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) } else if message.Hello.Auth.Type != HelloClientTypeClient { - log.Printf("Register %s@%s from %s in %s (%s) %s (private=%s)", message.Hello.Auth.Type, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) + h.logger.Printf("Register %s@%s from %s in %s (%s) %s (private=%s)", message.Hello.Auth.Type, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) } else { - log.Printf("Register anonymous@%s from %s in %s (%s) %s (private=%s)", backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) + h.logger.Printf("Register anonymous@%s from %s in %s (%s) %s (private=%s)", backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) } session, err := NewClientSession(h, privateSessionId, publicSessionId, sessionIdData, backend, message.Hello, auth.Auth) @@ -994,7 +996,7 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * } if err := backend.AddSession(session); err != nil { - log.Printf("Error adding session %s to backend %s: %s", session.PublicId(), backend.Id(), err) + h.logger.Printf("Error adding session %s to backend %s: %s", session.PublicId(), backend.Id(), err) session.Close() client.SendMessage(message.NewWrappedErrorServerMessage(err)) return @@ -1013,12 +1015,12 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * count, err := c.GetSessionCount(ctx, session.BackendUrl()) if err != nil { - log.Printf("Received error while getting session count for %s from %s: %s", session.BackendUrl(), c.Target(), err) + h.logger.Printf("Received error while getting session count for %s from %s: %s", session.BackendUrl(), c.Target(), err) return } if count > 0 { - log.Printf("%d sessions connected for %s on %s", count, session.BackendUrl(), c.Target()) + h.logger.Printf("%d sessions connected for %s on %s", count, session.BackendUrl(), c.Target()) totalCount.Add(count) } }(client) @@ -1026,7 +1028,7 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * wg.Wait() if totalCount.Load() > limit { backend.RemoveSession(session) - log.Printf("Error adding session %s to backend %s: %s", session.PublicId(), backend.Id(), SessionLimitExceeded) + h.logger.Printf("Error adding session %s to backend %s: %s", session.PublicId(), backend.Id(), SessionLimitExceeded) session.Close() client.SendMessage(message.NewWrappedErrorServerMessage(SessionLimitExceeded)) return @@ -1078,7 +1080,7 @@ func (h *Hub) processUnregister(client HandlerClient) Session { } h.mu.Unlock() if session != nil { - log.Printf("Unregister %s (private=%s)", session.PublicId(), session.PrivateId()) + h.logger.Printf("Unregister %s (private=%s)", session.PublicId(), session.PrivateId()) if c, ok := client.(*Client); ok { if cs, ok := session.(*ClientSession); ok { cs.ClearClient(c) @@ -1094,10 +1096,10 @@ func (h *Hub) processMessage(client HandlerClient, data []byte) { var message ClientMessage if err := message.UnmarshalJSON(data); err != nil { if session := client.GetSession(); session != nil { - log.Printf("Error decoding message from client %s: %v", session.PublicId(), err) + h.logger.Printf("Error decoding message from client %s: %v", session.PublicId(), err) session.SendError(InvalidFormat) } else { - log.Printf("Error decoding message from %s: %v", client.RemoteAddr(), err) + h.logger.Printf("Error decoding message from %s: %v", client.RemoteAddr(), err) client.SendError(InvalidFormat) } return @@ -1105,14 +1107,14 @@ func (h *Hub) processMessage(client HandlerClient, data []byte) { if err := message.CheckValid(); err != nil { if session := client.GetSession(); session != nil { - log.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) + h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) if err, ok := err.(*Error); ok { session.SendMessage(message.NewErrorServerMessage(err)) } else { session.SendMessage(message.NewErrorServerMessage(InvalidFormat)) } } else { - log.Printf("Invalid message %+v from %s: %v", message, client.RemoteAddr(), err) + h.logger.Printf("Invalid message %+v from %s: %v", message, client.RemoteAddr(), err) if err, ok := err.(*Error); ok { client.SendMessage(message.NewErrorServerMessage(err)) } else { @@ -1161,9 +1163,9 @@ func (h *Hub) processMessage(client HandlerClient, data []byte) { case "bye": h.processByeMsg(client, &message) case "hello": - log.Printf("Ignore hello %+v for already authenticated connection %s", message.Hello, session.PublicId()) + h.logger.Printf("Ignore hello %+v for already authenticated connection %s", message.Hello, session.PublicId()) default: - log.Printf("Ignore unknown message %+v from %s", message, session.PublicId()) + h.logger.Printf("Ignore unknown message %+v from %s", message, session.PublicId()) } } @@ -1220,7 +1222,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message response, err := client.LookupResumeId(ctx, resumeId) if err != nil { - log.Printf("Could not lookup resume id %s on %s: %s", resumeId, client.Target(), err) + h.logger.Printf("Could not lookup resume id %s on %s: %s", resumeId, client.Target(), err) return } @@ -1245,17 +1247,17 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message rs, err := NewRemoteSession(h, client, info.client, PublicSessionId(info.response.SessionId)) if err != nil { - log.Printf("Could not create remote session %s on %s: %s", info.response.SessionId, info.client.Target(), err) + h.logger.Printf("Could not create remote session %s on %s: %s", info.response.SessionId, info.client.Target(), err) return false } if err := rs.Start(message); err != nil { rs.Close() - log.Printf("Could not start remote session %s on %s: %s", info.response.SessionId, info.client.Target(), err) + h.logger.Printf("Could not start remote session %s on %s: %s", info.response.SessionId, info.client.Target(), err) return false } - log.Printf("Proxy session %s to %s", info.response.SessionId, info.client.Target()) + h.logger.Printf("Proxy session %s to %s", info.response.SessionId, info.client.Target()) h.mu.Lock() defer h.mu.Unlock() h.remoteSessions[rs] = true @@ -1264,7 +1266,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message } func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { - ctx := context.TODO() + ctx := NewLoggerContext(client.Context(), h.logger) resumeId := message.Hello.ResumeId if resumeId != "" { throttle, err := h.throttler.CheckBruteforce(ctx, client.RemoteAddr(), "HelloResume") @@ -1272,7 +1274,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { client.SendMessage(message.NewErrorServerMessage(TooManyRequests)) return } else if err != nil { - log.Printf("Error checking for bruteforce: %s", err) + h.logger.Printf("Error checking for bruteforce: %s", err) client.SendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1307,7 +1309,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { if !ok { // Should never happen as clients only can resume their own sessions. h.mu.Unlock() - log.Printf("Client resumed non-client session %s (private=%s)", session.PublicId(), session.PrivateId()) + h.logger.Printf("Client resumed non-client session %s (private=%s)", session.PublicId(), session.PrivateId()) statsHubSessionResumeFailed.Inc() client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) return @@ -1320,7 +1322,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { } if prev := clientSession.SetClient(client); prev != nil { - log.Printf("Closing previous client from %s for session %s", prev.RemoteAddr(), session.PublicId()) + h.logger.Printf("Closing previous client from %s for session %s", prev.RemoteAddr(), session.PublicId()) prev.SendByeResponseWithReason(nil, "session_resumed") } @@ -1329,7 +1331,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { delete(h.expectHelloClients, client) h.mu.Unlock() - log.Printf("Resume session from %s in %s (%s) %s (private=%s)", client.RemoteAddr(), client.Country(), client.UserAgent(), session.PublicId(), session.PrivateId()) + h.logger.Printf("Resume session from %s in %s (%s) %s (private=%s)", client.RemoteAddr(), client.Country(), client.UserAgent(), session.PublicId(), session.PrivateId()) statsHubSessionsResumedTotal.WithLabelValues(clientSession.Backend().Id(), string(clientSession.ClientType())).Inc() h.sendHelloResponse(clientSession, message) @@ -1437,7 +1439,7 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return jwt.ParseEdPublicKeyFromPEM(data) } default: - log.Printf("Unexpected signing method: %v", token.Header["alg"]) + h.logger.Printf("Unexpected signing method: %v", token.Header["alg"]) return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } @@ -1570,13 +1572,13 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) return } - ctx := context.TODO() + ctx := NewLoggerContext(client.Context(), h.logger) throttle, err := h.throttler.CheckBruteforce(ctx, client.RemoteAddr(), "HelloInternal") if err == ErrBruteforceDetected { client.SendMessage(message.NewErrorServerMessage(TooManyRequests)) return } else if err != nil { - log.Printf("Error checking for bruteforce: %s", err) + h.logger.Printf("Error checking for bruteforce: %s", err) client.SendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1611,7 +1613,7 @@ func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId RoomS if err == ErrNoSuchRoomSession { return } else if err != nil { - log.Printf("Could not get session id for room session %s: %s", roomSessionId, err) + h.logger.Printf("Could not get session id for room session %s: %s", roomSessionId, err) return } @@ -1629,12 +1631,12 @@ func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId RoomS }, } if err := h.events.PublishSessionMessage(sessionId, backend, msg); err != nil { - log.Printf("Could not send reconnect bye to session %s: %s", sessionId, err) + h.logger.Printf("Could not send reconnect bye to session %s: %s", sessionId, err) } return } - log.Printf("Closing session %s because same room session %s connected", session.PublicId(), roomSessionId) + h.logger.Printf("Closing session %s because same room session %s connected", session.PublicId(), roomSessionId) session.LeaveRoom(false) switch sess := session.(type) { case *ClientSession: @@ -1787,7 +1789,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { } } - log.Printf("Error creating federation client to %s for %s to join room %s: %s", federation.SignalingUrl, session.PublicId(), roomId, err) + h.logger.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), )) @@ -1799,14 +1801,14 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { roomSessionId := message.Room.SessionId if roomSessionId == "" { // TODO(jojo): Better make the session id required in the request. - log.Printf("User did not send a room session id, assuming session %s", session.PublicId()) + h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) roomSessionId = RoomSessionId(session.PublicId()) } // Prefix room session id to allow using the same signaling server for two Nextcloud instances during development. // Otherwise the same room session id will be detected and the other session will be kicked. if err := session.UpdateRoomSessionId(FederatedRoomSessionIdPrefix + roomSessionId); err != nil { - log.Printf("Error updating room session id for session %s: %s", session.PublicId(), err) + h.logger.Printf("Error updating room session id for session %s: %s", session.PublicId(), err) } h.mu.Lock() @@ -1821,12 +1823,12 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { roomSessionId := message.Room.SessionId if roomSessionId == "" { // TODO(jojo): Better make the session id required in the request. - log.Printf("User did not send a room session id, assuming session %s", session.PublicId()) + h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) roomSessionId = RoomSessionId(session.PublicId()) } if err := session.UpdateRoomSessionId(roomSessionId); err != nil { - log.Printf("Error updating room session id for session %s: %s", session.PublicId(), err) + h.logger.Printf("Error updating room session id for session %s: %s", session.PublicId(), err) } session.SendMessage(message.NewErrorServerMessage( NewErrorDetail("already_joined", "Already joined this room.", &RoomErrorDetails{ @@ -1856,7 +1858,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { sessionId := message.Room.SessionId if sessionId == "" { // TODO(jojo): Better make the session id required in the request. - log.Printf("User did not send a room session id, assuming session %s", session.PublicId()) + h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) sessionId = RoomSessionId(session.PublicId()) } request := NewBackendClientRoomRequest(roomId, session.UserId(), sessionId) @@ -1938,17 +1940,18 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { return 0, &wg } count := 0 + ctx := NewLoggerContext(context.Background(), h.logger) for roomId, entries := range rooms { for u, e := range entries { wg.Add(1) count += len(e) go func(roomId string, url *url.URL, entries []BackendPingEntry) { defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), h.backendTimeout) + sendCtx, cancel := context.WithTimeout(ctx, h.backendTimeout) defer cancel() - if err := h.roomPing.SendPings(ctx, roomId, url, entries); err != nil { - log.Printf("Error pinging room %s for active entries %+v: %s", roomId, entries, err) + if err := h.roomPing.SendPings(sendCtx, roomId, url, entries); err != nil { + h.logger.Printf("Error pinging room %s for active entries %+v: %s", roomId, entries, err) } }(roomId, urls[u], e) } @@ -2071,7 +2074,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { var data MessageClientMessageData if err := json.Unmarshal(msg.Data, &data); err == nil { if err := data.CheckValid(); err != nil { - log.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) + h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) if err, ok := err.(*Error); ok { session.SendMessage(message.NewErrorServerMessage(err)) } else { @@ -2119,7 +2122,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { return } - log.Printf("Closing screen publisher for %s", session.PublicId()) + h.logger.Printf("Closing screen publisher for %s", session.PublicId()) ctx, cancel := context.WithTimeout(context.Background(), h.mcuTimeout) defer cancel() publisher.Close(ctx) @@ -2188,7 +2191,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { var data MessageClientMessageData if err := json.Unmarshal(msg.Data, &data); err == nil { if err := data.CheckValid(); err != nil { - log.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) + h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) if err, ok := err.(*Error); ok { session.SendMessage(message.NewErrorServerMessage(err)) } else { @@ -2204,7 +2207,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } } if subject == "" { - log.Printf("Unknown recipient in message %+v from %s", msg, session.PublicId()) + h.logger.Printf("Unknown recipient in message %+v from %s", msg, session.PublicId()) return } @@ -2224,7 +2227,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { // The recipient is connected to this instance, no need to go through asynchronous events. if clientData != nil && clientData.Type == "sendoffer" { if err := session.IsAllowedToSend(clientData); err != nil { - log.Printf("Session %s is not allowed to send offer for %s, ignoring (%s)", session.PublicId(), clientData.RoomType, err) + h.logger.Printf("Session %s is not allowed to send offer for %s, ignoring (%s)", session.PublicId(), clientData.RoomType, err) sendNotAllowed(session, message, "Not allowed to send offer") return } @@ -2238,18 +2241,18 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { mc, err := recipient.GetOrCreateSubscriber(ctx, h.mcu, session.PublicId(), StreamType(clientData.RoomType)) if err != nil { - log.Printf("Could not create MCU subscriber for session %s to send %+v to %s: %s", session.PublicId(), clientData, recipient.PublicId(), err) + h.logger.Printf("Could not create MCU subscriber for session %s to send %+v to %s: %s", session.PublicId(), clientData, recipient.PublicId(), err) sendMcuClientNotFound(session, message) return } else if mc == nil { - log.Printf("No MCU subscriber found for session %s to send %+v to %s", session.PublicId(), clientData, recipient.PublicId()) + h.logger.Printf("No MCU subscriber found for session %s to send %+v to %s", session.PublicId(), clientData, recipient.PublicId()) sendMcuClientNotFound(session, message) return } mc.SendMessage(session.Context(), msg, clientData, func(err error, response api.StringMap) { if err != nil { - log.Printf("Could not send MCU message %+v for session %s to %s: %s", clientData, session.PublicId(), recipient.PublicId(), err) + h.logger.Printf("Could not send MCU message %+v for session %s to %s: %s", clientData, session.PublicId(), recipient.PublicId(), err) sendMcuProcessingFailed(session, message) return } else if response == nil { @@ -2270,7 +2273,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } else { if clientData != nil && clientData.Type == "sendoffer" { if err := session.IsAllowedToSend(clientData); err != nil { - log.Printf("Session %s is not allowed to send offer for %s, ignoring (%s)", session.PublicId(), clientData.RoomType, err) + h.logger.Printf("Session %s is not allowed to send offer for %s, ignoring (%s)", session.PublicId(), clientData.RoomType, err) sendNotAllowed(session, message, "Not allowed to send offer") return } @@ -2284,7 +2287,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { }, } if err := h.events.PublishSessionMessage(recipientSessionId, session.Backend(), async); err != nil { - log.Printf("Error publishing message to remote session: %s", err) + h.logger.Printf("Error publishing message to remote session: %s", err) } return } @@ -2308,7 +2311,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } if err != nil { - log.Printf("Error publishing message to remote session: %s", err) + h.logger.Printf("Error publishing message to remote session: %s", err) } } } @@ -2330,7 +2333,7 @@ func isAllowedToControl(session Session) bool { func (h *Hub) processControlMsg(session Session, message *ClientMessage) { msg := message.Control if !isAllowedToControl(session) { - log.Printf("Ignore control message %+v from %s", msg, session.PublicId()) + h.logger.Printf("Ignore control message %+v from %s", msg, session.PublicId()) return } @@ -2398,7 +2401,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { } } if subject == "" { - log.Printf("Unknown recipient in message %+v from %s", msg, session.PublicId()) + h.logger.Printf("Unknown recipient in message %+v from %s", msg, session.PublicId()) return } @@ -2435,7 +2438,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { err = fmt.Errorf("unsupported recipient type: %s", msg.Recipient.Type) } if err != nil { - log.Printf("Error publishing message to remote session: %s", err) + h.logger.Printf("Error publishing message to remote session: %s", err) } } } @@ -2447,7 +2450,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { // Client is not connected yet. return } else if session.ClientType() != HelloClientTypeInternal { - log.Printf("Ignore internal message %+v from %s", msg, session.PublicId()) + h.logger.Printf("Ignore internal message %+v from %s", msg, session.PublicId()) return } @@ -2460,19 +2463,19 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { msg := msg.AddSession room := h.GetRoomForBackend(msg.RoomId, session.Backend()) if room == nil { - log.Printf("Ignore add session message %+v for invalid room %s from %s", *msg, msg.RoomId, session.PublicId()) + h.logger.Printf("Ignore add session message %+v for invalid room %s from %s", *msg, msg.RoomId, session.PublicId()) return } sessionIdData := h.newSessionIdData(session.Backend()) privateSessionId, err := h.cookie.EncodePrivate(sessionIdData) if err != nil { - log.Printf("Could not encode private virtual session id: %s", err) + h.logger.Printf("Could not encode private virtual session id: %s", err) return } publicSessionId, err := h.cookie.EncodePublic(sessionIdData) if err != nil { - log.Printf("Could not encode public virtual session id: %s", err) + h.logger.Printf("Could not encode public virtual session id: %s", err) return } @@ -2483,7 +2486,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { sess, err := NewVirtualSession(session, privateSessionId, publicSessionId, sessionIdData, msg) if err != nil { - log.Printf("Could not create virtual session %s: %s", virtualSessionId, err) + h.logger.Printf("Could not create virtual session %s: %s", virtualSessionId, err) reply := message.NewErrorServerMessage(NewError("add_failed", "Could not create virtual session.")) session.SendMessage(reply) return @@ -2498,7 +2501,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { var response BackendClientResponse if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() - log.Printf("Could not join virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) + h.logger.Printf("Could not join virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) reply := message.NewErrorServerMessage(NewError("add_failed", "Could not join virtual session.")) session.SendMessage(reply) return @@ -2506,7 +2509,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { if response.Type == "error" { sess.Close() - log.Printf("Could not join virtual session %s at backend %s: %+v", virtualSessionId, session.BackendUrl(), response.Error) + h.logger.Printf("Could not join virtual session %s at backend %s: %+v", virtualSessionId, session.BackendUrl(), response.Error) reply := message.NewErrorServerMessage(NewError("add_failed", response.Error.Error())) session.SendMessage(reply) return @@ -2516,7 +2519,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { var response BackendClientSessionResponse if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() - log.Printf("Could not add virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) + h.logger.Printf("Could not add virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) reply := message.NewErrorServerMessage(NewError("add_failed", "Could not add virtual session.")) session.SendMessage(reply) return @@ -2529,7 +2532,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { h.mu.Unlock() statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), string(sess.ClientType())).Inc() statsHubSessionsTotal.WithLabelValues(session.Backend().Id(), string(sess.ClientType())).Inc() - log.Printf("Session %s added virtual session %s with initial flags %d", session.PublicId(), sess.PublicId(), sess.Flags()) + h.logger.Printf("Session %s added virtual session %s with initial flags %d", session.PublicId(), sess.PublicId(), sess.Flags()) session.AddVirtualSession(sess) sess.SetRoom(room) room.AddSession(sess, nil) @@ -2537,7 +2540,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { msg := msg.UpdateSession room := h.GetRoomForBackend(msg.RoomId, session.Backend()) if room == nil { - log.Printf("Ignore remove session message %+v for invalid room %s from %s", *msg, msg.RoomId, session.PublicId()) + h.logger.Printf("Ignore remove session message %+v for invalid room %s from %s", *msg, msg.RoomId, session.PublicId()) return } @@ -2565,7 +2568,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { } } } else { - log.Printf("Ignore update request for non-virtual session %s", sess.PublicId()) + h.logger.Printf("Ignore update request for non-virtual session %s", sess.PublicId()) } if changed != 0 { room.NotifySessionChanged(sess, changed) @@ -2575,7 +2578,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { msg := msg.RemoveSession room := h.GetRoomForBackend(msg.RoomId, session.Backend()) if room == nil { - log.Printf("Ignore remove session message %+v for invalid room %s from %s", *msg, msg.RoomId, session.PublicId()) + h.logger.Printf("Ignore remove session message %+v for invalid room %s from %s", *msg, msg.RoomId, session.PublicId()) return } @@ -2591,7 +2594,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { sess := h.sessions[sid] h.mu.Unlock() if sess != nil { - log.Printf("Session %s removed virtual session %s", session.PublicId(), sess.PublicId()) + h.logger.Printf("Session %s removed virtual session %s", session.PublicId(), sess.PublicId()) if vsess, ok := sess.(*VirtualSession); ok { // We should always have a VirtualSession here. vsess.CloseWithFeedback(session, message) @@ -2625,7 +2628,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { asyncMessage.Room.Transient.TTL = removeCallStatusTTL } if err := h.events.PublishBackendRoomMessage(roomId, session.Backend(), asyncMessage); err != nil { - log.Printf("Error publishing dialout message %+v to room %s", msg.Dialout, roomId) + h.logger.Printf("Error publishing dialout message %+v to room %s", msg.Dialout, roomId) } } else { if err := h.events.PublishRoomMessage(roomId, session.Backend(), &AsyncMessage{ @@ -2635,11 +2638,11 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { Dialout: msg.Dialout, }, }); err != nil { - log.Printf("Error publishing dialout message %+v to room %s", msg.Dialout, roomId) + h.logger.Printf("Error publishing dialout message %+v to room %s", msg.Dialout, roomId) } } default: - log.Printf("Ignore unsupported internal message %+v from %s", msg, session.PublicId()) + h.logger.Printf("Ignore unsupported internal message %+v from %s", msg, session.PublicId()) return } } @@ -2725,7 +2728,7 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi if errors.Is(err, context.Canceled) { return } else if err != nil { - log.Printf("Error checking session %s in call on %s: %s", recipientSessionId, client.Target(), err) + h.logger.Printf("Error checking session %s in call on %s: %s", recipientSessionId, client.Target(), err) return } else if !inCall { return @@ -2778,14 +2781,14 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe switch data.Type { case "requestoffer": if session.PublicId() == message.Recipient.SessionId { - log.Printf("Not requesting offer from itself for session %s", session.PublicId()) + h.logger.Printf("Not requesting offer from itself for session %s", session.PublicId()) return } // A user is only allowed to subscribe a stream if she is in the same room // as the other user and both have their "inCall" flag set. if !h.allowSubscribeAnyStream && !h.isInSameCall(ctx, session, message.Recipient.SessionId) { - log.Printf("Session %s is not in the same call as session %s, not requesting offer", session.PublicId(), message.Recipient.SessionId) + h.logger.Printf("Session %s is not in the same call as session %s, not requesting offer", session.PublicId(), message.Recipient.SessionId) sendNotAllowed(session, client_message, "Not allowed to request offer.") return } @@ -2799,13 +2802,13 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe clientType = "publisher" mc, err = session.GetOrCreatePublisher(ctx, h.mcu, StreamType(data.RoomType), data) if err, ok := err.(*PermissionError); ok { - log.Printf("Session %s is not allowed to offer %s, ignoring (%s)", session.PublicId(), data.RoomType, err) + h.logger.Printf("Session %s is not allowed to offer %s, ignoring (%s)", session.PublicId(), data.RoomType, err) sendNotAllowed(session, client_message, "Not allowed to publish.") return } case "selectStream": if session.PublicId() == message.Recipient.SessionId { - log.Printf("Not selecting substream for own %s stream in session %s", data.RoomType, session.PublicId()) + h.logger.Printf("Not selecting substream for own %s stream in session %s", data.RoomType, session.PublicId()) return } @@ -2819,7 +2822,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe if session.PublicId() == message.Recipient.SessionId { if err := session.IsAllowedToSend(data); err != nil { - log.Printf("Session %s is not allowed to send candidate for %s, ignoring (%s)", session.PublicId(), data.RoomType, err) + h.logger.Printf("Session %s is not allowed to send candidate for %s, ignoring (%s)", session.PublicId(), data.RoomType, err) sendNotAllowed(session, client_message, "Not allowed to send candidate.") return } @@ -2832,11 +2835,11 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe } } if err != nil { - log.Printf("Could not create MCU %s for session %s to send %+v to %s: %s", clientType, session.PublicId(), data, message.Recipient.SessionId, err) + h.logger.Printf("Could not create MCU %s for session %s to send %+v to %s: %s", clientType, session.PublicId(), data, message.Recipient.SessionId, err) sendMcuClientNotFound(session, client_message) return } else if mc == nil { - log.Printf("No MCU %s found for session %s to send %+v to %s", clientType, session.PublicId(), data, message.Recipient.SessionId) + h.logger.Printf("No MCU %s found for session %s to send %+v to %s", clientType, session.PublicId(), data, message.Recipient.SessionId) sendMcuClientNotFound(session, client_message) return } @@ -2844,7 +2847,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe mc.SendMessage(session.Context(), message, data, func(err error, response api.StringMap) { if err != nil { if !errors.Is(err, ErrCandidateFiltered) { - log.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) + h.logger.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) sendMcuProcessingFailed(session, client_message) } return @@ -2871,7 +2874,7 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient } answer_data, err := json.Marshal(answer_message) if err != nil { - log.Printf("Could not serialize answer %+v to %s: %s", answer_message, session.PublicId(), err) + h.logger.Printf("Could not serialize answer %+v to %s: %s", answer_message, session.PublicId(), err) return } response_message = &ServerMessage{ @@ -2896,7 +2899,7 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient } offer_data, err := json.Marshal(offer_message) if err != nil { - log.Printf("Could not serialize offer %+v to %s: %s", offer_message, session.PublicId(), err) + h.logger.Printf("Could not serialize offer %+v to %s: %s", offer_message, session.PublicId(), err) return } response_message = &ServerMessage{ @@ -2911,7 +2914,7 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient }, } default: - log.Printf("Unsupported response %+v received to send to %s", response, session.PublicId()) + h.logger.Printf("Unsupported response %+v received to send to %s", response, session.PublicId()) return } @@ -2952,7 +2955,7 @@ func (h *Hub) processRoomInCallChanged(message *BackendServerRoomRequest) { if err := json.Unmarshal(message.InCall.InCall, &flags); err != nil { var incall bool if err := json.Unmarshal(message.InCall.InCall, &incall); err != nil { - log.Printf("Unsupported InCall flags type: %+v, ignoring", string(message.InCall.InCall)) + h.logger.Printf("Unsupported InCall flags type: %+v, ignoring", string(message.InCall.InCall)) return } @@ -3093,18 +3096,19 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { conn, err := h.upgrader.Upgrade(w, r, header) if err != nil { - log.Printf("Could not upgrade request from %s: %s", addr, err) + h.logger.Printf("Could not upgrade request from %s: %s", addr, err) return } + ctx := NewLoggerContext(r.Context(), h.logger) if conn.Subprotocol() == JanusEventsSubprotocol { - RunJanusEventsHandler(r.Context(), h.mcu, conn, addr, agent) + RunJanusEventsHandler(ctx, h.mcu, conn, addr, agent) return } - client, err := NewClient(r.Context(), conn, addr, agent, h) + client, err := NewClient(ctx, conn, addr, agent, h) if err != nil { - log.Printf("Could not create client for %s: %s", addr, err) + h.logger.Printf("Could not create client for %s: %s", addr, err) return } @@ -3143,7 +3147,7 @@ func (h *Hub) OnLookupCountry(client HandlerClient) string { var err error country, err = h.geoip.LookupCountry(ip) if err != nil { - log.Printf("Could not lookup country for %s: %s", ip, err) + h.logger.Printf("Could not lookup country for %s: %s", ip, err) return unknownCountry } diff --git a/hub_test.go b/hub_test.go index 12c6615..3cda700 100644 --- a/hub_test.go +++ b/hub_test.go @@ -150,6 +150,8 @@ func getTestConfigWithMultipleUrls(server *httptest.Server) (*goconf.ConfigFile, } func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() registerBackendHandler(t, r) @@ -162,9 +164,9 @@ func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Serve events := getAsyncEventsForTest(t) config, err := getConfigFunc(server) require.NoError(err) - h, err := NewHub(config, events, nil, nil, nil, r, "no-version") + h, err := NewHub(ctx, config, events, nil, nil, nil, r, "no-version") require.NoError(err) - b, err := NewBackendServer(config, h, "no-version") + b, err := NewBackendServer(ctx, config, h, "no-version") require.NoError(err) require.NoError(b.Start(r)) @@ -199,6 +201,8 @@ func CreateHubWithMultipleUrlsForTest(t *testing.T) (*Hub, AsyncEvents, *mux.Rou } func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, *Hub, *mux.Router, *mux.Router, *httptest.Server, *httptest.Server) { + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) r1 := mux.NewRouter() registerBackendHandler(t, r1) @@ -231,7 +235,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http addr1, addr2 = addr2, addr1 } - events1, err := NewAsyncEvents(nats1.ClientURL()) + events1, err := NewAsyncEvents(ctx, nats1.ClientURL()) require.NoError(err) t.Cleanup(func() { events1.Close() @@ -239,11 +243,11 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http config1, err := getConfigFunc(server1) require.NoError(err) client1, _ := NewGrpcClientsForTest(t, addr2) - h1, err := NewHub(config1, events1, grpcServer1, client1, nil, r1, "no-version") + h1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") require.NoError(err) - b1, err := NewBackendServer(config1, h1, "no-version") + b1, err := NewBackendServer(ctx, config1, h1, "no-version") require.NoError(err) - events2, err := NewAsyncEvents(nats2.ClientURL()) + events2, err := NewAsyncEvents(ctx, nats2.ClientURL()) require.NoError(err) t.Cleanup(func() { events2.Close() @@ -251,9 +255,9 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http config2, err := getConfigFunc(server2) require.NoError(err) client2, _ := NewGrpcClientsForTest(t, addr1) - h2, err := NewHub(config2, events2, grpcServer2, client2, nil, r2, "no-version") + h2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") require.NoError(err) - b2, err := NewBackendServer(config2, h2, "no-version") + b2, err := NewBackendServer(ctx, config2, h2, "no-version") require.NoError(err) require.NoError(b1.Start(r1)) require.NoError(b2.Start(r2)) @@ -820,7 +824,6 @@ func performHousekeeping(hub *Hub, now time.Time) *sync.WaitGroup { func TestWebsocketFeatures(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) _, _, _, server := CreateHubForTest(t) @@ -852,7 +855,6 @@ func TestWebsocketFeatures(t *testing.T) { func TestInitialWelcome(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -873,7 +875,6 @@ func TestInitialWelcome(t *testing.T) { func TestExpectClientHello(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -900,7 +901,6 @@ func TestExpectClientHello(t *testing.T) { func TestExpectClientHelloUnsupportedVersion(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -925,7 +925,6 @@ func TestExpectClientHelloUnsupportedVersion(t *testing.T) { func TestClientHelloV1(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -939,7 +938,6 @@ func TestClientHelloV1(t *testing.T) { } func TestClientHelloV2(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -976,7 +974,6 @@ func TestClientHelloV2(t *testing.T) { } func TestClientHelloV2_IssuedInFuture(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -1002,7 +999,6 @@ func TestClientHelloV2_IssuedInFuture(t *testing.T) { } func TestClientHelloV2_IssuedFarInFuture(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -1024,7 +1020,6 @@ func TestClientHelloV2_IssuedFarInFuture(t *testing.T) { } func TestClientHelloV2_Expired(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -1046,7 +1041,6 @@ func TestClientHelloV2_Expired(t *testing.T) { } func TestClientHelloV2_IssuedAtMissing(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -1068,7 +1062,6 @@ func TestClientHelloV2_IssuedAtMissing(t *testing.T) { } func TestClientHelloV2_ExpiresAtMissing(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -1090,7 +1083,6 @@ func TestClientHelloV2_ExpiresAtMissing(t *testing.T) { } func TestClientHelloV2_CachedCapabilities(t *testing.T) { - CatchLogForTest(t) for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { require := require.New(t) @@ -1129,7 +1121,6 @@ func TestClientHelloV2_CachedCapabilities(t *testing.T) { func TestClientHelloWithSpaces(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1145,7 +1136,6 @@ func TestClientHelloWithSpaces(t *testing.T) { func TestClientHelloAllowAll(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTestWithConfig(t, func(server *httptest.Server) (*goconf.ConfigFile, error) { config, err := getTestConfig(server) @@ -1167,7 +1157,6 @@ func TestClientHelloAllowAll(t *testing.T) { } func TestClientHelloSessionLimit(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -1297,7 +1286,6 @@ func TestClientHelloSessionLimit(t *testing.T) { func TestSessionIdsUnordered(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1364,7 +1352,6 @@ func TestSessionIdsUnordered(t *testing.T) { func TestClientHelloResume(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1393,7 +1380,6 @@ func TestClientHelloResume(t *testing.T) { func TestClientHelloResumeThrottle(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1451,7 +1437,6 @@ func TestClientHelloResumeThrottle(t *testing.T) { func TestClientHelloResumeExpired(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1480,7 +1465,6 @@ func TestClientHelloResumeExpired(t *testing.T) { func TestClientHelloResumeTakeover(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1515,7 +1499,6 @@ func TestClientHelloResumeTakeover(t *testing.T) { func TestClientHelloResumeOtherHub(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1565,7 +1548,6 @@ func TestClientHelloResumeOtherHub(t *testing.T) { func TestClientHelloResumePublicId(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) // Test that a client can't resume a "public" session of another user. @@ -1608,7 +1590,6 @@ func TestClientHelloResumePublicId(t *testing.T) { func TestClientHelloByeResume(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1639,7 +1620,6 @@ func TestClientHelloByeResume(t *testing.T) { func TestClientHelloResumeAndJoin(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1711,7 +1691,6 @@ func runGrpcProxyTest(t *testing.T, f func(hub1, hub2 *Hub, server1, server2 *ht } func TestClientHelloResumeProxy(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) @@ -1762,7 +1741,6 @@ func TestClientHelloResumeProxy(t *testing.T) { } func TestClientHelloResumeProxy_Takeover(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) @@ -1817,7 +1795,6 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { } func TestClientHelloResumeProxy_Disconnect(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) @@ -1852,7 +1829,6 @@ func TestClientHelloResumeProxy_Disconnect(t *testing.T) { func TestClientHelloClient(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1867,7 +1843,6 @@ func TestClientHelloClient(t *testing.T) { func TestClientHelloClient_V3Api(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1894,7 +1869,6 @@ func TestClientHelloClient_V3Api(t *testing.T) { func TestClientHelloInternal(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1915,7 +1889,6 @@ func TestClientHelloInternal(t *testing.T) { } func TestClientMessageToSessionId(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -1935,13 +1908,11 @@ func TestClientMessageToSessionId(t *testing.T) { hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) } - mcu1, err := NewTestMCU() - require.NoError(err) + mcu1 := NewTestMCU(t) hub1.SetMcu(mcu1) if hub1 != hub2 { - mcu2, err := NewTestMCU() - require.NoError(err) + mcu2 := NewTestMCU(t) hub2.SetMcu(mcu2) } @@ -1982,7 +1953,6 @@ func TestClientMessageToSessionId(t *testing.T) { } func TestClientControlToSessionId(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2036,7 +2006,6 @@ func TestClientControlToSessionId(t *testing.T) { func TestClientControlMissingPermissions(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2092,7 +2061,6 @@ func TestClientControlMissingPermissions(t *testing.T) { func TestClientMessageToUserId(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2131,7 +2099,6 @@ func TestClientMessageToUserId(t *testing.T) { func TestClientControlToUserId(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2170,7 +2137,6 @@ func TestClientControlToUserId(t *testing.T) { func TestClientMessageToUserIdMultipleSessions(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2209,7 +2175,6 @@ func TestClientMessageToUserIdMultipleSessions(t *testing.T) { } func TestClientMessageToRoom(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2272,7 +2237,6 @@ func TestClientMessageToRoom(t *testing.T) { } func TestClientControlToRoom(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2335,7 +2299,6 @@ func TestClientControlToRoom(t *testing.T) { } func TestClientMessageToCall(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2440,7 +2403,6 @@ func TestClientMessageToCall(t *testing.T) { } func TestClientControlToCall(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2546,7 +2508,6 @@ func TestClientControlToCall(t *testing.T) { func TestJoinRoom(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2572,7 +2533,6 @@ func TestJoinRoom(t *testing.T) { func TestJoinRoomBackendBandwidth(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTestWithConfig(t, func(server *httptest.Server) (*goconf.ConfigFile, error) { @@ -2607,12 +2567,10 @@ func TestJoinRoomBackendBandwidth(t *testing.T) { func TestJoinRoomMcuBandwidth(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) hub.SetMcu(mcu) mcu.SetBandwidthLimits(1000, 2000) @@ -2638,7 +2596,6 @@ func TestJoinRoomMcuBandwidth(t *testing.T) { func TestJoinRoomPreferMcuBandwidth(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTestWithConfig(t, func(server *httptest.Server) (*goconf.ConfigFile, error) { @@ -2652,8 +2609,7 @@ func TestJoinRoomPreferMcuBandwidth(t *testing.T) { return config, nil }) - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) hub.SetMcu(mcu) // The MCU bandwidth limits overwrite any backend limits. @@ -2680,7 +2636,6 @@ func TestJoinRoomPreferMcuBandwidth(t *testing.T) { func TestJoinInvalidRoom(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2712,7 +2667,6 @@ func TestJoinInvalidRoom(t *testing.T) { func TestJoinRoomTwice(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2760,7 +2714,6 @@ func TestJoinRoomTwice(t *testing.T) { func TestExpectAnonymousJoinRoom(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2789,7 +2742,6 @@ func TestExpectAnonymousJoinRoom(t *testing.T) { func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2841,7 +2793,6 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { func TestJoinRoomChange(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2873,7 +2824,6 @@ func TestJoinRoomChange(t *testing.T) { func TestJoinMultiple(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2914,7 +2864,6 @@ func TestJoinMultiple(t *testing.T) { func TestJoinDisplaynamesPermission(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2962,7 +2911,6 @@ func TestJoinDisplaynamesPermission(t *testing.T) { func TestInitialRoomPermissions(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -2988,7 +2936,6 @@ func TestInitialRoomPermissions(t *testing.T) { func TestJoinRoomSwitchClient(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -3267,7 +3214,6 @@ func TestGetRealUserIP(t *testing.T) { func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -3312,7 +3258,6 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { func TestCombineChatRefreshWhileDisconnected(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -3387,7 +3332,6 @@ func TestCombineChatRefreshWhileDisconnected(t *testing.T) { func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -3467,7 +3411,6 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { } func TestClientTakeoverRoomSession(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -3555,15 +3498,13 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { func TestClientSendOfferPermissions(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3644,15 +3585,13 @@ func TestClientSendOfferPermissions(t *testing.T) { func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3707,7 +3646,6 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -3715,8 +3653,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3805,7 +3742,6 @@ loop: func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -3813,8 +3749,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3905,7 +3840,6 @@ loop: } func TestClientRequestOfferNotInRoom(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -3926,8 +3860,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -4066,7 +3999,6 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) // Clients can't send messages to sessions connected from other backends. hub, _, _, server := CreateHubWithMultipleBackendsForTest(t) @@ -4119,7 +4051,6 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { func TestSendBetweenDifferentUrls(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubWithMultipleUrlsForTest(t) @@ -4170,7 +4101,6 @@ func TestSendBetweenDifferentUrls(t *testing.T) { func TestNoSameRoomOnDifferentBackends(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubWithMultipleBackendsForTest(t) @@ -4244,7 +4174,6 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { func TestSameRoomOnDifferentUrls(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubWithMultipleUrlsForTest(t) @@ -4309,7 +4238,6 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { } func TestClientSendOffer(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -4330,8 +4258,7 @@ func TestClientSendOffer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -4390,15 +4317,13 @@ func TestClientSendOffer(t *testing.T) { func TestClientUnshareScreen(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) hub, _, _, server := CreateHubForTest(t) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu, err := NewTestMCU() - require.NoError(err) + mcu := NewTestMCU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -4455,7 +4380,6 @@ func TestClientUnshareScreen(t *testing.T) { } func TestVirtualClientSessions(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -4696,7 +4620,6 @@ func TestVirtualClientSessions(t *testing.T) { } func TestDuplicateVirtualSessions(t *testing.T) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -4970,7 +4893,6 @@ func TestDuplicateVirtualSessions(t *testing.T) { } func DoTestSwitchToOne(t *testing.T, details api.StringMap) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -5067,7 +4989,6 @@ func TestSwitchToOneList(t *testing.T) { } func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.StringMap) { - CatchLogForTest(t) for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -5175,7 +5096,6 @@ func TestSwitchToMultipleMixed(t *testing.T) { func TestGeoipOverrides(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) country1 := "DE" country2 := "IT" @@ -5201,7 +5121,8 @@ func TestGeoipOverrides(t *testing.T) { func TestDialoutStatus(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -5210,7 +5131,7 @@ func TestDialoutStatus(t *testing.T) { defer internalClient.CloseWithBye() require.NoError(internalClient.SendHelloInternalWithFeatures([]string{"start-dialout"})) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() MustSucceed1(t, internalClient.RunUntilHello, ctx) @@ -5357,7 +5278,6 @@ func TestDialoutStatus(t *testing.T) { func TestGracefulShutdownInitial(t *testing.T) { t.Parallel() - CatchLogForTest(t) hub, _, _, _ := CreateHubForTest(t) hub.ScheduleShutdown() @@ -5366,7 +5286,6 @@ func TestGracefulShutdownInitial(t *testing.T) { func TestGracefulShutdownOnBye(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -5393,7 +5312,6 @@ func TestGracefulShutdownOnBye(t *testing.T) { func TestGracefulShutdownOnExpiration(t *testing.T) { t.Parallel() - CatchLogForTest(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) diff --git a/mcu_common.go b/mcu_common.go index 75d5fb4..ba5a736 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -24,7 +24,6 @@ package signaling import ( "context" "fmt" - "log" "sync/atomic" "time" @@ -82,6 +81,8 @@ type McuSettings interface { } type mcuCommonSettings struct { + logger Logger + maxStreamBitrate api.AtomicBandwidth maxScreenBitrate api.AtomicBandwidth @@ -110,7 +111,7 @@ func (s *mcuCommonSettings) load(config *goconf.ConfigFile) error { maxStreamBitrateValue = int(defaultMaxStreamBitrate.Bits()) } maxStreamBitrate := api.BandwidthFromBits(uint64(maxStreamBitrateValue)) - log.Printf("Maximum bandwidth %s per publishing stream", maxStreamBitrate) + s.logger.Printf("Maximum bandwidth %s per publishing stream", maxStreamBitrate) s.maxStreamBitrate.Store(maxStreamBitrate) maxScreenBitrateValue, _ := config.GetInt("mcu", "maxscreenbitrate") @@ -118,7 +119,7 @@ func (s *mcuCommonSettings) load(config *goconf.ConfigFile) error { maxScreenBitrateValue = int(defaultMaxScreenBitrate.Bits()) } maxScreenBitrate := api.BandwidthFromBits(uint64(maxScreenBitrateValue)) - log.Printf("Maximum bandwidth %s per screensharing stream", maxScreenBitrate) + s.logger.Printf("Maximum bandwidth %s per screensharing stream", maxScreenBitrate) s.maxScreenBitrate.Store(maxScreenBitrate) return nil } diff --git a/mcu_janus.go b/mcu_janus.go index d4558d6..7e1e29b 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -26,7 +26,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "strconv" "sync" "sync/atomic" @@ -108,7 +107,7 @@ func convertIntValue(value any) (uint64, error) { } } -func getPluginIntValue(data janus.PluginData, pluginName string, key string) uint64 { +func getPluginIntValue(logger Logger, data janus.PluginData, pluginName string, key string) uint64 { val := getPluginValue(data, pluginName, key) if val == nil { return 0 @@ -116,7 +115,7 @@ func getPluginIntValue(data janus.PluginData, pluginName string, key string) uin result, err := convertIntValue(val) if err != nil { - log.Printf("Invalid value %+v for %s: %s", val, key, err) + logger.Printf("Invalid value %+v for %s: %s", val, key, err) result = 0 } return result @@ -154,8 +153,12 @@ type mcuJanusSettings struct { blockedCandidates atomic.Pointer[AllowedIps] } -func newMcuJanusSettings(config *goconf.ConfigFile) (*mcuJanusSettings, error) { - settings := &mcuJanusSettings{} +func newMcuJanusSettings(ctx context.Context, config *goconf.ConfigFile) (*mcuJanusSettings, error) { + settings := &mcuJanusSettings{ + mcuCommonSettings: mcuCommonSettings{ + logger: LoggerFromContext(ctx), + }, + } if err := settings.load(config); err != nil { return nil, err } @@ -173,7 +176,7 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { mcuTimeoutSeconds = defaultMcuTimeoutSeconds } mcuTimeout := time.Duration(mcuTimeoutSeconds) * time.Second - log.Printf("Using a timeout of %s for MCU requests", mcuTimeout) + s.logger.Printf("Using a timeout of %s for MCU requests", mcuTimeout) s.setTimeout(mcuTimeout) if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { @@ -182,10 +185,10 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { return fmt.Errorf("invalid allowedcandidates: %w", err) } - log.Printf("Candidates allowlist: %s", allowed) + s.logger.Printf("Candidates allowlist: %s", allowed) s.allowedCandidates.Store(allowed) } else { - log.Printf("No candidates allowlist") + s.logger.Printf("No candidates allowlist") s.allowedCandidates.Store(nil) } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { @@ -194,10 +197,10 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { return fmt.Errorf("invalid blockedcandidates: %w", err) } - log.Printf("Candidates blocklist: %s", blocked) + s.logger.Printf("Candidates blocklist: %s", blocked) s.blockedCandidates.Store(blocked) } else { - log.Printf("No candidates blocklist") + s.logger.Printf("No candidates blocklist") s.blockedCandidates.Store(nil) } @@ -206,11 +209,13 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { func (s *mcuJanusSettings) Reload(config *goconf.ConfigFile) { if err := s.load(config); err != nil { - log.Printf("Error reloading MCU settings: %s", err) + s.logger.Printf("Error reloading MCU settings: %s", err) } } type mcuJanus struct { + logger Logger + url string mu sync.Mutex @@ -251,12 +256,13 @@ func emptyOnConnected() {} func emptyOnDisconnected() {} func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mcu, error) { - settings, err := newMcuJanusSettings(config) + settings, err := newMcuJanusSettings(ctx, config) if err != nil { return nil, err } mcu := &mcuJanus{ + logger: LoggerFromContext(ctx), url: url, settings: settings, closeChan: make(chan struct{}, 1), @@ -290,18 +296,18 @@ func (m *mcuJanus) disconnect() { m.handle = nil m.closeChan <- struct{}{} if _, err := handle.Detach(context.TODO()); err != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err) } } if m.session != nil { if _, err := m.session.Destroy(context.TODO()); err != nil { - log.Printf("Error destroying session %d: %s", m.session.Id, err) + m.logger.Printf("Error destroying session %d: %s", m.session.Id, err) } m.session = nil } if m.gw != nil { if err := m.gw.Close(); err != nil { - log.Println("Error while closing connection to MCU", err) + m.logger.Println("Error while closing connection to MCU", err) } m.gw = nil } @@ -371,7 +377,7 @@ func (m *mcuJanus) doReconnect(ctx context.Context) { return } - log.Println("Reconnection to Janus gateway successful") + m.logger.Println("Reconnection to Janus gateway successful") m.mu.Lock() clear(m.publishers) m.publisherCreated.Reset() @@ -407,9 +413,9 @@ func (m *mcuJanus) scheduleReconnect(err error) { defer m.mu.Unlock() m.reconnectTimer.Reset(m.reconnectInterval) if err == nil { - log.Printf("Connection to Janus gateway was interrupted, reconnecting in %s", m.reconnectInterval) + m.logger.Printf("Connection to Janus gateway was interrupted, reconnecting in %s", m.reconnectInterval) } else { - log.Printf("Reconnect to Janus gateway failed (%s), reconnecting in %s", err, m.reconnectInterval) + m.logger.Printf("Reconnect to Janus gateway failed (%s), reconnecting in %s", err, m.reconnectInterval) } m.reconnectInterval = min(m.reconnectInterval*2, maxReconnectInterval) @@ -439,49 +445,49 @@ func (m *mcuJanus) Start(ctx context.Context) error { return err } - log.Printf("Connected to %s %s by %s", info.Name, info.VersionString, info.Author) + m.logger.Printf("Connected to %s %s by %s", info.Name, info.VersionString, info.Author) m.version = info.Version if plugin, found := info.Plugins[pluginVideoRoom]; found { - log.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) + m.logger.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) } else { return fmt.Errorf("plugin %s is not supported", pluginVideoRoom) } if plugin, found := info.Events[eventWebsocket]; found { if !info.EventHandlers { - log.Printf("Found %s %s by %s but event handlers are disabled, realtime usage will not be available", plugin.Name, plugin.VersionString, plugin.Author) + m.logger.Printf("Found %s %s by %s but event handlers are disabled, realtime usage will not be available", plugin.Name, plugin.VersionString, plugin.Author) } else { - log.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) + m.logger.Printf("Found %s %s by %s", plugin.Name, plugin.VersionString, plugin.Author) } } else { - log.Printf("Plugin %s not found, realtime usage will not be available", eventWebsocket) + m.logger.Printf("Plugin %s not found, realtime usage will not be available", eventWebsocket) } - log.Printf("Used dependencies: %+v", info.Dependencies) + m.logger.Printf("Used dependencies: %+v", info.Dependencies) if !info.DataChannels { return fmt.Errorf("data channels are not supported") } - log.Println("Data channels are supported") + m.logger.Println("Data channels are supported") if !info.FullTrickle { - log.Println("WARNING: Full-Trickle is NOT enabled in Janus!") + m.logger.Println("WARNING: Full-Trickle is NOT enabled in Janus!") } else { - log.Println("Full-Trickle is enabled") + m.logger.Println("Full-Trickle is enabled") } if m.session, err = m.gw.Create(ctx); err != nil { m.disconnect() return err } - log.Println("Created Janus session", m.session.Id) + m.logger.Println("Created Janus session", m.session.Id) m.connectedSince = time.Now() if m.handle, err = m.session.Attach(ctx, pluginVideoRoom); err != nil { m.disconnect() return err } - log.Println("Created Janus handle", m.handle.Id) + m.logger.Println("Created Janus handle", m.handle.Id) m.info.Store(info) @@ -627,7 +633,7 @@ func (m *mcuJanus) GetStats() any { func (m *mcuJanus) sendKeepalive(ctx context.Context) { if _, err := m.session.KeepAlive(ctx); err != nil { - log.Println("Could not send keepalive request", err) + m.logger.Println("Could not send keepalive request", err) if e, ok := err.(*janus.ErrorMsg); ok { switch e.Err.Code { case JANUS_ERROR_SESSION_NOT_FOUND: @@ -693,20 +699,20 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, create_response, err := handle.Request(ctx, create_msg) if err != nil { if _, err2 := handle.Detach(ctx); err2 != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err2) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err2) } return 0, 0, err } - roomId := getPluginIntValue(create_response.PluginData, pluginVideoRoom, "room") + roomId := getPluginIntValue(m.logger, create_response.PluginData, pluginVideoRoom, "room") if roomId == 0 { if _, err := handle.Detach(ctx); err != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err) } return 0, 0, fmt.Errorf("no room id received: %+v", create_response) } - log.Println("Created room", roomId, create_response.PluginData) + m.logger.Println("Created room", roomId, create_response.PluginData) return roomId, bitrate, nil } @@ -720,12 +726,12 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSess return nil, 0, 0, 0, err } - log.Printf("Attached %s as publisher %d to plugin %s in session %d", streamType, handle.Id, pluginVideoRoom, session.Id) + m.logger.Printf("Attached %s as publisher %d to plugin %s in session %d", streamType, handle.Id, pluginVideoRoom, session.Id) roomId, bitrate, err := m.createPublisherRoom(ctx, handle, id, streamType, settings) if err != nil { if _, err2 := handle.Detach(ctx); err2 != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err2) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err2) } return nil, 0, 0, 0, err } @@ -740,7 +746,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSess response, err := handle.Message(ctx, msg, nil) if err != nil { if _, err2 := handle.Detach(ctx); err2 != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err2) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err2) } return nil, 0, 0, 0, err } @@ -760,6 +766,7 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu client := &mcuJanusPublisher{ mcuJanusClient: mcuJanusClient{ + logger: m.logger, mcu: m, listener: listener, @@ -787,7 +794,7 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu client.mcuJanusClient.handleMedia = client.handleMedia m.registerClient(client) - log.Printf("Publisher %s is using handle %d", client.id, handle.Id) + m.logger.Printf("Publisher %s is using handle %d", client.id, handle.Id) go client.run(handle, client.closeChan) m.mu.Lock() m.publishers[getStreamId(id, streamType)] = client @@ -842,7 +849,7 @@ func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher Pu return nil, nil, err } - log.Printf("Attached subscriber to room %d of publisher %s in plugin %s in session %d as %d", pub.roomId, publisher, pluginVideoRoom, session.Id, handle.Id) + m.logger.Printf("Attached subscriber to room %d of publisher %s in plugin %s in session %d as %d", pub.roomId, publisher, pluginVideoRoom, session.Id, handle.Id) return handle, pub, nil } @@ -858,6 +865,7 @@ func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publ client := &mcuJanusSubscriber{ mcuJanusClient: mcuJanusClient{ + logger: m.logger, mcu: m, listener: listener, @@ -917,7 +925,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re roomId, maxBitrate, err := m.createPublisherRoom(ctx, handle, controller.PublisherId(), streamType, settings) if err != nil { if _, err2 := handle.Detach(ctx); err2 != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err2) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err2) } return nil, err } @@ -930,19 +938,20 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re }) if err != nil { if _, err2 := handle.Detach(ctx); err2 != nil { - log.Printf("Error detaching handle %d: %s", handle.Id, err2) + m.logger.Printf("Error detaching handle %d: %s", handle.Id, err2) } return nil, err } - id := getPluginIntValue(response.PluginData, pluginVideoRoom, "id") - port := getPluginIntValue(response.PluginData, pluginVideoRoom, "port") - rtcp_port := getPluginIntValue(response.PluginData, pluginVideoRoom, "rtcp_port") + id := getPluginIntValue(m.logger, response.PluginData, pluginVideoRoom, "id") + port := getPluginIntValue(m.logger, response.PluginData, pluginVideoRoom, "port") + rtcp_port := getPluginIntValue(m.logger, response.PluginData, pluginVideoRoom, "rtcp_port") pub = &mcuJanusRemotePublisher{ mcuJanusPublisher: mcuJanusPublisher{ mcuJanusClient: mcuJanusClient{ - mcu: m, + logger: m.logger, + mcu: m, id: id, session: response.Session, @@ -1018,11 +1027,12 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener return nil, err } - log.Printf("Attached subscriber to room %d of publisher %s in plugin %s in session %d as %d", pub.roomId, pub.id, pluginVideoRoom, session.Id, handle.Id) + m.logger.Printf("Attached subscriber to room %d of publisher %s in plugin %s in session %d as %d", pub.roomId, pub.id, pluginVideoRoom, session.Id, handle.Id) client := &mcuJanusRemoteSubscriber{ mcuJanusSubscriber: mcuJanusSubscriber{ mcuJanusClient: mcuJanusClient{ + logger: m.logger, mcu: m, listener: listener, diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 8945199..754ec2a 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -23,7 +23,6 @@ package signaling import ( "context" - "log" "reflect" "strconv" "sync" @@ -35,6 +34,7 @@ import ( ) type mcuJanusClient struct { + logger Logger mcu *mcuJanus listener McuListener mu sync.Mutex @@ -127,7 +127,7 @@ func (c *mcuJanusClient) closeClient(ctx context.Context) bool { close(c.closeChan) if _, err := handle.Detach(ctx); err != nil { if e, ok := err.(*janus.ErrorMsg); !ok || e.Err.Code != JANUS_ERROR_HANDLE_NOT_FOUND { - log.Println("Could not detach client", handle.Id, err) + c.logger.Println("Could not detach client", handle.Id, err) } } return true @@ -157,7 +157,7 @@ loop: case *TrickleMsg: c.handleTrickle(t) default: - log.Println("Received unsupported event type", msg, reflect.TypeOf(msg)) + c.logger.Println("Received unsupported event type", msg, reflect.TypeOf(msg)) } case f := <-c.deferred: f() @@ -205,7 +205,8 @@ func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer api.StringMap, c callback(err, nil) return } - log.Println("Started listener", start_response) + + c.logger.Println("Started listener", start_response) callback(nil, nil) } diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index 7d0ddc1..f49ba7a 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -26,7 +26,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "math" "net" "strconv" @@ -621,8 +620,9 @@ func (h *handleStats) LostRemote(media string, lost uint64) { type JanusEventsHandler struct { mu sync.Mutex - ctx context.Context - mcu McuEventHandler + logger Logger + ctx context.Context + mcu McuEventHandler // +checklocks:mu conn *websocket.Conn addr string @@ -654,7 +654,8 @@ func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, a client, err := NewJanusEventsHandler(ctx, m, conn, addr, agent) if err != nil { - log.Printf("Could not create Janus events handler for %s: %s", addr, err) + logger := LoggerFromContext(ctx) + logger.Printf("Could not create Janus events handler for %s: %s", addr, err) conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "error creating handler"), deadline) // nolint return } @@ -664,11 +665,12 @@ func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, a func NewJanusEventsHandler(ctx context.Context, mcu McuEventHandler, conn *websocket.Conn, addr string, agent string) (*JanusEventsHandler, error) { handler := &JanusEventsHandler{ - ctx: ctx, - mcu: mcu, - conn: conn, - addr: addr, - agent: agent, + logger: LoggerFromContext(ctx), + ctx: ctx, + mcu: mcu, + conn: conn, + addr: addr, + agent: agent, events: make(chan JanusEvent, 1), } @@ -677,7 +679,7 @@ func NewJanusEventsHandler(ctx context.Context, mcu McuEventHandler, conn *webso } func (h *JanusEventsHandler) Run() { - log.Printf("Processing Janus events from %s", h.addr) + h.logger.Printf("Processing Janus events from %s", h.addr) go h.writePump() go h.processEvents() @@ -692,7 +694,7 @@ func (h *JanusEventsHandler) close() { if conn != nil { if err := conn.Close(); err != nil { - log.Printf("Error closing %s", err) + h.logger.Printf("Error closing %s", err) } } } @@ -702,7 +704,7 @@ func (h *JanusEventsHandler) readPump() { conn := h.conn h.mu.Unlock() if conn == nil { - log.Printf("Connection from %s closed while starting readPump", h.addr) + h.logger.Printf("Connection from %s closed while starting readPump", h.addr) return } @@ -724,24 +726,24 @@ func (h *JanusEventsHandler) readPump() { websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { - log.Printf("Error reading from %s: %v", h.addr, err) + h.logger.Printf("Error reading from %s: %v", h.addr, err) } break } if messageType != websocket.TextMessage { - log.Printf("Unsupported message type %v from %s", messageType, h.addr) + h.logger.Printf("Unsupported message type %v from %s", messageType, h.addr) continue } decodeBuffer, err := bufferPool.ReadAll(reader) if err != nil { - log.Printf("Error reading message from %s: %v", h.addr, err) + h.logger.Printf("Error reading message from %s: %v", h.addr, err) break } if decodeBuffer.Len() == 0 { - log.Printf("Received empty message from %s", h.addr) + h.logger.Printf("Received empty message from %s", h.addr) bufferPool.Put(decodeBuffer) break } @@ -750,7 +752,7 @@ func (h *JanusEventsHandler) readPump() { if data := decodeBuffer.Bytes(); data[0] != '[' { var event JanusEvent if err := json.Unmarshal(data, &event); err != nil { - log.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) + h.logger.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) bufferPool.Put(decodeBuffer) break } @@ -758,7 +760,7 @@ func (h *JanusEventsHandler) readPump() { events = append(events, event) } else { if err := json.Unmarshal(data, &events); err != nil { - log.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) + h.logger.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) bufferPool.Put(decodeBuffer) break } @@ -782,7 +784,7 @@ func (h *JanusEventsHandler) sendPing() bool { msg := strconv.FormatInt(now, 10) h.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint if err := h.conn.WriteMessage(websocket.PingMessage, []byte(msg)); err != nil { - log.Printf("Could not send ping to %s: %v", h.addr, err) + h.logger.Printf("Could not send ping to %s: %v", h.addr, err) return false } @@ -848,7 +850,7 @@ func (h *JanusEventsHandler) getHandleStats(event JanusEvent) *handleStats { func (h *JanusEventsHandler) processEvent(event JanusEvent) { evt, err := event.Decode() if err != nil { - log.Printf("Error decoding event %s (%s)", event, err) + h.logger.Printf("Error decoding event %s (%s)", event, err) return } diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index b04d604..55aed9c 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -63,7 +63,9 @@ func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http. if host, _, err := net.SplitHostPort(addr); err == nil { addr = host } - RunJanusEventsHandler(r.Context(), h.mcu, conn, addr, r.Header.Get("User-Agent")) + logger := NewLoggerForTest(h.t) + ctx := NewLoggerContext(r.Context(), logger) + RunJanusEventsHandler(ctx, h.mcu, conn, addr, r.Header.Get("User-Agent")) return } diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index c0135f2..225365b 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -25,7 +25,6 @@ import ( "context" "errors" "fmt" - "log" "strconv" "strings" "sync/atomic" @@ -67,38 +66,38 @@ func (p *mcuJanusPublisher) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Publisher %d: associated room has been destroyed, closing", p.handleId.Load()) + p.logger.Printf("Publisher %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom publisher event in %d: %+v", p.handleId.Load(), event) + p.logger.Printf("Unsupported videoroom publisher event in %d: %+v", p.handleId.Load(), event) } } else { - log.Printf("Unsupported publisher event in %d: %+v", p.handleId.Load(), event) + p.logger.Printf("Unsupported publisher event in %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusPublisher) handleHangup(event *janus.HangupMsg) { - log.Printf("Publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) + p.logger.Printf("Publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusPublisher) handleDetached(event *janus.DetachedMsg) { - log.Printf("Publisher %d received detached, closing", p.handleId.Load()) + p.logger.Printf("Publisher %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusPublisher) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Publisher %d received connected", p.handleId.Load()) + p.logger.Printf("Publisher %d received connected", p.handleId.Load()) p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) } func (p *mcuJanusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -124,21 +123,21 @@ func (p *mcuJanusPublisher) NotifyReconnected() { ctx := context.TODO() handle, session, roomId, _, err := p.mcu.getOrCreatePublisherHandle(ctx, p.id, p.streamType, p.settings) if err != nil { - log.Printf("Could not reconnect publisher %s: %s", p.id, err) + p.logger.Printf("Could not reconnect publisher %s: %s", p.id, err) // TODO(jojo): Retry return } if prev := p.handle.Swap(handle); prev != nil { if _, err := prev.Detach(context.Background()); err != nil { - log.Printf("Error detaching old publisher handle %d: %s", prev.Id, err) + p.logger.Printf("Error detaching old publisher handle %d: %s", prev.Id, err) } } p.handleId.Store(handle.Id) p.session = session p.roomId = roomId - log.Printf("Publisher %s reconnected on handle %d", p.id, p.handleId.Load()) + p.logger.Printf("Publisher %s reconnected on handle %d", p.id, p.handleId.Load()) } func (p *mcuJanusPublisher) Close(ctx context.Context) { @@ -150,9 +149,9 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { "room": p.roomId, } if _, err := handle.Request(ctx, destroy_msg); err != nil { - log.Printf("Error destroying room %d: %s", p.roomId, err) + p.logger.Printf("Error destroying room %d: %s", p.roomId, err) } else { - log.Printf("Room %d destroyed", p.roomId) + p.logger.Printf("Room %d destroyed", p.roomId) } p.mcu.mu.Lock() delete(p.mcu.publishers, getStreamId(p.id, p.streamType)) @@ -215,9 +214,9 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli sdpString, found := api.GetStringMapEntry[string](jsep, "sdp") if !found { - log.Printf("No/invalid sdp found in answer %+v", jsep) + p.logger.Printf("No/invalid sdp found in answer %+v", jsep) } else if answerSdp, err := parseSDP(sdpString); err != nil { - log.Printf("Error parsing answer sdp %+v: %s", sdpString, err) + p.logger.Printf("Error parsing answer sdp %+v: %s", sdpString, err) p.answerSdp.Store(nil) p.sdpFlags.Remove(sdpHasAnswer) } else { @@ -355,7 +354,7 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, switch a.Key { case sdp.AttrKeyExtMap: if err := extmap.Unmarshal(extmap.Name() + ":" + a.Value); err != nil { - log.Printf("Error parsing extmap %s: %s", a.Value, err) + p.logger.Printf("Error parsing extmap %s: %s", a.Value, err) continue } @@ -388,7 +387,7 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, } else if strings.EqualFold(s.Type, "data") { // nolint // Already handled above. } else { - log.Printf("Skip type %s", s.Type) + p.logger.Printf("Skip type %s", s.Type) continue } @@ -423,7 +422,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSe } errorMessage := getPluginStringValue(response.PluginData, pluginVideoRoom, "error") - errorCode := getPluginIntValue(response.PluginData, pluginVideoRoom, "error_code") + errorCode := getPluginIntValue(p.logger, response.PluginData, pluginVideoRoom, "error_code") if errorMessage != "" || errorCode != 0 { if errorCode == 0 { errorCode = 500 @@ -440,7 +439,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSe } } - log.Printf("Publishing %s to %s (port=%d, rtcpPort=%d) for %s", p.id, hostname, port, rtcpPort, remoteId) + p.logger.Printf("Publishing %s to %s (port=%d, rtcpPort=%d) for %s", p.id, hostname, port, rtcpPort, remoteId) return nil } @@ -462,7 +461,7 @@ func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId Public } errorMessage := getPluginStringValue(response.PluginData, pluginVideoRoom, "error") - errorCode := getPluginIntValue(response.PluginData, pluginVideoRoom, "error_code") + errorCode := getPluginIntValue(p.logger, response.PluginData, pluginVideoRoom, "error_code") if errorMessage != "" || errorCode != 0 { if errorCode == 0 { errorCode = 500 @@ -479,6 +478,6 @@ func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId Public } } - log.Printf("Unpublished remote %s for %s", p.id, remoteId) + p.logger.Printf("Unpublished remote %s for %s", p.id, remoteId) return nil } diff --git a/mcu_janus_publisher_test.go b/mcu_janus_publisher_test.go index 98ae23b..31d3acf 100644 --- a/mcu_janus_publisher_test.go +++ b/mcu_janus_publisher_test.go @@ -102,7 +102,6 @@ func TestGetFmtpValueVP9(t *testing.T) { } func TestJanusPublisherRemote(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) diff --git a/mcu_janus_remote_publisher.go b/mcu_janus_remote_publisher.go index 078adc5..55ce873 100644 --- a/mcu_janus_remote_publisher.go +++ b/mcu_janus_remote_publisher.go @@ -23,7 +23,6 @@ package signaling import ( "context" - "log" "sync/atomic" "github.com/notedit/janus-go" @@ -63,38 +62,38 @@ func (p *mcuJanusRemotePublisher) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Remote publisher %d: associated room has been destroyed, closing", p.handleId.Load()) + p.logger.Printf("Remote publisher %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom remote publisher event in %d: %+v", p.handleId.Load(), event) + p.logger.Printf("Unsupported videoroom remote publisher event in %d: %+v", p.handleId.Load(), event) } } else { - log.Printf("Unsupported remote publisher event in %d: %+v", p.handleId.Load(), event) + p.logger.Printf("Unsupported remote publisher event in %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusRemotePublisher) handleHangup(event *janus.HangupMsg) { - log.Printf("Remote publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) + p.logger.Printf("Remote publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusRemotePublisher) handleDetached(event *janus.DetachedMsg) { - log.Printf("Remote publisher %d received detached, closing", p.handleId.Load()) + p.logger.Printf("Remote publisher %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusRemotePublisher) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Remote publisher %d received connected", p.handleId.Load()) + p.logger.Printf("Remote publisher %d received connected", p.handleId.Load()) p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) } func (p *mcuJanusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Remote publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Remote publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Remote publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Remote publisher %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -102,21 +101,21 @@ func (p *mcuJanusRemotePublisher) NotifyReconnected() { ctx := context.TODO() handle, session, roomId, _, err := p.mcu.getOrCreatePublisherHandle(ctx, p.id, p.streamType, p.settings) if err != nil { - log.Printf("Could not reconnect remote publisher %s: %s", p.id, err) + p.logger.Printf("Could not reconnect remote publisher %s: %s", p.id, err) // TODO(jojo): Retry return } if prev := p.handle.Swap(handle); prev != nil { if _, err := prev.Detach(context.Background()); err != nil { - log.Printf("Error detaching old remote publisher handle %d: %s", prev.Id, err) + p.logger.Printf("Error detaching old remote publisher handle %d: %s", prev.Id, err) } } p.handleId.Store(handle.Id) p.session = session p.roomId = roomId - log.Printf("Remote publisher %s reconnected on handle %d", p.id, p.handleId.Load()) + p.logger.Printf("Remote publisher %s reconnected on handle %d", p.id, p.handleId.Load()) } func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { @@ -125,7 +124,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { } if err := p.controller.StopPublishing(ctx, p); err != nil { - log.Printf("Error stopping remote publisher %s in room %d: %s", p.id, p.roomId, err) + p.logger.Printf("Error stopping remote publisher %s in room %d: %s", p.id, p.roomId, err) } p.mu.Lock() @@ -138,9 +137,9 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { "id": streamTypeUserIds[p.streamType], }) if err != nil { - log.Printf("Error removing remote publisher %s in room %d: %s", p.id, p.roomId, err) + p.logger.Printf("Error removing remote publisher %s in room %d: %s", p.id, p.roomId, err) } else { - log.Printf("Removed remote publisher: %+v", response) + p.logger.Printf("Removed remote publisher: %+v", response) } if p.roomId != 0 { destroy_msg := api.StringMap{ @@ -148,9 +147,9 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { "room": p.roomId, } if _, err := handle.Request(ctx, destroy_msg); err != nil { - log.Printf("Error destroying room %d: %s", p.roomId, err) + p.logger.Printf("Error destroying room %d: %s", p.roomId, err) } else { - log.Printf("Room %d destroyed", p.roomId) + p.logger.Printf("Room %d destroyed", p.roomId) } p.mcu.mu.Lock() delete(p.mcu.remotePublishers, getStreamId(p.id, p.streamType)) diff --git a/mcu_janus_remote_subscriber.go b/mcu_janus_remote_subscriber.go index 356bb65..5d984b7 100644 --- a/mcu_janus_remote_subscriber.go +++ b/mcu_janus_remote_subscriber.go @@ -23,7 +23,6 @@ package signaling import ( "context" - "log" "strconv" "sync/atomic" @@ -41,7 +40,7 @@ func (p *mcuJanusRemoteSubscriber) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Remote subscriber %d: associated room has been destroyed, closing", p.handleId.Load()) + p.logger.Printf("Remote subscriber %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "event": // Handle renegotiations, but ignore other events like selected @@ -53,33 +52,33 @@ func (p *mcuJanusRemoteSubscriber) handleEvent(event *janus.EventMsg) { case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom event %s for remote subscriber %d: %+v", videoroom, p.handleId.Load(), event) + p.logger.Printf("Unsupported videoroom event %s for remote subscriber %d: %+v", videoroom, p.handleId.Load(), event) } } else { - log.Printf("Unsupported event for remote subscriber %d: %+v", p.handleId.Load(), event) + p.logger.Printf("Unsupported event for remote subscriber %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusRemoteSubscriber) handleHangup(event *janus.HangupMsg) { - log.Printf("Remote subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) + p.logger.Printf("Remote subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusRemoteSubscriber) handleDetached(event *janus.DetachedMsg) { - log.Printf("Remote subscriber %d received detached, closing", p.handleId.Load()) + p.logger.Printf("Remote subscriber %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusRemoteSubscriber) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Remote subscriber %d received connected", p.handleId.Load()) + p.logger.Printf("Remote subscriber %d received connected", p.handleId.Load()) p.mcu.SubscriberConnected(p.Id(), p.publisher, p.streamType) } func (p *mcuJanusRemoteSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -93,21 +92,21 @@ func (p *mcuJanusRemoteSubscriber) NotifyReconnected() { handle, pub, err := p.mcu.getOrCreateSubscriberHandle(ctx, p.publisher, p.streamType) if err != nil { // TODO(jojo): Retry? - log.Printf("Could not reconnect remote subscriber for publisher %s: %s", p.publisher, err) + p.logger.Printf("Could not reconnect remote subscriber for publisher %s: %s", p.publisher, err) p.Close(context.Background()) return } if prev := p.handle.Swap(handle); prev != nil { if _, err := prev.Detach(context.Background()); err != nil { - log.Printf("Error detaching old remote subscriber handle %d: %s", prev.Id, err) + p.logger.Printf("Error detaching old remote subscriber handle %d: %s", prev.Id, err) } } p.handleId.Store(handle.Id) p.roomId = pub.roomId p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) - log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) + p.logger.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) } func (p *mcuJanusRemoteSubscriber) Close(ctx context.Context) { diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index dfb21fe..5fc1565 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -24,7 +24,6 @@ package signaling import ( "context" "fmt" - "log" "strconv" "github.com/notedit/janus-go" @@ -47,7 +46,7 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { ctx := context.TODO() switch videoroom { case "destroyed": - log.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId.Load()) + p.logger.Printf("Subscriber %d: associated room has been destroyed, closing", p.handleId.Load()) go p.Close(ctx) case "updated": streams, ok := getPluginValue(event.Plugindata, pluginVideoRoom, "streams").([]any) @@ -64,48 +63,48 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { } } - log.Printf("Subscriber %d: received updated event with no active media streams, closing", p.handleId.Load()) + p.logger.Printf("Subscriber %d: received updated event with no active media streams, closing", p.handleId.Load()) go p.Close(ctx) case "event": // Handle renegotiations, but ignore other events like selected // substream / temporal layer. if getPluginStringValue(event.Plugindata, pluginVideoRoom, "configured") == "ok" && event.Jsep != nil && event.Jsep["type"] == "offer" && event.Jsep["sdp"] != nil { - log.Printf("Subscriber %d: received updated offer", p.handleId.Load()) + p.logger.Printf("Subscriber %d: received updated offer", p.handleId.Load()) p.listener.OnUpdateOffer(p, event.Jsep) } else { - log.Printf("Subscriber %d: received unsupported event %+v", p.handleId.Load(), event) + p.logger.Printf("Subscriber %d: received unsupported event %+v", p.handleId.Load(), event) } case "slow_link": // Ignore, processed through "handleSlowLink" in the general events. default: - log.Printf("Unsupported videoroom event %s for subscriber %d: %+v", videoroom, p.handleId.Load(), event) + p.logger.Printf("Unsupported videoroom event %s for subscriber %d: %+v", videoroom, p.handleId.Load(), event) } } else { - log.Printf("Unsupported event for subscriber %d: %+v", p.handleId.Load(), event) + p.logger.Printf("Unsupported event for subscriber %d: %+v", p.handleId.Load(), event) } } func (p *mcuJanusSubscriber) handleHangup(event *janus.HangupMsg) { - log.Printf("Subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) + p.logger.Printf("Subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } func (p *mcuJanusSubscriber) handleDetached(event *janus.DetachedMsg) { - log.Printf("Subscriber %d received detached, closing", p.handleId.Load()) + p.logger.Printf("Subscriber %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } func (p *mcuJanusSubscriber) handleConnected(event *janus.WebRTCUpMsg) { - log.Printf("Subscriber %d received connected", p.handleId.Load()) + p.logger.Printf("Subscriber %d received connected", p.handleId.Load()) p.mcu.SubscriberConnected(p.Id(), p.publisher, p.streamType) } func (p *mcuJanusSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { - log.Printf("Subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { - log.Printf("Subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) + p.logger.Printf("Subscriber %s (%d) is reporting %d lost packets on the downlink (client -> Janus)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } } @@ -119,21 +118,21 @@ func (p *mcuJanusSubscriber) NotifyReconnected() { handle, pub, err := p.mcu.getOrCreateSubscriberHandle(ctx, p.publisher, p.streamType) if err != nil { // TODO(jojo): Retry? - log.Printf("Could not reconnect subscriber for publisher %s: %s", p.publisher, err) + p.logger.Printf("Could not reconnect subscriber for publisher %s: %s", p.publisher, err) p.Close(context.Background()) return } if prev := p.handle.Swap(handle); prev != nil { if _, err := prev.Detach(context.Background()); err != nil { - log.Printf("Error detaching old subscriber handle %d: %s", prev.Id, err) + p.logger.Printf("Error detaching old subscriber handle %d: %s", prev.Id, err) } } p.handleId.Store(handle.Id) p.roomId = pub.roomId p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) - log.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) + p.logger.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) } func (p *mcuJanusSubscriber) closeClient(ctx context.Context) bool { @@ -193,7 +192,7 @@ retry: return } - if error_code := getPluginIntValue(join_response.Plugindata, pluginVideoRoom, "error_code"); error_code > 0 { + if error_code := getPluginIntValue(p.logger, join_response.Plugindata, pluginVideoRoom, "error_code"); error_code > 0 { switch error_code { case JANUS_VIDEOROOM_ERROR_ALREADY_JOINED: // The subscriber is already connected to the room. This can happen @@ -219,7 +218,7 @@ retry: if prev := p.handle.Swap(handle); prev != nil { if _, err := prev.Detach(context.Background()); err != nil { - log.Printf("Error detaching old subscriber handle %d: %s", prev.Id, err) + p.logger.Printf("Error detaching old subscriber handle %d: %s", prev.Id, err) } } p.handleId.Store(handle.Id) @@ -229,19 +228,19 @@ retry: p.closeChan = make(chan struct{}, 1) statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Inc() go p.run(handle, p.closeChan) - log.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId.Load()) + p.logger.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId.Load()) goto retry case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: fallthrough case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: switch error_code { case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: - log.Printf("Publisher %s not created yet for %s, not joining room %d as subscriber", p.publisher, p.streamType, p.roomId) + p.logger.Printf("Publisher %s not created yet for %s, not joining room %d as subscriber", p.publisher, p.streamType, p.roomId) go p.Close(context.Background()) callback(fmt.Errorf("Publisher %s not created yet for %s", p.publisher, p.streamType), nil) return case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: - log.Printf("Publisher %s not sending yet for %s, wait and retry to join room %d as subscriber", p.publisher, p.streamType, p.roomId) + p.logger.Printf("Publisher %s not sending yet for %s, wait and retry to join room %d as subscriber", p.publisher, p.streamType, p.roomId) } if !loggedNotPublishingYet { @@ -254,7 +253,7 @@ retry: callback(err, nil) return } - log.Printf("Retry subscribing %s from %s", p.streamType, p.publisher) + p.logger.Printf("Retry subscribing %s from %s", p.streamType, p.publisher) goto retry default: // TODO(jojo): Should we handle other errors, too? @@ -262,7 +261,7 @@ retry: return } } - //log.Println("Joined as listener", join_response) + //p.logger.Println("Joined as listener", join_response) p.session = join_response.Session callback(nil, join_response.Jsep) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index a818f35..99eab18 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -590,7 +590,9 @@ func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { if strings.Contains(t.Name(), "Filter") { config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") } - mcu, err := NewMcuJanus(context.Background(), "", config) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + mcu, err := NewMcuJanus(ctx, "", config) require.NoError(t, err) t.Cleanup(func() { mcu.Stop() @@ -600,7 +602,7 @@ func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { mcuJanus.createJanusGateway = func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) { return gateway, nil } - require.NoError(t, mcu.Start(context.Background())) + require.NoError(t, mcu.Start(ctx)) return mcuJanus, gateway } @@ -675,7 +677,6 @@ func (i *TestMcuInitiator) Country() string { } func Test_JanusPublisherFilterOffer(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -786,7 +787,6 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { } func Test_JanusSubscriberFilterAnswer(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -908,7 +908,6 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { } func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -992,7 +991,6 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { } func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -1086,7 +1084,6 @@ func Test_JanusPublisherSubscriber(t *testing.T) { ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming")) ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing")) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -1166,7 +1163,6 @@ func Test_JanusPublisherSubscriber(t *testing.T) { } func Test_JanusSubscriberPublisher(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) @@ -1215,7 +1211,6 @@ func Test_JanusSubscriberPublisher(t *testing.T) { } func Test_JanusSubscriberRequestOffer(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -1317,7 +1312,6 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { } func Test_JanusRemotePublisher(t *testing.T) { - CatchLogForTest(t) t.Parallel() assert := assert.New(t) require := require.New(t) @@ -1409,7 +1403,6 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { } }) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -1509,7 +1502,6 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { } }) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -1619,7 +1611,6 @@ func Test_JanusSubscriberTimeout(t *testing.T) { } }) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -1723,7 +1714,6 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { } }) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -1834,7 +1824,6 @@ func Test_JanusSubscriberRoomDestroyed(t *testing.T) { } }) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) @@ -1945,7 +1934,6 @@ func Test_JanusSubscriberUpdateOffer(t *testing.T) { } }) - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) diff --git a/mcu_proxy.go b/mcu_proxy.go index 85ce622..37ee0ff 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -29,7 +29,6 @@ import ( "errors" "fmt" "iter" - "log" "math/rand/v2" "net" "net/http" @@ -80,6 +79,8 @@ type McuProxy interface { } type mcuProxyPubSubCommon struct { + logger Logger + sid string streamType StreamType maxBitrate api.Bandwidth @@ -112,7 +113,7 @@ func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClie } if proxyDebugMessages { - log.Printf("Response from %s: %+v", c.conn, response) + c.logger.Printf("Response from %s: %+v", c.conn, response) } if response.Type == "error" { callback(response.Error, nil) @@ -129,7 +130,7 @@ func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadPr case "offer": offer, ok := api.ConvertStringMap(msg.Payload["offer"]) if !ok { - log.Printf("Unsupported payload from %s: %+v", c.conn, msg) + c.logger.Printf("Unsupported payload from %s: %+v", c.conn, msg) return } @@ -137,7 +138,7 @@ func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadPr case "candidate": c.listener.OnIceCandidate(client, msg.Payload["candidate"]) default: - log.Printf("Unsupported payload from %s: %+v", c.conn, msg) + c.logger.Printf("Unsupported payload from %s: %+v", c.conn, msg) } } @@ -148,9 +149,11 @@ type mcuProxyPublisher struct { settings NewPublisherSettings } -func newMcuProxyPublisher(id PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { +func newMcuProxyPublisher(logger Logger, id PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { return &mcuProxyPublisher{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ + logger: logger, + sid: sid, streamType: streamType, maxBitrate: maxBitrate, @@ -177,7 +180,7 @@ func (p *mcuProxyPublisher) SetMedia(mt MediaType) { } func (p *mcuProxyPublisher) NotifyClosed() { - log.Printf("Publisher %s at %s was closed", p.proxyId, p.conn) + p.logger.Printf("Publisher %s at %s was closed", p.proxyId, p.conn) p.listener.PublisherClosed(p) p.conn.removePublisher(p) } @@ -194,14 +197,14 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { } if response, _, err := p.conn.performSyncRequest(ctx, msg); err != nil { - log.Printf("Could not delete publisher %s at %s: %s", p.proxyId, p.conn, err) + p.logger.Printf("Could not delete publisher %s at %s: %s", p.proxyId, p.conn, err) return } else if response.Type == "error" { - log.Printf("Could not delete publisher %s at %s: %s", p.proxyId, p.conn, response.Error) + p.logger.Printf("Could not delete publisher %s at %s: %s", p.proxyId, p.conn, response.Error) return } - log.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) + p.logger.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) } func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { @@ -229,7 +232,7 @@ func (p *mcuProxyPublisher) ProcessEvent(msg *EventProxyServerMessage) { case "publisher-closed": p.NotifyClosed() default: - log.Printf("Unsupported event from %s: %+v", p.conn, msg) + p.logger.Printf("Unsupported event from %s: %+v", p.conn, msg) } } @@ -240,9 +243,11 @@ type mcuProxySubscriber struct { publisherConn *mcuProxyConnection } -func newMcuProxySubscriber(publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { +func newMcuProxySubscriber(logger Logger, publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { return &mcuProxySubscriber{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ + logger: logger, + sid: sid, streamType: streamType, maxBitrate: maxBitrate, @@ -262,9 +267,9 @@ func (s *mcuProxySubscriber) Publisher() PublicSessionId { func (s *mcuProxySubscriber) NotifyClosed() { if s.publisherConn != nil { - log.Printf("Remote subscriber %s at %s (forwarded to %s) was closed", s.proxyId, s.conn, s.publisherConn) + s.logger.Printf("Remote subscriber %s at %s (forwarded to %s) was closed", s.proxyId, s.conn, s.publisherConn) } else { - log.Printf("Subscriber %s at %s was closed", s.proxyId, s.conn) + s.logger.Printf("Subscriber %s at %s was closed", s.proxyId, s.conn) } s.listener.SubscriberClosed(s) s.conn.removeSubscriber(s) @@ -283,24 +288,24 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { if response, _, err := s.conn.performSyncRequest(ctx, msg); err != nil { if s.publisherConn != nil { - log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", s.proxyId, s.conn, s.publisherConn, err) + s.logger.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", s.proxyId, s.conn, s.publisherConn, err) } else { - log.Printf("Could not delete subscriber %s at %s: %s", s.proxyId, s.conn, err) + s.logger.Printf("Could not delete subscriber %s at %s: %s", s.proxyId, s.conn, err) } return } else if response.Type == "error" { if s.publisherConn != nil { - log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", s.proxyId, s.conn, s.publisherConn, response.Error) + s.logger.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", s.proxyId, s.conn, s.publisherConn, response.Error) } else { - log.Printf("Could not delete subscriber %s at %s: %s", s.proxyId, s.conn, response.Error) + s.logger.Printf("Could not delete subscriber %s at %s: %s", s.proxyId, s.conn, response.Error) } return } if s.publisherConn != nil { - log.Printf("Deleted remote subscriber %s at %s (forwarded to %s)", s.proxyId, s.conn, s.publisherConn) + s.logger.Printf("Deleted remote subscriber %s at %s (forwarded to %s)", s.proxyId, s.conn, s.publisherConn) } else { - log.Printf("Deleted subscriber %s at %s", s.proxyId, s.conn) + s.logger.Printf("Deleted subscriber %s at %s", s.proxyId, s.conn) } } @@ -332,13 +337,14 @@ func (s *mcuProxySubscriber) ProcessEvent(msg *EventProxyServerMessage) { case "subscriber-closed": s.NotifyClosed() default: - log.Printf("Unsupported event from %s: %+v", s.conn, msg) + s.logger.Printf("Unsupported event from %s: %+v", s.conn, msg) } } type mcuProxyCallback func(response *ProxyServerMessage) type mcuProxyConnection struct { + logger Logger proxy *mcuProxy rawUrl string url *url.URL @@ -395,6 +401,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str } conn := &mcuProxyConnection{ + logger: proxy.logger, proxy: proxy, rawUrl: baseUrl, url: parsed, @@ -598,7 +605,7 @@ func (c *mcuProxyConnection) readPump() { rtt := now.Sub(time.Unix(0, ts)) if rtt >= rttLogDuration { rtt_ms := rtt.Nanoseconds() / time.Millisecond.Nanoseconds() - log.Printf("Proxy at %s has RTT of %d ms (%s)", c, rtt_ms, rtt) + c.logger.Printf("Proxy at %s has RTT of %d ms (%s)", c, rtt_ms, rtt) } } return nil @@ -614,14 +621,14 @@ func (c *mcuProxyConnection) readPump() { websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { - log.Printf("Error reading from %s: %v", c, err) + c.logger.Printf("Error reading from %s: %v", c, err) } break } var msg ProxyServerMessage if err := json.Unmarshal(message, &msg); err != nil { - log.Printf("Error unmarshaling %s from %s: %s", string(message), c, err) + c.logger.Printf("Error unmarshaling %s from %s: %s", string(message), c, err) continue } @@ -640,7 +647,7 @@ func (c *mcuProxyConnection) sendPing() bool { msg := strconv.FormatInt(now.UnixNano(), 10) c.conn.SetWriteDeadline(now.Add(writeWait)) // nolint if err := c.conn.WriteMessage(websocket.PingMessage, []byte(msg)); err != nil { - log.Printf("Could not send ping to proxy at %s: %v", c, err) + c.logger.Printf("Could not send ping to proxy at %s: %v", c, err) go c.scheduleReconnect() return false } @@ -692,7 +699,7 @@ func (c *mcuProxyConnection) stop(ctx context.Context) { c.closer.Close() if err := c.sendClose(); err != nil { if err != ErrNotConnected { - log.Printf("Could not send close message to %s: %s", c, err) + c.logger.Printf("Could not send close message to %s: %s", c, err) } c.close() return @@ -702,7 +709,7 @@ func (c *mcuProxyConnection) stop(ctx context.Context) { case <-c.closedDone.C: case <-ctx.Done(): if err := ctx.Err(); err != nil { - log.Printf("Error waiting for connection to %s get closed: %s", c, err) + c.logger.Printf("Error waiting for connection to %s get closed: %s", c, err) c.close() } } @@ -742,7 +749,7 @@ func (c *mcuProxyConnection) closeIfEmpty() bool { c.subscribersLock.RUnlock() if total > 0 { // Connection will be closed once all clients have disconnected. - log.Printf("Connection to %s is still used by %d clients, defer closing", c, total) + c.logger.Printf("Connection to %s is still used by %d clients, defer closing", c, total) return false } @@ -750,7 +757,7 @@ func (c *mcuProxyConnection) closeIfEmpty() bool { ctx, cancel := context.WithTimeout(context.Background(), closeTimeout) defer cancel() - log.Printf("All clients disconnected, closing connection to %s", c) + c.logger.Printf("All clients disconnected, closing connection to %s", c) c.stop(ctx) statsProxyBackendLoadCurrent.DeleteLabelValues(c.url.String()) @@ -766,7 +773,7 @@ func (c *mcuProxyConnection) closeIfEmpty() bool { func (c *mcuProxyConnection) scheduleReconnect() { if err := c.sendClose(); err != nil && err != ErrNotConnected { - log.Printf("Could not send close message to %s: %s", c, err) + c.logger.Printf("Could not send close message to %s: %s", c, err) } c.close() @@ -783,7 +790,7 @@ func (c *mcuProxyConnection) scheduleReconnect() { func (c *mcuProxyConnection) reconnect() { u, err := c.url.Parse("proxy") if err != nil { - log.Printf("Could not resolve url to proxy at %s: %s", c, err) + c.logger.Printf("Could not resolve url to proxy at %s: %s", c, err) c.scheduleReconnect() return } @@ -813,12 +820,12 @@ func (c *mcuProxyConnection) reconnect() { } conn, _, err := dialer.Dial(u.String(), nil) if err != nil { - log.Printf("Could not connect to %s: %s", c, err) + c.logger.Printf("Could not connect to %s: %s", c, err) c.scheduleReconnect() return } - log.Printf("Connected to %s", c) + c.logger.Printf("Connected to %s", c) c.closed.Store(false) c.helloProcessed.Store(false) c.connectedSince.Store(time.Now().UnixMicro()) @@ -830,7 +837,7 @@ func (c *mcuProxyConnection) reconnect() { c.reconnectInterval.Store(int64(initialReconnectInterval)) c.shutdownScheduled.Store(false) if err := c.sendHello(); err != nil { - log.Printf("Could not send hello request to %s: %s", c, err) + c.logger.Printf("Could not send hello request to %s: %s", c, err) c.scheduleReconnect() return } @@ -976,19 +983,19 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { switch msg.Type { case "error": if msg.Error.Code == "no_such_session" { - log.Printf("Session %s could not be resumed on %s, registering new", c.SessionId(), c) + c.logger.Printf("Session %s could not be resumed on %s, registering new", c.SessionId(), c) c.clearPublishers() c.clearSubscribers() c.clearCallbacks() c.sessionId.Store(PublicSessionId("")) if err := c.sendHello(); err != nil { - log.Printf("Could not send hello request to %s: %s", c, err) + c.logger.Printf("Could not send hello request to %s: %s", c, err) c.scheduleReconnect() } return } - log.Printf("Hello connection to %s failed with %+v, reconnecting", c, msg.Error) + c.logger.Printf("Hello connection to %s failed with %+v, reconnecting", c, msg.Error) c.scheduleReconnect() case "hello": resumed := c.SessionId() == msg.Hello.SessionId @@ -996,7 +1003,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { country := "" if server := msg.Hello.Server; server != nil { if country = server.Country; country != "" && !IsValidCountry(country) { - log.Printf("Proxy %s sent invalid country %s in hello response", c, country) + c.logger.Printf("Proxy %s sent invalid country %s in hello response", c, country) country = "" } c.version.Store(server.Version) @@ -1010,11 +1017,11 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { } c.country.Store(country) if resumed { - log.Printf("Resumed session %s on %s", c.SessionId(), c) + c.logger.Printf("Resumed session %s on %s", c.SessionId(), c) } else if country != "" { - log.Printf("Received session %s from %s (in %s)", c.SessionId(), c, country) + c.logger.Printf("Received session %s from %s (in %s)", c.SessionId(), c, country) } else { - log.Printf("Received session %s from %s", c.SessionId(), c) + c.logger.Printf("Received session %s from %s", c.SessionId(), c) } if c.trackClose.CompareAndSwap(false, true) { statsConnectedProxyBackendsCurrent.WithLabelValues(c.Country()).Inc() @@ -1023,14 +1030,14 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { c.helloProcessed.Store(true) c.connectedNotifier.Notify() default: - log.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) + c.logger.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) c.scheduleReconnect() } return } if proxyDebugMessages { - log.Printf("Received from %s: %+v", c, msg) + c.logger.Printf("Received from %s: %+v", c, msg) } if callback := c.getCallback(msg.Id); callback != nil { callback(msg) @@ -1048,7 +1055,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { case "bye": c.processBye(msg) default: - log.Printf("Unsupported message received from %s: %+v", c, msg) + c.logger.Printf("Unsupported message received from %s: %+v", c, msg) } } @@ -1070,25 +1077,25 @@ func (c *mcuProxyConnection) processPayload(msg *ProxyServerMessage) { return } - log.Printf("Received payload for unknown client %+v from %s", payload, c) + c.logger.Printf("Received payload for unknown client %+v from %s", payload, c) } func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { event := msg.Event switch event.Type { case "backend-disconnected": - log.Printf("Upstream backend at %s got disconnected, reset MCU objects", c) + c.logger.Printf("Upstream backend at %s got disconnected, reset MCU objects", c) c.clearPublishers() c.clearSubscribers() c.clearCallbacks() // TODO: Should we also reconnect? return case "backend-connected": - log.Printf("Upstream backend at %s is connected", c) + c.logger.Printf("Upstream backend at %s is connected", c) return case "update-load": if proxyDebugMessages { - log.Printf("Load of %s now at %d (%s)", c, event.Load, event.Bandwidth) + c.logger.Printf("Load of %s now at %d (%s)", c, event.Load, event.Bandwidth) } c.load.Store(event.Load) c.bandwidth.Store(event.Bandwidth) @@ -1109,13 +1116,13 @@ func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { } return case "shutdown-scheduled": - log.Printf("Proxy %s is scheduled to shutdown", c) + c.logger.Printf("Proxy %s is scheduled to shutdown", c) c.shutdownScheduled.Store(true) return } if proxyDebugMessages { - log.Printf("Process event from %s: %+v", c, event) + c.logger.Printf("Process event from %s: %+v", c, event) } c.publishersLock.RLock() publisher, found := c.publishers[event.ClientId] @@ -1133,20 +1140,20 @@ func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { return } - log.Printf("Received event for unknown client %+v from %s", event, c) + c.logger.Printf("Received event for unknown client %+v from %s", event, c) } func (c *mcuProxyConnection) processBye(msg *ProxyServerMessage) { bye := msg.Bye switch bye.Reason { case "session_resumed": - log.Printf("Session %s on %s was resumed by other client, resetting", c.SessionId(), c) + c.logger.Printf("Session %s on %s was resumed by other client, resetting", c.SessionId(), c) case "session_expired": - log.Printf("Session %s expired on %s, resetting", c.SessionId(), c) + c.logger.Printf("Session %s expired on %s, resetting", c.SessionId(), c) case "session_closed": - log.Printf("Session %s was closed on %s, resetting", c.SessionId(), c) + c.logger.Printf("Session %s was closed on %s, resetting", c.SessionId(), c) default: - log.Printf("Received bye with unsupported reason from %s %+v", c, bye) + c.logger.Printf("Received bye with unsupported reason from %s %+v", c, bye) } c.sessionId.Store(PublicSessionId("")) } @@ -1185,7 +1192,7 @@ func (c *mcuProxyConnection) sendMessage(msg *ProxyClientMessage) error { // +checklocks:c.mu func (c *mcuProxyConnection) sendMessageLocked(msg *ProxyClientMessage) error { if proxyDebugMessages { - log.Printf("Send message to %s: %+v", c, msg) + c.logger.Printf("Send message to %s: %+v", c, msg) } if c.conn == nil { return ErrNotConnected @@ -1246,12 +1253,12 @@ func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyC func (c *mcuProxyConnection) deferredDeletePublisher(id PublicSessionId, streamType StreamType, response *ProxyServerMessage) { if response.Type == "error" { - log.Printf("Publisher for %s was not created at %s: %s", id, c, response.Error) + c.logger.Printf("Publisher for %s was not created at %s: %s", id, c, response.Error) return } proxyId := response.Command.Id - log.Printf("Created unused %s publisher %s on %s for %s", streamType, proxyId, c, id) + c.logger.Printf("Created unused %s publisher %s on %s for %s", streamType, proxyId, c, id) msg := &ProxyClientMessage{ Type: "command", Command: &CommandProxyClientMessage{ @@ -1264,14 +1271,14 @@ func (c *mcuProxyConnection) deferredDeletePublisher(id PublicSessionId, streamT defer cancel() if response, _, err := c.performSyncRequest(ctx, msg); err != nil { - log.Printf("Could not delete publisher %s at %s: %s", proxyId, c, err) + c.logger.Printf("Could not delete publisher %s at %s: %s", proxyId, c, err) return } else if response.Type == "error" { - log.Printf("Could not delete publisher %s at %s: %s", proxyId, c, response.Error) + c.logger.Printf("Could not delete publisher %s at %s: %s", proxyId, c, response.Error) return } - log.Printf("Deleted publisher %s at %s", proxyId, c) + c.logger.Printf("Deleted publisher %s at %s", proxyId, c) } func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { @@ -1301,8 +1308,8 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe } proxyId := response.Command.Id - log.Printf("Created %s publisher %s on %s for %s", streamType, proxyId, c, id) - publisher := newMcuProxyPublisher(id, sid, streamType, response.Command.Bitrate, settings, proxyId, c, listener) + c.logger.Printf("Created %s publisher %s on %s for %s", streamType, proxyId, c, id) + publisher := newMcuProxyPublisher(c.logger, id, sid, streamType, response.Command.Bitrate, settings, proxyId, c, listener) c.publishersLock.Lock() c.publishers[proxyId] = publisher c.publisherIds[getStreamId(id, streamType)] = PublicSessionId(proxyId) @@ -1314,12 +1321,12 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { if response.Type == "error" { - log.Printf("Subscriber for %s was not created at %s: %s", publisherSessionId, c, response.Error) + c.logger.Printf("Subscriber for %s was not created at %s: %s", publisherSessionId, c, response.Error) return } proxyId := response.Command.Id - log.Printf("Created unused %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) + c.logger.Printf("Created unused %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) msg := &ProxyClientMessage{ Type: "command", @@ -1334,24 +1341,24 @@ func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId PublicS if response, _, err := c.performSyncRequest(ctx, msg); err != nil { if publisherConn != nil { - log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", proxyId, c, publisherConn, err) + c.logger.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", proxyId, c, publisherConn, err) } else { - log.Printf("Could not delete subscriber %s at %s: %s", proxyId, c, err) + c.logger.Printf("Could not delete subscriber %s at %s: %s", proxyId, c, err) } return } else if response.Type == "error" { if publisherConn != nil { - log.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", proxyId, c, publisherConn, response.Error) + c.logger.Printf("Could not delete remote subscriber %s at %s (forwarded to %s): %s", proxyId, c, publisherConn, response.Error) } else { - log.Printf("Could not delete subscriber %s at %s: %s", proxyId, c, response.Error) + c.logger.Printf("Could not delete subscriber %s at %s: %s", proxyId, c, response.Error) } return } if publisherConn != nil { - log.Printf("Deleted remote subscriber %s at %s (forwarded to %s)", proxyId, c, publisherConn) + c.logger.Printf("Deleted remote subscriber %s at %s (forwarded to %s)", proxyId, c, publisherConn) } else { - log.Printf("Deleted subscriber %s at %s", proxyId, c) + c.logger.Printf("Deleted subscriber %s at %s", proxyId, c) } } @@ -1378,8 +1385,8 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList } proxyId := response.Command.Id - log.Printf("Created %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) - subscriber := newMcuProxySubscriber(publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, nil) + c.logger.Printf("Created %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) + subscriber := newMcuProxySubscriber(c.logger, publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, nil) c.subscribersLock.Lock() c.subscribers[proxyId] = subscriber c.subscribersLock.Unlock() @@ -1425,8 +1432,8 @@ func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener M } proxyId := response.Command.Id - log.Printf("Created remote %s subscriber %s on %s for %s (forwarded to %s)", streamType, proxyId, c, publisherSessionId, publisherConn) - subscriber := newMcuProxySubscriber(publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, publisherConn) + c.logger.Printf("Created remote %s subscriber %s on %s for %s (forwarded to %s)", streamType, proxyId, c, publisherSessionId, publisherConn) + subscriber := newMcuProxySubscriber(c.logger, publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, publisherConn) c.subscribersLock.Lock() c.subscribers[proxyId] = subscriber c.subscribersLock.Unlock() @@ -1439,8 +1446,12 @@ type mcuProxySettings struct { mcuCommonSettings } -func newMcuProxySettings(config *goconf.ConfigFile) (McuSettings, error) { - settings := &mcuProxySettings{} +func newMcuProxySettings(ctx context.Context, config *goconf.ConfigFile) (McuSettings, error) { + settings := &mcuProxySettings{ + mcuCommonSettings: mcuCommonSettings{ + logger: LoggerFromContext(ctx), + }, + } if err := settings.load(config); err != nil { return nil, err } @@ -1458,18 +1469,19 @@ func (s *mcuProxySettings) load(config *goconf.ConfigFile) error { proxyTimeoutSeconds = defaultProxyTimeoutSeconds } proxyTimeout := time.Duration(proxyTimeoutSeconds) * time.Second - log.Printf("Using a timeout of %s for proxy requests", proxyTimeout) + s.logger.Printf("Using a timeout of %s for proxy requests", proxyTimeout) s.setTimeout(proxyTimeout) return nil } func (s *mcuProxySettings) Reload(config *goconf.ConfigFile) { if err := s.load(config); err != nil { - log.Printf("Error reloading proxy settings: %s", err) + s.logger.Printf("Error reloading proxy settings: %s", err) } } type mcuProxy struct { + logger Logger urlType string tokenId string tokenKey *rsa.PrivateKey @@ -1497,7 +1509,8 @@ type mcuProxy struct { rpcClients *GrpcClients } -func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients *GrpcClients, dnsMonitor *DnsMonitor) (Mcu, error) { +func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients *GrpcClients, dnsMonitor *DnsMonitor) (Mcu, error) { + logger := LoggerFromContext(ctx) urlType, _ := config.GetString("mcu", "urltype") if urlType == "" { urlType = proxyUrlTypeStatic @@ -1520,12 +1533,13 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * return nil, fmt.Errorf("could not parse private key from %s: %s", tokenKeyFilename, err) } - settings, err := newMcuProxySettings((config)) + settings, err := newMcuProxySettings(ctx, config) if err != nil { return nil, err } mcu := &mcuProxy{ + logger: logger, urlType: urlType, tokenId: tokenId, tokenKey: tokenKey, @@ -1548,7 +1562,7 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * skipverify, _ := config.GetBool("mcu", "skipverify") if skipverify { - log.Println("WARNING: MCU verification is disabled!") + logger.Println("WARNING: MCU verification is disabled!") mcu.dialer.TLSClientConfig = &tls.Config{ InsecureSkipVerify: skipverify, } @@ -1556,9 +1570,9 @@ func NewMcuProxy(config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients * switch urlType { case proxyUrlTypeStatic: - mcu.config, err = NewProxyConfigStatic(config, mcu, dnsMonitor) + mcu.config, err = NewProxyConfigStatic(logger, config, mcu, dnsMonitor) case proxyUrlTypeEtcd: - mcu.config, err = NewProxyConfigEtcd(config, etcdClient, mcu) + mcu.config, err = NewProxyConfigEtcd(logger, config, etcdClient, mcu) default: err = fmt.Errorf("unsupported proxy URL type %s", urlType) } @@ -1588,7 +1602,7 @@ func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { for option, value := range options { option = strings.ToUpper(strings.TrimSpace(option)) if !IsValidContinent(option) { - log.Printf("Ignore unknown continent %s", option) + m.logger.Printf("Ignore unknown continent %s", option) continue } @@ -1596,18 +1610,18 @@ func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { for v := range SplitEntries(value, ",") { v = strings.ToUpper(v) if !IsValidContinent(v) { - log.Printf("Ignore unknown continent %s for override %s", v, option) + m.logger.Printf("Ignore unknown continent %s for override %s", v, option) continue } values = append(values, v) } if len(values) == 0 { - log.Printf("No valid values found for continent override %s, ignoring", option) + m.logger.Printf("No valid values found for continent override %s, ignoring", option) continue } continentsMap[option] = values - log.Printf("Mapping users on continent %s to %s", option, values) + m.logger.Printf("Mapping users on continent %s to %s", option, values) } m.setContinentsMap(continentsMap) @@ -1701,7 +1715,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e conn, err := newMcuProxyConnection(m, url, nil, "") if err != nil { if ignoreErrors { - log.Printf("Could not create proxy connection to %s: %s", url, err) + m.logger.Printf("Could not create proxy connection to %s: %s", url, err) return nil } @@ -1714,7 +1728,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e conn, err := newMcuProxyConnection(m, url, ip, "") if err != nil { if ignoreErrors { - log.Printf("Could not create proxy connection to %s (%s): %s", url, ip, err) + m.logger.Printf("Could not create proxy connection to %s (%s): %s", url, ip, err) continue } @@ -1726,7 +1740,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e } for _, conn := range conns { - log.Printf("Adding new connection to %s", conn) + m.logger.Printf("Adding new connection to %s", conn) conn.start() m.connections = append(m.connections, conn) @@ -1773,7 +1787,7 @@ func (m *mcuProxy) iterateConnections(url string, ips []net.IP) iter.Seq[*mcuPro func (m *mcuProxy) RemoveConnection(url string, ips ...net.IP) { for conn := range m.iterateConnections(url, ips) { - log.Printf("Removing connection to %s", conn) + m.logger.Printf("Removing connection to %s", conn) conn.closeIfEmpty() } } @@ -1793,11 +1807,11 @@ func (m *mcuProxy) Reload(config *goconf.ConfigFile) { } if err := m.loadContinentsMap(config); err != nil { - log.Printf("Error loading continents map: %s", err) + m.logger.Printf("Error loading continents map: %s", err) } if err := m.config.Reload(config); err != nil { - log.Printf("could not reload proxy configuration: %s", err) + m.logger.Printf("could not reload proxy configuration: %s", err) } } @@ -2033,7 +2047,7 @@ func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id publisher, err := conn.newPublisher(subctx, listener, id, sid, streamType, publisherSettings) if err != nil { - log.Printf("Could not create %s publisher for %s on %s: %s", streamType, id, conn, err) + m.logger.Printf("Could not create %s publisher for %s on %s: %s", streamType, id, conn, err) continue } @@ -2160,7 +2174,7 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i subscriber, err = conn.newRemoteSubscriber(subctx, listener, info.id, publisher, streamType, info.conn, info.token) } if err != nil { - log.Printf("Could not create subscriber for %s publisher %s on %s: %s", streamType, publisher, conn, err) + m.logger.Printf("Could not create subscriber for %s publisher %s on %s: %s", streamType, publisher, conn, err) continue } @@ -2186,7 +2200,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ conn: conn, } } else { - log.Printf("No %s publisher %s found yet, deferring", streamType, publisher) + m.logger.Printf("No %s publisher %s found yet, deferring", streamType, publisher) ch := make(chan *proxyPublisherInfo, 1) getctx, cancel := context.WithCancel(ctx) defer cancel() @@ -2227,7 +2241,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ if errors.Is(err, context.Canceled) { return } else if err != nil { - log.Printf("Error getting %s publisher id %s from %s: %s", streamType, publisher, client.Target(), err) + m.logger.Printf("Error getting %s publisher id %s from %s: %s", streamType, publisher, client.Target(), err) return } else if id == "" { // Publisher not found on other server @@ -2235,7 +2249,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ } cancel() // Cancel pending RPC calls. - log.Printf("Found publisher id %s through %s on proxy %s", id, client.Target(), url) + m.logger.Printf("Found publisher id %s through %s on proxy %s", id, client.Target(), url) m.connectionsMu.RLock() connections := m.connections @@ -2254,14 +2268,14 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ if publisherConn == nil { publisherConn, err = newMcuProxyConnection(m, url, ip, connectToken) if err != nil { - log.Printf("Could not create temporary connection to %s for %s publisher %s: %s", url, streamType, publisher, err) + m.logger.Printf("Could not create temporary connection to %s for %s publisher %s: %s", url, streamType, publisher, err) return } publisherConn.setTemporary() publisherConn.start() if err := publisherConn.waitUntilConnected(ctx); err != nil { - log.Printf("Could not establish new connection to %s: %s", publisherConn, err) + m.logger.Printf("Could not establish new connection to %s: %s", publisherConn, err) publisherConn.closeIfEmpty() return } @@ -2366,7 +2380,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ if publisherInfo.conn.IsTemporary() { publisherInfo.conn.closeIfEmpty() } - log.Printf("Could not create subscriber for %s publisher %s on %s: %s", streamType, publisher, publisherInfo.conn, err) + m.logger.Printf("Could not create subscriber for %s publisher %s on %s: %s", streamType, publisher, publisherInfo.conn, err) return nil, err } diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 268eaa7..258f92a 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -883,19 +883,21 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i etcdConfig.AddOption("etcd", "endpoints", options.etcd.Config().ListenClientUrls[0].String()) etcdConfig.AddOption("etcd", "loglevel", "error") - etcdClient, err := NewEtcdClient(etcdConfig, "") + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + etcdClient, err := NewEtcdClient(logger, etcdConfig, "") require.NoError(err) t.Cleanup(func() { assert.NoError(t, etcdClient.Close()) }) - mcu, err := NewMcuProxy(cfg, etcdClient, grpcClients, dnsMonitor) + mcu, err := NewMcuProxy(ctx, cfg, etcdClient, grpcClients, dnsMonitor) require.NoError(err) t.Cleanup(func() { mcu.Stop() }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() require.NoError(mcu.Start(ctx)) @@ -942,11 +944,10 @@ func newMcuProxyForTest(t *testing.T, idx int) *mcuProxy { } func Test_ProxyAddRemoveConnections(t *testing.T) { - CatchLogForTest(t) t.Parallel() assert := assert.New(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() server1 := NewProxyServerForTest(t, "DE") @@ -1017,7 +1018,6 @@ func Test_ProxyAddRemoveConnections(t *testing.T) { } func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { - CatchLogForTest(t) assert := assert.New(t) require := require.New(t) @@ -1039,7 +1039,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { ip1, }) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ @@ -1134,11 +1134,10 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { } func Test_ProxyPublisherSubscriber(t *testing.T) { - CatchLogForTest(t) t.Parallel() mcu := newMcuProxyForTest(t, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1170,11 +1169,10 @@ func Test_ProxyPublisherSubscriber(t *testing.T) { } func Test_ProxyPublisherCodecs(t *testing.T) { - CatchLogForTest(t) t.Parallel() mcu := newMcuProxyForTest(t, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1197,11 +1195,10 @@ func Test_ProxyPublisherCodecs(t *testing.T) { } func Test_ProxyWaitForPublisher(t *testing.T) { - CatchLogForTest(t) t.Parallel() mcu := newMcuProxyForTest(t, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1247,7 +1244,6 @@ func Test_ProxyWaitForPublisher(t *testing.T) { } func Test_ProxyPublisherBandwidth(t *testing.T) { - CatchLogForTest(t) t.Parallel() server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") @@ -1256,7 +1252,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { server2, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pub1Id := PublicSessionId("the-publisher-1") @@ -1317,7 +1313,6 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { } func Test_ProxyPublisherBandwidthOverload(t *testing.T) { - CatchLogForTest(t) t.Parallel() server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") @@ -1326,7 +1321,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { server2, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pub1Id := PublicSessionId("the-publisher-1") @@ -1390,7 +1385,6 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { } func Test_ProxyPublisherLoad(t *testing.T) { - CatchLogForTest(t) t.Parallel() server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") @@ -1399,7 +1393,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { server2, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pub1Id := PublicSessionId("the-publisher-1") @@ -1440,7 +1434,6 @@ func Test_ProxyPublisherLoad(t *testing.T) { } func Test_ProxyPublisherCountry(t *testing.T) { - CatchLogForTest(t) t.Parallel() serverDE := NewProxyServerForTest(t, "DE") serverUS := NewProxyServerForTest(t, "US") @@ -1449,7 +1442,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { serverUS, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubDEId := PublicSessionId("the-publisher-de") @@ -1488,7 +1481,6 @@ func Test_ProxyPublisherCountry(t *testing.T) { } func Test_ProxyPublisherContinent(t *testing.T) { - CatchLogForTest(t) t.Parallel() serverDE := NewProxyServerForTest(t, "DE") serverUS := NewProxyServerForTest(t, "US") @@ -1497,7 +1489,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { serverUS, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubDEId := PublicSessionId("the-publisher-de") @@ -1536,7 +1528,6 @@ func Test_ProxyPublisherContinent(t *testing.T) { } func Test_ProxySubscriberCountry(t *testing.T) { - CatchLogForTest(t) t.Parallel() serverDE := NewProxyServerForTest(t, "DE") serverUS := NewProxyServerForTest(t, "US") @@ -1545,7 +1536,7 @@ func Test_ProxySubscriberCountry(t *testing.T) { serverUS, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1580,7 +1571,6 @@ func Test_ProxySubscriberCountry(t *testing.T) { } func Test_ProxySubscriberContinent(t *testing.T) { - CatchLogForTest(t) t.Parallel() serverDE := NewProxyServerForTest(t, "DE") serverUS := NewProxyServerForTest(t, "US") @@ -1589,7 +1579,7 @@ func Test_ProxySubscriberContinent(t *testing.T) { serverUS, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1624,7 +1614,6 @@ func Test_ProxySubscriberContinent(t *testing.T) { } func Test_ProxySubscriberBandwidth(t *testing.T) { - CatchLogForTest(t) t.Parallel() serverDE := NewProxyServerForTest(t, "DE") serverUS := NewProxyServerForTest(t, "US") @@ -1633,7 +1622,7 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { serverUS, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1688,7 +1677,6 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { } func Test_ProxySubscriberBandwidthOverload(t *testing.T) { - CatchLogForTest(t) t.Parallel() serverDE := NewProxyServerForTest(t, "DE") serverUS := NewProxyServerForTest(t, "US") @@ -1697,7 +1685,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { serverUS, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1806,7 +1794,6 @@ func (h *mockGrpcServerHub) CreateProxyToken(publisherId string) (string, error) } func Test_ProxyRemotePublisher(t *testing.T) { - CatchLogForTest(t) t.Parallel() etcd := NewEtcdForTest(t) @@ -1842,7 +1829,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { }, 2) hub2.proxy.Store(mcu2) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1886,7 +1873,6 @@ func Test_ProxyRemotePublisher(t *testing.T) { } func Test_ProxyMultipleRemotePublisher(t *testing.T) { - CatchLogForTest(t) t.Parallel() etcd := NewEtcdForTest(t) @@ -1938,7 +1924,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { }, 3) hub3.proxy.Store(mcu3) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -1993,7 +1979,6 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { } func Test_ProxyRemotePublisherWait(t *testing.T) { - CatchLogForTest(t) t.Parallel() etcd := NewEtcdForTest(t) @@ -2029,7 +2014,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { }, 2) hub2.proxy.Store(mcu2) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -2089,7 +2074,6 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { } func Test_ProxyRemotePublisherTemporary(t *testing.T) { - CatchLogForTest(t) t.Parallel() etcd := NewEtcdForTest(t) @@ -2123,7 +2107,7 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { }, 2) hub2.proxy.Store(mcu2) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -2198,7 +2182,6 @@ loop: } func Test_ProxyConnectToken(t *testing.T) { - CatchLogForTest(t) t.Parallel() etcd := NewEtcdForTest(t) @@ -2235,7 +2218,7 @@ func Test_ProxyConnectToken(t *testing.T) { }, 2) hub2.proxy.Store(mcu2) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -2279,7 +2262,6 @@ func Test_ProxyConnectToken(t *testing.T) { } func Test_ProxyPublisherToken(t *testing.T) { - CatchLogForTest(t) t.Parallel() etcd := NewEtcdForTest(t) @@ -2321,7 +2303,7 @@ func Test_ProxyPublisherToken(t *testing.T) { server1.servers = append(server1.servers, server2) server2.servers = append(server2.servers, server1) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -2365,7 +2347,6 @@ func Test_ProxyPublisherToken(t *testing.T) { } func Test_ProxyPublisherTimeout(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -2374,7 +2355,7 @@ func Test_ProxyPublisherTimeout(t *testing.T) { servers: []*TestProxyServerHandler{server}, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -2406,7 +2387,6 @@ func Test_ProxyPublisherTimeout(t *testing.T) { } func Test_ProxySubscriberTimeout(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -2415,7 +2395,7 @@ func Test_ProxySubscriberTimeout(t *testing.T) { servers: []*TestProxyServerHandler{server}, }, 0) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() pubId := PublicSessionId("the-publisher") @@ -2466,7 +2446,6 @@ func Test_ProxyReconnectAfter(t *testing.T) { } for _, reason := range reasons { t.Run(reason, func(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -2479,7 +2458,7 @@ func Test_ProxyReconnectAfter(t *testing.T) { require.Len(connections, 1) sessionId := connections[0].SessionId() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() client := server.GetSingleClient() @@ -2507,7 +2486,6 @@ func Test_ProxyReconnectAfter(t *testing.T) { } func Test_ProxyReconnectAfterShutdown(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -2520,7 +2498,7 @@ func Test_ProxyReconnectAfterShutdown(t *testing.T) { require.Len(connections, 1) sessionId := connections[0].SessionId() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() client := server.GetSingleClient() @@ -2547,7 +2525,6 @@ func Test_ProxyReconnectAfterShutdown(t *testing.T) { } func Test_ProxyResume(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -2560,7 +2537,7 @@ func Test_ProxyResume(t *testing.T) { require.Len(connections, 1) sessionId := connections[0].SessionId() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() client := server.GetSingleClient() @@ -2580,7 +2557,6 @@ func Test_ProxyResume(t *testing.T) { } func Test_ProxyResumeFail(t *testing.T) { - CatchLogForTest(t) t.Parallel() require := require.New(t) assert := assert.New(t) @@ -2593,7 +2569,7 @@ func Test_ProxyResumeFail(t *testing.T) { require.Len(connections, 1) sessionId := connections[0].SessionId() - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() client := server.GetSingleClient() diff --git a/mcu_test.go b/mcu_test.go index 4023a90..378a2ed 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -25,10 +25,10 @@ import ( "context" "errors" "fmt" - "log" "maps" "sync" "sync/atomic" + "testing" "github.com/dlintw/goconf" @@ -41,6 +41,7 @@ var ( ) type TestMCU struct { + t *testing.T mu sync.Mutex // +checklocks:mu publishers map[PublicSessionId]*TestMCUPublisher @@ -51,11 +52,13 @@ type TestMCU struct { maxScreenBitrate api.AtomicBandwidth } -func NewTestMCU() (*TestMCU, error) { +func NewTestMCU(t *testing.T) *TestMCU { return &TestMCU{ + t: t, + publishers: make(map[PublicSessionId]*TestMCUPublisher), subscribers: make(map[string]*TestMCUSubscriber), - }, nil + } } func (m *TestMCU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { @@ -105,6 +108,7 @@ func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id Pub } pub := &TestMCUPublisher{ TestMCUClient: TestMCUClient{ + t: m.t, id: string(id), sid: sid, streamType: streamType, @@ -147,6 +151,7 @@ func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publi id := newRandomString(8) sub := &TestMCUSubscriber{ TestMCUClient: TestMCUClient{ + t: m.t, id: id, streamType: streamType, }, @@ -157,6 +162,7 @@ func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publi } type TestMCUClient struct { + t *testing.T closed atomic.Bool id string @@ -182,7 +188,8 @@ func (c *TestMCUClient) MaxBitrate() api.Bandwidth { func (c *TestMCUClient) Close(ctx context.Context) { if c.closed.CompareAndSwap(false, true) { - log.Printf("Close MCU client %s", c.id) + logger := NewLoggerForTest(c.t) + logger.Printf("Close MCU client %s", c.id) } } diff --git a/natsclient.go b/natsclient.go index ea0a364..1474155 100644 --- a/natsclient.go +++ b/natsclient.go @@ -26,7 +26,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "log" "net/url" "os" "os/signal" @@ -64,17 +63,19 @@ func GetEncodedSubject(prefix string, suffix string) string { } type natsClient struct { - conn *nats.Conn + logger Logger + conn *nats.Conn } -func NewNatsClient(url string, options ...nats.Option) (NatsClient, error) { +func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (NatsClient, error) { + logger := LoggerFromContext(ctx) if url == ":loopback:" { - log.Printf("WARNING: events url %s is deprecated, please use %s instead", url, NatsLoopbackUrl) + logger.Printf("WARNING: events url %s is deprecated, please use %s instead", url, NatsLoopbackUrl) url = NatsLoopbackUrl } if url == NatsLoopbackUrl { - log.Println("Using internal NATS loopback client") - return NewLoopbackNatsClient() + logger.Println("Using internal NATS loopback client") + return NewLoopbackNatsClient(logger) } backoff, err := NewExponentialBackoff(initialConnectInterval, maxConnectInterval) @@ -82,7 +83,9 @@ func NewNatsClient(url string, options ...nats.Option) (NatsClient, error) { return nil, err } - client := &natsClient{} + client := &natsClient{ + logger: logger, + } options = append([]nats.Option{ nats.ClosedHandler(client.onClosed), @@ -92,12 +95,12 @@ func NewNatsClient(url string, options ...nats.Option) (NatsClient, error) { }, options...) client.conn, err = nats.Connect(url, options...) - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + ctx, stop := signal.NotifyContext(ctx, os.Interrupt) defer stop() // The initial connect must succeed, so we retry in the case of an error. for err != nil { - log.Printf("Could not create connection (%s), will retry in %s", err, backoff.NextWait()) + logger.Printf("Could not create connection (%s), will retry in %s", err, backoff.NextWait()) backoff.Wait(ctx) if ctx.Err() != nil { return nil, fmt.Errorf("interrupted") @@ -105,7 +108,7 @@ func NewNatsClient(url string, options ...nats.Option) (NatsClient, error) { client.conn, err = nats.Connect(url) } - log.Printf("Connection established to %s (%s)", removeURLCredentials(client.conn.ConnectedUrl()), client.conn.ConnectedServerId()) + logger.Printf("Connection established to %s (%s)", removeURLCredentials(client.conn.ConnectedUrl()), client.conn.ConnectedServerId()) return client, nil } @@ -114,15 +117,15 @@ func (c *natsClient) Close() { } func (c *natsClient) onClosed(conn *nats.Conn) { - log.Println("NATS client closed", conn.LastError()) + c.logger.Println("NATS client closed", conn.LastError()) } func (c *natsClient) onDisconnected(conn *nats.Conn) { - log.Println("NATS client disconnected") + c.logger.Println("NATS client disconnected") } func (c *natsClient) onReconnected(conn *nats.Conn) { - log.Printf("NATS client reconnected to %s (%s)", conn.ConnectedUrl(), conn.ConnectedServerId()) + c.logger.Printf("NATS client reconnected to %s (%s)", conn.ConnectedUrl(), conn.ConnectedServerId()) } func (c *natsClient) Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) { diff --git a/natsclient_loopback.go b/natsclient_loopback.go index 6b8f56a..1421d9a 100644 --- a/natsclient_loopback.go +++ b/natsclient_loopback.go @@ -24,7 +24,6 @@ package signaling import ( "container/list" "encoding/json" - "log" "strings" "sync" @@ -32,6 +31,8 @@ import ( ) type LoopbackNatsClient struct { + logger Logger + mu sync.Mutex // +checklocks:mu subscriptions map[string]map[*loopbackNatsSubscription]bool @@ -42,8 +43,10 @@ type LoopbackNatsClient struct { incoming list.List } -func NewLoopbackNatsClient() (NatsClient, error) { +func NewLoopbackNatsClient(logger Logger) (NatsClient, error) { client := &LoopbackNatsClient{ + logger: logger, + subscriptions: make(map[string]map[*loopbackNatsSubscription]bool), } client.wakeup.L = &client.mu @@ -85,7 +88,7 @@ func (c *LoopbackNatsClient) processMessage(msg *nats.Msg) { select { case ch <- msg: default: - log.Printf("Slow consumer %s, dropping message", msg.Subject) + c.logger.Printf("Slow consumer %s, dropping message", msg.Subject) } } } diff --git a/natsclient_loopback_test.go b/natsclient_loopback_test.go index d6cf5de..6cf6ed7 100644 --- a/natsclient_loopback_test.go +++ b/natsclient_loopback_test.go @@ -52,7 +52,8 @@ func (c *LoopbackNatsClient) waitForSubscriptionsEmpty(ctx context.Context, t *t } func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { - result, err := NewLoopbackNatsClient() + logger := NewLoggerForTest(t) + result, err := NewLoopbackNatsClient(logger) require.NoError(t, err) t.Cleanup(func() { result.Close() diff --git a/natsclient_test.go b/natsclient_test.go index 473a543..cc3b22b 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -55,7 +55,9 @@ func startLocalNatsServerPort(t *testing.T, port int) (*server.Server, int) { func CreateLocalNatsClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, NatsClient) { t.Helper() server, port := startLocalNatsServer(t) - result, err := NewNatsClient(server.ClientURL(), options...) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) + result, err := NewNatsClient(ctx, server.ClientURL(), options...) require.NoError(t, err) t.Cleanup(func() { result.Close() @@ -106,7 +108,6 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { } func TestNatsClient_Subscribe(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -121,7 +122,6 @@ func testNatsClient_PublishAfterClose(t *testing.T, client NatsClient) { } func TestNatsClient_PublishAfterClose(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -138,7 +138,6 @@ func testNatsClient_SubscribeAfterClose(t *testing.T, client NatsClient) { } func TestNatsClient_SubscribeAfterClose(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -161,7 +160,6 @@ func testNatsClient_BadSubjects(t *testing.T, client NatsClient) { } func TestNatsClient_BadSubjects(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -170,7 +168,6 @@ func TestNatsClient_BadSubjects(t *testing.T) { } func TestNatsClient_MaxReconnects(t *testing.T) { - CatchLogForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) diff --git a/proxy/main.go b/proxy/main.go index f768eb5..acc024e 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -22,6 +22,7 @@ package main import ( + "context" "flag" "fmt" "log" @@ -64,28 +65,33 @@ func main() { } sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt) signal.Notify(sigChan, syscall.SIGHUP) signal.Notify(sigChan, syscall.SIGUSR1) - log.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) + stopCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + logger := log.Default() + stopCtx = signaling.NewLoggerContext(stopCtx, logger) + + logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) config, err := goconf.ReadConfigFile(*configFlag) if err != nil { - log.Fatal("Could not read configuration: ", err) + logger.Fatal("Could not read configuration: ", err) } - log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) + logger.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) r := mux.NewRouter() - proxy, err := NewProxyServer(r, version, config) + proxy, err := NewProxyServer(stopCtx, r, version, config) if err != nil { - log.Fatal(err) + logger.Fatal(err) } if err := proxy.Start(config); err != nil { - log.Fatal(err) + logger.Fatal(err) } defer proxy.Stop() @@ -101,10 +107,10 @@ func main() { for address := range signaling.SplitEntries(addr, " ") { go func(address string) { - log.Println("Listening on", address) + logger.Println("Listening on", address) listener, err := net.Listen("tcp", address) if err != nil { - log.Fatal("Could not start listening: ", err) + logger.Fatal("Could not start listening: ", err) } srv := &http.Server{ Handler: r, @@ -114,7 +120,7 @@ func main() { WriteTimeout: time.Duration(writeTimeout) * time.Second, } if err := srv.Serve(listener); err != nil { - log.Fatal("Could not start server: ", err) + logger.Fatal("Could not start server: ", err) } }(address) } @@ -123,24 +129,24 @@ func main() { loop: for { select { + case <-stopCtx.Done(): + logger.Println("Interrupted") + break loop case sig := <-sigChan: switch sig { - case os.Interrupt: - log.Println("Interrupted") - break loop case syscall.SIGHUP: - log.Printf("Received SIGHUP, reloading %s", *configFlag) + logger.Printf("Received SIGHUP, reloading %s", *configFlag) if config, err := goconf.ReadConfigFile(*configFlag); err != nil { - log.Printf("Could not read configuration from %s: %s", *configFlag, err) + logger.Printf("Could not read configuration from %s: %s", *configFlag, err) } else { proxy.Reload(config) } case syscall.SIGUSR1: - log.Printf("Received SIGUSR1, scheduling server to shutdown") + logger.Printf("Received SIGUSR1, scheduling server to shutdown") proxy.ScheduleShutdown() } case <-proxy.ShutdownChannel(): - log.Printf("All clients disconnected, shutting down") + logger.Printf("All clients disconnected, shutting down") break loop } } diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 6eaad05..9a73122 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -27,7 +27,6 @@ import ( "crypto/tls" "encoding/json" "errors" - "log" "math/rand/v2" "net" "net/http" @@ -62,9 +61,10 @@ var ( ) type RemoteConnection struct { - mu sync.Mutex - p *ProxyServer - url *url.URL + logger signaling.Logger + mu sync.Mutex + p *ProxyServer + url *url.URL // +checklocks:mu conn *websocket.Conn closeCtx context.Context @@ -102,6 +102,7 @@ func NewRemoteConnection(p *ProxyServer, proxyUrl string, tokenId string, tokenK closeCtx, closeFunc := context.WithCancel(context.Background()) result := &RemoteConnection{ + logger: p.logger, p: p, url: u, closeCtx: closeCtx, @@ -135,7 +136,7 @@ func (c *RemoteConnection) SessionId() signaling.PublicSessionId { func (c *RemoteConnection) reconnect() { u, err := c.url.Parse("proxy") if err != nil { - log.Printf("Could not resolve url to proxy at %s: %s", c, err) + c.logger.Printf("Could not resolve url to proxy at %s: %s", c, err) c.scheduleReconnect() return } @@ -153,12 +154,12 @@ func (c *RemoteConnection) reconnect() { conn, _, err := dialer.DialContext(context.TODO(), u.String(), nil) if err != nil { - log.Printf("Error connecting to proxy at %s: %s", c, err) + c.logger.Printf("Error connecting to proxy at %s: %s", c, err) c.scheduleReconnect() return } - log.Printf("Connected to %s", c) + c.logger.Printf("Connected to %s", c) c.mu.Lock() c.connectedSince = time.Now() @@ -180,7 +181,7 @@ func (c *RemoteConnection) sendReconnectHello() bool { defer c.mu.Unlock() if err := c.sendHello(c.closeCtx); err != nil { - log.Printf("Error sending hello request to proxy at %s: %s", c, err) + c.logger.Printf("Error sending hello request to proxy at %s: %s", c, err) return false } @@ -197,7 +198,7 @@ func (c *RemoteConnection) scheduleReconnect() { // +checklocks:c.mu func (c *RemoteConnection) scheduleReconnectLocked() { if err := c.sendCloseLocked(); err != nil && err != ErrNotConnected { - log.Printf("Could not send close message to %s: %s", c, err) + c.logger.Printf("Could not send close message to %s: %s", c, err) } c.closeLocked() @@ -366,20 +367,20 @@ func (c *RemoteConnection) readPump(conn *websocket.Conn) { websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { if !errors.Is(err, net.ErrClosed) || c.closeCtx.Err() == nil { - log.Printf("Error reading from %s: %v", c, err) + c.logger.Printf("Error reading from %s: %v", c, err) } } break } if msgType != websocket.TextMessage { - log.Printf("unexpected message type %q (%s)", msgType, string(msg)) + c.logger.Printf("unexpected message type %q (%s)", msgType, string(msg)) continue } var message signaling.ProxyServerMessage if err := json.Unmarshal(msg, &message); err != nil { - log.Printf("could not decode message %s: %s", string(msg), err) + c.logger.Printf("could not decode message %s: %s", string(msg), err) continue } @@ -406,7 +407,7 @@ func (c *RemoteConnection) sendPing() bool { msg := strconv.FormatInt(now.UnixNano(), 10) c.conn.SetWriteDeadline(now.Add(writeWait)) // nolint if err := c.conn.WriteMessage(websocket.PingMessage, []byte(msg)); err != nil { - log.Printf("Could not send ping to proxy at %s: %v", c, err) + c.logger.Printf("Could not send ping to proxy at %s: %v", c, err) go c.scheduleReconnect() return false } @@ -441,16 +442,16 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { switch msg.Type { case "error": if msg.Error.Code == "no_such_session" { - log.Printf("Session %s could not be resumed on %s, registering new", c.sessionId, c) + c.logger.Printf("Session %s could not be resumed on %s, registering new", c.sessionId, c) c.sessionId = "" if err := c.sendHello(c.closeCtx); err != nil { - log.Printf("Could not send hello request to %s: %s", c, err) + c.logger.Printf("Could not send hello request to %s: %s", c, err) c.scheduleReconnectLocked() } return } - log.Printf("Hello connection to %s failed with %+v, reconnecting", c, msg.Error) + c.logger.Printf("Hello connection to %s failed with %+v, reconnecting", c, msg.Error) c.scheduleReconnectLocked() case "hello": resumed := c.sessionId == msg.Hello.SessionId @@ -459,16 +460,16 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { country := "" if msg.Hello.Server != nil { if country = msg.Hello.Server.Country; country != "" && !signaling.IsValidCountry(country) { - log.Printf("Proxy %s sent invalid country %s in hello response", c, country) + c.logger.Printf("Proxy %s sent invalid country %s in hello response", c, country) country = "" } } if resumed { - log.Printf("Resumed session %s on %s", c.sessionId, c) + c.logger.Printf("Resumed session %s on %s", c.sessionId, c) } else if country != "" { - log.Printf("Received session %s from %s (in %s)", c.sessionId, c, country) + c.logger.Printf("Received session %s from %s (in %s)", c.sessionId, c, country) } else { - log.Printf("Received session %s from %s", c.sessionId, c) + c.logger.Printf("Received session %s from %s", c.sessionId, c) } pending := c.pendingMessages @@ -479,11 +480,11 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { } if err := c.sendMessageLocked(c.closeCtx, m); err != nil { - log.Printf("Could not send pending message %+v to %s: %s", m, c, err) + c.logger.Printf("Could not send pending message %+v to %s: %s", m, c, err) } } default: - log.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) + c.logger.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) c.scheduleReconnectLocked() } } @@ -516,7 +517,7 @@ func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { case "event": c.processEvent(msg) case "bye": - log.Printf("Connection to %s was closed: %s", c, msg.Bye.Reason) + c.logger.Printf("Connection to %s was closed: %s", c, msg.Bye.Reason) if msg.Bye.Reason == "session_expired" { // Don't try to resume expired session. c.mu.Lock() @@ -525,7 +526,7 @@ func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { } c.scheduleReconnect() default: - log.Printf("Received unsupported message %+v from %s", msg, c) + c.logger.Printf("Received unsupported message %+v from %s", msg, c) } } @@ -534,10 +535,10 @@ func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { case "update-load": // Ignore case "publisher-closed": - log.Printf("Remote publisher %s was closed on %s", msg.Event.ClientId, c) + c.logger.Printf("Remote publisher %s was closed on %s", msg.Event.ClientId, c) c.p.RemotePublisherDeleted(signaling.PublicSessionId(msg.Event.ClientId)) default: - log.Printf("Received unsupported event %+v from %s", msg, c) + c.logger.Printf("Received unsupported event %+v from %s", msg, c) } } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index ad932db..72ed239 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -30,7 +30,6 @@ import ( "errors" "fmt" "io" - "log" "net" "net/http" "net/http/pprof" @@ -111,6 +110,7 @@ type ProxyServer struct { welcomeMsg *signaling.WelcomeServerMessage config *goconf.ConfigFile mcuTimeout time.Duration + logger signaling.Logger url string mcu signaling.Mcu @@ -189,16 +189,16 @@ func GetLocalIP() (string, error) { return "", nil } -func getTargetBandwidths(config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidth) { +func getTargetBandwidths(logger signaling.Logger, config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidth) { maxIncomingValue, _ := config.GetInt("bandwidth", "incoming") if maxIncomingValue < 0 { maxIncomingValue = 0 } maxIncoming := api.BandwidthFromMegabits(uint64(maxIncomingValue)) if maxIncoming > 0 { - log.Printf("Target bandwidth for incoming streams: %s", maxIncoming) + logger.Printf("Target bandwidth for incoming streams: %s", maxIncoming) } else { - log.Printf("Target bandwidth for incoming streams: unlimited") + logger.Printf("Target bandwidth for incoming streams: unlimited") } maxOutgoingValue, _ := config.GetInt("bandwidth", "outgoing") @@ -207,15 +207,16 @@ func getTargetBandwidths(config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidt } maxOutgoing := api.BandwidthFromMegabits(uint64(maxOutgoingValue)) if maxOutgoing > 0 { - log.Printf("Target bandwidth for outgoing streams: %s", maxOutgoing) + logger.Printf("Target bandwidth for outgoing streams: %s", maxOutgoing) } else { - log.Printf("Target bandwidth for outgoing streams: unlimited") + logger.Printf("Target bandwidth for outgoing streams: unlimited") } return maxIncoming, maxOutgoing } -func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*ProxyServer, error) { +func NewProxyServer(ctx context.Context, r *mux.Router, version string, config *goconf.ConfigFile) (*ProxyServer, error) { + logger := signaling.LoggerFromContext(ctx) hashKey := make([]byte, 64) if _, err := rand.Read(hashKey); err != nil { return nil, fmt.Errorf("could not generate random hash key: %s", err) @@ -235,9 +236,9 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* switch tokenType { case TokenTypeEtcd: - tokens, err = NewProxyTokensEtcd(config) + tokens, err = NewProxyTokensEtcd(logger, config) case TokenTypeStatic: - tokens, err = NewProxyTokensStatic(config) + tokens, err = NewProxyTokensStatic(logger, config) default: return nil, fmt.Errorf("unsupported token type configured: %s", tokenType) } @@ -252,10 +253,10 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* } if !statsAllowedIps.Empty() { - log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) + logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { statsAllowedIps = signaling.DefaultAllowedIps() - log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) + logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } trustedProxies, _ := config.GetString("app", "trustedproxies") @@ -265,20 +266,20 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* } if !trustedProxiesIps.Empty() { - log.Printf("Trusted proxies: %s", trustedProxiesIps) + logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { trustedProxiesIps = signaling.DefaultTrustedProxies - log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) + logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } country, _ := config.GetString("app", "country") country = strings.ToUpper(country) if signaling.IsValidCountry(country) { - log.Printf("Sending %s as country information", country) + logger.Printf("Sending %s as country information", country) } else if country != "" { return nil, fmt.Errorf("invalid country: %s", country) } else { - log.Printf("Not sending country information") + logger.Printf("Not sending country information") } welcome := map[string]string{ @@ -308,7 +309,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* if err != nil { return nil, fmt.Errorf("could not parse private key from %s: %s", tokenKeyFilename, err) } - log.Printf("Using \"%s\" as token id for remote streams", tokenId) + logger.Printf("Using \"%s\" as token id for remote streams", tokenId) remoteHostname, _ = config.GetString("app", "hostname") if remoteHostname == "" { @@ -318,23 +319,23 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* } } if remoteHostname == "" { - log.Printf("WARNING: Could not determine hostname for remote streams, will be disabled. Please configure manually.") + logger.Printf("WARNING: Could not determine hostname for remote streams, will be disabled. Please configure manually.") } else { - log.Printf("Using \"%s\" as hostname for remote streams", remoteHostname) + logger.Printf("Using \"%s\" as hostname for remote streams", remoteHostname) } skipverify, _ := config.GetBool("backend", "skipverify") if skipverify { - log.Println("WARNING: Remote stream requests verification is disabled!") + logger.Println("WARNING: Remote stream requests verification is disabled!") remoteTlsConfig = &tls.Config{ InsecureSkipVerify: skipverify, } } } else { - log.Printf("No token id configured, remote streams will be disabled") + logger.Printf("No token id configured, remote streams will be disabled") } - maxIncoming, maxOutgoing := getTargetBandwidths(config) + maxIncoming, maxOutgoing := getTargetBandwidths(logger, config) mcuTimeoutSeconds, _ := config.GetInt("mcu", "timeout") if mcuTimeoutSeconds <= 0 { @@ -353,6 +354,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* }, config: config, mcuTimeout: mcuTimeout, + logger: logger, shutdownChannel: make(chan struct{}), @@ -389,7 +391,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (* statsLoadCurrent.Set(0) if debug, _ := config.GetBool("app", "debug"); debug { - log.Println("Installing debug handlers in \"/debug/pprof\"") + logger.Println("Installing debug handlers in \"/debug/pprof\"") s := r.PathPrefix("/debug/pprof").Subrouter() s.HandleFunc("", result.setCommonHeaders(result.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/debug/pprof/", http.StatusTemporaryRedirect) @@ -455,14 +457,14 @@ func (s *ProxyServer) Start(config *goconf.ConfigFile) error { mcu.SetOnDisconnected(s.onMcuDisconnected) err = mcu.Start(ctx) if err != nil { - log.Printf("Could not create %s MCU at %s: %s", mcuType, s.url, err) + s.logger.Printf("Could not create %s MCU at %s: %s", mcuType, s.url, err) } } if err == nil { break } - log.Printf("Could not initialize %s MCU at %s (%s) will retry in %s", mcuType, s.url, err, backoff.NextWait()) + s.logger.Printf("Could not initialize %s MCU at %s (%s) will retry in %s", mcuType, s.url, err, backoff.NextWait()) backoff.Wait(ctx) if ctx.Err() != nil { return fmt.Errorf("cancelled") @@ -572,7 +574,7 @@ func (s *ProxyServer) expireSessions() { continue } - log.Printf("Delete expired session %s", session.PublicId()) + s.logger.Printf("Delete expired session %s", session.PublicId()) s.deleteSessionLocked(session.Sid()) } } @@ -616,30 +618,30 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { statsAllowed, _ := config.GetString("stats", "allowed_ips") if statsAllowedIps, err := signaling.ParseAllowedIps(statsAllowed); err == nil { if !statsAllowedIps.Empty() { - log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) + s.logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { statsAllowedIps = signaling.DefaultAllowedIps() - log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) + s.logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } s.statsAllowedIps.Store(statsAllowedIps) } else { - log.Printf("Error parsing allowed stats ips from \"%s\": %s", statsAllowedIps, err) + s.logger.Printf("Error parsing allowed stats ips from \"%s\": %s", statsAllowedIps, err) } trustedProxies, _ := config.GetString("app", "trustedproxies") if trustedProxiesIps, err := signaling.ParseAllowedIps(trustedProxies); err == nil { if !trustedProxiesIps.Empty() { - log.Printf("Trusted proxies: %s", trustedProxiesIps) + s.logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { trustedProxiesIps = signaling.DefaultTrustedProxies - log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) + s.logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } s.trustedProxies.Store(trustedProxiesIps) } else { - log.Printf("Error parsing trusted proxies from \"%s\": %s", trustedProxies, err) + s.logger.Printf("Error parsing trusted proxies from \"%s\": %s", trustedProxies, err) } - maxIncoming, maxOutgoing := getTargetBandwidths(config) + maxIncoming, maxOutgoing := getTargetBandwidths(s.logger, config) oldIncoming := s.maxIncoming.Swap(maxIncoming) oldOutgoing := s.maxOutgoing.Swap(maxOutgoing) if oldIncoming != maxIncoming || oldOutgoing != maxOutgoing { @@ -672,19 +674,20 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { header.Set("X-Spreed-Signaling-Features", strings.Join(s.welcomeMsg.Features, ", ")) conn, err := s.upgrader.Upgrade(w, r, header) if err != nil { - log.Printf("Could not upgrade request from %s: %s", addr, err) + s.logger.Printf("Could not upgrade request from %s: %s", addr, err) return } + ctx := signaling.NewLoggerContext(r.Context(), s.logger) if conn.Subprotocol() == signaling.JanusEventsSubprotocol { agent := r.Header.Get("User-Agent") - signaling.RunJanusEventsHandler(r.Context(), s.mcu, conn, addr, agent) + signaling.RunJanusEventsHandler(ctx, s.mcu, conn, addr, agent) return } - client, err := NewProxyClient(r.Context(), s, conn, addr) + client, err := NewProxyClient(ctx, s, conn, addr) if err != nil { - log.Printf("Could not create client for %s: %s", addr, err) + s.logger.Printf("Could not create client for %s: %s", addr, err) return } @@ -693,11 +696,11 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { } func (s *ProxyServer) clientClosed(client *signaling.Client) { - log.Printf("Connection from %s closed", client.RemoteAddr()) + s.logger.Printf("Connection from %s closed", client.RemoteAddr()) } func (s *ProxyServer) onMcuConnected() { - log.Printf("Connection to %s established", s.url) + s.logger.Printf("Connection to %s established", s.url) msg := &signaling.ProxyServerMessage{ Type: "event", Event: &signaling.EventProxyServerMessage{ @@ -716,7 +719,7 @@ func (s *ProxyServer) onMcuDisconnected() { return } - log.Printf("Connection to %s lost", s.url) + s.logger.Printf("Connection to %s lost", s.url) msg := &signaling.ProxyServerMessage{ Type: "event", Event: &signaling.EventProxyServerMessage{ @@ -747,14 +750,14 @@ func (s *ProxyServer) sendShutdownScheduled(session *ProxySession) { func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { if proxyDebugMessages { - log.Printf("Message: %s", string(data)) + s.logger.Printf("Message: %s", string(data)) } var message signaling.ProxyClientMessage if err := message.UnmarshalJSON(data); err != nil { if session := client.GetSession(); session != nil { - log.Printf("Error decoding message from client %s: %v", session.PublicId(), err) + s.logger.Printf("Error decoding message from client %s: %v", session.PublicId(), err) } else { - log.Printf("Error decoding message from %s: %v", client.RemoteAddr(), err) + s.logger.Printf("Error decoding message from %s: %v", client.RemoteAddr(), err) } client.SendError(signaling.InvalidFormat) return @@ -762,9 +765,9 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { if err := message.CheckValid(); err != nil { if session := client.GetSession(); session != nil { - log.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) + s.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) } else { - log.Printf("Invalid message %+v from %s: %v", message, client.RemoteAddr(), err) + s.logger.Printf("Invalid message %+v from %s: %v", message, client.RemoteAddr(), err) } client.SendMessage(message.NewErrorServerMessage(signaling.InvalidFormat)) return @@ -788,7 +791,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { return } - log.Printf("Resumed session %s", session.PublicId()) + s.logger.Printf("Resumed session %s", session.PublicId()) session.MarkUsed() if s.shutdownScheduled.Load() { s.sendShutdownScheduled(session) @@ -945,7 +948,7 @@ func (s *ProxyServer) addRemotePublisher(publisher *proxyRemotePublisher) { } publishers[publisher] = true - log.Printf("Add remote publisher to %s", publisher.remoteUrl) + s.logger.Printf("Add remote publisher to %s", publisher.remoteUrl) } func (s *ProxyServer) hasRemotePublishers() bool { @@ -959,7 +962,7 @@ func (s *ProxyServer) removeRemotePublisher(publisher *proxyRemotePublisher) { s.remoteConnectionsLock.Lock() defer s.remoteConnectionsLock.Unlock() - log.Printf("Removing remote publisher to %s", publisher.remoteUrl) + s.logger.Printf("Removing remote publisher to %s", publisher.remoteUrl) publishers, found := s.remotePublishers[publisher.remoteUrl] if !found { return @@ -974,9 +977,9 @@ func (s *ProxyServer) removeRemotePublisher(publisher *proxyRemotePublisher) { if conn, found := s.remoteConnections[publisher.remoteUrl]; found { delete(s.remoteConnections, publisher.remoteUrl) if err := conn.Close(); err != nil { - log.Printf("Error closing remote connection to %s: %s", publisher.remoteUrl, err) + s.logger.Printf("Error closing remote connection to %s: %s", publisher.remoteUrl, err) } else { - log.Printf("Remote connection to %s closed", publisher.remoteUrl) + s.logger.Printf("Remote connection to %s closed", publisher.remoteUrl) } } } @@ -1006,16 +1009,16 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s } publisher, err := s.mcu.NewPublisher(ctx2, session, signaling.PublicSessionId(id), cmd.Sid, cmd.StreamType, *settings, &emptyInitiator{}) if err == context.DeadlineExceeded { - log.Printf("Timeout while creating %s publisher %s for %s", cmd.StreamType, id, session.PublicId()) + s.logger.Printf("Timeout while creating %s publisher %s for %s", cmd.StreamType, id, session.PublicId()) session.sendMessage(message.NewErrorServerMessage(TimeoutCreatingPublisher)) return } else if err != nil { - log.Printf("Error while creating %s publisher %s for %s: %s", cmd.StreamType, id, session.PublicId(), err) + s.logger.Printf("Error while creating %s publisher %s for %s: %s", cmd.StreamType, id, session.PublicId(), err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return } - log.Printf("Created %s publisher %s as %s for %s", cmd.StreamType, publisher.Id(), id, session.PublicId()) + s.logger.Printf("Created %s publisher %s as %s for %s", cmd.StreamType, publisher.Id(), id, session.PublicId()) session.StorePublisher(ctx, id, publisher) s.StoreClient(id, publisher) @@ -1038,7 +1041,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s handleCreateError := func(err error) { if err == context.DeadlineExceeded { - log.Printf("Timeout while creating %s subscriber on %s for %s", cmd.StreamType, publisherId, session.PublicId()) + s.logger.Printf("Timeout while creating %s subscriber on %s for %s", cmd.StreamType, publisherId, session.PublicId()) session.sendMessage(message.NewErrorServerMessage(TimeoutCreatingSubscriber)) return } else if errors.Is(err, signaling.ErrRemoteStreamsNotSupported) { @@ -1046,7 +1049,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - log.Printf("Error while creating %s subscriber on %s for %s: %s", cmd.StreamType, publisherId, session.PublicId(), err) + s.logger.Printf("Error while creating %s subscriber on %s for %s: %s", cmd.StreamType, publisherId, session.PublicId(), err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) } @@ -1080,7 +1083,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s subCtx, cancel := context.WithTimeout(ctx, remotePublisherTimeout) defer cancel() - log.Printf("Creating remote subscriber for %s on %s", publisherId, cmd.RemoteUrl) + s.logger.Printf("Creating remote subscriber for %s on %s", publisherId, cmd.RemoteUrl) controller := &proxyRemotePublisher{ proxy: s, @@ -1107,7 +1110,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - log.Printf("Created remote %s subscriber %s as %s for %s on %s", cmd.StreamType, subscriber.Id(), id, session.PublicId(), cmd.RemoteUrl) + s.logger.Printf("Created remote %s subscriber %s as %s for %s on %s", cmd.StreamType, subscriber.Id(), id, session.PublicId(), cmd.RemoteUrl) } else { ctx2, cancel := context.WithTimeout(ctx, s.mcuTimeout) defer cancel() @@ -1118,7 +1121,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - log.Printf("Created %s subscriber %s as %s for %s", cmd.StreamType, subscriber.Id(), id, session.PublicId()) + s.logger.Printf("Created %s subscriber %s as %s for %s", cmd.StreamType, subscriber.Id(), id, session.PublicId()) } session.StoreSubscriber(ctx, id, subscriber) @@ -1158,7 +1161,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s } go func() { - log.Printf("Closing %s publisher %s as %s", client.StreamType(), client.Id(), cmd.ClientId) + s.logger.Printf("Closing %s publisher %s as %s", client.StreamType(), client.Id(), cmd.ClientId) client.Close(context.Background()) }() @@ -1193,7 +1196,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s } go func() { - log.Printf("Closing %s subscriber %s as %s", client.StreamType(), client.Id(), cmd.ClientId) + s.logger.Printf("Closing %s subscriber %s as %s", client.StreamType(), client.Id(), cmd.ClientId) client.Close(context.Background()) }() @@ -1224,7 +1227,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s if err := publisher.PublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { var je *janus.ErrorMsg if !errors.As(err, &je) || je.Err.Code != signaling.JANUS_VIDEOROOM_ERROR_ID_EXISTS { - log.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) + s.logger.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1233,7 +1236,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s defer cancel() if err := publisher.UnpublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { - log.Printf("Error unpublishing old %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) + s.logger.Printf("Error unpublishing old %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1242,7 +1245,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s defer cancel() if err := publisher.PublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { - log.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) + s.logger.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1274,7 +1277,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s defer cancel() if err := publisher.UnpublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { - log.Printf("Error unpublishing %s %s from remote %s: %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, err) + s.logger.Printf("Error unpublishing %s %s from remote %s: %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1304,7 +1307,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s streams, err := publisher.GetStreams(ctx) if err != nil { - log.Printf("Could not get streams of publisher %s: %s", publisher.Id(), err) + s.logger.Printf("Could not get streams of publisher %s: %s", publisher.Id(), err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return } @@ -1319,7 +1322,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s } session.sendMessage(response) default: - log.Printf("Unsupported command %+v", message.Command) + s.logger.Printf("Unsupported command %+v", message.Command) session.sendMessage(message.NewErrorServerMessage(UnsupportedCommand)) } } @@ -1374,7 +1377,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s } if err := mcuData.CheckValid(); err != nil { - log.Printf("Received invalid payload %+v for %s client %s: %s", mcuData, mcuClient.StreamType(), payload.ClientId, err) + s.logger.Printf("Received invalid payload %+v for %s client %s: %s", mcuData, mcuClient.StreamType(), payload.ClientId, err) session.sendMessage(message.NewErrorServerMessage(UnsupportedPayload)) return } @@ -1390,7 +1393,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s } if err != nil { - log.Printf("Error sending %+v to %s client %s: %s", mcuData, mcuClient.StreamType(), payload.ClientId, err) + s.logger.Printf("Error sending %+v to %s client %s: %s", mcuData, mcuClient.StreamType(), payload.ClientId, err) responseMsg = message.NewWrappedErrorServerMessage(err) } else { responseMsg = &signaling.ProxyServerMessage{ @@ -1409,7 +1412,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s } func (s *ProxyServer) processBye(ctx context.Context, client *ProxyClient, session *ProxySession, message *signaling.ProxyClientMessage) { - log.Printf("Closing session %s", session.PublicId()) + s.logger.Printf("Closing session %s", session.PublicId()) s.DeleteSession(session.Sid()) } @@ -1418,27 +1421,27 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str token, err := jwt.ParseWithClaims(tokenValue, &signaling.TokenClaims{}, func(token *jwt.Token) (any, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - log.Printf("Unexpected signing method: %v", token.Header["alg"]) + s.logger.Printf("Unexpected signing method: %v", token.Header["alg"]) reason = "unsupported-signing-method" return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } claims, ok := token.Claims.(*signaling.TokenClaims) if !ok { - log.Printf("Unsupported claims type: %+v", token.Claims) + s.logger.Printf("Unsupported claims type: %+v", token.Claims) reason = "unsupported-claims" return nil, fmt.Errorf("unsupported claims type") } tokenKey, err := s.tokens.Get(claims.Issuer) if err != nil { - log.Printf("Could not get token for %s: %s", claims.Issuer, err) + s.logger.Printf("Could not get token for %s: %s", claims.Issuer, err) reason = "missing-issuer" return nil, err } if tokenKey == nil || tokenKey.key == nil { - log.Printf("Issuer %s is not supported", claims.Issuer) + s.logger.Printf("Issuer %s is not supported", claims.Issuer) reason = "unsupported-issuer" return nil, fmt.Errorf("no key found for issuer") } @@ -1475,7 +1478,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*ProxySession, error) { if proxyDebugMessages { - log.Printf("Hello: %+v", hello) + s.logger.Printf("Hello: %+v", hello) } claims, reason, err := s.parseToken(hello.Token) @@ -1499,7 +1502,7 @@ func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*Pro return nil, err } - log.Printf("Created session %s for %+v", encoded, claims) + s.logger.Printf("Created session %s for %+v", encoded, claims) session := NewProxySession(s, sid, encoded) s.StoreSession(sid, session) statsSessionsCurrent.Inc() @@ -1669,7 +1672,7 @@ func (s *ProxyServer) statsHandler(w http.ResponseWriter, r *http.Request) { stats := s.getStats() statsData, err := json.MarshalIndent(stats, "", " ") if err != nil { - log.Printf("Could not serialize stats %+v: %s", stats, err) + s.logger.Printf("Could not serialize stats %+v: %s", stats, err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 39b9324..76b9b3d 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -138,7 +138,9 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httpte config := goconf.NewConfigFile() config.AddOption("tokens", TokenIdForTest, pubkey.Name()) - proxy, err = NewProxyServer(r, "0.0", config) + logger := signaling.NewLoggerForTest(t) + ctx := signaling.NewLoggerContext(t.Context(), logger) + proxy, err = NewProxyServer(ctx, r, "0.0", config) require.NoError(err) server := httptest.NewServer(r) @@ -150,7 +152,6 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httpte } func TestTokenValid(t *testing.T) { - signaling.CatchLogForTest(t) proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -173,7 +174,6 @@ func TestTokenValid(t *testing.T) { } func TestTokenNotSigned(t *testing.T) { - signaling.CatchLogForTest(t) proxy, _, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -198,7 +198,6 @@ func TestTokenNotSigned(t *testing.T) { } func TestTokenUnknown(t *testing.T) { - signaling.CatchLogForTest(t) proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -223,7 +222,6 @@ func TestTokenUnknown(t *testing.T) { } func TestTokenInFuture(t *testing.T) { - signaling.CatchLogForTest(t) proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -248,7 +246,6 @@ func TestTokenInFuture(t *testing.T) { } func TestTokenExpired(t *testing.T) { - signaling.CatchLogForTest(t) proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -305,7 +302,6 @@ func TestPublicIPs(t *testing.T) { } func TestWebsocketFeatures(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) _, _, server := newProxyServerForTest(t) @@ -333,7 +329,6 @@ func TestWebsocketFeatures(t *testing.T) { } func TestProxyCreateSession(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) _, key, server := newProxyServerForTest(t) @@ -485,7 +480,6 @@ func NewPublisherTestMCU(t *testing.T) *PublisherTestMCU { } func TestProxyPublisherBandwidth(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -605,7 +599,6 @@ func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.M } func TestProxyCancelOnClose(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -684,7 +677,6 @@ func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.Mcu } func TestProxyCodecs(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -767,7 +759,6 @@ func NewStreamTestMCU(t *testing.T, streams []signaling.PublisherStream) *Stream } func TestProxyStreams(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -992,7 +983,6 @@ func (m *RemoteSubscriberTestMCU) NewRemoteSubscriber(ctx context.Context, liste } func TestProxyRemoteSubscriber(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1087,7 +1077,6 @@ func TestProxyRemoteSubscriber(t *testing.T) { } func TestProxyCloseRemoteOnSessionClose(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1250,7 +1239,6 @@ func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remo } func TestProxyUnpublishRemote(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1367,7 +1355,6 @@ func TestProxyUnpublishRemote(t *testing.T) { } func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1499,7 +1486,6 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { } func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index c97acc5..750ac2a 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -24,7 +24,6 @@ package main import ( "context" "fmt" - "log" "sync" "sync/atomic" "time" @@ -46,6 +45,7 @@ type remotePublisherData struct { } type ProxySession struct { + logger signaling.Logger proxy *ProxyServer id signaling.PublicSessionId sid uint64 @@ -79,6 +79,7 @@ type ProxySession struct { func NewProxySession(proxy *ProxyServer, sid uint64, id signaling.PublicSessionId) *ProxySession { ctx, closeFunc := context.WithCancel(context.Background()) result := &ProxySession{ + logger: proxy.logger, proxy: proxy, id: id, sid: sid, @@ -169,7 +170,7 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient { func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer api.StringMap) { id := s.proxy.GetClientId(client) if id == "" { - log.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client) + s.logger.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client) return } @@ -189,7 +190,7 @@ func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer api.Strin func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate any) { id := s.proxy.GetClientId(client) if id == "" { - log.Printf("Received candidate %+v from unknown %s client %s (%+v)", candidate, client.StreamType(), client.Id(), client) + s.logger.Printf("Received candidate %+v from unknown %s client %s (%+v)", candidate, client.StreamType(), client.Id(), client) return } @@ -222,7 +223,7 @@ func (s *ProxySession) sendMessage(message *signaling.ProxyServerMessage) { func (s *ProxySession) OnIceCompleted(client signaling.McuClient) { id := s.proxy.GetClientId(client) if id == "" { - log.Printf("Received ice completed event from unknown %s client %s (%+v)", client.StreamType(), client.Id(), client) + s.logger.Printf("Received ice completed event from unknown %s client %s (%+v)", client.StreamType(), client.Id(), client) return } @@ -239,7 +240,7 @@ func (s *ProxySession) OnIceCompleted(client signaling.McuClient) { func (s *ProxySession) SubscriberSidUpdated(subscriber signaling.McuSubscriber) { id := s.proxy.GetClientId(subscriber) if id == "" { - log.Printf("Received subscriber sid updated event from unknown %s subscriber %s (%+v)", subscriber.StreamType(), subscriber.Id(), subscriber) + s.logger.Printf("Received subscriber sid updated event from unknown %s subscriber %s (%+v)", subscriber.StreamType(), subscriber.Id(), subscriber) return } @@ -363,7 +364,7 @@ func (s *ProxySession) clearRemotePublishers() { for publisher, entries := range remotePublishers { for _, data := range entries { if err := publisher.UnpublishRemote(context.Background(), s.PublicId(), data.hostname, data.port, data.rtcpPort); err != nil { - log.Printf("Error unpublishing %s %s from remote %s: %s", publisher.StreamType(), publisher.Id(), data.hostname, err) + s.logger.Printf("Error unpublishing %s %s from remote %s: %s", publisher.StreamType(), publisher.Id(), data.hostname, err) } } } @@ -476,7 +477,7 @@ func (s *ProxySession) OnRemotePublisherDeleted(publisherId signaling.PublicSess delete(s.subscribers, id) delete(s.subscriberIds, sub) - log.Printf("Remote subscriber %s was closed, closing %s subscriber %s", publisherId, sub.StreamType(), sub.Id()) + s.logger.Printf("Remote subscriber %s was closed, closing %s subscriber %s", publisherId, sub.StreamType(), sub.Id()) go sub.Close(context.Background()) } } diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index f542d62..ccaa837 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -25,7 +25,6 @@ import ( "bytes" "context" "fmt" - "log" "strings" "sync/atomic" "time" @@ -46,14 +45,15 @@ type tokenCacheEntry struct { } type tokensEtcd struct { + logger signaling.Logger client *signaling.EtcdClient tokenFormats atomic.Value tokenCache *signaling.LruCache[*tokenCacheEntry] } -func NewProxyTokensEtcd(config *goconf.ConfigFile) (ProxyTokens, error) { - client, err := signaling.NewEtcdClient(config, "tokens") +func NewProxyTokensEtcd(logger signaling.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { + client, err := signaling.NewEtcdClient(logger, config, "tokens") if err != nil { return nil, err } @@ -63,6 +63,7 @@ func NewProxyTokensEtcd(config *goconf.ConfigFile) (ProxyTokens, error) { } result := &tokensEtcd{ + logger: logger, client: client, tokenCache: signaling.NewLruCache[*tokenCacheEntry](tokenCacheSize), } @@ -94,7 +95,7 @@ func (t *tokensEtcd) getByKey(id string, key string) (*ProxyToken, error) { if len(resp.Kvs) == 0 { return nil, nil } else if len(resp.Kvs) > 1 { - log.Printf("Received multiple keys for %s, using last", key) + t.logger.Printf("Received multiple keys for %s, using last", key) } keyValue := resp.Kvs[len(resp.Kvs)-1].Value @@ -123,7 +124,7 @@ func (t *tokensEtcd) Get(id string) (*ProxyToken, error) { for _, k := range t.getKeys(id) { token, err := t.getByKey(id, k) if err != nil { - log.Printf("Could not get public key from %s for %s: %s", k, id, err) + t.logger.Printf("Could not get public key from %s for %s: %s", k, id, err) continue } else if token == nil { continue @@ -151,18 +152,18 @@ func (t *tokensEtcd) load(config *goconf.ConfigFile, ignoreErrors bool) error { } t.tokenFormats.Store(tokenFormats) - log.Printf("Using %v as token formats", tokenFormats) + t.logger.Printf("Using %v as token formats", tokenFormats) return nil } func (t *tokensEtcd) Reload(config *goconf.ConfigFile) { if err := t.load(config, true); err != nil { - log.Printf("Error reloading etcd tokens: %s", err) + t.logger.Printf("Error reloading etcd tokens: %s", err) } } func (t *tokensEtcd) Close() { if err := t.client.Close(); err != nil { - log.Printf("Error while closing etcd client: %s", err) + t.logger.Printf("Error while closing etcd client: %s", err) } } diff --git a/proxy/proxy_tokens_etcd_test.go b/proxy/proxy_tokens_etcd_test.go index 8b23ff3..be600f2 100644 --- a/proxy/proxy_tokens_etcd_test.go +++ b/proxy/proxy_tokens_etcd_test.go @@ -115,7 +115,8 @@ func newTokensEtcdForTesting(t *testing.T) (*tokensEtcd, *embed.Etcd) { cfg.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) cfg.AddOption("tokens", "keyformat", "/%s, /testing/%s/key") - tokens, err := NewProxyTokensEtcd(cfg) + logger := signaling.NewLoggerForTest(t) + tokens, err := NewProxyTokensEtcd(logger, cfg) require.NoError(t, err) t.Cleanup(func() { tokens.Close() @@ -155,7 +156,6 @@ func generateAndSaveKey(t *testing.T, etcd *embed.Etcd, name string) *rsa.Privat } func TestProxyTokensEtcd(t *testing.T) { - signaling.CatchLogForTest(t) assert := assert.New(t) tokens, etcd := newTokensEtcdForTesting(t) diff --git a/proxy/proxy_tokens_static.go b/proxy/proxy_tokens_static.go index 37c23e4..8dc2118 100644 --- a/proxy/proxy_tokens_static.go +++ b/proxy/proxy_tokens_static.go @@ -23,7 +23,6 @@ package main import ( "fmt" - "log" "os" "slices" "sync/atomic" @@ -35,11 +34,14 @@ import ( ) type tokensStatic struct { + logger signaling.Logger tokenKeys atomic.Value } -func NewProxyTokensStatic(config *goconf.ConfigFile) (ProxyTokens, error) { - result := &tokensStatic{} +func NewProxyTokensStatic(logger signaling.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { + result := &tokensStatic{ + logger: logger, + } if err := result.load(config, false); err != nil { return nil, err } @@ -74,7 +76,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error return fmt.Errorf("no filename given for token %s", id) } - log.Printf("No filename given for token %s, ignoring", id) + t.logger.Printf("No filename given for token %s, ignoring", id) continue } @@ -84,7 +86,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error return fmt.Errorf("could not read public key from %s: %s", filename, err) } - log.Printf("Could not read public key from %s, ignoring: %s", filename, err) + t.logger.Printf("Could not read public key from %s, ignoring: %s", filename, err) continue } key, err := jwt.ParseRSAPublicKeyFromPEM(keyData) @@ -93,7 +95,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error return fmt.Errorf("could not parse public key from %s: %s", filename, err) } - log.Printf("Could not parse public key from %s, ignoring: %s", filename, err) + t.logger.Printf("Could not parse public key from %s, ignoring: %s", filename, err) continue } @@ -104,14 +106,14 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error } if len(tokenKeys) == 0 { - log.Printf("No token keys loaded") + t.logger.Printf("No token keys loaded") } else { var keyIds []string for k := range tokenKeys { keyIds = append(keyIds, k) } slices.Sort(keyIds) - log.Printf("Enabled token keys: %v", keyIds) + t.logger.Printf("Enabled token keys: %v", keyIds) } t.setTokenKeys(tokenKeys) return nil @@ -119,7 +121,7 @@ func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error func (t *tokensStatic) Reload(config *goconf.ConfigFile) { if err := t.load(config, true); err != nil { - log.Printf("Error reloading static tokens: %s", err) + t.logger.Printf("Error reloading static tokens: %s", err) } } diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index faf1582..94365c4 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "errors" - "log" "sync" "time" @@ -34,8 +33,9 @@ import ( ) type proxyConfigEtcd struct { - mu sync.Mutex - proxy McuProxy // +checklocksignore: Only written to from constructor. + logger Logger + mu sync.Mutex + proxy McuProxy // +checklocksignore: Only written to from constructor. client *EtcdClient keyPrefix string @@ -48,7 +48,7 @@ type proxyConfigEtcd struct { closeFunc context.CancelFunc } -func NewProxyConfigEtcd(config *goconf.ConfigFile, etcdClient *EtcdClient, proxy McuProxy) (ProxyConfig, error) { +func NewProxyConfigEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, proxy McuProxy) (ProxyConfig, error) { if !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } @@ -56,7 +56,8 @@ func NewProxyConfigEtcd(config *goconf.ConfigFile, etcdClient *EtcdClient, proxy closeCtx, closeFunc := context.WithCancel(context.Background()) result := &proxyConfigEtcd{ - proxy: proxy, + logger: logger, + proxy: proxy, client: etcdClient, keyInfos: make(map[string]*ProxyInformationEtcd), @@ -118,9 +119,9 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client *EtcdClient) { if errors.Is(err, context.Canceled) { return } else if errors.Is(err, context.DeadlineExceeded) { - log.Printf("Timeout getting initial list of proxy URLs, retry in %s", backoff.NextWait()) + p.logger.Printf("Timeout getting initial list of proxy URLs, retry in %s", backoff.NextWait()) } else { - log.Printf("Could not get initial list of proxy URLs, retry in %s: %s", backoff.NextWait(), err) + p.logger.Printf("Could not get initial list of proxy URLs, retry in %s: %s", backoff.NextWait(), err) } backoff.Wait(p.closeCtx) @@ -139,7 +140,7 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client *EtcdClient) { for p.closeCtx.Err() == nil { var err error if nextRevision, err = client.Watch(p.closeCtx, p.keyPrefix, nextRevision, p, clientv3.WithPrefix()); err != nil { - log.Printf("Error processing watch for %s (%s), retry in %s", p.keyPrefix, err, backoff.NextWait()) + p.logger.Printf("Error processing watch for %s (%s), retry in %s", p.keyPrefix, err, backoff.NextWait()) backoff.Wait(p.closeCtx) continue } @@ -148,7 +149,7 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client *EtcdClient) { backoff.Reset() prevRevision = nextRevision } else { - log.Printf("Processing watch for %s interrupted, retry in %s", p.keyPrefix, backoff.NextWait()) + p.logger.Printf("Processing watch for %s interrupted, retry in %s", p.keyPrefix, backoff.NextWait()) backoff.Wait(p.closeCtx) } } @@ -168,11 +169,11 @@ func (p *proxyConfigEtcd) getProxyUrls(ctx context.Context, client *EtcdClient, func (p *proxyConfigEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { var info ProxyInformationEtcd if err := json.Unmarshal(data, &info); err != nil { - log.Printf("Could not decode proxy information %s: %s", string(data), err) + p.logger.Printf("Could not decode proxy information %s: %s", string(data), err) return } if err := info.CheckValid(); err != nil { - log.Printf("Received invalid proxy information %s: %s", string(data), err) + p.logger.Printf("Received invalid proxy information %s: %s", string(data), err) return } @@ -187,7 +188,7 @@ func (p *proxyConfigEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data [] } if otherKey, otherFound := p.urlToKey[info.Address]; otherFound && otherKey != key { - log.Printf("Address %s is already registered for key %s, ignoring %s", info.Address, otherKey, key) + p.logger.Printf("Address %s is already registered for key %s, ignoring %s", info.Address, otherKey, key) return } @@ -196,11 +197,11 @@ func (p *proxyConfigEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data [] p.proxy.KeepConnection(info.Address) } else { if err := p.proxy.AddConnection(false, info.Address); err != nil { - log.Printf("Could not create proxy connection to %s: %s", info.Address, err) + p.logger.Printf("Could not create proxy connection to %s: %s", info.Address, err) return } - log.Printf("Added new connection to %s (from %s)", info.Address, key) + p.logger.Printf("Added new connection to %s (from %s)", info.Address, key) p.keyInfos[key] = &info p.urlToKey[info.Address] = key } @@ -223,6 +224,6 @@ func (p *proxyConfigEtcd) removeEtcdProxyLocked(key string) { delete(p.keyInfos, key) delete(p.urlToKey, info.Address) - log.Printf("Removing connection to %s (from %s)", info.Address, key) + p.logger.Printf("Removing connection to %s (from %s)", info.Address, key) p.proxy.RemoveConnection(info.Address) } diff --git a/proxy_config_etcd_test.go b/proxy_config_etcd_test.go index 353f690..814b9c3 100644 --- a/proxy_config_etcd_test.go +++ b/proxy_config_etcd_test.go @@ -43,7 +43,8 @@ func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*embed.Etcd, ProxyConfig) etcd, client := NewEtcdClientForTest(t) cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "keyprefix", "proxies/") - p, err := NewProxyConfigEtcd(cfg, client, proxy) + logger := NewLoggerForTest(t) + p, err := NewProxyConfigEtcd(logger, cfg, client, proxy) require.NoError(t, err) t.Cleanup(func() { p.Stop() @@ -60,11 +61,10 @@ func SetEtcdProxy(t *testing.T, etcd *embed.Etcd, path string, proxy *TestProxyI func TestProxyConfigEtcd(t *testing.T) { t.Parallel() - CatchLogForTest(t) proxy := newMcuProxyForConfig(t) etcd, config := newProxyConfigEtcd(t, proxy) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(t.Context(), time.Second) defer cancel() SetEtcdProxy(t, etcd, "proxies/a", &TestProxyInformationEtcd{ diff --git a/proxy_config_static.go b/proxy_config_static.go index dcdaa59..0dcaa55 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -23,7 +23,6 @@ package signaling import ( "errors" - "log" "maps" "net" "net/url" @@ -40,8 +39,9 @@ type ipList struct { } type proxyConfigStatic struct { - mu sync.Mutex - proxy McuProxy + logger Logger + mu sync.Mutex + proxy McuProxy dnsMonitor *DnsMonitor // +checklocks:mu @@ -51,8 +51,9 @@ type proxyConfigStatic struct { connectionsMap map[string]*ipList } -func NewProxyConfigStatic(config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *DnsMonitor) (ProxyConfig, error) { +func NewProxyConfigStatic(logger Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *DnsMonitor) (ProxyConfig, error) { result := &proxyConfigStatic{ + logger: logger, proxy: proxy, dnsMonitor: dnsMonitor, connectionsMap: make(map[string]*ipList), @@ -100,7 +101,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool return err } - log.Printf("Could not parse URL %s: %s", u, err) + p.logger.Printf("Could not parse URL %s: %s", u, err) continue } @@ -121,7 +122,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool return err } - log.Printf("Could not create proxy connection to %s: %s", u, err) + p.logger.Printf("Could not create proxy connection to %s: %s", u, err) continue } } @@ -198,7 +199,7 @@ func (p *proxyConfigStatic) onLookup(entry *DnsMonitorEntry, all []net.IP, added if len(added) > 0 { if err := p.proxy.AddConnection(true, u, added...); err != nil { - log.Printf("Could not add proxy connection to %s with %+v: %s", u, added, err) + p.logger.Printf("Could not add proxy connection to %s with %+v: %s", u, added, err) } } diff --git a/proxy_config_static_test.go b/proxy_config_static_test.go index 70884e8..5354d48 100644 --- a/proxy_config_static_test.go +++ b/proxy_config_static_test.go @@ -38,7 +38,8 @@ func newProxyConfigStatic(t *testing.T, proxy McuProxy, dns bool, urls ...string cfg.AddOption("mcu", "dnsdiscovery", "true") } dnsMonitor := newDnsMonitorForTest(t, time.Hour) // will be updated manually - p, err := NewProxyConfigStatic(cfg, proxy, dnsMonitor) + logger := NewLoggerForTest(t) + p, err := NewProxyConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) t.Cleanup(func() { p.Stop() @@ -56,7 +57,6 @@ func updateProxyConfigStatic(t *testing.T, config ProxyConfig, dns bool, urls .. } func TestProxyConfigStaticSimple(t *testing.T) { - CatchLogForTest(t) proxy := newMcuProxyForConfig(t) config, _ := newProxyConfigStatic(t, proxy, false, "https://foo/") proxy.Expect("add", "https://foo/") @@ -73,7 +73,6 @@ func TestProxyConfigStaticSimple(t *testing.T) { } func TestProxyConfigStaticDNS(t *testing.T) { - CatchLogForTest(t) lookup := newMockDnsLookupForTest(t) proxy := newMcuProxyForConfig(t) config, dnsMonitor := newProxyConfigStatic(t, proxy, true, "https://foo/") diff --git a/remotesession.go b/remotesession.go index c548f9e..0787785 100644 --- a/remotesession.go +++ b/remotesession.go @@ -26,12 +26,12 @@ import ( "encoding/json" "errors" "fmt" - "log" "sync/atomic" "time" ) type RemoteSession struct { + logger Logger hub *Hub client *Client remoteClient *GrpcClient @@ -42,6 +42,7 @@ type RemoteSession struct { func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessionId PublicSessionId) (*RemoteSession, error) { remoteSession := &RemoteSession{ + logger: hub.logger, hub: hub, client: client, remoteClient: remoteClient, @@ -97,7 +98,7 @@ func (s *RemoteSession) OnProxyMessage(msg *ServerSessionMessage) error { func (s *RemoteSession) OnProxyClose(err error) { if err != nil { - log.Printf("Proxy connection for session %s to %s was closed with error: %s", s.sessionId, s.remoteClient.Target(), err) + s.logger.Printf("Proxy connection for session %s to %s was closed with error: %s", s.sessionId, s.remoteClient.Target(), err) } s.Close() } @@ -145,7 +146,7 @@ func (s *RemoteSession) OnClosed(client HandlerClient) { func (s *RemoteSession) OnMessageReceived(client HandlerClient, message []byte) { if err := s.sendProxyMessage(message); err != nil { - log.Printf("Error sending %s to the proxy for session %s: %s", string(message), s.sessionId, err) + s.logger.Printf("Error sending %s to the proxy for session %s: %s", string(message), s.sessionId, err) s.Close() } } diff --git a/room.go b/room.go index 372cf5b..6e75077 100644 --- a/room.go +++ b/room.go @@ -26,7 +26,6 @@ import ( "context" "encoding/json" "fmt" - "log" "maps" "net/url" "strconv" @@ -65,6 +64,7 @@ func init() { type Room struct { id string + logger Logger hub *Hub events AsyncEvents backend *Backend @@ -108,6 +108,7 @@ func getRoomIdForBackend(id string, backend *Backend) string { func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEvents, backend *Backend) (*Room, error) { room := &Room{ id: roomId, + logger: hub.logger, hub: hub, events: events, backend: backend, @@ -223,7 +224,7 @@ func (r *Room) ProcessBackendRoomRequest(message *AsyncMessage) { case "asyncroom": r.processBackendRoomRequestAsyncRoom(message.AsyncRoom) default: - log.Printf("Unsupported backend room request with type %s in %s: %+v", message.Type, r.id, message) + r.logger.Printf("Unsupported backend room request with type %s in %s: %+v", message.Type, r.id, message) } } @@ -231,9 +232,9 @@ func (r *Room) processBackendRoomRequestRoom(message *BackendServerRoomRequest) received := message.ReceivedTime if last, found := r.lastRoomRequests[message.Type]; found && last > received { if msg, err := json.Marshal(message); err == nil { - log.Printf("Ignore old backend room request for %s: %s", r.Id(), string(msg)) + r.logger.Printf("Ignore old backend room request for %s: %s", r.Id(), string(msg)) } else { - log.Printf("Ignore old backend room request for %s: %+v", r.Id(), message) + r.logger.Printf("Ignore old backend room request for %s: %+v", r.Id(), message) } return } @@ -261,10 +262,10 @@ func (r *Room) processBackendRoomRequestRoom(message *BackendServerRoomRequest) case TransientActionDelete: r.RemoveTransientData(message.Transient.Key) default: - log.Printf("Unsupported transient action in room %s: %+v", r.Id(), message.Transient) + r.logger.Printf("Unsupported transient action in room %s: %+v", r.Id(), message.Transient) } default: - log.Printf("Unsupported backend room request with type %s in %s: %+v", message.Type, r.Id(), message) + r.logger.Printf("Unsupported backend room request with type %s in %s: %+v", message.Type, r.Id(), message) } } @@ -276,7 +277,7 @@ func (r *Room) processBackendRoomRequestAsyncRoom(message *AsyncRoomMessage) { r.publishUsersChangedWithInternal() } default: - log.Printf("Unsupported async room request with type %s in %s: %+v", message.Type, r.Id(), message) + r.logger.Printf("Unsupported async room request with type %s in %s: %+v", message.Type, r.Id(), message) } } @@ -285,7 +286,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { if len(sessionData) > 0 { roomSessionData = &RoomSessionData{} if err := json.Unmarshal(sessionData, roomSessionData); err != nil { - log.Printf("Error decoding room session data \"%s\": %s", string(sessionData), err) + r.logger.Printf("Error decoding room session data \"%s\": %s", string(sessionData), err) roomSessionData = nil } } @@ -319,7 +320,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { } if roomSessionData != nil { r.roomSessionData[sid] = roomSessionData - log.Printf("Session %s sent room session data %+v", session.PublicId(), roomSessionData) + r.logger.Printf("Session %s sent room session data %+v", session.PublicId(), roomSessionData) } r.mu.Unlock() if !found { @@ -344,7 +345,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { ClientType: session.ClientType(), }, }); err != nil { - log.Printf("Error publishing joined event for session %s: %s", sid, err) + r.logger.Printf("Error publishing joined event for session %s: %s", sid, err) } } @@ -402,7 +403,7 @@ func (r *Room) notifySessionJoined(sessionId PublicSessionId) { Type: "message", Message: msg, }); err != nil { - log.Printf("Error publishing joined events to session %s: %s", sessionId, err) + r.logger.Printf("Error publishing joined events to session %s: %s", sessionId, err) } // Notify about initial flags of virtual sessions. @@ -434,7 +435,7 @@ func (r *Room) notifySessionJoined(sessionId PublicSessionId) { Type: "message", Message: msg, }); err != nil { - log.Printf("Error publishing initial flags to session %s: %s", sessionId, err) + r.logger.Printf("Error publishing initial flags to session %s: %s", sessionId, err) } } } @@ -526,7 +527,7 @@ func (r *Room) UpdateProperties(properties json.RawMessage) { }, } if err := r.publish(message); err != nil { - log.Printf("Could not publish update properties message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish update properties message in room %s: %s", r.Id(), err) } } @@ -567,7 +568,7 @@ func (r *Room) PublishSessionJoined(session Session, sessionData *RoomSessionDat 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) + r.logger.Printf("Could not publish session joined message in room %s: %s", r.Id(), err) } } @@ -588,7 +589,7 @@ func (r *Room) PublishSessionLeft(session Session) { }, } if err := r.publish(message); err != nil { - log.Printf("Could not publish session left message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish session left message in room %s: %s", r.Id(), err) } if session.ClientType() == HelloClientTypeInternal { @@ -604,7 +605,8 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSession r.mu.RUnlock() defer r.mu.RLock() - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx := NewLoggerContext(context.Background(), r.logger) + ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() var mu sync.Mutex @@ -616,7 +618,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSession clientInternal, clientVirtual, err := c.GetInternalSessions(ctx, r.Id(), r.Backend().Urls()) if err != nil { - log.Printf("Received error while getting internal sessions for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) + r.logger.Printf("Received error while getting internal sessions for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) return } @@ -786,7 +788,7 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St r.mu.Lock() if !r.inCallSessions[session] { r.inCallSessions[session] = true - log.Printf("Session %s joined call %s", session.PublicId(), r.id) + r.logger.Printf("Session %s joined call %s", session.PublicId(), r.id) } r.mu.Unlock() } else { @@ -815,7 +817,7 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St }, } if err := r.publish(message); err != nil { - log.Printf("Could not publish incall message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish incall message in room %s: %s", r.Id(), err) } } @@ -849,7 +851,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { return } - log.Printf("Sessions %v joined call %s", joined, r.id) + r.logger.Printf("Sessions %v joined call %s", joined, r.id) } else if len(r.inCallSessions) > 0 { // Perform actual leaving asynchronously. ch := make(chan *ClientSession, 1) @@ -902,7 +904,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { for _, session := range notify { if !session.SendMessage(message) { - log.Printf("Could not send incall message from room %s to %s", r.Id(), session.PublicId()) + r.logger.Printf("Could not send incall message from room %s to %s", r.Id(), session.PublicId()) } } } @@ -924,7 +926,7 @@ func (r *Room) PublishUsersChanged(changed []api.StringMap, users []api.StringMa }, } if err := r.publish(message); err != nil { - log.Printf("Could not publish users changed message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish users changed message in room %s: %s", r.Id(), err) } } @@ -978,7 +980,7 @@ func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) { r.mu.Lock() if !r.inCallSessions[session] { r.inCallSessions[session] = true - log.Printf("Session %s joined call %s", session.PublicId(), r.id) + r.logger.Printf("Session %s joined call %s", session.PublicId(), r.id) } r.mu.Unlock() case 2: @@ -1003,7 +1005,7 @@ func (r *Room) publishUsersChangedWithInternal() { } if err := r.publish(message); err != nil { - log.Printf("Could not publish users changed message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish users changed message in room %s: %s", r.Id(), err) } } @@ -1021,7 +1023,7 @@ func (r *Room) publishSessionFlagsChanged(session *VirtualSession) { }, } if err := r.publish(message); err != nil { - log.Printf("Could not publish flags changed message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish flags changed message in room %s: %s", r.Id(), err) } } @@ -1040,7 +1042,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { u += PathToOcsSignalingBackend parsed, err := url.Parse(u) if err != nil { - log.Printf("Could not parse backend url %s: %s", u, err) + r.logger.Printf("Could not parse backend url %s: %s", u, err) continue } @@ -1087,16 +1089,17 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { return 0, &wg } var count int + ctx := NewLoggerContext(context.Background(), r.logger) for u, e := range entries { wg.Add(1) count += len(e) go func(url *url.URL, entries []BackendPingEntry) { defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), r.hub.backendTimeout) + sendCtx, cancel := context.WithTimeout(ctx, r.hub.backendTimeout) defer cancel() - if err := r.hub.roomPing.SendPings(ctx, r.id, url, entries); err != nil { - log.Printf("Error pinging room %s for active entries %+v: %s", r.id, entries, err) + if err := r.hub.roomPing.SendPings(sendCtx, r.id, url, entries); err != nil { + r.logger.Printf("Error pinging room %s for active entries %+v: %s", r.id, entries, err) } }(urls[u], e) } @@ -1120,7 +1123,7 @@ func (r *Room) publishRoomMessage(message *BackendRoomMessageRequest) { }, } if err := r.publish(msg); err != nil { - log.Printf("Could not publish room message in room %s: %s", r.Id(), err) + r.logger.Printf("Could not publish room message in room %s: %s", r.Id(), err) } } @@ -1147,7 +1150,7 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { Type: "message", Message: msg, }); err != nil { - log.Printf("Error publishing switchto event to session %s: %s", sessionId, err) + r.logger.Printf("Error publishing switchto event to session %s: %s", sessionId, err) } }(sessionId) } @@ -1175,7 +1178,7 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { Type: "message", Message: msg, }); err != nil { - log.Printf("Error publishing switchto event to session %s: %s", sessionId, err) + r.logger.Printf("Error publishing switchto event to session %s: %s", sessionId, err) } }(sessionId, details) } diff --git a/room_ping.go b/room_ping.go index 3e9d708..7958370 100644 --- a/room_ping.go +++ b/room_ping.go @@ -23,7 +23,6 @@ package signaling import ( "context" - "log" "net/url" "slices" "sync" @@ -100,7 +99,7 @@ loop: case <-p.closer.C: break loop case <-ticker.C: - p.publishActiveSessions() + p.publishActiveSessions(context.Background()) } } } @@ -114,20 +113,21 @@ func (p *RoomPing) getAndClearEntries() map[string]*pingEntries { return entries } -func (p *RoomPing) publishEntries(entries *pingEntries, timeout time.Duration) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) +func (p *RoomPing) publishEntries(ctx context.Context, entries *pingEntries, timeout time.Duration) { + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() limit, _, found := p.capabilities.GetIntegerConfig(ctx, entries.url, ConfigGroupSignaling, ConfigKeySessionPingLimit) if !found || limit <= 0 { // Limit disabled while waiting for the next iteration, fallback to sending // one request per room. + logger := LoggerFromContext(ctx) for roomId, e := range entries.entries { - ctx2, cancel2 := context.WithTimeout(context.Background(), timeout) + ctx2, cancel2 := context.WithTimeout(context.WithoutCancel(ctx), timeout) defer cancel2() if err := p.sendPingsDirect(ctx2, roomId, entries.url, e); err != nil { - log.Printf("Error pinging room %s for active entries %+v: %s", roomId, e, err) + logger.Printf("Error pinging room %s for active entries %+v: %s", roomId, e, err) } } return @@ -137,10 +137,10 @@ func (p *RoomPing) publishEntries(entries *pingEntries, timeout time.Duration) { for _, e := range entries.entries { allEntries = append(allEntries, e...) } - p.sendPingsCombined(entries.url, allEntries, limit, timeout) + p.sendPingsCombined(ctx, entries.url, allEntries, limit, timeout) } -func (p *RoomPing) publishActiveSessions() { +func (p *RoomPing) publishActiveSessions(ctx context.Context) { var timeout time.Duration if p.backend.hub != nil { timeout = p.backend.hub.backendTimeout @@ -154,7 +154,7 @@ func (p *RoomPing) publishActiveSessions() { for _, e := range entries { go func(e *pingEntries) { defer wg.Done() - p.publishEntries(e, timeout) + p.publishEntries(ctx, e, timeout) }(e) } wg.Wait() @@ -166,15 +166,16 @@ func (p *RoomPing) sendPingsDirect(ctx context.Context, roomId string, url *url. return p.backend.PerformJSONRequest(ctx, url, request, &response) } -func (p *RoomPing) sendPingsCombined(url *url.URL, entries []BackendPingEntry, limit int, timeout time.Duration) { +func (p *RoomPing) sendPingsCombined(ctx context.Context, url *url.URL, entries []BackendPingEntry, limit int, timeout time.Duration) { + logger := LoggerFromContext(ctx) for tosend := range slices.Chunk(entries, limit) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) + subCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() request := NewBackendClientPingRequest("", tosend) var response BackendClientResponse - if err := p.backend.PerformJSONRequest(ctx, url, request, &response); err != nil { - log.Printf("Error sending combined ping session entries %+v to %s: %s", tosend, url, err) + if err := p.backend.PerformJSONRequest(subCtx, url, request, &response); err != nil { + logger.Printf("Error sending combined ping session entries %+v to %s: %s", tosend, url, err) } } } diff --git a/room_ping_test.go b/room_ping_test.go index 58ce74d..a89f536 100644 --- a/room_ping_test.go +++ b/room_ping_test.go @@ -32,7 +32,7 @@ import ( "github.com/stretchr/testify/require" ) -func NewRoomPingForTest(t *testing.T) (*url.URL, *RoomPing) { +func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) { require := require.New(t) r := mux.NewRouter() registerBackendHandler(t, r) @@ -45,7 +45,7 @@ func NewRoomPingForTest(t *testing.T) (*url.URL, *RoomPing) { config, err := getTestConfig(server) require.NoError(err) - backend, err := NewBackendClient(config, 1, "0.0", nil) + backend, err := NewBackendClient(ctx, config, 1, "0.0", nil) require.NoError(err) p, err := NewRoomPing(backend, backend.capabilities) @@ -58,11 +58,12 @@ func NewRoomPingForTest(t *testing.T) (*url.URL, *RoomPing) { } func TestSingleRoomPing(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) - u, ping := NewRoomPingForTest(t) + u, ping := NewRoomPingForTest(ctx, t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() room1 := &Room{ @@ -95,16 +96,17 @@ func TestSingleRoomPing(t *testing.T) { } clearPingRequests(t) - ping.publishActiveSessions() + ping.publishActiveSessions(ctx) assert.Empty(getPingRequests(t)) } func TestMultiRoomPing(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) - u, ping := NewRoomPingForTest(t) + u, ping := NewRoomPingForTest(ctx, t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() room1 := &Room{ @@ -131,18 +133,19 @@ func TestMultiRoomPing(t *testing.T) { assert.NoError(ping.SendPings(ctx, room2.Id(), u, entries2)) assert.Empty(getPingRequests(t)) - ping.publishActiveSessions() + ping.publishActiveSessions(ctx) if requests := getPingRequests(t); assert.Len(requests, 1) { assert.Len(requests[0].Ping.Entries, 2) } } func TestMultiRoomPing_Separate(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) - u, ping := NewRoomPingForTest(t) + u, ping := NewRoomPingForTest(ctx, t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() room1 := &Room{ @@ -165,18 +168,19 @@ func TestMultiRoomPing_Separate(t *testing.T) { assert.NoError(ping.SendPings(ctx, room1.Id(), u, entries2)) assert.Empty(getPingRequests(t)) - ping.publishActiveSessions() + ping.publishActiveSessions(ctx) if requests := getPingRequests(t); assert.Len(requests, 1) { assert.Len(requests[0].Ping.Entries, 2) } } func TestMultiRoomPing_DeleteRoom(t *testing.T) { - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) - u, ping := NewRoomPingForTest(t) + u, ping := NewRoomPingForTest(ctx, t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() room1 := &Room{ @@ -205,7 +209,7 @@ func TestMultiRoomPing_DeleteRoom(t *testing.T) { ping.DeleteRoom(room2.Id()) - ping.publishActiveSessions() + ping.publishActiveSessions(ctx) if requests := getPingRequests(t); assert.Len(requests, 1) { assert.Len(requests[0].Ping.Entries, 1) } diff --git a/room_test.go b/room_test.go index eadcd19..8901d0b 100644 --- a/room_test.go +++ b/room_test.go @@ -76,18 +76,19 @@ func TestRoom_InCall(t *testing.T) { func TestRoom_Update(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) config, err := getTestConfig(server) require.NoError(err) - b, err := NewBackendServer(config, hub, "no-version") + b, err := NewBackendServer(ctx, config, hub, "no-version") require.NoError(err) require.NoError(b.Start(router)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) @@ -170,18 +171,19 @@ loop: func TestRoom_Delete(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) config, err := getTestConfig(server) require.NoError(err) - b, err := NewBackendServer(config, hub, "no-version") + b, err := NewBackendServer(ctx, config, hub, "no-version") require.NoError(err) require.NoError(b.Start(router)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) @@ -267,14 +269,15 @@ loop: func TestRoom_RoomJoinFeatures(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) config, err := getTestConfig(server) require.NoError(err) - b, err := NewBackendServer(config, hub, "no-version") + b, err := NewBackendServer(ctx, config, hub, "no-version") require.NoError(err) require.NoError(b.Start(router)) @@ -284,7 +287,7 @@ func TestRoom_RoomJoinFeatures(t *testing.T) { features := []string{"one", "two", "three"} require.NoError(client.SendHelloClientWithFeatures(testDefaultUserId, features)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() hello := MustSucceed1(t, client.RunUntilHello, ctx) @@ -304,18 +307,19 @@ func TestRoom_RoomJoinFeatures(t *testing.T) { func TestRoom_RoomSessionData(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) config, err := getTestConfig(server) require.NoError(err) - b, err := NewBackendServer(config, hub, "no-version") + b, err := NewBackendServer(ctx, config, hub, "no-version") require.NoError(err) require.NoError(b.Start(router)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, authAnonymousUserId) @@ -347,18 +351,19 @@ func TestRoom_RoomSessionData(t *testing.T) { func TestRoom_InCallAll(t *testing.T) { t.Parallel() - CatchLogForTest(t) + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) config, err := getTestConfig(server) require.NoError(err) - b, err := NewBackendServer(config, hub, "no-version") + b, err := NewBackendServer(ctx, config, hub, "no-version") require.NoError(err) require.NoError(b.Start(router)) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") diff --git a/roomsessions_builtin.go b/roomsessions_builtin.go index 02a53c4..9c54bf5 100644 --- a/roomsessions_builtin.go +++ b/roomsessions_builtin.go @@ -24,7 +24,6 @@ package signaling import ( "context" "errors" - "log" "sync" "sync/atomic" ) @@ -117,6 +116,7 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId var wg sync.WaitGroup var result atomic.Value + logger := LoggerFromContext(ctx) for _, client := range clients { wg.Add(1) go func(client *GrpcClient) { @@ -126,10 +126,10 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId if errors.Is(err, context.Canceled) { return } else if err != nil { - log.Printf("Received error while checking for room session id %s on %s: %s", roomSessionId, client.Target(), err) + logger.Printf("Received error while checking for room session id %s on %s: %s", roomSessionId, client.Target(), err) return } else if sid == "" { - log.Printf("Received empty session id for room session id %s from %s", roomSessionId, client.Target()) + logger.Printf("Received empty session id for room session id %s from %s", roomSessionId, client.Target()) return } diff --git a/server/main.go b/server/main.go index 879accb..444d2ca 100644 --- a/server/main.go +++ b/server/main.go @@ -93,7 +93,8 @@ func createTLSListener(addr string, certFile, keyFile string) (net.Listener, err } type Listeners struct { - mu sync.Mutex + logger signaling.Logger // +checklocksignore + mu sync.Mutex // +checklocks:mu listeners []net.Listener } @@ -111,7 +112,7 @@ func (l *Listeners) Close() { for _, listener := range l.listeners { if err := listener.Close(); err != nil { - log.Printf("Error closing listener %s: %s", listener.Addr(), err) + l.logger.Printf("Error closing listener %s: %s", listener.Addr(), err) } } } @@ -126,46 +127,51 @@ func main() { } sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt) signal.Notify(sigChan, syscall.SIGHUP) signal.Notify(sigChan, syscall.SIGUSR1) + stopCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + logger := log.Default() + stopCtx = signaling.NewLoggerContext(stopCtx, logger) + if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { - log.Fatal(err) + logger.Fatal(err) } if err := runtimepprof.StartCPUProfile(f); err != nil { - log.Fatalf("Error writing CPU profile to %s: %s", *cpuprofile, err) + logger.Fatalf("Error writing CPU profile to %s: %s", *cpuprofile, err) } - log.Printf("Writing CPU profile to %s ...", *cpuprofile) + logger.Printf("Writing CPU profile to %s ...", *cpuprofile) defer runtimepprof.StopCPUProfile() } if *memprofile != "" { f, err := os.Create(*memprofile) if err != nil { - log.Fatal(err) + logger.Fatal(err) } defer func() { - log.Printf("Writing Memory profile to %s ...", *memprofile) + logger.Printf("Writing Memory profile to %s ...", *memprofile) runtime.GC() if err := runtimepprof.WriteHeapProfile(f); err != nil { - log.Printf("Error writing Memory profile to %s: %s", *memprofile, err) + logger.Printf("Error writing Memory profile to %s: %s", *memprofile, err) } }() } - log.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) + logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) config, err := goconf.ReadConfigFile(*configFlag) if err != nil { - log.Fatal("Could not read configuration: ", err) + logger.Fatal("Could not read configuration: ", err) } - log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) + logger.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) signaling.RegisterStats() @@ -174,61 +180,61 @@ func main() { natsUrl = nats.DefaultURL } - events, err := signaling.NewAsyncEvents(natsUrl) + events, err := signaling.NewAsyncEvents(stopCtx, natsUrl) if err != nil { - log.Fatal("Could not create async events client: ", err) + logger.Fatal("Could not create async events client: ", err) } defer events.Close() - dnsMonitor, err := signaling.NewDnsMonitor(dnsMonitorInterval) + dnsMonitor, err := signaling.NewDnsMonitor(logger, dnsMonitorInterval) if err != nil { - log.Fatal("Could not create DNS monitor: ", err) + logger.Fatal("Could not create DNS monitor: ", err) } if err := dnsMonitor.Start(); err != nil { - log.Fatal("Could not start DNS monitor: ", err) + logger.Fatal("Could not start DNS monitor: ", err) } defer dnsMonitor.Stop() - etcdClient, err := signaling.NewEtcdClient(config, "mcu") + etcdClient, err := signaling.NewEtcdClient(logger, config, "mcu") if err != nil { - log.Fatalf("Could not create etcd client: %s", err) + logger.Fatalf("Could not create etcd client: %s", err) } defer func() { if err := etcdClient.Close(); err != nil { - log.Printf("Error while closing etcd client: %s", err) + logger.Printf("Error while closing etcd client: %s", err) } }() - rpcServer, err := signaling.NewGrpcServer(config, version) + rpcServer, err := signaling.NewGrpcServer(stopCtx, config, version) if err != nil { - log.Fatalf("Could not create RPC server: %s", err) + logger.Fatalf("Could not create RPC server: %s", err) } go func() { if err := rpcServer.Run(); err != nil { - log.Fatalf("Could not start RPC server: %s", err) + logger.Fatalf("Could not start RPC server: %s", err) } }() defer rpcServer.Close() - rpcClients, err := signaling.NewGrpcClients(config, etcdClient, dnsMonitor, version) + rpcClients, err := signaling.NewGrpcClients(stopCtx, config, etcdClient, dnsMonitor, version) if err != nil { - log.Fatalf("Could not create RPC clients: %s", err) + logger.Fatalf("Could not create RPC clients: %s", err) } defer rpcClients.Close() r := mux.NewRouter() - hub, err := signaling.NewHub(config, events, rpcServer, rpcClients, etcdClient, r, version) + hub, err := signaling.NewHub(stopCtx, config, events, rpcServer, rpcClients, etcdClient, r, version) if err != nil { - log.Fatal("Could not create hub: ", err) + logger.Fatal("Could not create hub: ", err) } mcuUrl, _ := signaling.GetStringOptionWithEnv(config, "mcu", "url") mcuType, _ := config.GetString("mcu", "type") if mcuType == "" && mcuUrl != "" { - log.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) + logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) mcuType = signaling.McuTypeJanus } else if mcuType == signaling.McuTypeJanus && mcuUrl == "" { - log.Printf("WARNING: Old-style MCU configuration detected with type but no url, disabling") + logger.Printf("WARNING: Old-style MCU configuration detected with type but no url, disabling") mcuType = "" } @@ -246,41 +252,41 @@ func main() { signaling.UnregisterProxyMcuStats() signaling.RegisterJanusMcuStats() case signaling.McuTypeProxy: - mcu, err = signaling.NewMcuProxy(config, etcdClient, rpcClients, dnsMonitor) + mcu, err = signaling.NewMcuProxy(ctx, config, etcdClient, rpcClients, dnsMonitor) signaling.UnregisterJanusMcuStats() signaling.RegisterProxyMcuStats() default: - log.Fatal("Unsupported MCU type: ", mcuType) + logger.Fatal("Unsupported MCU type: ", mcuType) } if err == nil { err = mcu.Start(ctx) if err != nil { - log.Printf("Could not create %s MCU: %s", mcuType, err) + logger.Printf("Could not create %s MCU: %s", mcuType, err) } } if err == nil { break } - log.Printf("Could not initialize %s MCU (%s) will retry in %s", mcuType, err, mcuRetry) + logger.Printf("Could not initialize %s MCU (%s) will retry in %s", mcuType, err, mcuRetry) mcuRetryTimer.Reset(mcuRetry) select { + case <-stopCtx.Done(): + logger.Fatalf("Cancelled") case sig := <-sigChan: switch sig { - case os.Interrupt: - log.Fatalf("Cancelled") case syscall.SIGHUP: - log.Printf("Received SIGHUP, reloading %s", *configFlag) + logger.Printf("Received SIGHUP, reloading %s", *configFlag) if config, err = goconf.ReadConfigFile(*configFlag); err != nil { - log.Printf("Could not read configuration from %s: %s", *configFlag, err) + logger.Printf("Could not read configuration from %s: %s", *configFlag, err) } else { mcuUrl, _ = signaling.GetStringOptionWithEnv(config, "mcu", "url") mcuType, _ = config.GetString("mcu", "type") if mcuType == "" && mcuUrl != "" { - log.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) + logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) mcuType = signaling.McuTypeJanus } else if mcuType == signaling.McuTypeJanus && mcuUrl == "" { - log.Printf("WARNING: Old-style MCU configuration detected with type but no url, disabling") + logger.Printf("WARNING: Old-style MCU configuration detected with type but no url, disabling") mcuType = "" break mcuTypeLoop } @@ -294,7 +300,7 @@ func main() { if mcu != nil { defer mcu.Stop() - log.Printf("Using %s MCU", mcuType) + logger.Printf("Using %s MCU", mcuType) hub.SetMcu(mcu) } } @@ -302,21 +308,23 @@ func main() { go hub.Run() defer hub.Stop() - server, err := signaling.NewBackendServer(config, hub, version) + server, err := signaling.NewBackendServer(stopCtx, config, hub, version) if err != nil { - log.Fatal("Could not create backend server: ", err) + logger.Fatal("Could not create backend server: ", err) } if err := server.Start(r); err != nil { - log.Fatal("Could not start backend server: ", err) + logger.Fatal("Could not start backend server: ", err) } - var listeners Listeners + listeners := Listeners{ + logger: logger, + } if saddr, _ := signaling.GetStringOptionWithEnv(config, "https", "listen"); saddr != "" { cert, _ := config.GetString("https", "certificate") key, _ := config.GetString("https", "key") if cert == "" || key == "" { - log.Fatal("Need a certificate and key for the HTTPS listener") + logger.Fatal("Need a certificate and key for the HTTPS listener") } readTimeout, _ := config.GetInt("https", "readtimeout") @@ -329,10 +337,10 @@ func main() { } for address := range signaling.SplitEntries(saddr, " ") { go func(address string) { - log.Println("Listening on", address) + logger.Println("Listening on", address) listener, err := createTLSListener(address, cert, key) if err != nil { - log.Fatal("Could not start listening: ", err) + logger.Fatal("Could not start listening: ", err) } srv := &http.Server{ Handler: r, @@ -343,7 +351,7 @@ func main() { listeners.Add(listener) if err := srv.Serve(listener); err != nil { if !hub.IsShutdownScheduled() || !errors.Is(err, net.ErrClosed) { - log.Fatal("Could not start server: ", err) + logger.Fatal("Could not start server: ", err) } } }(address) @@ -362,10 +370,10 @@ func main() { for address := range signaling.SplitEntries(addr, " ") { go func(address string) { - log.Println("Listening on", address) + logger.Println("Listening on", address) listener, err := createListener(address) if err != nil { - log.Fatal("Could not start listening: ", err) + logger.Fatal("Could not start listening: ", err) } srv := &http.Server{ Handler: r, @@ -377,7 +385,7 @@ func main() { listeners.Add(listener) if err := srv.Serve(listener); err != nil { if !hub.IsShutdownScheduled() || !errors.Is(err, net.ErrClosed) { - log.Fatal("Could not start server: ", err) + logger.Fatal("Could not start server: ", err) } } }(address) @@ -387,26 +395,26 @@ func main() { loop: for { select { + case <-stopCtx.Done(): + logger.Println("Interrupted") + break loop case sig := <-sigChan: switch sig { - case os.Interrupt: - log.Println("Interrupted") - break loop case syscall.SIGHUP: - log.Printf("Received SIGHUP, reloading %s", *configFlag) + logger.Printf("Received SIGHUP, reloading %s", *configFlag) if config, err := goconf.ReadConfigFile(*configFlag); err != nil { - log.Printf("Could not read configuration from %s: %s", *configFlag, err) + logger.Printf("Could not read configuration from %s: %s", *configFlag, err) } else { - hub.Reload(config) + hub.Reload(stopCtx, config) server.Reload(config) } case syscall.SIGUSR1: - log.Printf("Received SIGUSR1, scheduling server to shutdown") + logger.Printf("Received SIGUSR1, scheduling server to shutdown") hub.ScheduleShutdown() listeners.Close() } case <-hub.ShutdownChannel(): - log.Printf("All clients disconnected, shutting down") + logger.Printf("All clients disconnected, shutting down") break loop } } diff --git a/test_helpers.go b/test_helpers.go index 9c1a7f7..a0ca4ec 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -24,22 +24,11 @@ package signaling import ( "bytes" "fmt" - "io" "log" "sync" "testing" ) -var ( - prevWriter io.Writer - prevFlags int -) - -func init() { - prevWriter = log.Writer() - prevFlags = log.Flags() -} - type testLogWriter struct { mu sync.Mutex t testing.TB @@ -55,18 +44,6 @@ func (w *testLogWriter) Write(b []byte) (int, error) { return writeTestOutput(w.t, b) } -func CatchLogForTest(t testing.TB) { - t.Cleanup(func() { - log.SetOutput(prevWriter) - log.SetFlags(prevFlags) - }) - - log.SetOutput(&testLogWriter{ - t: t, - }) - log.SetFlags(prevFlags | log.Lmicroseconds | log.Lshortfile) -} - var ( // +checklocks:testLoggersLock testLoggers = map[testing.TB]Logger{} diff --git a/throttle.go b/throttle.go index 6e78582..a19aa25 100644 --- a/throttle.go +++ b/throttle.go @@ -24,7 +24,6 @@ package signaling import ( "context" "errors" - "log" "net" "strconv" "sync" @@ -277,7 +276,8 @@ func (t *memoryThrottler) CheckBruteforce(ctx context.Context, client string, ac if l >= maxBruteforceAttempts { delta := now.Sub(entries[l-maxBruteforceAttempts].ts) if delta <= maxBruteforceDurationThreshold { - log.Printf("Detected bruteforce attempt on \"%s\" from %s", action, client) + logger := LoggerFromContext(ctx) + logger.Printf("Detected bruteforce attempt on \"%s\" from %s", action, client) statsThrottleBruteforceTotal.WithLabelValues(action).Inc() return doThrottle, ErrBruteforceDetected } @@ -301,7 +301,8 @@ func (t *memoryThrottler) throttle(ctx context.Context, client string, action st } count := t.addEntry(client, action, entry) delay := t.getDelay(count - 1) - log.Printf("Failed attempt on \"%s\" from %s, throttling by %s", action, client, delay) + logger := LoggerFromContext(ctx) + logger.Printf("Failed attempt on \"%s\" from %s, throttling by %s", action, client, delay) statsThrottleDelayedTotal.WithLabelValues(action, strconv.FormatInt(delay.Milliseconds(), 10)).Inc() t.doDelay(ctx, delay) } diff --git a/throttle_test.go b/throttle_test.go index 62ebf7f..07f3520 100644 --- a/throttle_test.go +++ b/throttle_test.go @@ -71,7 +71,8 @@ func TestThrottler(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -104,7 +105,8 @@ func TestThrottlerIPv6(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) // Make sure full /64 subnets are throttled for IPv6. throttle1, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::1", "action1") @@ -140,7 +142,8 @@ func TestThrottler_Bruteforce(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) delay := 100 * time.Millisecond for range maxBruteforceAttempts { @@ -167,7 +170,8 @@ func TestThrottler_Cleanup(t *testing.T) { th, ok := throttler.(*memoryThrottler) require.True(t, ok, "required memoryThrottler, got %T", throttler) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -220,7 +224,8 @@ func TestThrottler_ExpirePartial(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -251,7 +256,8 @@ func TestThrottler_ExpireAll(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -282,7 +288,8 @@ func TestThrottler_Negative(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - ctx := context.Background() + logger := NewLoggerForTest(t) + ctx := NewLoggerContext(t.Context(), logger) delay := 100 * time.Millisecond for range maxBruteforceAttempts * 10 { diff --git a/transient_data_test.go b/transient_data_test.go index ca554fc..2ee8331 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -139,7 +139,6 @@ func Test_TransientDataDeadlock(t *testing.T) { func Test_TransientMessages(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) hub, _, _, server := CreateHubForTest(t) diff --git a/virtualsession.go b/virtualsession.go index 4ec31cf..57ef6aa 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -24,7 +24,6 @@ package signaling import ( "context" "encoding/json" - "log" "net/url" "sync/atomic" @@ -38,6 +37,7 @@ const ( ) type VirtualSession struct { + logger Logger hub *Hub session *ClientSession privateId PrivateSessionId @@ -61,6 +61,7 @@ func GetVirtualSessionId(session Session, sessionId PublicSessionId) PublicSessi func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, msg *AddSessionInternalClientMessage) (*VirtualSession, error) { result := &VirtualSession{ + logger: session.hub.logger, hub: session.hub, session: session, privateId: privateId, @@ -154,7 +155,7 @@ func (s *VirtualSession) SetRoom(room *Room) { s.room.Store(room) if room != nil { if err := s.hub.roomSessions.SetRoomSession(s, RoomSessionId(s.PublicId())); err != nil { - log.Printf("Error adding virtual room session %s: %s", s.PublicId(), err) + s.logger.Printf("Error adding virtual room session %s: %s", s.PublicId(), err) } } else { s.hub.roomSessions.DeleteRoomSession(s) @@ -191,7 +192,8 @@ func (s *VirtualSession) CloseWithFeedback(session Session, message *ClientMessa } func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, message *ClientMessage) { - ctx, cancel := context.WithTimeout(context.Background(), s.hub.backendTimeout) + ctx := NewLoggerContext(context.Background(), s.logger) + ctx, cancel := context.WithTimeout(ctx, s.hub.backendTimeout) defer cancel() if options := s.Options(); options != nil && options.ActorId != "" && options.ActorType != "" { @@ -203,7 +205,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa var response BackendClientResponse if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { virtualSessionId := GetVirtualSessionId(s.session, s.PublicId()) - log.Printf("Could not leave virtual session %s at backend %s: %s", virtualSessionId, s.BackendUrl(), err) + s.logger.Printf("Could not leave virtual session %s at backend %s: %s", virtualSessionId, s.BackendUrl(), err) if session != nil && message != nil { reply := message.NewErrorServerMessage(NewError("remove_failed", "Could not remove virtual session from backend.")) session.SendMessage(reply) @@ -214,7 +216,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa if response.Type == "error" { virtualSessionId := GetVirtualSessionId(s.session, s.PublicId()) if session != nil && message != nil && (response.Error == nil || response.Error.Code != "no_such_room") { - log.Printf("Could not leave virtual session %s at backend %s: %+v", virtualSessionId, s.BackendUrl(), response.Error) + s.logger.Printf("Could not leave virtual session %s at backend %s: %+v", virtualSessionId, s.BackendUrl(), response.Error) reply := message.NewErrorServerMessage(NewError("remove_failed", response.Error.Error())) session.SendMessage(reply) } @@ -228,7 +230,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa var response BackendClientSessionResponse err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response) if err != nil { - log.Printf("Could not remove virtual session %s from backend %s: %s", s.PublicId(), s.BackendUrl(), err) + s.logger.Printf("Could not remove virtual session %s from backend %s: %s", s.PublicId(), s.BackendUrl(), err) if session != nil && message != nil { reply := message.NewErrorServerMessage(NewError("remove_failed", "Could not remove virtual session from backend.")) session.SendMessage(reply) @@ -291,7 +293,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { message.Message.Event.Type == "disinvite" && message.Message.Event.Disinvite != nil && message.Message.Event.Disinvite.RoomId == room.Id() { - log.Printf("Virtual session %s was disinvited from room %s, hanging up", s.PublicId(), room.Id()) + s.logger.Printf("Virtual session %s was disinvited from room %s, hanging up", s.PublicId(), room.Id()) payload := api.StringMap{ "type": "hangup", "hangup": map[string]string{ @@ -300,7 +302,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { } data, err := json.Marshal(payload) if err != nil { - log.Printf("could not marshal control payload %+v: %s", payload, err) + s.logger.Printf("could not marshal control payload %+v: %s", payload, err) return } diff --git a/virtualsession_test.go b/virtualsession_test.go index 3e7ddf1..cf01ae4 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -35,7 +35,6 @@ import ( func TestVirtualSession(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -219,7 +218,6 @@ func TestVirtualSession(t *testing.T) { func TestVirtualSessionActorInformation(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -429,7 +427,6 @@ func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sess func TestVirtualSessionCustomInCall(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -571,7 +568,6 @@ func TestVirtualSessionCustomInCall(t *testing.T) { func TestVirtualSessionCleanup(t *testing.T) { t.Parallel() - CatchLogForTest(t) require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) From 5863cdd795f2d872a6543252895879a7715678de Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 19 Nov 2025 22:23:49 +0100 Subject: [PATCH 292/549] Fix remote connection still logging after close. Was triggered by TestProxyRemoteSubscriber. --- proxy/proxy_remote.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 9a73122..3c16da2 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -152,7 +152,7 @@ func (c *RemoteConnection) reconnect() { TLSClientConfig: c.tlsConfig, } - conn, _, err := dialer.DialContext(context.TODO(), u.String(), nil) + conn, _, err := dialer.DialContext(c.closeCtx, u.String(), nil) if err != nil { c.logger.Printf("Error connecting to proxy at %s: %s", c, err) c.scheduleReconnect() @@ -162,6 +162,14 @@ func (c *RemoteConnection) reconnect() { c.logger.Printf("Connected to %s", c) c.mu.Lock() + if c.closeCtx.Err() != nil { + // Closed while waiting for lock. + c.mu.Unlock() + if err := conn.Close(); err != nil { + c.logger.Printf("Error closing connection to %s: %s", c, err) + } + return + } c.connectedSince = time.Now() c.conn = conn c.mu.Unlock() @@ -267,9 +275,6 @@ func (c *RemoteConnection) Close() error { c.mu.Lock() defer c.mu.Unlock() c.reconnectTimer.Stop() - if c.conn == nil { - return nil - } if c.closeCtx.Err() != nil { // Already closed @@ -277,9 +282,13 @@ func (c *RemoteConnection) Close() error { } c.closeFunc() - err1 := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{}) - err2 := c.conn.Close() - c.conn = nil + var err1 error + var err2 error + if c.conn != nil { + err1 = c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{}) + err2 = c.conn.Close() + c.conn = nil + } c.connectedSince = time.Time{} c.helloReceived = false if err1 != nil { From bfcabaa2fc8f200263e12e09584ef11924231eeb Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 20 Nov 2025 10:14:48 +0100 Subject: [PATCH 293/549] Wait for NATS client to be closed. --- async_events.go | 2 +- async_events_nats.go | 5 +++-- async_events_test.go | 6 +++++- backend_server_test.go | 9 +++++++-- hub_test.go | 9 +++++++-- natsclient.go | 19 ++++++++++++++++--- natsclient_loopback.go | 20 ++++++++++++++++++-- natsclient_loopback_test.go | 4 +++- natsclient_test.go | 9 ++++++--- server/main.go | 8 +++++++- 10 files changed, 73 insertions(+), 18 deletions(-) diff --git a/async_events.go b/async_events.go index 6598cd3..bbb8aab 100644 --- a/async_events.go +++ b/async_events.go @@ -43,7 +43,7 @@ type AsyncSessionEventListener interface { } type AsyncEvents interface { - Close() + Close(ctx context.Context) error RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) error UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) diff --git a/async_events_nats.go b/async_events_nats.go index ab9fe13..d0ce29b 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -22,6 +22,7 @@ package signaling import ( + "context" "fmt" "sync" "time" @@ -281,7 +282,7 @@ func (e *asyncEventsNats) GetServerInfoNats() *BackendServerInfoNats { return nats } -func (e *asyncEventsNats) Close() { +func (e *asyncEventsNats) Close(ctx context.Context) error { e.mu.Lock() defer e.mu.Unlock() var wg sync.WaitGroup @@ -320,7 +321,7 @@ func (e *asyncEventsNats) Close() { e.userSubscriptions = make(map[string]*asyncUserSubscriberNats) e.sessionSubscriptions = make(map[string]*asyncSessionSubscriberNats) wg.Wait() - e.client.Close() + return e.client.Close(ctx) } func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) error { diff --git a/async_events_test.go b/async_events_test.go index 02d6145..ac14dfb 100644 --- a/async_events_test.go +++ b/async_events_test.go @@ -25,7 +25,9 @@ import ( "context" "strings" "testing" + "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -44,7 +46,9 @@ func getAsyncEventsForTest(t *testing.T) AsyncEvents { events = getLoopbackAsyncEventsForTest(t) } t.Cleanup(func() { - events.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(t, events.Close(ctx)) }) return events } diff --git a/backend_server_test.go b/backend_server_test.go index 460bb7d..7a89a82 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -125,6 +125,7 @@ func CreateBackendServerWithClusteringForTest(t *testing.T) (*BackendServer, *Ba func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *goconf.ConfigFile, config2 *goconf.ConfigFile) (*BackendServer, *BackendServer, *Hub, *Hub, *httptest.Server, *httptest.Server) { require := require.New(t) + assert := assert.New(t) r1 := mux.NewRouter() registerBackendHandler(t, r1) @@ -166,7 +167,9 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g events1, err := NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) t.Cleanup(func() { - events1.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(events1.Close(ctx)) }) client1, _ := NewGrpcClientsForTest(t, addr2) hub1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") @@ -189,7 +192,9 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g events2, err := NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) t.Cleanup(func() { - events2.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(events2.Close(ctx)) }) client2, _ := NewGrpcClientsForTest(t, addr1) hub2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") diff --git a/hub_test.go b/hub_test.go index 3cda700..fab8ad4 100644 --- a/hub_test.go +++ b/hub_test.go @@ -204,6 +204,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) require := require.New(t) + assert := assert.New(t) r1 := mux.NewRouter() registerBackendHandler(t, r1) @@ -238,7 +239,9 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http events1, err := NewAsyncEvents(ctx, nats1.ClientURL()) require.NoError(err) t.Cleanup(func() { - events1.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(events1.Close(ctx)) }) config1, err := getConfigFunc(server1) require.NoError(err) @@ -250,7 +253,9 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http events2, err := NewAsyncEvents(ctx, nats2.ClientURL()) require.NoError(err) t.Cleanup(func() { - events2.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(events2.Close(ctx)) }) config2, err := getConfigFunc(server2) require.NoError(err) diff --git a/natsclient.go b/natsclient.go index 1474155..10292e7 100644 --- a/natsclient.go +++ b/natsclient.go @@ -47,7 +47,7 @@ type NatsSubscription interface { } type NatsClient interface { - Close() + Close(ctx context.Context) error Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) Publish(subject string, message any) error @@ -65,6 +65,7 @@ func GetEncodedSubject(prefix string, suffix string) string { type natsClient struct { logger Logger conn *nats.Conn + closed chan struct{} } func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (NatsClient, error) { @@ -85,6 +86,7 @@ func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (Nat client := &natsClient{ logger: logger, + closed: make(chan struct{}), } options = append([]nats.Option{ @@ -112,12 +114,23 @@ func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (Nat return client, nil } -func (c *natsClient) Close() { +func (c *natsClient) Close(ctx context.Context) error { c.conn.Close() + select { + case <-c.closed: + return nil + case <-ctx.Done(): + return ctx.Err() + } } func (c *natsClient) onClosed(conn *nats.Conn) { - c.logger.Println("NATS client closed", conn.LastError()) + if err := conn.LastError(); err != nil { + c.logger.Printf("NATS client closed, last error %s", conn.LastError()) + } else { + c.logger.Println("NATS client closed") + } + close(c.closed) } func (c *natsClient) onDisconnected(conn *nats.Conn) { diff --git a/natsclient_loopback.go b/natsclient_loopback.go index 1421d9a..a478beb 100644 --- a/natsclient_loopback.go +++ b/natsclient_loopback.go @@ -23,6 +23,7 @@ package signaling import ( "container/list" + "context" "encoding/json" "strings" "sync" @@ -33,7 +34,9 @@ import ( type LoopbackNatsClient struct { logger Logger - mu sync.Mutex + mu sync.Mutex + closed chan struct{} + // +checklocks:mu subscriptions map[string]map[*loopbackNatsSubscription]bool @@ -46,6 +49,7 @@ type LoopbackNatsClient struct { func NewLoopbackNatsClient(logger Logger) (NatsClient, error) { client := &LoopbackNatsClient{ logger: logger, + closed: make(chan struct{}), subscriptions: make(map[string]map[*loopbackNatsSubscription]bool), } @@ -55,6 +59,8 @@ func NewLoopbackNatsClient(logger Logger) (NatsClient, error) { } func (c *LoopbackNatsClient) processMessages() { + defer close(c.closed) + c.mu.Lock() defer c.mu.Unlock() for { @@ -93,7 +99,7 @@ func (c *LoopbackNatsClient) processMessage(msg *nats.Msg) { } } -func (c *LoopbackNatsClient) Close() { +func (c *LoopbackNatsClient) doClose() { c.mu.Lock() defer c.mu.Unlock() @@ -102,6 +108,16 @@ func (c *LoopbackNatsClient) Close() { c.wakeup.Signal() } +func (c *LoopbackNatsClient) Close(ctx context.Context) error { + c.doClose() + select { + case <-c.closed: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + type loopbackNatsSubscription struct { subject string client *LoopbackNatsClient diff --git a/natsclient_loopback_test.go b/natsclient_loopback_test.go index 6cf6ed7..9a299ca 100644 --- a/natsclient_loopback_test.go +++ b/natsclient_loopback_test.go @@ -56,7 +56,9 @@ func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { result, err := NewLoopbackNatsClient(logger) require.NoError(t, err) t.Cleanup(func() { - result.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(t, result.Close(ctx)) }) return result } diff --git a/natsclient_test.go b/natsclient_test.go index cc3b22b..766e123 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -22,6 +22,7 @@ package signaling import ( + "context" "sync/atomic" "testing" "time" @@ -60,7 +61,9 @@ func CreateLocalNatsClientForTest(t *testing.T, options ...nats.Option) (*server result, err := NewNatsClient(ctx, server.ClientURL(), options...) require.NoError(t, err) t.Cleanup(func() { - result.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + assert.NoError(t, result.Close(ctx)) }) return server, port, result } @@ -116,7 +119,7 @@ func TestNatsClient_Subscribe(t *testing.T) { } func testNatsClient_PublishAfterClose(t *testing.T, client NatsClient) { - client.Close() + assert.NoError(t, client.Close(t.Context())) assert.ErrorIs(t, client.Publish("foo", "bar"), nats.ErrConnectionClosed) } @@ -130,7 +133,7 @@ func TestNatsClient_PublishAfterClose(t *testing.T) { } func testNatsClient_SubscribeAfterClose(t *testing.T, client NatsClient) { - client.Close() + assert.NoError(t, client.Close(t.Context())) ch := make(chan *nats.Msg) _, err := client.Subscribe("foo", ch) diff --git a/server/main.go b/server/main.go index 444d2ca..fd34249 100644 --- a/server/main.go +++ b/server/main.go @@ -184,7 +184,13 @@ func main() { if err != nil { logger.Fatal("Could not create async events client: ", err) } - defer events.Close() + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if err := events.Close(ctx); err != nil { + logger.Printf("Error closing events handler: %s", err) + } + }() dnsMonitor, err := signaling.NewDnsMonitor(logger, dnsMonitorInterval) if err != nil { From 2881ca98dceb55eba6bddbac6bf21ef0b4960d7d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 20 Nov 2025 16:46:56 +0100 Subject: [PATCH 294/549] Fix transient data for clustered setups. --- hub.go | 45 +++++++++- transient_data_test.go | 196 ++++++++++++++++++++++------------------- 2 files changed, 148 insertions(+), 93 deletions(-) diff --git a/hub.go b/hub.go index 75a683a..5f59e5d 100644 --- a/hub.go +++ b/hub.go @@ -2676,10 +2676,36 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { return } + var err error if msg.Value == nil { - room.SetTransientDataTTL(msg.Key, nil, msg.TTL) + err = h.events.PublishBackendRoomMessage(room.Id(), room.Backend(), &AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "transient", + Transient: &BackendRoomTransientRequest{ + Action: TransientActionDelete, + Key: msg.Key, + }, + }, + }) } else { - room.SetTransientDataTTL(msg.Key, msg.Value, msg.TTL) + err = h.events.PublishBackendRoomMessage(room.Id(), room.Backend(), &AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "transient", + Transient: &BackendRoomTransientRequest{ + Action: TransientActionSet, + Key: msg.Key, + Value: msg.Value, + TTL: msg.TTL, + }, + }, + }) + } + if err != nil { + response := message.NewWrappedErrorServerMessage(err) + session.SendMessage(response) + return } case "remove": if !isAllowedToUpdateTransientData(session) { @@ -2687,7 +2713,20 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { return } - room.RemoveTransientData(msg.Key) + if err := h.events.PublishBackendRoomMessage(room.Id(), room.Backend(), &AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "transient", + Transient: &BackendRoomTransientRequest{ + Action: TransientActionDelete, + Key: msg.Key, + }, + }, + }); err != nil { + response := message.NewWrappedErrorServerMessage(err) + session.SendMessage(response) + return + } default: response := message.NewErrorServerMessage(NewError("ignored", "Unsupported message type.")) session.SendMessage(response) diff --git a/transient_data_test.go b/transient_data_test.go index 2ee8331..39e29df 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -23,6 +23,7 @@ package signaling import ( "context" + "net/http/httptest" "sync" "testing" "time" @@ -138,123 +139,138 @@ func Test_TransientDataDeadlock(t *testing.T) { } func Test_TransientMessages(t *testing.T) { - t.Parallel() - require := require.New(t) - hub, _, _, server := CreateHubForTest(t) + for _, subtest := range clusteredTests { + t.Run(subtest, func(t *testing.T) { + t.Parallel() + require := require.New(t) + var hub1 *Hub + var hub2 *Hub + var server1 *httptest.Server + var server2 *httptest.Server + if isLocalTest(t) { + hub1, _, _, server1 = CreateHubForTest(t) - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() + hub2 = hub1 + server2 = server1 + } else { + hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) + } - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() - require.NoError(client1.SetTransientData("foo", "bar", 0)) - if msg, ok := client1.RunUntilMessage(ctx); ok { - checkMessageError(t, msg, "not_in_room") - } + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NoError(client1.SetTransientData("foo", "bar", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_in_room") + } - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") - // Give message processing some time. - time.Sleep(10 * time.Millisecond) + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) + // Give message processing some time. + time.Sleep(10 * time.Millisecond) - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) - session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) - require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) - session2 := hub.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) - require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - // Client 1 may modify transient data. - session1.SetPermissions([]Permission{PERMISSION_TRANSIENT_DATA}) - // Client 2 may not modify transient data. - session2.SetPermissions([]Permission{}) + session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) + require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) + session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) + require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) - require.NoError(client2.SetTransientData("foo", "bar", 0)) - if msg, ok := client2.RunUntilMessage(ctx); ok { - checkMessageError(t, msg, "not_allowed") - } + // Client 1 may modify transient data. + session1.SetPermissions([]Permission{PERMISSION_TRANSIENT_DATA}) + // Client 2 may not modify transient data. + session2.SetPermissions([]Permission{}) - require.NoError(client1.SetTransientData("foo", "bar", 0)) + require.NoError(client2.SetTransientData("foo", "bar", 0)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") + } - if msg, ok := client1.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, "foo", "bar", nil) - } - if msg, ok := client2.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, "foo", "bar", nil) - } + require.NoError(client1.SetTransientData("foo", "bar", 0)) - require.NoError(client2.RemoveTransientData("foo")) - if msg, ok := client2.RunUntilMessage(ctx); ok { - checkMessageError(t, msg, "not_allowed") - } + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", "bar", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", "bar", nil) + } - // Setting the same value is ignored by the server. - require.NoError(client1.SetTransientData("foo", "bar", 0)) - ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel2() + require.NoError(client2.RemoveTransientData("foo")) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") + } - client1.RunUntilErrorIs(ctx2, context.DeadlineExceeded) + // Setting the same value is ignored by the server. + require.NoError(client1.SetTransientData("foo", "bar", 0)) + ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel2() - data := map[string]any{ - "hello": "world", - } - require.NoError(client1.SetTransientData("foo", data, 0)) + client1.RunUntilErrorIs(ctx2, context.DeadlineExceeded) - if msg, ok := client1.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, "foo", data, "bar") - } - if msg, ok := client2.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, "foo", data, "bar") - } + data := map[string]any{ + "hello": "world", + } + require.NoError(client1.SetTransientData("foo", data, 0)) - require.NoError(client1.RemoveTransientData("foo")) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", data, "bar") + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", data, "bar") + } - if msg, ok := client1.RunUntilMessage(ctx); ok { - checkMessageTransientRemove(t, msg, "foo", data) - } - if msg, ok := client2.RunUntilMessage(ctx); ok { - checkMessageTransientRemove(t, msg, "foo", data) - } + require.NoError(client1.RemoveTransientData("foo")) - // Removing a non-existing key is ignored by the server. - require.NoError(client1.RemoveTransientData("foo")) - ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel3() + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "foo", data) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "foo", data) + } - client1.RunUntilErrorIs(ctx3, context.DeadlineExceeded) + // Removing a non-existing key is ignored by the server. + require.NoError(client1.RemoveTransientData("foo")) + ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel3() - require.NoError(client1.SetTransientData("abc", data, 10*time.Millisecond)) + client1.RunUntilErrorIs(ctx3, context.DeadlineExceeded) - client3, hello3 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"3") - roomMsg = MustSucceed2(t, client3.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) + require.NoError(client1.SetTransientData("abc", data, 10*time.Millisecond)) - _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello, hello3.Hello) - require.True(ok) + client3, hello3 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"3") + roomMsg = MustSucceed2(t, client3.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) - var msg *ServerMessage - if len(ignored) == 0 { - msg = MustSucceed1(t, client3.RunUntilMessage, ctx) - } else if len(ignored) == 1 { - msg = ignored[0] - } else { - require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) - } + _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello, hello3.Hello) + require.True(ok) - checkMessageTransientInitial(t, msg, api.StringMap{ - "abc": data, - }) + var msg *ServerMessage + if len(ignored) == 0 { + msg = MustSucceed1(t, client3.RunUntilMessage, ctx) + } else if len(ignored) == 1 { + msg = ignored[0] + } else { + require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) + } - time.Sleep(10 * time.Millisecond) - if msg, ok = client3.RunUntilMessage(ctx); ok { - checkMessageTransientRemove(t, msg, "abc", data) + checkMessageTransientInitial(t, msg, api.StringMap{ + "abc": data, + }) + + time.Sleep(10 * time.Millisecond) + if msg, ok = client3.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "abc", data) + } + }) } } From 73fc0d747e5684c08b5f3bd7a6376be6905a98bc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 20 Nov 2025 16:52:16 +0100 Subject: [PATCH 295/549] Add test for transient data with federation. --- federation_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/federation_test.go b/federation_test.go index e97996b..f1c362d 100644 --- a/federation_test.go +++ b/federation_test.go @@ -1042,3 +1042,92 @@ func Test_FederationResumeNewSession(t *testing.T) { } client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) } + +func Test_FederationTransientData(t *testing.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 := MustSucceed1(t, client1.RunUntilHello, ctx) + hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1 := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, room1.Room.RoomId) + + client1.RunUntilJoined(ctx, hello1.Hello) + + now := time.Now() + userdata := api.StringMap{ + "displayname": "Federated user", + "actorType": "federated_users", + "actorId": "the-federated-user-id", + } + token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, ok := client2.RunUntilMessage(ctx); ok { + 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 PublicSessionId + if message, ok := client1.RunUntilMessage(ctx); ok { + 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. + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) + + require.NoError(client1.SetTransientData("foo", "bar", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", "bar", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "foo", "bar", nil) + } + + require.NoError(client2.SetTransientData("bar", "baz", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "bar", "baz", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "bar", "baz", nil) + } +} From 5e291e4cce6f07b6884fb1f470bf9d1860c4fab7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:01:32 +0000 Subject: [PATCH 296/549] Bump go.uber.org/zap from 1.27.0 to 1.27.1 Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.27.0 to 1.27.1. - [Release notes](https://github.com/uber-go/zap/releases) - [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/zap/compare/v1.27.0...v1.27.1) --- updated-dependencies: - dependency-name: go.uber.org/zap dependency-version: 1.27.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 98d5362..5067645 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/pkg/v3 v3.6.6 go.etcd.io/etcd/client/v3 v3.6.6 go.etcd.io/etcd/server/v3 v3.6.6 - go.uber.org/zap v1.27.0 + go.uber.org/zap v1.27.1 google.golang.org/grpc v1.77.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 google.golang.org/protobuf v1.36.10 diff --git a/go.sum b/go.sum index 32d6fc4..f49abe0 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 48c6a783cea470ab2ed74ec9276b1c83bbab20dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:01:50 +0000 Subject: [PATCH 297/549] Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-continentmap.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/command-rebase.yml | 2 +- .github/workflows/deploydocker.yml | 4 ++-- .github/workflows/docker-compose.yml | 4 ++-- .github/workflows/docker-janus.yml | 2 +- .github/workflows/docker.yml | 4 ++-- .github/workflows/generated.yml | 4 ++-- .github/workflows/govuln.yml | 2 +- .github/workflows/licensecheck.yml | 2 +- .github/workflows/lint.yml | 8 ++++---- .github/workflows/shellcheck.yml | 2 +- .github/workflows/tarball.yml | 2 +- .github/workflows/test.yml | 6 +++--- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/check-continentmap.yml b/.github/workflows/check-continentmap.yml index 6f76ff0..3c26ed2 100644 --- a/.github/workflows/check-continentmap.yml +++ b/.github/workflows/check-continentmap.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Check continentmap run: make check-continentmap diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5f93431..c65e540 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/command-rebase.yml b/.github/workflows/command-rebase.yml index be2d0ed..489574b 100644 --- a/.github/workflows/command-rebase.yml +++ b/.github/workflows/command-rebase.yml @@ -31,7 +31,7 @@ jobs: reaction-type: "+1" - name: Checkout the latest code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.COMMAND_BOT_PAT }} diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 0ff24dc..3b8510d 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Check Out Repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -92,7 +92,7 @@ jobs: steps: - name: Check Out Repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/docker-compose.yml b/.github/workflows/docker-compose.yml index 826f86f..7636c95 100644 --- a/.github/workflows/docker-compose.yml +++ b/.github/workflows/docker-compose.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Update docker-compose run: | @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Update docker-compose run: | diff --git a/.github/workflows/docker-janus.yml b/.github/workflows/docker-janus.yml index ba27074..a6b82d7 100644 --- a/.github/workflows/docker-janus.yml +++ b/.github/workflows/docker-janus.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fd050de..352ba27 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/generated.yml b/.github/workflows/generated.yml index 3fba84f..1c08588 100644 --- a/.github/workflows/generated.yml +++ b/.github/workflows/generated.yml @@ -53,7 +53,7 @@ jobs: contents: write continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: token: ${{ secrets.CODE_GENERATOR_PAT }} ref: ${{ github.event.pull_request.head.ref }} @@ -97,7 +97,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: "stable" diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index ca286c5..8c6de48 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -27,7 +27,7 @@ jobs: - "1.24" - "1.25" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/licensecheck.yml b/.github/workflows/licensecheck.yml index cb3f2ad..b2d2de7 100644 --- a/.github/workflows/licensecheck.yml +++ b/.github/workflows/licensecheck.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install licensecheck run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 73d350c..44df88c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: "1.24" @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: "1.24" @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: "1.24" @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: "stable" diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 42d0e87..31954f7 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -20,7 +20,7 @@ jobs: name: shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: shellcheck run: | diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 4693927..0fd0e8a 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -28,7 +28,7 @@ jobs: - "1.25" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95d4e07..f9a0bdd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: - "1.25" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} @@ -53,7 +53,7 @@ jobs: - "1.25" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} @@ -73,7 +73,7 @@ jobs: - "1.25" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} From a8b46d59a6a233925150954df9a0372ea3065487 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:01:42 +0000 Subject: [PATCH 298/549] Bump golangci/golangci-lint-action from 9.0.0 to 9.1.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.0.0 to 9.1.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v9.0.0...v9.1.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 44df88c..facdd06 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,7 +33,7 @@ jobs: go-version: "1.24" - name: lint - uses: golangci/golangci-lint-action@v9.0.0 + uses: golangci/golangci-lint-action@v9.1.0 with: version: latest args: --timeout=2m0s From 8935965df67cb98d19782739d060cfd64fbe8efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 23 Nov 2025 12:24:32 +0100 Subject: [PATCH 299/549] fix(docs): already_joined error response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- docs/standalone-signaling-api-v1.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 80be66a..7318a57 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -493,9 +493,11 @@ Message format (Server -> Client if already joined before): "code": "already_joined", "message": "Human readable error message", "details": { - "roomid": "the-room-id", - "properties": { - ...additional room properties... + "room": { + "roomid": "the-room-id", + "properties": { + ...additional room properties... + } } } } From c1618155b7a0fb4873d18ff81deb7da867d55687 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 21 Nov 2025 11:57:43 +0100 Subject: [PATCH 300/549] Get transient data from other nodes on first room join. --- grpc_client.go | 34 +++++++++ grpc_internal.proto | 15 ++++ grpc_server.go | 62 ++++++++++++++++ hub.go | 39 +--------- room.go | 112 +++++++++++++++++++++++++++-- transient_data.go | 157 ++++++++++++++++++++++++++++++++++++----- transient_data_test.go | 31 +++++--- 7 files changed, 382 insertions(+), 68 deletions(-) diff --git a/grpc_client.go b/grpc_client.go index 81b449e..64e45f8 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -326,6 +326,40 @@ func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, e return response.GetCount(), nil } +func (c *GrpcClient) GetTransientData(ctx context.Context, room *Room) (TransientDataEntries, error) { + statsGrpcClientCalls.WithLabelValues("GetTransientData").Inc() + // TODO: Remove debug logging + c.logger.Printf("Get transient data for %s@%s on %s", room.Id(), room.Backend().Id(), c.Target()) + response, err := c.impl.GetTransientData(ctx, &GetTransientDataRequest{ + RoomId: room.Id(), + BackendUrls: room.Backend().Urls(), + }, grpc.WaitForReady(true)) + if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { + return nil, nil + } else if err != nil { + return nil, err + } + + entries := response.GetEntries() + if len(entries) == 0 { + return nil, nil + } + + result := make(TransientDataEntries, len(entries)) + for k, v := range entries { + var value any + if err := json.Unmarshal(v.Value, &value); err != nil { + return nil, err + } + if v.Expires > 0 { + result[k] = NewTransientDataEntryWithExpires(value, time.UnixMicro(v.Expires)) + } else { + result[k] = NewTransientDataEntry(value, 0) + } + } + return result, nil +} + type ProxySessionReceiver interface { RemoteAddr() string Country() string diff --git a/grpc_internal.proto b/grpc_internal.proto index 6093f78..22c873f 100644 --- a/grpc_internal.proto +++ b/grpc_internal.proto @@ -27,6 +27,7 @@ package signaling; service RpcInternal { rpc GetServerId(GetServerIdRequest) returns (GetServerIdReply) {} + rpc GetTransientData(GetTransientDataRequest) returns (GetTransientDataReply) {} } message GetServerIdRequest { @@ -36,3 +37,17 @@ message GetServerIdReply { string serverId = 1; string version = 2; } + +message GetTransientDataRequest { + string roomId = 1; + repeated string backendUrls = 2; +} + +message GrpcTransientDataEntry { + bytes value = 1; + int64 expires = 2; +} + +message GetTransientDataReply { + map entries = 1; +} diff --git a/grpc_server.go b/grpc_server.go index c7863c4..27dc993 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -25,6 +25,7 @@ import ( "context" "crypto/sha256" "encoding/hex" + "encoding/json" "errors" "fmt" "net" @@ -304,6 +305,67 @@ func (s *GrpcServer) GetServerId(ctx context.Context, request *GetServerIdReques }, nil } +func (s *GrpcServer) GetTransientData(ctx context.Context, request *GetTransientDataRequest) (*GetTransientDataReply, error) { + statsGrpcServerCalls.WithLabelValues("GetTransientData").Inc() + + backendUrls := request.BackendUrls + if len(backendUrls) == 0 { + // Only compat backend. + backendUrls = []string{""} + } + + result := &GetTransientDataReply{} + processed := make(map[string]bool) + for _, bu := range backendUrls { + var parsed *url.URL + if bu != "" { + var err error + parsed, err = url.Parse(bu) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid url") + } + } + + backend := s.hub.GetBackend(parsed) + if backend == nil { + return nil, status.Error(codes.NotFound, "no such backend") + } + + // Only process each backend once. + if processed[backend.Id()] { + continue + } + processed[backend.Id()] = true + + room := s.hub.GetRoomForBackend(request.RoomId, backend) + if room == nil { + return nil, status.Error(codes.NotFound, "no such room") + } + + entries := room.transientData.GetEntries() + if len(entries) == 0 { + return nil, status.Error(codes.NotFound, "room has no transient data") + } + + if result.Entries == nil { + result.Entries = make(map[string]*GrpcTransientDataEntry) + } + for k, v := range entries { + e := &GrpcTransientDataEntry{} + var err error + if e.Value, err = json.Marshal(v.Value); err != nil { + return nil, status.Errorf(codes.Internal, "error marshalling data: %s", err) + } + if !v.Expires.IsZero() { + e.Expires = v.Expires.UnixMicro() + } + result.Entries[k] = e + } + } + + return result, nil +} + func (s *GrpcServer) GetSessionCount(ctx context.Context, request *GetSessionCountRequest) (*GetSessionCountReply, error) { statsGrpcServerCalls.WithLabelValues("SessionCount").Inc() diff --git a/hub.go b/hub.go index 5f59e5d..9a61087 100644 --- a/hub.go +++ b/hub.go @@ -2676,33 +2676,7 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { return } - var err error - if msg.Value == nil { - err = h.events.PublishBackendRoomMessage(room.Id(), room.Backend(), &AsyncMessage{ - Type: "room", - Room: &BackendServerRoomRequest{ - Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionDelete, - Key: msg.Key, - }, - }, - }) - } else { - err = h.events.PublishBackendRoomMessage(room.Id(), room.Backend(), &AsyncMessage{ - Type: "room", - Room: &BackendServerRoomRequest{ - Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionSet, - Key: msg.Key, - Value: msg.Value, - TTL: msg.TTL, - }, - }, - }) - } - if err != nil { + if err := room.SetTransientDataTTL(msg.Key, msg.Value, msg.TTL); err != nil { response := message.NewWrappedErrorServerMessage(err) session.SendMessage(response) return @@ -2713,16 +2687,7 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { return } - if err := h.events.PublishBackendRoomMessage(room.Id(), room.Backend(), &AsyncMessage{ - Type: "room", - Room: &BackendServerRoomRequest{ - Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionDelete, - Key: msg.Key, - }, - }, - }); err != nil { + if err := room.RemoveTransientData(msg.Key); err != nil { response := message.NewWrappedErrorServerMessage(err) session.SendMessage(response) return diff --git a/room.go b/room.go index 6e75077..4cad160 100644 --- a/room.go +++ b/room.go @@ -258,9 +258,13 @@ func (r *Room) processBackendRoomRequestRoom(message *BackendServerRoomRequest) case "transient": switch message.Transient.Action { case TransientActionSet: - r.SetTransientDataTTL(message.Transient.Key, message.Transient.Value, message.Transient.TTL) + if message.Transient.TTL == 0 { + r.doSetTransientData(message.Transient.Key, message.Transient.Value) + } else { + r.doSetTransientDataTTL(message.Transient.Key, message.Transient.Value, message.Transient.TTL) + } case TransientActionDelete: - r.RemoveTransientData(message.Transient.Key) + r.doRemoveTransientData(message.Transient.Key) default: r.logger.Printf("Unsupported transient action in room %s: %+v", r.Id(), message.Transient) } @@ -293,6 +297,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { sid := session.PublicId() r.mu.Lock() + isFirst := len(r.sessions) == 0 _, found := r.sessions[sid] r.sessions[sid] = session if !found { @@ -334,6 +339,9 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { if clientSession, ok := session.(*ClientSession); ok { r.transientData.AddListener(clientSession) } + if isFirst { + r.fetchInitialTransientData() + } } // Trigger notifications that the session joined. @@ -1202,14 +1210,108 @@ func (r *Room) notifyInternalRoomDeleted() { } } -func (r *Room) SetTransientData(key string, value any) { +func (r *Room) SetTransientData(key string, value any) error { + if value == nil { + return r.RemoveTransientData(key) + } + + return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "transient", + Transient: &BackendRoomTransientRequest{ + Action: TransientActionSet, + Key: key, + Value: value, + }, + }, + }) +} + +func (r *Room) doSetTransientData(key string, value any) { r.transientData.Set(key, value) } -func (r *Room) SetTransientDataTTL(key string, value any, ttl time.Duration) { +func (r *Room) SetTransientDataTTL(key string, value any, ttl time.Duration) error { + if value == nil { + return r.RemoveTransientData(key) + } else if ttl == 0 { + return r.SetTransientData(key, value) + } + + return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "transient", + Transient: &BackendRoomTransientRequest{ + Action: TransientActionSet, + Key: key, + Value: value, + TTL: ttl, + }, + }, + }) +} + +func (r *Room) doSetTransientDataTTL(key string, value any, ttl time.Duration) { r.transientData.SetTTL(key, value, ttl) } -func (r *Room) RemoveTransientData(key string) { +func (r *Room) RemoveTransientData(key string) error { + return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ + Type: "room", + Room: &BackendServerRoomRequest{ + Type: "transient", + Transient: &BackendRoomTransientRequest{ + Action: TransientActionDelete, + Key: key, + }, + }, + }) +} + +func (r *Room) doRemoveTransientData(key string) { r.transientData.Remove(key) } + +func (r *Room) fetchInitialTransientData() { + if r.hub.rpcClients == nil { + return + } + + ctx := NewLoggerContext(context.Background(), r.logger) + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + var wg sync.WaitGroup + var mu sync.Mutex + // +checklocks:mu + var initial TransientDataEntries + for _, client := range r.hub.rpcClients.GetClients() { + wg.Add(1) + go func(c *GrpcClient) { + defer wg.Done() + + data, err := c.GetTransientData(ctx, r) + if err != nil { + r.logger.Printf("Received error while getting transient data for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) + return + } + + r.logger.Printf("Received initial transient data %+v from %s", data, c.Target()) + mu.Lock() + defer mu.Unlock() + if initial == nil { + initial = make(TransientDataEntries) + } + maps.Copy(initial, data) + }(client) + } + wg.Wait() + + mu.Lock() + defer mu.Unlock() + if len(initial) > 0 { + r.transientData.SetInitial(initial) + } +} diff --git a/transient_data.go b/transient_data.go index f3fbf9f..5ce9559 100644 --- a/transient_data.go +++ b/transient_data.go @@ -22,7 +22,8 @@ package signaling import ( - "maps" + "encoding/json" + "fmt" "reflect" "sync" "time" @@ -34,10 +35,58 @@ type TransientListener interface { SendMessage(message *ServerMessage) bool } +type TransientDataEntry struct { + Value any `json:"value"` + Expires time.Time `json:"expires,omitzero"` +} + +func NewTransientDataEntry(value any, ttl time.Duration) *TransientDataEntry { + entry := &TransientDataEntry{ + Value: value, + } + if ttl > 0 { + entry.Expires = time.Now().Add(ttl) + } + return entry +} + +func NewTransientDataEntryWithExpires(value any, expires time.Time) *TransientDataEntry { + entry := &TransientDataEntry{ + Value: value, + Expires: expires, + } + return entry +} + +func (e *TransientDataEntry) clone() *TransientDataEntry { + result := *e + return &result +} + +func (e *TransientDataEntry) update(value any, ttl time.Duration) { + e.Value = value + if ttl > 0 { + e.Expires = time.Now().Add(ttl) + } else { + e.Expires = time.Time{} + } +} + +type TransientDataEntries map[string]*TransientDataEntry + +func (e TransientDataEntries) String() string { + data, err := json.Marshal(e) + if err != nil { + return fmt.Sprintf("Could not serialize %#v: %s", e, err) + } + + return string(data) +} + type TransientData struct { mu sync.Mutex // +checklocks:mu - data api.StringMap + data TransientDataEntries // +checklocks:mu listeners map[TransientListener]bool // +checklocks:mu @@ -66,8 +115,8 @@ func (t *TransientData) notifySet(key string, prev, value any) { TransientData: &TransientDataServerMessage{ Type: "set", Key: key, - OldValue: prev, Value: value, + OldValue: prev, }, } for listener := range t.listeners { @@ -76,15 +125,17 @@ func (t *TransientData) notifySet(key string, prev, value any) { } // +checklocks:t.mu -func (t *TransientData) notifyDeleted(key string, prev any) { +func (t *TransientData) notifyDeleted(key string, prev *TransientDataEntry) { msg := &ServerMessage{ Type: "transient", TransientData: &TransientDataServerMessage{ - Type: "remove", - Key: key, - OldValue: prev, + Type: "remove", + Key: key, }, } + if prev != nil { + msg.TransientData.OldValue = prev.Value + } for listener := range t.listeners { t.sendMessageToListener(listener, msg) } @@ -100,11 +151,15 @@ func (t *TransientData) AddListener(listener TransientListener) { } t.listeners[listener] = true if len(t.data) > 0 { + data := make(api.StringMap, len(t.data)) + for k, v := range t.data { + data[k] = v.Value + } msg := &ServerMessage{ Type: "transient", TransientData: &TransientDataServerMessage{ Type: "initial", - Data: t.data, + Data: data, }, } t.sendMessageToListener(listener, msg) @@ -157,12 +212,19 @@ func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) } // +checklocks:t.mu -func (t *TransientData) doSet(key string, value any, prev any, ttl time.Duration) { +func (t *TransientData) doSet(key string, value any, prev *TransientDataEntry, ttl time.Duration) { if t.data == nil { - t.data = make(api.StringMap) + t.data = make(TransientDataEntries) } - t.data[key] = value - t.notifySet(key, prev, value) + var oldValue any + if prev == nil { + entry := NewTransientDataEntry(value, ttl) + t.data[key] = entry + } else { + oldValue = prev.Value + prev.update(value, ttl) + } + t.notifySet(key, oldValue, value) t.removeAfterTTL(key, value, ttl) } @@ -183,7 +245,7 @@ func (t *TransientData) SetTTL(key string, value any, ttl time.Duration) bool { defer t.mu.Unlock() prev, found := t.data[key] - if found && reflect.DeepEqual(prev, value) { + if found && reflect.DeepEqual(prev.Value, value) { t.updateTTL(key, value, ttl) return false } @@ -210,7 +272,7 @@ func (t *TransientData) CompareAndSetTTL(key string, old, value any, ttl time.Du defer t.mu.Unlock() prev, found := t.data[key] - if old != nil && (!found || !reflect.DeepEqual(prev, old)) { + if old != nil && (!found || !reflect.DeepEqual(prev.Value, old)) { return false } else if old == nil && found { return false @@ -221,7 +283,7 @@ func (t *TransientData) CompareAndSetTTL(key string, old, value any, ttl time.Du } // +checklocks:t.mu -func (t *TransientData) doRemove(key string, prev any) { +func (t *TransientData) doRemove(key string, prev *TransientDataEntry) { delete(t.data, key) if old, found := t.timers[key]; found { old.Stop() @@ -257,7 +319,7 @@ func (t *TransientData) CompareAndRemove(key string, old any) bool { // +checklocks:t.mu func (t *TransientData) compareAndRemove(key string, old any) bool { prev, found := t.data[key] - if !found || !reflect.DeepEqual(prev, old) { + if !found || !reflect.DeepEqual(prev.Value, old) { return false } @@ -270,7 +332,66 @@ func (t *TransientData) GetData() api.StringMap { t.mu.Lock() defer t.mu.Unlock() - result := make(api.StringMap) - maps.Copy(result, t.data) + if len(t.data) == 0 { + return nil + } + + result := make(api.StringMap, len(t.data)) + for k, entry := range t.data { + result[k] = entry.Value + } return result } + +// GetEntries returns a copy of the internal data entries. +func (t *TransientData) GetEntries() TransientDataEntries { + t.mu.Lock() + defer t.mu.Unlock() + + if len(t.data) == 0 { + return nil + } + + result := make(TransientDataEntries, len(t.data)) + for k, e := range t.data { + result[k] = e.clone() + } + return result +} + +// SetInitial sets the initial data and notifies listeners. +func (t *TransientData) SetInitial(data TransientDataEntries) { + if len(data) == 0 { + return + } + + t.mu.Lock() + defer t.mu.Unlock() + + if t.data == nil { + t.data = make(TransientDataEntries) + } + + msgData := make(api.StringMap, len(data)) + for k, v := range data { + if _, found := t.data[k]; found { + // Entry already present (i.e. was set by regular event). + continue + } + + msgData[k] = v.Value + } + if len(msgData) == 0 { + return + } + msg := &ServerMessage{ + Type: "transient", + TransientData: &TransientDataServerMessage{ + Type: "initial", + Data: msgData, + }, + } + for listener := range t.listeners { + t.sendMessageToListener(listener, msg) + } +} diff --git a/transient_data_test.go b/transient_data_test.go index 39e29df..b089044 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -143,6 +143,7 @@ func Test_TransientMessages(t *testing.T) { t.Run(subtest, func(t *testing.T) { t.Parallel() require := require.New(t) + assert := assert.New(t) var hub1 *Hub var hub2 *Hub var server1 *httptest.Server @@ -245,13 +246,24 @@ func Test_TransientMessages(t *testing.T) { client1.RunUntilErrorIs(ctx3, context.DeadlineExceeded) - require.NoError(client1.SetTransientData("abc", data, 10*time.Millisecond)) + ttl := 200 * time.Millisecond + require.NoError(client1.SetTransientData("abc", data, ttl)) + setAt := time.Now() + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "abc", data, nil) + } + + client1.CloseWithBye() + require.NoError(client1.WaitForClientRemoved(ctx)) + client2.RunUntilLeft(ctx, hello1.Hello) client3, hello3 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"3") roomMsg = MustSucceed2(t, client3.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello1.Hello, hello2.Hello, hello3.Hello) + client2.RunUntilJoined(ctx, hello3.Hello) + + _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello2.Hello, hello3.Hello) require.True(ok) var msg *ServerMessage @@ -263,13 +275,16 @@ func Test_TransientMessages(t *testing.T) { require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } - checkMessageTransientInitial(t, msg, api.StringMap{ - "abc": data, - }) + delta := time.Until(setAt.Add(ttl)) + if assert.Greater(delta, time.Duration(0), "test runner too slow?") { + checkMessageTransientInitial(t, msg, api.StringMap{ + "abc": data, + }) - time.Sleep(10 * time.Millisecond) - if msg, ok = client3.RunUntilMessage(ctx); ok { - checkMessageTransientRemove(t, msg, "abc", data) + time.Sleep(delta) + if msg, ok = client2.RunUntilMessage(ctx); ok { + checkMessageTransientRemove(t, msg, "abc", data) + } } }) } From 2d8fbda85db044bb4d626bfcbf67690fd2c37431 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 21 Nov 2025 11:58:07 +0100 Subject: [PATCH 301/549] Update generated files. --- grpc_internal.pb.go | 194 ++++++++++++++++++++++++++++++++++++--- grpc_internal_grpc.pb.go | 40 +++++++- 2 files changed, 220 insertions(+), 14 deletions(-) diff --git a/grpc_internal.pb.go b/grpc_internal.pb.go index 470c6a0..b0e4219 100644 --- a/grpc_internal.pb.go +++ b/grpc_internal.pb.go @@ -127,6 +127,154 @@ func (x *GetServerIdReply) GetVersion() string { return "" } +type GetTransientDataRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoomId string `protobuf:"bytes,1,opt,name=roomId,proto3" json:"roomId,omitempty"` + BackendUrls []string `protobuf:"bytes,2,rep,name=backendUrls,proto3" json:"backendUrls,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTransientDataRequest) Reset() { + *x = GetTransientDataRequest{} + mi := &file_grpc_internal_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTransientDataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransientDataRequest) ProtoMessage() {} + +func (x *GetTransientDataRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_internal_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTransientDataRequest.ProtoReflect.Descriptor instead. +func (*GetTransientDataRequest) Descriptor() ([]byte, []int) { + return file_grpc_internal_proto_rawDescGZIP(), []int{2} +} + +func (x *GetTransientDataRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *GetTransientDataRequest) GetBackendUrls() []string { + if x != nil { + return x.BackendUrls + } + return nil +} + +type GrpcTransientDataEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + Expires int64 `protobuf:"varint,2,opt,name=expires,proto3" json:"expires,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GrpcTransientDataEntry) Reset() { + *x = GrpcTransientDataEntry{} + mi := &file_grpc_internal_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GrpcTransientDataEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GrpcTransientDataEntry) ProtoMessage() {} + +func (x *GrpcTransientDataEntry) ProtoReflect() protoreflect.Message { + mi := &file_grpc_internal_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GrpcTransientDataEntry.ProtoReflect.Descriptor instead. +func (*GrpcTransientDataEntry) Descriptor() ([]byte, []int) { + return file_grpc_internal_proto_rawDescGZIP(), []int{3} +} + +func (x *GrpcTransientDataEntry) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *GrpcTransientDataEntry) GetExpires() int64 { + if x != nil { + return x.Expires + } + return 0 +} + +type GetTransientDataReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Entries map[string]*GrpcTransientDataEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTransientDataReply) Reset() { + *x = GetTransientDataReply{} + mi := &file_grpc_internal_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTransientDataReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransientDataReply) ProtoMessage() {} + +func (x *GetTransientDataReply) ProtoReflect() protoreflect.Message { + mi := &file_grpc_internal_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTransientDataReply.ProtoReflect.Descriptor instead. +func (*GetTransientDataReply) Descriptor() ([]byte, []int) { + return file_grpc_internal_proto_rawDescGZIP(), []int{4} +} + +func (x *GetTransientDataReply) GetEntries() map[string]*GrpcTransientDataEntry { + if x != nil { + return x.Entries + } + return nil +} + var File_grpc_internal_proto protoreflect.FileDescriptor const file_grpc_internal_proto_rawDesc = "" + @@ -135,9 +283,21 @@ const file_grpc_internal_proto_rawDesc = "" + "\x12GetServerIdRequest\"H\n" + "\x10GetServerIdReply\x12\x1a\n" + "\bserverId\x18\x01 \x01(\tR\bserverId\x12\x18\n" + - "\aversion\x18\x02 \x01(\tR\aversion2Z\n" + + "\aversion\x18\x02 \x01(\tR\aversion\"S\n" + + "\x17GetTransientDataRequest\x12\x16\n" + + "\x06roomId\x18\x01 \x01(\tR\x06roomId\x12 \n" + + "\vbackendUrls\x18\x02 \x03(\tR\vbackendUrls\"H\n" + + "\x16GrpcTransientDataEntry\x12\x14\n" + + "\x05value\x18\x01 \x01(\fR\x05value\x12\x18\n" + + "\aexpires\x18\x02 \x01(\x03R\aexpires\"\xbf\x01\n" + + "\x15GetTransientDataReply\x12G\n" + + "\aentries\x18\x01 \x03(\v2-.signaling.GetTransientDataReply.EntriesEntryR\aentries\x1a]\n" + + "\fEntriesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x127\n" + + "\x05value\x18\x02 \x01(\v2!.signaling.GrpcTransientDataEntryR\x05value:\x028\x012\xb6\x01\n" + "\vRpcInternal\x12K\n" + - "\vGetServerId\x12\x1d.signaling.GetServerIdRequest\x1a\x1b.signaling.GetServerIdReply\"\x00B signaling.GetServerIdRequest - 1, // 1: signaling.RpcInternal.GetServerId:output_type -> signaling.GetServerIdReply - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 5, // 0: signaling.GetTransientDataReply.entries:type_name -> signaling.GetTransientDataReply.EntriesEntry + 3, // 1: signaling.GetTransientDataReply.EntriesEntry.value:type_name -> signaling.GrpcTransientDataEntry + 0, // 2: signaling.RpcInternal.GetServerId:input_type -> signaling.GetServerIdRequest + 2, // 3: signaling.RpcInternal.GetTransientData:input_type -> signaling.GetTransientDataRequest + 1, // 4: signaling.RpcInternal.GetServerId:output_type -> signaling.GetServerIdReply + 4, // 5: signaling.RpcInternal.GetTransientData:output_type -> signaling.GetTransientDataReply + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_grpc_internal_proto_init() } @@ -177,7 +345,7 @@ func file_grpc_internal_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_internal_proto_rawDesc), len(file_grpc_internal_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/grpc_internal_grpc.pb.go b/grpc_internal_grpc.pb.go index 577ef0d..ace5174 100644 --- a/grpc_internal_grpc.pb.go +++ b/grpc_internal_grpc.pb.go @@ -37,7 +37,8 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - RpcInternal_GetServerId_FullMethodName = "/signaling.RpcInternal/GetServerId" + RpcInternal_GetServerId_FullMethodName = "/signaling.RpcInternal/GetServerId" + RpcInternal_GetTransientData_FullMethodName = "/signaling.RpcInternal/GetTransientData" ) // RpcInternalClient is the client API for RpcInternal service. @@ -45,6 +46,7 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RpcInternalClient interface { GetServerId(ctx context.Context, in *GetServerIdRequest, opts ...grpc.CallOption) (*GetServerIdReply, error) + GetTransientData(ctx context.Context, in *GetTransientDataRequest, opts ...grpc.CallOption) (*GetTransientDataReply, error) } type rpcInternalClient struct { @@ -65,11 +67,22 @@ func (c *rpcInternalClient) GetServerId(ctx context.Context, in *GetServerIdRequ return out, nil } +func (c *rpcInternalClient) GetTransientData(ctx context.Context, in *GetTransientDataRequest, opts ...grpc.CallOption) (*GetTransientDataReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetTransientDataReply) + err := c.cc.Invoke(ctx, RpcInternal_GetTransientData_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // RpcInternalServer is the server API for RpcInternal service. // All implementations must embed UnimplementedRpcInternalServer // for forward compatibility. type RpcInternalServer interface { GetServerId(context.Context, *GetServerIdRequest) (*GetServerIdReply, error) + GetTransientData(context.Context, *GetTransientDataRequest) (*GetTransientDataReply, error) mustEmbedUnimplementedRpcInternalServer() } @@ -83,6 +96,9 @@ type UnimplementedRpcInternalServer struct{} func (UnimplementedRpcInternalServer) GetServerId(context.Context, *GetServerIdRequest) (*GetServerIdReply, error) { return nil, status.Errorf(codes.Unimplemented, "method GetServerId not implemented") } +func (UnimplementedRpcInternalServer) GetTransientData(context.Context, *GetTransientDataRequest) (*GetTransientDataReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTransientData not implemented") +} func (UnimplementedRpcInternalServer) mustEmbedUnimplementedRpcInternalServer() {} func (UnimplementedRpcInternalServer) testEmbeddedByValue() {} @@ -122,6 +138,24 @@ func _RpcInternal_GetServerId_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _RpcInternal_GetTransientData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTransientDataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RpcInternalServer).GetTransientData(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RpcInternal_GetTransientData_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RpcInternalServer).GetTransientData(ctx, req.(*GetTransientDataRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RpcInternal_ServiceDesc is the grpc.ServiceDesc for RpcInternal service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -133,6 +167,10 @@ var RpcInternal_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetServerId", Handler: _RpcInternal_GetServerId_Handler, }, + { + MethodName: "GetTransientData", + Handler: _RpcInternal_GetTransientData_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "grpc_internal.proto", From 8532428ec11f3958e7a4c02a7e30b000ac138ba6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 21 Nov 2025 11:58:42 +0100 Subject: [PATCH 302/549] Add note that initial data may be sent using multiple events. --- docs/standalone-signaling-api-v1.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 80be66a..f8963e3 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1342,7 +1342,9 @@ Message format (Server -> Client): ### Initial data When sessions initially join a room, they receive the current state of the -transient data. +transient data. Please note that the initial data can be sent in multiple +events of type `initial` which must be combined to generate the total initial +data. Message format (Server -> Client): From 9e98b7bf13d939ef219d02eb649cbd12d4fc9ddc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 10:01:26 +0100 Subject: [PATCH 303/549] Fix storing initial data when clustered. --- transient_data.go | 1 + transient_data_test.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/transient_data.go b/transient_data.go index 5ce9559..9dc6cb9 100644 --- a/transient_data.go +++ b/transient_data.go @@ -380,6 +380,7 @@ func (t *TransientData) SetInitial(data TransientDataEntries) { } msgData[k] = v.Value + t.data[k] = v } if len(msgData) == 0 { return diff --git a/transient_data_test.go b/transient_data_test.go index b089044..20aed68 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -275,12 +275,34 @@ func Test_TransientMessages(t *testing.T) { require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) } + checkMessageTransientInitial(t, msg, api.StringMap{ + "abc": data, + }) + + client4, hello4 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"4") + roomMsg = MustSucceed2(t, client4.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client2.RunUntilJoined(ctx, hello4.Hello) + client3.RunUntilJoined(ctx, hello4.Hello) + + _, ignored, ok = client4.RunUntilJoinedAndReturn(ctx, hello2.Hello, hello3.Hello, hello4.Hello) + require.True(ok) + + if len(ignored) == 0 { + msg = MustSucceed1(t, client4.RunUntilMessage, ctx) + } else if len(ignored) == 1 { + msg = ignored[0] + } else { + require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) + } + + checkMessageTransientInitial(t, msg, api.StringMap{ + "abc": data, + }) + delta := time.Until(setAt.Add(ttl)) if assert.Greater(delta, time.Duration(0), "test runner too slow?") { - checkMessageTransientInitial(t, msg, api.StringMap{ - "abc": data, - }) - time.Sleep(delta) if msg, ok = client2.RunUntilMessage(ctx); ok { checkMessageTransientRemove(t, msg, "abc", data) From 51a6162514cc8eeea86d80e9f426a16299b3709a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 10:07:48 +0100 Subject: [PATCH 304/549] CI: Split tarball jobs to speed up total actions time. --- .github/workflows/tarball.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 0fd0e8a..55330ad 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -44,7 +44,7 @@ jobs: name: tarball-${{ matrix.go-version }} path: nextcloud-spreed-signaling*.tar.gz - test: + build: strategy: matrix: go-version: @@ -73,6 +73,30 @@ jobs: echo "Building with $(nproc) threads" make -C tmp build -j$(nproc) + test: + strategy: + matrix: + go-version: + - "1.24" + - "1.25" + runs-on: ubuntu-latest + needs: [create] + steps: + - uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + + - name: Download tarball + uses: actions/download-artifact@v6 + with: + name: tarball-${{ matrix.go-version }} + + - name: Extract tarball + run: | + mkdir -p tmp + tar xvf nextcloud-spreed-signaling*.tar.gz --strip-components=1 -C tmp + [ -d "tmp/vendor" ] || exit 1 + - name: Run tests env: USE_DB_IP_GEOIP_DATABASE: "1" From a31e3c4c530ea02de10232c75761ab7fbdeed8d0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 11:19:25 +0100 Subject: [PATCH 305/549] Update client code for changed backend auth url, fix overflow in delta calculations. --- client/main.go | 58 +++++++++++--------------------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/client/main.go b/client/main.go index 3102cff..4633680 100644 --- a/client/main.go +++ b/client/main.go @@ -25,7 +25,6 @@ import ( "bytes" "encoding/json" "flag" - "fmt" "io" "log" pseudorand "math/rand" @@ -75,10 +74,10 @@ const ( ) type Stats struct { - numRecvMessages atomic.Uint64 - numSentMessages atomic.Uint64 - resetRecvMessages uint64 - resetSentMessages uint64 + numRecvMessages atomic.Int64 + numSentMessages atomic.Int64 + resetRecvMessages int64 + resetSentMessages int64 start time.Time } @@ -92,7 +91,7 @@ func (s *Stats) reset(start time.Time) { func (s *Stats) Log() { now := time.Now() duration := now.Sub(s.start) - perSec := uint64(duration / time.Second) + perSec := int64(duration / time.Second) if perSec == 0 { return } @@ -114,7 +113,6 @@ type MessagePayload struct { type SignalingClient struct { readyWg *sync.WaitGroup // +checklocksignore: Only written to from constructor. - cookie *signaling.SessionIdCodec conn *websocket.Conn @@ -132,7 +130,7 @@ type SignalingClient struct { userId string } -func NewSignalingClient(cookie *signaling.SessionIdCodec, url string, stats *Stats, readyWg *sync.WaitGroup, doneWg *sync.WaitGroup) (*SignalingClient, error) { +func NewSignalingClient(url string, stats *Stats, readyWg *sync.WaitGroup, doneWg *sync.WaitGroup) (*SignalingClient, error) { conn, _, err := websocket.DefaultDialer.Dial(url, nil) if err != nil { return nil, err @@ -140,7 +138,6 @@ func NewSignalingClient(cookie *signaling.SessionIdCodec, url string, stats *Sta client := &SignalingClient{ readyWg: readyWg, - cookie: cookie, conn: conn, @@ -196,6 +193,8 @@ func (c *SignalingClient) Send(message *signaling.ClientMessage) { func (c *SignalingClient) processMessage(message *signaling.ServerMessage) { c.stats.numRecvMessages.Add(1) switch message.Type { + case "welcome": + // Ignore welcome message. case "hello": c.processHelloMessage(message) case "message": @@ -211,23 +210,11 @@ func (c *SignalingClient) processMessage(message *signaling.ServerMessage) { } } -func (c *SignalingClient) privateToPublicSessionId(privateId signaling.PrivateSessionId) signaling.PublicSessionId { - data, err := c.cookie.DecodePrivate(privateId) - if err != nil { - panic(fmt.Sprintf("could not decode private session id: %s", err)) - } - publicId, err := c.cookie.EncodePublic(data) - if err != nil { - panic(fmt.Sprintf("could not encode public id: %s", err)) - } - return publicId -} - func (c *SignalingClient) processHelloMessage(message *signaling.ServerMessage) { c.lock.Lock() defer c.lock.Unlock() c.privateSessionId = message.Hello.ResumeId - c.publicSessionId = c.privateToPublicSessionId(c.privateSessionId) + c.publicSessionId = message.Hello.SessionId c.userId = message.Hello.UserId log.Printf("Registered as %s (userid %s)", c.privateSessionId, c.userId) c.readyWg.Done() @@ -407,7 +394,7 @@ func (c *SignalingClient) SendMessages(clients []*SignalingClient) { } func registerAuthHandler(router *mux.Router) { - router.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) { + router.HandleFunc("/ocs/v2.php/apps/spreed/api/v1/signaling/backend", func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { log.Println("Error reading body:", err) @@ -498,27 +485,6 @@ func main() { secret, _ := signaling.GetStringOptionWithEnv(config, "backend", "secret") backendSecret = []byte(secret) - hashKey, _ := signaling.GetStringOptionWithEnv(config, "sessions", "hashkey") - switch len(hashKey) { - case 32: - case 64: - default: - log.Printf("WARNING: The sessions hash key should be 32 or 64 bytes but is %d bytes", len(hashKey)) - } - - blockKey, _ := signaling.GetStringOptionWithEnv(config, "sessions", "blockkey") - blockBytes := []byte(blockKey) - switch len(blockKey) { - case 0: - blockBytes = nil - case 16: - case 24: - case 32: - default: - log.Fatalf("The sessions block key must be 16, 24 or 32 bytes but is %d bytes", len(blockKey)) - } - cookie := signaling.NewSessionIdCodec([]byte(hashKey), blockBytes) - log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) interrupt := make(chan os.Signal, 1) @@ -568,7 +534,7 @@ func main() { var readyWg sync.WaitGroup for i := 0; i < *maxClients; i++ { - client, err := NewSignalingClient(cookie, urls[i%len(urls)].String(), stats, &readyWg, &doneWg) + client, err := NewSignalingClient(urls[i%len(urls)].String(), stats, &readyWg, &doneWg) if err != nil { log.Fatal(err) } @@ -580,7 +546,7 @@ func main() { Hello: &signaling.HelloClientMessage{ Version: signaling.HelloVersionV1, Auth: &signaling.HelloClientMessageAuth{ - Url: backendUrl + "/auth", + Url: backendUrl, Params: json.RawMessage("{}"), }, }, From 0a669b067ef5c2fd5db65be592c60942e868b998 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 12:30:20 +0100 Subject: [PATCH 306/549] Use string concatenation instead of simple fmt.Sprintf. --- async_events_nats.go | 3 +-- hub.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/async_events_nats.go b/async_events_nats.go index d0ce29b..55b3bf8 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -23,7 +23,6 @@ package signaling import ( "context" - "fmt" "sync" "time" @@ -55,7 +54,7 @@ func GetSubjectForUserId(userId string, backend *Backend) string { } func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string { - return fmt.Sprintf("session.%s", sessionId) + return string("session." + sessionId) } type asyncSubscriberNats struct { diff --git a/hub.go b/hub.go index 9a61087..485f1de 100644 --- a/hub.go +++ b/hub.go @@ -663,7 +663,7 @@ func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { return nil } - cache_key := fmt.Sprintf("%s|%s", id, privateSessionName) + cache_key := string(id + "|" + privateSessionName) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { return result @@ -683,7 +683,7 @@ func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { return nil } - cache_key := fmt.Sprintf("%s|%s", id, publicSessionName) + cache_key := string(id + "|" + publicSessionName) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { return result From f412ef533a225dc7c008bcfc2d101b8aa1b178f6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 12:30:52 +0100 Subject: [PATCH 307/549] Use "UnmarshalJSON" instead of "json.Unmarshal" for some speedup. --- hub.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hub.go b/hub.go index 485f1de..ab2da57 100644 --- a/hub.go +++ b/hub.go @@ -2072,7 +2072,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { if h.mcu != nil { // Maybe this is a message to be processed by the MCU. var data MessageClientMessageData - if err := json.Unmarshal(msg.Data, &data); err == nil { + if err := data.UnmarshalJSON(msg.Data); err == nil { if err := data.CheckValid(); err != nil { h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) if err, ok := err.(*Error); ok { @@ -2189,7 +2189,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { if h.mcu != nil { var data MessageClientMessageData - if err := json.Unmarshal(msg.Data, &data); err == nil { + if err := data.UnmarshalJSON(msg.Data); err == nil { if err := data.CheckValid(); err != nil { h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) if err, ok := err.(*Error); ok { From 4250d912ae31e28f3036586d1d690c9b7748dbff Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 12:31:21 +0100 Subject: [PATCH 308/549] Add benchmarks for string concatenation. --- async_events_nats_test.go | 47 +++++++++++++++++++++++++++++++ hub_test.go | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 async_events_nats_test.go diff --git a/async_events_nats_test.go b/async_events_nats_test.go new file mode 100644 index 0000000..35a1484 --- /dev/null +++ b/async_events_nats_test.go @@ -0,0 +1,47 @@ +/** + * 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 signaling + +import ( + "testing" + + "google.golang.org/protobuf/types/known/timestamppb" +) + +func Benchmark_GetSubjectForSessionId(b *testing.B) { + backend := &Backend{ + id: "compat", + } + data := &SessionIdData{ + Sid: 1, + Created: timestamppb.Now(), + BackendId: backend.Id(), + } + codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + sid, err := codec.EncodePublic(data) + if err != nil { + b.Fatalf("could not create session id: %s", err) + } + for b.Loop() { + GetSubjectForSessionId(sid, backend) + } +} diff --git a/hub_test.go b/hub_test.go index fab8ad4..f6cafed 100644 --- a/hub_test.go +++ b/hub_test.go @@ -51,6 +51,7 @@ import ( "github.com/nats-io/nats-server/v2/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/strukturag/nextcloud-spreed-signaling/api" ) @@ -827,6 +828,64 @@ func performHousekeeping(hub *Hub, now time.Time) *sync.WaitGroup { return &wg } +func Benchmark_DecodePrivateSessionId(b *testing.B) { + decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) + for range numDecodeCaches { + decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) + } + backend := &Backend{ + id: "compat", + } + data := &SessionIdData{ + Sid: 1, + Created: timestamppb.Now(), + BackendId: backend.Id(), + } + codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + sid, err := codec.EncodePrivate(data) + if err != nil { + b.Fatalf("could not create session id: %s", err) + } + hub := &Hub{ + cookie: codec, + decodeCaches: decodeCaches, + } + // Decode once to populate cache. + hub.decodePrivateSessionId(sid) + for b.Loop() { + hub.decodePrivateSessionId(sid) + } +} + +func Benchmark_DecodePublicSessionId(b *testing.B) { + decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) + for range numDecodeCaches { + decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) + } + backend := &Backend{ + id: "compat", + } + data := &SessionIdData{ + Sid: 1, + Created: timestamppb.Now(), + BackendId: backend.Id(), + } + codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + sid, err := codec.EncodePublic(data) + if err != nil { + b.Fatalf("could not create session id: %s", err) + } + hub := &Hub{ + cookie: codec, + decodeCaches: decodeCaches, + } + // Decode once to populate cache. + hub.decodePublicSessionId(sid) + for b.Loop() { + hub.decodePublicSessionId(sid) + } +} + func TestWebsocketFeatures(t *testing.T) { t.Parallel() require := require.New(t) From 855d2c82310d9a4ca4e9824024cb25a7c9dd128f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 12:32:42 +0100 Subject: [PATCH 309/549] CI: Run benchmarks. --- .github/workflows/test.yml | 20 ++++++++++++++++++++ Makefile | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9a0bdd..14f5a07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,26 @@ jobs: run: | make test TIMEOUT=120s + benchmark: + env: + MAXMIND_GEOLITE2_LICENSE: ${{ secrets.MAXMIND_GEOLITE2_LICENSE }} + USE_DB_IP_GEOIP_DATABASE: "1" + strategy: + matrix: + go-version: + - "1.24" + - "1.25" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + + - name: Run benchmarks + run: | + make benchmark + coverage: env: MAXMIND_GEOLITE2_LICENSE: ${{ secrets.MAXMIND_GEOLITE2_LICENSE }} diff --git a/Makefile b/Makefile index de8898e..8ef1d2c 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,10 @@ ifeq ($(TIMEOUT),) TIMEOUT := 60s endif +ifeq ($(BENCHMARK),) +BENCHMARK := . +endif + ifneq ($(TEST),) TESTARGS := $(TESTARGS) -run "$(TEST)" endif @@ -111,6 +115,9 @@ vet: test: vet GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) $(TESTARGS) ./... +benchmark: + GOEXPERIMENT=synctest $(GO) test -bench=$(BENCHMARK) -benchmem -run=^$$ -timeout $(TIMEOUT) $(TESTARGS) ./... + checklocks: $(GOPATHBIN)/checklocks GOEXPERIMENT=synctest go vet -vettool=$(GOPATHBIN)/checklocks ./... From e90760a2f141c1ae811e6e96428ab5aee678cd41 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 14:04:15 +0100 Subject: [PATCH 310/549] Use test-related logger for embedded etcd. --- etcd_client_test.go | 2 +- proxy/proxy_tokens_etcd_test.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/etcd_client_test.go b/etcd_client_test.go index 8c2e07d..4a6fe8e 100644 --- a/etcd_client_test.go +++ b/etcd_client_test.go @@ -79,6 +79,7 @@ func NewEtcdForTestWithTls(t *testing.T, withTLS bool) (*embed.Etcd, string, str os.Chmod(cfg.Dir, 0700) // nolint cfg.LogLevel = "warn" cfg.Name = "signalingtest" + cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))) u, err := url.Parse(etcdListenUrl) require.NoError(err) @@ -119,7 +120,6 @@ func NewEtcdForTestWithTls(t *testing.T, withTLS bool) (*embed.Etcd, string, str cfg.ListenPeerUrls = []url.URL{*peerListener} cfg.AdvertisePeerUrls = []url.URL{*peerListener} cfg.InitialCluster = "signalingtest=" + peerListener.String() - cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))) etcd, err = embed.StartEtcd(cfg) if isErrorAddressAlreadyInUse(err) { continue diff --git a/proxy/proxy_tokens_etcd_test.go b/proxy/proxy_tokens_etcd_test.go index be600f2..4161d12 100644 --- a/proxy/proxy_tokens_etcd_test.go +++ b/proxy/proxy_tokens_etcd_test.go @@ -41,6 +41,8 @@ import ( "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" "go.etcd.io/etcd/server/v3/lease" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" signaling "github.com/strukturag/nextcloud-spreed-signaling" ) @@ -73,6 +75,7 @@ func newEtcdForTesting(t *testing.T) *embed.Etcd { cfg.Dir = t.TempDir() os.Chmod(cfg.Dir, 0700) // nolint cfg.LogLevel = "warn" + cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))) u, err := url.Parse(etcdListenUrl) require.NoError(t, err) From 3d4a16bdad45818586465feaed1c8d24b27293dd Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 15:06:07 +0100 Subject: [PATCH 311/549] No need to use list of pointers, use objects directly. --- api_signaling.go | 17 ++++++++++------- clientsession.go | 8 ++++---- federation.go | 3 ++- room.go | 6 +++--- testclient_test.go | 4 ++-- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/api_signaling.go b/api_signaling.go index 4af7c28..32f83ef 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -724,6 +724,9 @@ type MessageClientMessage struct { } type MessageClientMessageData struct { + json.Marshaler + json.Unmarshaler + Type string `json:"type"` Sid string `json:"sid"` RoomType string `json:"roomType"` @@ -1196,11 +1199,11 @@ type EventServerMessage struct { Type string `json:"type"` // Used for target "room" - Join []*EventServerMessageSessionEntry `json:"join,omitempty"` - Leave []PublicSessionId `json:"leave,omitempty"` - Change []*EventServerMessageSessionEntry `json:"change,omitempty"` - SwitchTo *EventServerMessageSwitchTo `json:"switchto,omitempty"` - Resumed *bool `json:"resumed,omitempty"` + Join []EventServerMessageSessionEntry `json:"join,omitempty"` + Leave []PublicSessionId `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"` @@ -1229,8 +1232,8 @@ type EventServerMessageSessionEntry struct { Federated bool `json:"federated,omitempty"` } -func (e *EventServerMessageSessionEntry) Clone() *EventServerMessageSessionEntry { - return &EventServerMessageSessionEntry{ +func (e EventServerMessageSessionEntry) Clone() EventServerMessageSessionEntry { + return EventServerMessageSessionEntry{ SessionId: e.SessionId, UserId: e.UserId, Features: e.Features, diff --git a/clientsession.go b/clientsession.go index a22af47..ee0fbd2 100644 --- a/clientsession.go +++ b/clientsession.go @@ -1178,8 +1178,8 @@ func (s *ClientSession) storePendingMessage(message *ServerMessage) { } } -func filterDisplayNames(events []*EventServerMessageSessionEntry) []*EventServerMessageSessionEntry { - result := make([]*EventServerMessageSessionEntry, 0, len(events)) +func filterDisplayNames(events []EventServerMessageSessionEntry) []EventServerMessageSessionEntry { + result := make([]EventServerMessageSessionEntry, 0, len(events)) for _, event := range events { if len(event.User) == 0 { result = append(result, event) @@ -1219,14 +1219,14 @@ func filterDisplayNames(events []*EventServerMessageSessionEntry) []*EventServer return result } -func (s *ClientSession) filterDuplicateJoin(entries []*EventServerMessageSessionEntry) []*EventServerMessageSessionEntry { +func (s *ClientSession) filterDuplicateJoin(entries []EventServerMessageSessionEntry) []EventServerMessageSessionEntry { s.seenJoinedLock.Lock() defer s.seenJoinedLock.Unlock() // Due to the asynchronous events, a session might received a "Joined" event // for the same (other) session twice, so filter these out on a per-session // level. - result := make([]*EventServerMessageSessionEntry, 0, len(entries)) + result := make([]EventServerMessageSessionEntry, 0, len(entries)) for _, e := range entries { if s.seenJoinedEvents[e.SessionId] { s.logger.Printf("Session %s got duplicate joined event for %s, ignoring", s.publicId, e.SessionId) diff --git a/federation.go b/federation.go index 5fa9ec1..e704bf8 100644 --- a/federation.go +++ b/federation.go @@ -803,9 +803,10 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { switch msg.Event.Type { case "join": if remoteSessionId != "" { - for _, j := range msg.Event.Join { + for idx, j := range msg.Event.Join { if j.SessionId == remoteSessionId { j.SessionId = localSessionId + msg.Event.Join[idx] = j break } } diff --git a/room.go b/room.go index 4cad160..b97d979 100644 --- a/room.go +++ b/room.go @@ -383,9 +383,9 @@ func (r *Room) notifySessionJoined(sessionId PublicSessionId) { session = nil } - events := make([]*EventServerMessageSessionEntry, 0, len(sessions)) + events := make([]EventServerMessageSessionEntry, 0, len(sessions)) for _, s := range sessions { - entry := &EventServerMessageSessionEntry{ + entry := EventServerMessageSessionEntry{ SessionId: s.PublicId(), UserId: s.UserId(), User: s.UserData(), @@ -561,7 +561,7 @@ func (r *Room) PublishSessionJoined(session Session, sessionData *RoomSessionDat Event: &EventServerMessage{ Target: "room", Type: "join", - Join: []*EventServerMessageSessionEntry{ + Join: []EventServerMessageSessionEntry{ { SessionId: sessionId, UserId: userid, diff --git a/testclient_test.go b/testclient_test.go index 8ce9081..e10d8c7 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -847,8 +847,8 @@ func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId return !failed } -func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*HelloServerMessage) ([]*EventServerMessageSessionEntry, []*ServerMessage, bool) { - received := make([]*EventServerMessageSessionEntry, len(hello)) +func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*HelloServerMessage) ([]EventServerMessageSessionEntry, []*ServerMessage, bool) { + received := make([]EventServerMessageSessionEntry, len(hello)) var ignored []*ServerMessage hellos := make(map[*HelloServerMessage]int, len(hello)) for idx, h := range hello { From 535a36042cab9e06832a4c86de3cbc223e04640c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 15:06:21 +0100 Subject: [PATCH 312/549] Update generated files. --- api_signaling_easyjson.go | 44 +++++++++------------------------------ 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 67ecbdb..d85860a 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -4425,27 +4425,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Delim('[') if out.Join == nil { if !in.IsDelim(']') { - out.Join = make([]*EventServerMessageSessionEntry, 0, 8) + out.Join = make([]EventServerMessageSessionEntry, 0, 0) } else { - out.Join = []*EventServerMessageSessionEntry{} + out.Join = []EventServerMessageSessionEntry{} } } else { out.Join = (out.Join)[:0] } for !in.IsDelim(']') { - var v34 *EventServerMessageSessionEntry + var v34 EventServerMessageSessionEntry if in.IsNull() { in.Skip() - v34 = nil } else { - if v34 == nil { - v34 = new(EventServerMessageSessionEntry) - } - if in.IsNull() { - in.Skip() - } else { - (*v34).UnmarshalEasyJSON(in) - } + (v34).UnmarshalEasyJSON(in) } out.Join = append(out.Join, v34) in.WantComma() @@ -4487,27 +4479,19 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Delim('[') if out.Change == nil { if !in.IsDelim(']') { - out.Change = make([]*EventServerMessageSessionEntry, 0, 8) + out.Change = make([]EventServerMessageSessionEntry, 0, 0) } else { - out.Change = []*EventServerMessageSessionEntry{} + out.Change = []EventServerMessageSessionEntry{} } } else { out.Change = (out.Change)[:0] } for !in.IsDelim(']') { - var v36 *EventServerMessageSessionEntry + var v36 EventServerMessageSessionEntry if in.IsNull() { in.Skip() - v36 = nil } else { - if v36 == nil { - v36 = new(EventServerMessageSessionEntry) - } - if in.IsNull() { - in.Skip() - } else { - (*v36).UnmarshalEasyJSON(in) - } + (v36).UnmarshalEasyJSON(in) } out.Change = append(out.Change, v36) in.WantComma() @@ -4645,11 +4629,7 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw if v37 > 0 { out.RawByte(',') } - if v38 == nil { - out.RawString("null") - } else { - (*v38).MarshalEasyJSON(out) - } + (v38).MarshalEasyJSON(out) } out.RawByte(']') } @@ -4677,11 +4657,7 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw if v41 > 0 { out.RawByte(',') } - if v42 == nil { - out.RawString("null") - } else { - (*v42).MarshalEasyJSON(out) - } + (v42).MarshalEasyJSON(out) } out.RawByte(']') } From aefa5c9e36244ba24555ab543d76b006539d1ac8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 15:07:00 +0100 Subject: [PATCH 313/549] Break loop when looking for joined events if no hello matched. --- testclient_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testclient_test.go b/testclient_test.go index e10d8c7..e3cc012 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -886,7 +886,9 @@ func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*Hell } } } - c.assert.True(found, "expected one of the passed hello sessions, got %+v", message.Event.Join) + if !c.assert.True(found, "expected one of the passed hello sessions, got %+v", message.Event.Join) { + break + } } } return received, ignored, true From a042d00d25eb2ea88f85d0d373fc6d7d82e5a033 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:01:44 +0000 Subject: [PATCH 314/549] Bump github.com/pion/ice/v4 from 4.0.10 to 4.0.11 Bumps [github.com/pion/ice/v4](https://github.com/pion/ice) from 4.0.10 to 4.0.11. - [Release notes](https://github.com/pion/ice/releases) - [Changelog](https://github.com/pion/ice/blob/master/.goreleaser.yml) - [Commits](https://github.com/pion/ice/compare/v4.0.10...v4.0.11) --- updated-dependencies: - dependency-name: github.com/pion/ice/v4 dependency-version: 4.0.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 5067645..49c9481 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/ice/v4 v4.0.10 + github.com/pion/ice/v4 v4.0.11 github.com/pion/sdp/v3 v3.0.16 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 @@ -58,13 +58,13 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.0.6 // indirect - github.com/pion/logging v0.2.3 // indirect - github.com/pion/mdns/v2 v2.0.7 // indirect + github.com/pion/dtls/v3 v3.0.7 // indirect + github.com/pion/logging v0.2.4 // indirect + github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/stun/v3 v3.0.0 // indirect - github.com/pion/transport/v3 v3.0.7 // indirect - github.com/pion/turn/v4 v4.0.0 // indirect + github.com/pion/stun/v3 v3.0.1 // indirect + github.com/pion/transport/v3 v3.1.1 // indirect + github.com/pion/turn/v4 v4.1.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect diff --git a/go.sum b/go.sum index f49abe0..b4fe787 100644 --- a/go.sum +++ b/go.sum @@ -90,24 +90,24 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= -github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= -github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= -github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= -github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= -github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= -github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= +github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q= +github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8= +github.com/pion/ice/v4 v4.0.11 h1:KRKfAH2G1+WahCARyy6ZJ1MwDQ+1q9wblDV8sAoz6Dg= +github.com/pion/ice/v4 v4.0.11/go.mod h1:tAp574oAufhHRHr8EO1xgPmVKVDBROX+708WggYD6NE= +github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= +github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= +github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= +github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= -github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= -github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= -github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= -github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= -github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= -github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= +github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA= +github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw= +github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= +github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= +github.com/pion/turn/v4 v4.1.3 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA= +github.com/pion/turn/v4 v4.1.3/go.mod h1:TD/eiBUf5f5LwXbCJa35T7dPtTpCHRJ9oJWmyPLVT3A= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= From 49d9f873dd213e6fee09ebcfbcfa78f764a77658 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:01:27 +0000 Subject: [PATCH 315/549] Bump google.golang.org/grpc/cmd/protoc-gen-go-grpc from 1.5.1 to 1.6.0 Bumps [google.golang.org/grpc/cmd/protoc-gen-go-grpc](https://github.com/grpc/grpc-go) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.5.1...v1.6.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc/cmd/protoc-gen-go-grpc dependency-version: 1.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 49c9481..5d07f20 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.etcd.io/etcd/server/v3 v3.6.6 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.77.0 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 google.golang.org/protobuf v1.36.10 ) diff --git a/go.sum b/go.sum index b4fe787..b957331 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 595a23ca0ace359401b988e7c979b344a2308163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:03:34 +0000 Subject: [PATCH 316/549] Update generated files from 49d9f873dd213e6fee09ebcfbcfa78f764a77658 --- grpc_backend_grpc.pb.go | 4 ++-- grpc_internal_grpc.pb.go | 6 +++--- grpc_mcu_grpc.pb.go | 4 ++-- grpc_sessions_grpc.pb.go | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/grpc_backend_grpc.pb.go b/grpc_backend_grpc.pb.go index 9f690fc..6f8c1c9 100644 --- a/grpc_backend_grpc.pb.go +++ b/grpc_backend_grpc.pb.go @@ -81,7 +81,7 @@ type RpcBackendServer interface { type UnimplementedRpcBackendServer struct{} func (UnimplementedRpcBackendServer) GetSessionCount(context.Context, *GetSessionCountRequest) (*GetSessionCountReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSessionCount not implemented") + return nil, status.Error(codes.Unimplemented, "method GetSessionCount not implemented") } func (UnimplementedRpcBackendServer) mustEmbedUnimplementedRpcBackendServer() {} func (UnimplementedRpcBackendServer) testEmbeddedByValue() {} @@ -94,7 +94,7 @@ type UnsafeRpcBackendServer interface { } func RegisterRpcBackendServer(s grpc.ServiceRegistrar, srv RpcBackendServer) { - // If the following call pancis, it indicates UnimplementedRpcBackendServer was + // If the following call panics, it indicates UnimplementedRpcBackendServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/grpc_internal_grpc.pb.go b/grpc_internal_grpc.pb.go index ace5174..9899a97 100644 --- a/grpc_internal_grpc.pb.go +++ b/grpc_internal_grpc.pb.go @@ -94,10 +94,10 @@ type RpcInternalServer interface { type UnimplementedRpcInternalServer struct{} func (UnimplementedRpcInternalServer) GetServerId(context.Context, *GetServerIdRequest) (*GetServerIdReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetServerId not implemented") + return nil, status.Error(codes.Unimplemented, "method GetServerId not implemented") } func (UnimplementedRpcInternalServer) GetTransientData(context.Context, *GetTransientDataRequest) (*GetTransientDataReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTransientData not implemented") + return nil, status.Error(codes.Unimplemented, "method GetTransientData not implemented") } func (UnimplementedRpcInternalServer) mustEmbedUnimplementedRpcInternalServer() {} func (UnimplementedRpcInternalServer) testEmbeddedByValue() {} @@ -110,7 +110,7 @@ type UnsafeRpcInternalServer interface { } func RegisterRpcInternalServer(s grpc.ServiceRegistrar, srv RpcInternalServer) { - // If the following call pancis, it indicates UnimplementedRpcInternalServer was + // If the following call panics, it indicates UnimplementedRpcInternalServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/grpc_mcu_grpc.pb.go b/grpc_mcu_grpc.pb.go index 3b37591..af6565c 100644 --- a/grpc_mcu_grpc.pb.go +++ b/grpc_mcu_grpc.pb.go @@ -81,7 +81,7 @@ type RpcMcuServer interface { type UnimplementedRpcMcuServer struct{} func (UnimplementedRpcMcuServer) GetPublisherId(context.Context, *GetPublisherIdRequest) (*GetPublisherIdReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPublisherId not implemented") + return nil, status.Error(codes.Unimplemented, "method GetPublisherId not implemented") } func (UnimplementedRpcMcuServer) mustEmbedUnimplementedRpcMcuServer() {} func (UnimplementedRpcMcuServer) testEmbeddedByValue() {} @@ -94,7 +94,7 @@ type UnsafeRpcMcuServer interface { } func RegisterRpcMcuServer(s grpc.ServiceRegistrar, srv RpcMcuServer) { - // If the following call pancis, it indicates UnimplementedRpcMcuServer was + // If the following call panics, it indicates UnimplementedRpcMcuServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/grpc_sessions_grpc.pb.go b/grpc_sessions_grpc.pb.go index 8b9c6a1..dc2173a 100644 --- a/grpc_sessions_grpc.pb.go +++ b/grpc_sessions_grpc.pb.go @@ -136,19 +136,19 @@ type RpcSessionsServer interface { type UnimplementedRpcSessionsServer struct{} func (UnimplementedRpcSessionsServer) LookupResumeId(context.Context, *LookupResumeIdRequest) (*LookupResumeIdReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method LookupResumeId not implemented") + return nil, status.Error(codes.Unimplemented, "method LookupResumeId not implemented") } func (UnimplementedRpcSessionsServer) LookupSessionId(context.Context, *LookupSessionIdRequest) (*LookupSessionIdReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method LookupSessionId not implemented") + return nil, status.Error(codes.Unimplemented, "method LookupSessionId not implemented") } func (UnimplementedRpcSessionsServer) IsSessionInCall(context.Context, *IsSessionInCallRequest) (*IsSessionInCallReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method IsSessionInCall not implemented") + return nil, status.Error(codes.Unimplemented, "method IsSessionInCall not implemented") } func (UnimplementedRpcSessionsServer) GetInternalSessions(context.Context, *GetInternalSessionsRequest) (*GetInternalSessionsReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetInternalSessions not implemented") + return nil, status.Error(codes.Unimplemented, "method GetInternalSessions not implemented") } func (UnimplementedRpcSessionsServer) ProxySession(grpc.BidiStreamingServer[ClientSessionMessage, ServerSessionMessage]) error { - return status.Errorf(codes.Unimplemented, "method ProxySession not implemented") + return status.Error(codes.Unimplemented, "method ProxySession not implemented") } func (UnimplementedRpcSessionsServer) mustEmbedUnimplementedRpcSessionsServer() {} func (UnimplementedRpcSessionsServer) testEmbeddedByValue() {} @@ -161,7 +161,7 @@ type UnsafeRpcSessionsServer interface { } func RegisterRpcSessionsServer(s grpc.ServiceRegistrar, srv RpcSessionsServer) { - // If the following call pancis, it indicates UnimplementedRpcSessionsServer was + // If the following call panics, it indicates UnimplementedRpcSessionsServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. From aee0e6d866cb4674bf35fe6e7481f2abb0f979e6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 16:31:28 +0100 Subject: [PATCH 317/549] client: Log bandwidth used (incoming / outgoing). --- client/main.go | 42 ++++--------------- client/stats.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ client/stats_test.go | 79 ++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 client/stats.go create mode 100644 client/stats_test.go diff --git a/client/main.go b/client/main.go index 4633680..e863e20 100644 --- a/client/main.go +++ b/client/main.go @@ -73,40 +73,6 @@ const ( maxMessageSize = 64 * 1024 ) -type Stats struct { - numRecvMessages atomic.Int64 - numSentMessages atomic.Int64 - resetRecvMessages int64 - resetSentMessages int64 - - start time.Time -} - -func (s *Stats) reset(start time.Time) { - s.resetRecvMessages = s.numRecvMessages.Load() - s.resetSentMessages = s.numSentMessages.Load() - s.start = start -} - -func (s *Stats) Log() { - now := time.Now() - duration := now.Sub(s.start) - perSec := int64(duration / time.Second) - if perSec == 0 { - return - } - - totalSentMessages := s.numSentMessages.Load() - sentMessages := totalSentMessages - s.resetSentMessages - totalRecvMessages := s.numRecvMessages.Load() - recvMessages := totalRecvMessages - s.resetRecvMessages - log.Printf("Stats: sent=%d (%d/sec), recv=%d (%d/sec), delta=%d", - totalSentMessages, sentMessages/perSec, - totalRecvMessages, recvMessages/perSec, - totalSentMessages-totalRecvMessages) - s.reset(now) -} - type MessagePayload struct { Now time.Time `json:"now"` } @@ -283,6 +249,8 @@ func (c *SignalingClient) readPump() { break } + c.stats.numRecvBytes.Add(uint64(decodeBuffer.Len())) + var message signaling.ServerMessage if err := message.UnmarshalJSON(decodeBuffer.Bytes()); err != nil { log.Printf("Error: %v", err) @@ -297,9 +265,10 @@ func (c *SignalingClient) writeInternal(message *signaling.ClientMessage) bool { var closeData []byte c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint + var written int writer, err := c.conn.NextWriter(websocket.TextMessage) if err == nil { - _, err = easyjson.MarshalToWriter(message, writer) + written, err = easyjson.MarshalToWriter(message, writer) } if err != nil { if err == websocket.ErrCloseSent { @@ -315,6 +284,9 @@ func (c *SignalingClient) writeInternal(message *signaling.ClientMessage) bool { writer.Close() c.stats.numSentMessages.Add(1) + if written > 0 { + c.stats.numSentBytes.Add(uint64(written)) + } return true close: diff --git a/client/stats.go b/client/stats.go new file mode 100644 index 0000000..63e4e8b --- /dev/null +++ b/client/stats.go @@ -0,0 +1,97 @@ +/** + * 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 main + +import ( + "log" + "sync/atomic" + "time" + + "github.com/strukturag/nextcloud-spreed-signaling/api" +) + +type Stats struct { + numRecvMessages atomic.Int64 + numSentMessages atomic.Int64 + resetRecvMessages int64 + resetSentMessages int64 + numRecvBytes atomic.Uint64 + numSentBytes atomic.Uint64 + resetRecvBytes uint64 + resetSentBytes uint64 + + start time.Time +} + +func (s *Stats) reset(start time.Time) { + s.resetRecvMessages = s.numRecvMessages.Load() + s.resetSentMessages = s.numSentMessages.Load() + s.resetRecvBytes = s.numRecvBytes.Load() + s.resetSentBytes = s.numSentBytes.Load() + s.start = start +} + +type statsLogEntries struct { + totalSentMessages int64 + sentMessagesPerSec int64 + sentBytesPerSec api.Bandwidth + + totalRecvMessages int64 + recvMessagesPerSec int64 + recvBytesPerSec api.Bandwidth +} + +func (s *Stats) getLogEntries(now time.Time) *statsLogEntries { + duration := now.Sub(s.start) + perSec := int64(duration / time.Second) + if perSec == 0 { + return nil + } + + totalSentMessages := s.numSentMessages.Load() + sentMessages := totalSentMessages - s.resetSentMessages + sentBytes := api.BandwidthFromBytes(s.numSentBytes.Load() - s.resetSentBytes) + totalRecvMessages := s.numRecvMessages.Load() + recvMessages := totalRecvMessages - s.resetRecvMessages + recvBytes := api.BandwidthFromBytes(s.numRecvBytes.Load() - s.resetRecvBytes) + + s.reset(now) + return &statsLogEntries{ + totalSentMessages: totalSentMessages, + sentMessagesPerSec: sentMessages / perSec, + sentBytesPerSec: sentBytes, + + totalRecvMessages: totalRecvMessages, + recvMessagesPerSec: recvMessages / perSec, + recvBytesPerSec: recvBytes, + } +} + +func (s *Stats) Log() { + now := time.Now() + if entries := s.getLogEntries(now); entries != nil { + log.Printf("Stats: sent=%d (%d/sec, %s), recv=%d (%d/sec, %s), delta=%d", + entries.totalSentMessages, entries.sentMessagesPerSec, entries.sentBytesPerSec, + entries.totalRecvMessages, entries.recvMessagesPerSec, entries.recvBytesPerSec, + entries.totalSentMessages-entries.totalRecvMessages) + } +} diff --git a/client/stats_test.go b/client/stats_test.go new file mode 100644 index 0000000..9ef132a --- /dev/null +++ b/client/stats_test.go @@ -0,0 +1,79 @@ +/** + * 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 main + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/strukturag/nextcloud-spreed-signaling/api" +) + +func TestStats(t *testing.T) { + assert := assert.New(t) + + var stats Stats + assert.Nil(stats.getLogEntries(time.Time{})) + now := time.Now() + if entries := stats.getLogEntries(now); assert.NotNil(entries) { + assert.EqualValues(0, entries.totalSentMessages) + assert.EqualValues(0, entries.sentMessagesPerSec) + assert.EqualValues(0, entries.sentBytesPerSec) + + assert.EqualValues(0, entries.totalRecvMessages) + assert.EqualValues(0, entries.recvMessagesPerSec) + assert.EqualValues(0, entries.recvBytesPerSec) + } + + stats.numSentMessages.Add(10) + stats.numSentBytes.Add((api.Bandwidth(20) * api.Kilobit).Bits()) + + stats.numRecvMessages.Add(30) + stats.numRecvBytes.Add((api.Bandwidth(40) * api.Kilobit).Bits()) + + if entries := stats.getLogEntries(now.Add(time.Second)); assert.NotNil(entries) { + assert.EqualValues(10, entries.totalSentMessages) + assert.EqualValues(10, entries.sentMessagesPerSec) + assert.EqualValues(20*1024*8, entries.sentBytesPerSec) + + assert.EqualValues(30, entries.totalRecvMessages) + assert.EqualValues(30, entries.recvMessagesPerSec) + assert.EqualValues(40*1024*8, entries.recvBytesPerSec) + } + + stats.numSentMessages.Add(100) + stats.numSentBytes.Add((api.Bandwidth(200) * api.Kilobit).Bits()) + + stats.numRecvMessages.Add(300) + stats.numRecvBytes.Add((api.Bandwidth(400) * api.Kilobit).Bits()) + + if entries := stats.getLogEntries(now.Add(2 * time.Second)); assert.NotNil(entries) { + assert.EqualValues(110, entries.totalSentMessages) + assert.EqualValues(100, entries.sentMessagesPerSec) + assert.EqualValues(200*1024*8, entries.sentBytesPerSec) + + assert.EqualValues(330, entries.totalRecvMessages) + assert.EqualValues(300, entries.recvMessagesPerSec) + assert.EqualValues(400*1024*8, entries.recvBytesPerSec) + } +} From 87345118d8207035b5dbb820f2d9479cb6a004f1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 16:31:41 +0100 Subject: [PATCH 318/549] client: Send "bye" before closing connection. --- client/main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/main.go b/client/main.go index e863e20..942e94b 100644 --- a/client/main.go +++ b/client/main.go @@ -134,6 +134,10 @@ func (c *SignalingClient) Close() { c.lock.Lock() c.publicSessionId = "" c.privateSessionId = "" + c.writeInternal(&signaling.ClientMessage{ + Type: "bye", + Bye: &signaling.ByeClientMessage{}, + }) c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) // nolint c.conn.Close() From 9ee64b8c660cd0332d947bd5d3f74dc265e13fb4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 24 Nov 2025 17:03:14 +0100 Subject: [PATCH 319/549] Add metrics about client bytes/messages sent/received. --- client.go | 29 ++++++++++++++++++++++-- client_stats_prometheus.go | 14 ++++++++++++ client_test.go | 46 ++++++++++++++++++++++++++++++++++++++ docs/prometheus-metrics.md | 2 ++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 client_test.go diff --git a/client.go b/client.go index 7ffff52..46f979c 100644 --- a/client.go +++ b/client.go @@ -26,6 +26,7 @@ import ( "context" "encoding/json" "errors" + "io" "net" "strconv" "strings" @@ -344,6 +345,7 @@ func (c *Client) ReadPump() { if msg == "" { return nil } + statsClientBytesTotal.WithLabelValues("incoming").Add(float64(len(msg))) if ts, err := strconv.ParseInt(msg, 10, 64); err == nil { rtt := now.Sub(time.Unix(0, ts)) if c.logRTT { @@ -406,6 +408,8 @@ func (c *Client) ReadPump() { break } + statsClientBytesTotal.WithLabelValues("incoming").Add(float64(decodeBuffer.Len())) + statsClientMessagesTotal.WithLabelValues("incoming").Inc() c.messageChan <- decodeBuffer } } @@ -425,16 +429,33 @@ func (c *Client) processMessages() { c.doClose() } +type counterWriter struct { + w io.Writer + counter *int +} + +func (w *counterWriter) Write(p []byte) (int, error) { + written, err := w.w.Write(p) + if written > 0 { + *w.counter += written + } + return written, err +} + func (c *Client) writeInternal(message json.Marshaler) bool { var closeData []byte c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint writer, err := c.conn.NextWriter(websocket.TextMessage) + var written int if err == nil { if m, ok := (any(message)).(easyjson.Marshaler); ok { - _, err = easyjson.MarshalToWriter(m, writer) + written, err = easyjson.MarshalToWriter(m, writer) } else { - err = json.NewEncoder(writer).Encode(message) + err = json.NewEncoder(&counterWriter{ + w: writer, + counter: &written, + }).Encode(message) } } if err == nil { @@ -454,6 +475,9 @@ func (c *Client) writeInternal(message json.Marshaler) bool { closeData = websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "") goto close } + + statsClientBytesTotal.WithLabelValues("outgoing").Add(float64(written)) + statsClientMessagesTotal.WithLabelValues("outgoing").Inc() return true close: @@ -542,6 +566,7 @@ func (c *Client) sendPing() bool { return false } + statsClientBytesTotal.WithLabelValues("outgoing").Add(float64(len(msg))) return true } diff --git a/client_stats_prometheus.go b/client_stats_prometheus.go index 6a6d140..cb53299 100644 --- a/client_stats_prometheus.go +++ b/client_stats_prometheus.go @@ -39,10 +39,24 @@ var ( Help: "The roundtrip time of WebSocket ping messages in milliseconds", Buckets: prometheus.ExponentialBucketsRange(1, 30000, 50), }) + statsClientBytesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "client", + Name: "bytes_total", + Help: "The total number of bytes sent to or received by clients", + }, []string{"direction"}) + statsClientMessagesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "client", + Name: "messages_total", + Help: "The total number of messages sent to or received by clients", + }, []string{"direction"}) clientStats = []prometheus.Collector{ statsClientCountries, statsClientRTT, + statsClientBytesTotal, + statsClientMessagesTotal, } ) diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..8afce5d --- /dev/null +++ b/client_test.go @@ -0,0 +1,46 @@ +/** + * 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 signaling + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCounterWriter(t *testing.T) { + assert := assert.New(t) + + var b bytes.Buffer + var written int + w := &counterWriter{ + w: &b, + counter: &written, + } + if count, err := w.Write(nil); assert.NoError(err) && assert.EqualValues(0, count) { + assert.EqualValues(0, written) + } + if count, err := w.Write([]byte("foo")); assert.NoError(err) && assert.EqualValues(3, count) { + assert.EqualValues(3, written) + } +} diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 3016d6a..9a5f365 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -72,3 +72,5 @@ The following metrics are available: | `signaling_mcu_media_retransmissions_total` | Counter | 2.0.5 | The total number of received retransmissions | `media` | | `signaling_mcu_media_bytes_total` | Counter | 2.0.5 | The total number of media bytes sent / received | `media`, `direction` | | `signaling_mcu_media_lost_total` | Counter | 2.0.5 | The total number of lost media packets | `media`, `origin` | +| `signaling_client_bytes_total` | Counter | 2.0.5 | The total number of bytes sent to or received by clients | `direction` | +| `signaling_client_messages_total` | Counter | 2.0.5 | The total number of messages sent to or received by clients | `direction` | From 4f4d673e5afb8165ec3405b4bb889204833f416b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:20:31 +0000 Subject: [PATCH 320/549] Bump github.com/pion/ice/v4 from 4.0.11 to 4.0.12 Bumps [github.com/pion/ice/v4](https://github.com/pion/ice) from 4.0.11 to 4.0.12. - [Release notes](https://github.com/pion/ice/releases) - [Commits](https://github.com/pion/ice/compare/v4.0.11...v4.0.12) --- updated-dependencies: - dependency-name: github.com/pion/ice/v4 dependency-version: 4.0.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5d07f20..179cb7a 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/ice/v4 v4.0.11 + github.com/pion/ice/v4 v4.0.12 github.com/pion/sdp/v3 v3.0.16 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 diff --git a/go.sum b/go.sum index b957331..27a79d1 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q= github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8= -github.com/pion/ice/v4 v4.0.11 h1:KRKfAH2G1+WahCARyy6ZJ1MwDQ+1q9wblDV8sAoz6Dg= -github.com/pion/ice/v4 v4.0.11/go.mod h1:tAp574oAufhHRHr8EO1xgPmVKVDBROX+708WggYD6NE= +github.com/pion/ice/v4 v4.0.12 h1:vuI3h9OD5M0Z+V304qLC1/o16ahHVnDgqNCWbONv6s0= +github.com/pion/ice/v4 v4.0.12/go.mod h1:tAp574oAufhHRHr8EO1xgPmVKVDBROX+708WggYD6NE= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= From 805c4cdc81c64ab50d59e91723b1361c3284d5c1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 20 Nov 2025 12:31:50 +0100 Subject: [PATCH 321/549] Describe transient session data. --- docs/standalone-signaling-api-v1.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index 92f26f8..e961dfb 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1274,6 +1274,23 @@ Transient data is supported if the server returns the `transient-data` feature id in the [hello response](#establish-connection). +### Transient session data + +Sessions can set data for a special key in the format `sd:` where +`` is the public id of the session updating the key. A session can +only set its own session data and the entry is removed automatically once the +session leaves the room (either explicitly or because it is expired). + +This can be used to publish local information on a given session to all other +sessions in a room (e.g. the display name of guest users) without having to +manually keep track of which other session that information would have to be +sent to and who already got it. + +Transient session data is supported if the server returns the +`transient-sessiondata` feature id in the +[hello response](#establish-connection). + + ### Set value Message format (Client -> Server): From 3fe8de1167e203831896779141989c94d9b04756 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 20 Nov 2025 13:03:58 +0100 Subject: [PATCH 322/549] Implement transient session data. --- api_signaling.go | 4 + federation.go | 10 +++ federation_test.go | 20 +++++ hub.go | 20 +++++ room.go | 6 ++ transient_data.go | 4 + transient_data_test.go | 173 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 237 insertions(+) diff --git a/api_signaling.go b/api_signaling.go index 32f83ef..18c7df5 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -544,6 +544,7 @@ const ( ServerFeatureOfferCodecs = "offer-codecs" ServerFeatureServerInfo = "serverinfo" ServerFeatureChatRelay = "chat-relay" + ServerFeatureTransientSessionData = "transient-sessiondata" // Features to send to internal clients only. ServerFeatureInternalVirtualSessions = "virtual-sessions" @@ -569,6 +570,7 @@ var ( ServerFeatureOfferCodecs, ServerFeatureServerInfo, ServerFeatureChatRelay, + ServerFeatureTransientSessionData, } DefaultFeaturesInternal = []string{ ServerFeatureInternalVirtualSessions, @@ -584,6 +586,7 @@ var ( ServerFeatureOfferCodecs, ServerFeatureServerInfo, ServerFeatureChatRelay, + ServerFeatureTransientSessionData, } DefaultWelcomeFeatures = []string{ ServerFeatureAudioVideoPermissions, @@ -600,6 +603,7 @@ var ( ServerFeatureOfferCodecs, ServerFeatureServerInfo, ServerFeatureChatRelay, + ServerFeatureTransientSessionData, } ) diff --git a/federation.go b/federation.go index e704bf8..b4f1650 100644 --- a/federation.go +++ b/federation.go @@ -934,6 +934,10 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { } } } + case "transient": + if remoteSessionId != "" && msg.TransientData != nil && msg.TransientData.Key == TransientSessionDataPrefix+string(remoteSessionId) { + msg.TransientData.Key = TransientSessionDataPrefix + string(localSessionId) + } } c.session.SendMessage(msg) @@ -948,6 +952,12 @@ func (c *FederationClient) ProxyMessage(message *ClientMessage) error { if hello := c.hello.Load(); hello != nil { c.updateSessionRecipient(&message.Message.Recipient, hello.SessionId, c.session.PublicId()) } + case "transient": + if hello := c.hello.Load(); hello != nil { + if message.TransientData != nil && message.TransientData.Key == TransientSessionDataPrefix+string(c.session.PublicId()) { + message.TransientData.Key = TransientSessionDataPrefix + string(hello.SessionId) + } + } } return c.SendMessage(message) diff --git a/federation_test.go b/federation_test.go index f1c362d..92eabf0 100644 --- a/federation_test.go +++ b/federation_test.go @@ -1115,6 +1115,7 @@ func Test_FederationTransientData(t *testing.T) { // The client2 will see its own session id, not the one from the remote server. client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) + // Regular transient data. require.NoError(client1.SetTransientData("foo", "bar", 0)) if msg, ok := client1.RunUntilMessage(ctx); ok { checkMessageTransientSet(t, msg, "foo", "bar", nil) @@ -1130,4 +1131,23 @@ func Test_FederationTransientData(t *testing.T) { if msg, ok := client2.RunUntilMessage(ctx); ok { checkMessageTransientSet(t, msg, "bar", "baz", nil) } + + // Transient session data + sessionKey1 := "sd:" + string(hello1.Hello.SessionId) + require.NoError(client1.SetTransientData(sessionKey1, "12345", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey1, "12345", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey1, "12345", nil) + } + + sessionKey2 := "sd:" + string(hello2.Hello.SessionId) + require.NoError(client2.SetTransientData(sessionKey2, "54321", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, "sd:"+string(remoteSessionId), "54321", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey2, "54321", nil) + } } diff --git a/hub.go b/hub.go index ab2da57..1f39215 100644 --- a/hub.go +++ b/hub.go @@ -2660,6 +2660,20 @@ func isAllowedToUpdateTransientData(session Session) bool { return false } +func isAllowedToUpdateTransientDataKey(session Session, key string) bool { + if session.ClientType() == HelloClientTypeInternal { + // Internal clients may update all transient keys. + return true + } + + if sid, found := strings.CutPrefix(key, TransientSessionDataPrefix); found { + // Session data may only be modified by the session itself. + return sid == string(session.PublicId()) + } + + return true +} + func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { room := session.GetRoom() if room == nil { @@ -2674,6 +2688,9 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { if !isAllowedToUpdateTransientData(session) { sendNotAllowed(session, message, "Not allowed to update transient data.") return + } else if !isAllowedToUpdateTransientDataKey(session, msg.Key) { + sendNotAllowed(session, message, "Not allowed to update this transient data entry.") + return } if err := room.SetTransientDataTTL(msg.Key, msg.Value, msg.TTL); err != nil { @@ -2685,6 +2702,9 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { if !isAllowedToUpdateTransientData(session) { sendNotAllowed(session, message, "Not allowed to update transient data.") return + } else if !isAllowedToUpdateTransientDataKey(session, msg.Key) { + sendNotAllowed(session, message, "Not allowed to update this transient data entry.") + return } if err := room.RemoveTransientData(msg.Key); err != nil { diff --git a/room.go b/room.go index b97d979..69de4bd 100644 --- a/room.go +++ b/room.go @@ -494,6 +494,9 @@ func (r *Room) RemoveSession(session Session) bool { delete(r.roomSessionData, sid) if len(r.sessions) > 0 { r.mu.Unlock() + if err := r.RemoveTransientData(TransientSessionDataPrefix + string(sid)); err != nil { + r.logger.Printf("Error removing transient data for session %s", sid) + } r.PublishSessionLeft(session) return true } @@ -505,6 +508,9 @@ func (r *Room) RemoveSession(session Session) bool { r.unsubscribeBackend() r.doClose() r.mu.Unlock() + if err := r.RemoveTransientData(TransientSessionDataPrefix + string(sid)); err != nil { + r.logger.Printf("Error removing transient data for session %s", sid) + } // Still need to publish an event so sessions on other servers get notified. r.PublishSessionLeft(session) return false diff --git a/transient_data.go b/transient_data.go index 9dc6cb9..a96bb3a 100644 --- a/transient_data.go +++ b/transient_data.go @@ -31,6 +31,10 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" ) +const ( + TransientSessionDataPrefix = "sd:" +) + type TransientListener interface { SendMessage(message *ServerMessage) bool } diff --git a/transient_data_test.go b/transient_data_test.go index 20aed68..742d178 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -311,3 +311,176 @@ func Test_TransientMessages(t *testing.T) { }) } } + +func Test_TransientSessionData(t *testing.T) { + for _, subtest := range clusteredTests { + t.Run(subtest, func(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + var hub1 *Hub + var hub2 *Hub + var server1 *httptest.Server + var server2 *httptest.Server + if isLocalTest(t) { + hub1, _, _, server1 = CreateHubForTest(t) + + hub2 = hub1 + server2 = server1 + } else { + hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) + } + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") + + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + sessionKey1 := "sd:" + string(hello1.Hello.SessionId) + sessionKey2 := "sd:" + string(hello2.Hello.SessionId) + require.NotEqual(sessionKey1, sessionKey2) + + require.NoError(client1.SetTransientData(sessionKey2, "foo", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") + } + require.NoError(client2.SetTransientData(sessionKey1, "bar", 0)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") + } + + require.NoError(client1.SetTransientData(sessionKey1, "foo", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey1, "foo", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey1, "foo", nil) + } + + require.NoError(client2.RemoveTransientData(sessionKey1)) + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageError(t, msg, "not_allowed") + } + + require.NoError(client2.SetTransientData(sessionKey2, "bar", 0)) + if msg, ok := client1.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey2, "bar", nil) + } + if msg, ok := client2.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey2, "bar", nil) + } + + client1.CloseWithBye() + assert.NoError(client1.WaitForClientRemoved(ctx)) + + var messages []*ServerMessage + for range 2 { + if msg, ok := client2.RunUntilMessage(ctx); ok { + messages = append(messages, msg) + } + } + if assert.Len(messages, 2) { + if messages[0].Type == "transient" { + messages[0], messages[1] = messages[1], messages[0] + } + client2.checkMessageRoomLeaveSession(messages[0], hello1.Hello.SessionId) + checkMessageTransientRemove(t, messages[1], sessionKey1, "foo") + } + + client3, hello3 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"3") + roomMsg = MustSucceed2(t, client3.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello2.Hello, hello3.Hello) + require.True(ok) + + var msg *ServerMessage + if len(ignored) == 0 { + msg = MustSucceed1(t, client3.RunUntilMessage, ctx) + } else if len(ignored) == 1 { + msg = ignored[0] + } else { + require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) + } + + checkMessageTransientInitial(t, msg, api.StringMap{ + sessionKey2: "bar", + }) + + client2.CloseWithBye() + assert.NoError(client2.WaitForClientRemoved(ctx)) + + messages = nil + for range 2 { + if msg, ok := client3.RunUntilMessage(ctx); ok { + messages = append(messages, msg) + } + } + if assert.Len(messages, 2) { + if messages[0].Type == "transient" { + messages[0], messages[1] = messages[1], messages[0] + } + client3.checkMessageRoomLeaveSession(messages[0], hello2.Hello.SessionId) + checkMessageTransientRemove(t, messages[1], sessionKey2, "bar") + } + + // Internal clients may set transient data of any session. + client4 := NewTestClient(t, server1, hub1) + defer client4.CloseWithBye() + + require.NoError(client4.SendHelloInternal()) + hello4 := MustSucceed1(t, client4.RunUntilHello, ctx) + roomMsg = MustSucceed2(t, client4.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + _, ignored, ok = client4.RunUntilJoinedAndReturn(ctx, hello3.Hello, hello4.Hello) + require.True(ok) + + if len(ignored) == 0 { + msg = MustSucceed1(t, client4.RunUntilMessage, ctx) + } else if len(ignored) == 1 { + msg = ignored[0] + } else { + require.LessOrEqual(len(ignored), 1, "Received too many messages: %+v", ignored) + } + + if msgInCall, ok := checkMessageParticipantsInCall(t, msg); ok { + if assert.Len(msgInCall.Users, 1) { + assert.Equal(true, msgInCall.Users[0]["internal"]) + assert.EqualValues(hello4.Hello.SessionId, msgInCall.Users[0]["sessionId"]) + assert.EqualValues(FlagInCall|FlagWithAudio, msgInCall.Users[0]["inCall"]) + } + } + + client3.RunUntilJoined(ctx, hello4.Hello) + if msg, ok := client3.RunUntilMessage(ctx); ok { + if msgInCall, ok := checkMessageParticipantsInCall(t, msg); ok { + if assert.Len(msgInCall.Users, 1) { + assert.Equal(true, msgInCall.Users[0]["internal"]) + assert.EqualValues(hello4.Hello.SessionId, msgInCall.Users[0]["sessionId"]) + assert.EqualValues(FlagInCall|FlagWithAudio, msgInCall.Users[0]["inCall"]) + } + } + } + + sessionKey3 := string("sd:" + hello3.Hello.SessionId) + require.NoError(client4.SetTransientData(sessionKey3, "baz", 0)) + if msg, ok := client4.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey3, "baz", nil) + } + + if msg, ok := client3.RunUntilMessage(ctx); ok { + checkMessageTransientSet(t, msg, sessionKey3, "baz", nil) + } + }) + } +} From f4fca4f52b9289822c1a93feb8ca2154fae80b2f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 2 Dec 2025 10:05:32 +0100 Subject: [PATCH 323/549] Use microseconds instead of timestamppb object for session creation. --- async_events_nats_test.go | 5 ++--- hub.go | 3 +-- hub_test.go | 5 ++--- proxy/proxy_server.go | 3 +-- session.pb.go | 27 ++++++++++++--------------- session.proto | 4 +--- sessionid_codec_test.go | 4 ++-- 7 files changed, 21 insertions(+), 30 deletions(-) diff --git a/async_events_nats_test.go b/async_events_nats_test.go index 35a1484..63e5d9a 100644 --- a/async_events_nats_test.go +++ b/async_events_nats_test.go @@ -23,8 +23,7 @@ package signaling import ( "testing" - - "google.golang.org/protobuf/types/known/timestamppb" + "time" ) func Benchmark_GetSubjectForSessionId(b *testing.B) { @@ -33,7 +32,7 @@ func Benchmark_GetSubjectForSessionId(b *testing.B) { } data := &SessionIdData{ Sid: 1, - Created: timestamppb.Now(), + Created: time.Now().UnixMicro(), BackendId: backend.Id(), } codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) diff --git a/hub.go b/hub.go index 1f39215..a5b1cbe 100644 --- a/hub.go +++ b/hub.go @@ -49,7 +49,6 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/gorilla/mux" "github.com/gorilla/websocket" - "google.golang.org/protobuf/types/known/timestamppb" "github.com/strukturag/nextcloud-spreed-signaling/api" ) @@ -941,7 +940,7 @@ func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData { } sessionIdData := &SessionIdData{ Sid: sid, - Created: timestamppb.Now(), + Created: time.Now().UnixMicro(), BackendId: backend.Id(), } return sessionIdData diff --git a/hub_test.go b/hub_test.go index f6cafed..5105034 100644 --- a/hub_test.go +++ b/hub_test.go @@ -51,7 +51,6 @@ import ( "github.com/nats-io/nats-server/v2/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/timestamppb" "github.com/strukturag/nextcloud-spreed-signaling/api" ) @@ -838,7 +837,7 @@ func Benchmark_DecodePrivateSessionId(b *testing.B) { } data := &SessionIdData{ Sid: 1, - Created: timestamppb.Now(), + Created: time.Now().UnixMicro(), BackendId: backend.Id(), } codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) @@ -867,7 +866,7 @@ func Benchmark_DecodePublicSessionId(b *testing.B) { } data := &SessionIdData{ Sid: 1, - Created: timestamppb.Now(), + Created: time.Now().UnixMicro(), BackendId: backend.Id(), } codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 72ed239..d3dd253 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -48,7 +48,6 @@ import ( "github.com/gorilla/websocket" "github.com/notedit/janus-go" "github.com/prometheus/client_golang/prometheus/promhttp" - "google.golang.org/protobuf/types/known/timestamppb" signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" @@ -1494,7 +1493,7 @@ func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*Pro sessionIdData := &signaling.SessionIdData{ Sid: sid, - Created: timestamppb.Now(), + Created: time.Now().UnixMicro(), } encoded, err := s.cookie.EncodePublic(sessionIdData) diff --git a/session.pb.go b/session.pb.go index 52fc4bd..a998ae3 100644 --- a/session.pb.go +++ b/session.pb.go @@ -27,7 +27,6 @@ package signaling import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -43,7 +42,7 @@ const ( type SessionIdData struct { state protoimpl.MessageState `protogen:"open.v1"` Sid uint64 `protobuf:"varint,1,opt,name=Sid,proto3" json:"Sid,omitempty"` - Created *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=Created,proto3" json:"Created,omitempty"` + Created int64 `protobuf:"varint,2,opt,name=Created,proto3" json:"Created,omitempty"` BackendId string `protobuf:"bytes,3,opt,name=BackendId,proto3" json:"BackendId,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -86,11 +85,11 @@ func (x *SessionIdData) GetSid() uint64 { return 0 } -func (x *SessionIdData) GetCreated() *timestamppb.Timestamp { +func (x *SessionIdData) GetCreated() int64 { if x != nil { return x.Created } - return nil + return 0 } func (x *SessionIdData) GetBackendId() string { @@ -104,10 +103,10 @@ var File_session_proto protoreflect.FileDescriptor const file_session_proto_rawDesc = "" + "\n" + - "\rsession.proto\x12\tsignaling\x1a\x1fgoogle/protobuf/timestamp.proto\"u\n" + + "\rsession.proto\x12\tsignaling\"Y\n" + "\rSessionIdData\x12\x10\n" + - "\x03Sid\x18\x01 \x01(\x04R\x03Sid\x124\n" + - "\aCreated\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\aCreated\x12\x1c\n" + + "\x03Sid\x18\x01 \x01(\x04R\x03Sid\x12\x18\n" + + "\aCreated\x18\x02 \x01(\x03R\aCreated\x12\x1c\n" + "\tBackendId\x18\x03 \x01(\tR\tBackendIdB google.protobuf.Timestamp - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_session_proto_init() } diff --git a/session.proto b/session.proto index 4b1cc71..b57c9ac 100644 --- a/session.proto +++ b/session.proto @@ -21,14 +21,12 @@ */ syntax = "proto3"; -import "google/protobuf/timestamp.proto"; - option go_package = "github.com/strukturag/nextcloud-spreed-signaling;signaling"; package signaling; message SessionIdData { uint64 Sid = 1; - google.protobuf.Timestamp Created = 2; + int64 Created = 2; string BackendId = 3; } diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 35b7e9b..3bf405c 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -24,10 +24,10 @@ package signaling import ( "encoding/base64" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/timestamppb" ) func TestReverseSessionId(t *testing.T) { @@ -59,7 +59,7 @@ func TestPublicPrivate(t *testing.T) { require := require.New(t) sd := &SessionIdData{ Sid: 1, - Created: timestamppb.Now(), + Created: time.Now().UnixMicro(), BackendId: "foo", } From f3a81c23c390889ee55d9342db3edbc381630a51 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 2 Dec 2025 16:55:43 +0100 Subject: [PATCH 324/549] Implement custom session id codec that generates shorter ids. Inspired by the previously used https://github.com/gorilla/securecookie and simplified / adjusted to our use case. --- async_events_nats_test.go | 10 +- go.mod | 1 - go.sum | 4 - hub.go | 21 +-- hub_test.go | 28 ++-- proxy/proxy_server.go | 8 +- sessionid_codec.go | 282 ++++++++++++++++++++++++++++++-------- sessionid_codec_test.go | 122 ++++++++++++++--- 8 files changed, 368 insertions(+), 108 deletions(-) diff --git a/async_events_nats_test.go b/async_events_nats_test.go index 63e5d9a..dadd9e2 100644 --- a/async_events_nats_test.go +++ b/async_events_nats_test.go @@ -24,9 +24,12 @@ package signaling import ( "testing" "time" + + "github.com/stretchr/testify/require" ) func Benchmark_GetSubjectForSessionId(b *testing.B) { + require := require.New(b) backend := &Backend{ id: "compat", } @@ -35,11 +38,10 @@ func Benchmark_GetSubjectForSessionId(b *testing.B) { Created: time.Now().UnixMicro(), BackendId: backend.Id(), } - codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) sid, err := codec.EncodePublic(data) - if err != nil { - b.Fatalf("could not create session id: %s", err) - } + require.NoError(err, "could not create session id") for b.Loop() { GetSubjectForSessionId(sid, backend) } diff --git a/go.mod b/go.mod index 179cb7a..e6b08ff 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/gorilla/securecookie v1.1.2 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.12.2 diff --git a/go.sum b/go.sum index 27a79d1..de933cc 100644 --- a/go.sum +++ b/go.sum @@ -40,14 +40,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA= github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= diff --git a/hub.go b/hub.go index a5b1cbe..9e6c4b6 100644 --- a/hub.go +++ b/hub.go @@ -141,7 +141,7 @@ type Hub struct { logger Logger events AsyncEvents upgrader websocket.Upgrader - cookie *SessionIdCodec + sessionIds *SessionIdCodec info *WelcomeServerMessage infoInternal *WelcomeServerMessage welcome atomic.Value // *ServerMessage @@ -240,6 +240,11 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, return nil, fmt.Errorf("the sessions block key must be 16, 24 or 32 bytes but is %d bytes", len(blockKey)) } + sessionIds, err := NewSessionIdCodec([]byte(hashKey), blockBytes) + if err != nil { + return nil, fmt.Errorf("error creating session id codec: %w", err) + } + internalClientsSecret, _ := GetStringOptionWithEnv(config, "clients", "internalsecret") if internalClientsSecret == "" { logger.Println("WARNING: No shared secret has been set for internal clients.") @@ -360,7 +365,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, JanusEventsSubprotocol, }, }, - cookie: NewSessionIdCodec([]byte(hashKey), blockBytes), + sessionIds: sessionIds, info: NewWelcomeServerMessage(version, DefaultFeatures...), infoInternal: NewWelcomeServerMessage(version, DefaultFeaturesInternal...), @@ -668,7 +673,7 @@ func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { return result } - data, err := h.cookie.DecodePrivate(id) + data, err := h.sessionIds.DecodePrivate(id) if err != nil { return nil } @@ -688,7 +693,7 @@ func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { return result } - data, err := h.cookie.DecodePublic(id) + data, err := h.sessionIds.DecodePublic(id) if err != nil { return nil } @@ -968,12 +973,12 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * } sessionIdData := h.newSessionIdData(backend) - privateSessionId, err := h.cookie.EncodePrivate(sessionIdData) + privateSessionId, err := h.sessionIds.EncodePrivate(sessionIdData) if err != nil { client.SendMessage(message.NewWrappedErrorServerMessage(err)) return } - publicSessionId, err := h.cookie.EncodePublic(sessionIdData) + publicSessionId, err := h.sessionIds.EncodePublic(sessionIdData) if err != nil { client.SendMessage(message.NewWrappedErrorServerMessage(err)) return @@ -2467,12 +2472,12 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { } sessionIdData := h.newSessionIdData(session.Backend()) - privateSessionId, err := h.cookie.EncodePrivate(sessionIdData) + privateSessionId, err := h.sessionIds.EncodePrivate(sessionIdData) if err != nil { h.logger.Printf("Could not encode private virtual session id: %s", err) return } - publicSessionId, err := h.cookie.EncodePublic(sessionIdData) + publicSessionId, err := h.sessionIds.EncodePublic(sessionIdData) if err != nil { h.logger.Printf("Could not encode public virtual session id: %s", err) return diff --git a/hub_test.go b/hub_test.go index 5105034..3a5ed91 100644 --- a/hub_test.go +++ b/hub_test.go @@ -827,7 +827,8 @@ func performHousekeeping(hub *Hub, now time.Time) *sync.WaitGroup { return &wg } -func Benchmark_DecodePrivateSessionId(b *testing.B) { +func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { + require := require.New(b) decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) @@ -840,23 +841,23 @@ func Benchmark_DecodePrivateSessionId(b *testing.B) { Created: time.Now().UnixMicro(), BackendId: backend.Id(), } - codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) sid, err := codec.EncodePrivate(data) - if err != nil { - b.Fatalf("could not create session id: %s", err) - } + require.NoError(err, "could not create session id") hub := &Hub{ - cookie: codec, + sessionIds: codec, decodeCaches: decodeCaches, } // Decode once to populate cache. - hub.decodePrivateSessionId(sid) + require.NotNil(hub.decodePrivateSessionId(sid)) for b.Loop() { hub.decodePrivateSessionId(sid) } } -func Benchmark_DecodePublicSessionId(b *testing.B) { +func Benchmark_DecodePublicSessionIdCached(b *testing.B) { + require := require.New(b) decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) @@ -869,17 +870,16 @@ func Benchmark_DecodePublicSessionId(b *testing.B) { Created: time.Now().UnixMicro(), BackendId: backend.Id(), } - codec := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) sid, err := codec.EncodePublic(data) - if err != nil { - b.Fatalf("could not create session id: %s", err) - } + require.NoError(err, "could not create session id") hub := &Hub{ - cookie: codec, + sessionIds: codec, decodeCaches: decodeCaches, } // Decode once to populate cache. - hub.decodePublicSessionId(sid) + require.NotNil(hub.decodePublicSessionId(sid)) for b.Loop() { hub.decodePublicSessionId(sid) } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index d3dd253..5c29077 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -226,8 +226,12 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * return nil, fmt.Errorf("could not generate random block key: %s", err) } + sessionIds, err := signaling.NewSessionIdCodec(hashKey, blockKey) + if err != nil { + return nil, fmt.Errorf("error creating session id codec: %w", err) + } + var tokens ProxyTokens - var err error tokenType, _ := config.GetString("app", "tokentype") if tokenType == "" { tokenType = TokenTypeDefault @@ -367,7 +371,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * tokens: tokens, - cookie: signaling.NewSessionIdCodec(hashKey, blockKey), + cookie: sessionIds, sessions: make(map[uint64]*ProxySession), clients: make(map[string]signaling.McuClient), diff --git a/sessionid_codec.go b/sessionid_codec.go index 990596a..0d5ef86 100644 --- a/sessionid_codec.go +++ b/sessionid_codec.go @@ -22,74 +22,237 @@ package signaling import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/subtle" "encoding/base64" + "errors" "fmt" + "hash" + "io" + "sync" + "unsafe" - "github.com/gorilla/securecookie" "google.golang.org/protobuf/proto" ) -type protoSerializer struct { -} - -func (s *protoSerializer) Serialize(src any) ([]byte, error) { - msg, ok := src.(proto.Message) - if !ok { - return nil, fmt.Errorf("can't serialize type %T", src) - } - return proto.Marshal(msg) -} - -func (s *protoSerializer) Deserialize(src []byte, dst any) error { - msg, ok := dst.(proto.Message) - if !ok { - return fmt.Errorf("can't deserialize type %T", src) - } - return proto.Unmarshal(src, msg) -} - const ( privateSessionName = "private-session" publicSessionName = "public-session" + + // hmacLength specifies the length of the HMAC to use. 80 bits should be enough + // to prevent tampering. + hmacLength = 10 ) -type SessionIdCodec struct { - cookie *securecookie.SecureCookie +var ( + sessionHashFunc = sha256.New + sessionEncoding = base64.URLEncoding.WithPadding(base64.NoPadding) + sessionMarshalOptions = proto.MarshalOptions{ + UseCachedSize: true, + } + sessionUnmarshalOptions = proto.UnmarshalOptions{} + sessionSeparator = []byte{'|'} +) + +type bytesPool struct { + pool sync.Pool } -func NewSessionIdCodec(hashKey []byte, blockKey []byte) *SessionIdCodec { - cookie := securecookie.New(hashKey, blockKey). - MaxAge(0). - SetSerializer(&protoSerializer{}) - return &SessionIdCodec{ - cookie: cookie, +func (p *bytesPool) Get(size int) []byte { + bb := p.pool.Get() + if bb == nil { + return make([]byte, size) } + + b := *(bb.(*[]byte)) + if cap(b) < size { + b = make([]byte, size) + } else { + b = b[:size] + } + return b +} + +func (p *bytesPool) Put(b []byte) { + p.pool.Put(&b) +} + +// SessionIdCodec encodes and decodes session ids. +// +// Inspired by https://github.com/gorilla/securecookie +type SessionIdCodec struct { + hashKey []byte + cipher cipher.Block + + bytesPool bytesPool + hmacPool sync.Pool + dataPool sync.Pool +} + +func NewSessionIdCodec(hashKey []byte, blockKey []byte) (*SessionIdCodec, error) { + if len(hashKey) == 0 { + return nil, errors.New("hash key is not set") + } + + codec := &SessionIdCodec{ + hashKey: hashKey, + hmacPool: sync.Pool{ + New: func() any { + return hmac.New(sessionHashFunc, hashKey) + }, + }, + dataPool: sync.Pool{ + New: func() any { + return &SessionIdData{} + }, + }, + } + if len(blockKey) > 0 { + block, err := aes.NewCipher(blockKey) + if err != nil { + return nil, fmt.Errorf("error creating cipher: %w", err) + } + codec.cipher = block + } + return codec, nil +} + +func (c *SessionIdCodec) encrypt(data []byte) ([]byte, error) { + iv := c.bytesPool.Get(c.cipher.BlockSize() + len(data))[:c.cipher.BlockSize()] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, fmt.Errorf("error creating iv: %w", err) + } + + ctr := cipher.NewCTR(c.cipher, iv) + ctr.XORKeyStream(data, data) + return append(iv, data...), nil +} + +func (c *SessionIdCodec) decrypt(data []byte) ([]byte, error) { + bs := c.cipher.BlockSize() + if len(data) <= bs { + return nil, errors.New("no iv found in data") + } + + iv := data[:bs] + data = data[bs:] + ctr := cipher.NewCTR(c.cipher, iv) + ctr.XORKeyStream(data, data) + return data, nil +} + +func (c *SessionIdCodec) encodeToString(b []byte) string { + s := c.bytesPool.Get(sessionEncoding.EncodedLen(len(b))) + defer c.bytesPool.Put(s) + + sessionEncoding.Encode(s, b) + return string(s) +} + +func (c *SessionIdCodec) decodeFromString(s string) ([]byte, error) { + b := c.bytesPool.Get(sessionEncoding.DecodedLen(len(s))) + n, err := sessionEncoding.Decode(b, []byte(s)) + if err != nil { + c.bytesPool.Put(b) + return nil, err + } + + return b[:n], nil +} + +func (c *SessionIdCodec) encodeRaw(name string, data *SessionIdData) ([]byte, error) { + body := c.bytesPool.Get(sessionMarshalOptions.Size(data)) + defer c.bytesPool.Put(body) + + body, err := sessionMarshalOptions.MarshalAppend(body[:0], data) + if err != nil { + return nil, fmt.Errorf("error marshaling data: %w", err) + } + + if c.cipher != nil { + body, err = c.encrypt(body) + if err != nil { + return nil, fmt.Errorf("error encrypting data: %w", err) + } + + defer c.bytesPool.Put(body) + } + + h := c.hmacPool.Get().(hash.Hash) + defer c.hmacPool.Put(h) + h.Reset() + h.Write(unsafe.Slice(unsafe.StringData(name), len(name))) // nolint + h.Write(sessionSeparator) // nolint + h.Write(body) // nolint + mac := c.bytesPool.Get(h.Size()) + defer c.bytesPool.Put(mac) + mac = h.Sum(mac[:0]) + + result := c.bytesPool.Get(len(body) + hmacLength)[:0] + result = append(result, body...) + result = append(result, mac[:hmacLength]...) + return result, nil +} + +func (c *SessionIdCodec) decodeRaw(name string, value []byte) (*SessionIdData, error) { + h := c.hmacPool.Get().(hash.Hash) + defer c.hmacPool.Put(h) + size := min(hmacLength, h.Size()) + if len(value) <= size { + return nil, errors.New("no hmac found in session id") + } + + h.Reset() + mac := value[len(value)-size:] + decoded := value[:len(value)-size] + + h.Write(unsafe.Slice(unsafe.StringData(name), len(name))) // nolint + h.Write(sessionSeparator) // nolint + h.Write(decoded) // nolint + check := c.bytesPool.Get(h.Size()) + defer c.bytesPool.Put(check) + if subtle.ConstantTimeCompare(mac, h.Sum(check[:0])[:hmacLength]) == 0 { + return nil, errors.New("invalid hmac in session id") + } + + if c.cipher != nil { + var err error + if decoded, err = c.decrypt(decoded); err != nil { + return nil, fmt.Errorf("invalid session id: %w", err) + } + } + + data := c.dataPool.Get().(*SessionIdData) + if err := sessionUnmarshalOptions.Unmarshal(decoded, data); err != nil { + c.dataPool.Put(data) + return nil, fmt.Errorf("invalid session id: %w", err) + } + + return data, nil } func (c *SessionIdCodec) EncodePrivate(sessionData *SessionIdData) (PrivateSessionId, error) { - id, err := c.cookie.Encode(privateSessionName, sessionData) + id, err := c.encodeRaw(privateSessionName, sessionData) if err != nil { return "", err } - return PrivateSessionId(id), nil + defer c.bytesPool.Put(id) + return PrivateSessionId(c.encodeToString(id)), nil } -func reverseSessionId(s string) (string, error) { - // Note that we are assuming base64 encoded strings here. - decoded, err := base64.URLEncoding.DecodeString(s) - if err != nil { - return "", err +func (c *SessionIdCodec) reverseSessionId(data []byte) { + for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { + data[i], data[j] = data[j], data[i] } - - for i, j := 0, len(decoded)-1; i < j; i, j = i+1, j-1 { - decoded[i], decoded[j] = decoded[j], decoded[i] - } - return base64.URLEncoding.EncodeToString(decoded), nil } func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (PublicSessionId, error) { - encoded, err := c.cookie.Encode(publicSessionName, sessionData) + encoded, err := c.encodeRaw(publicSessionName, sessionData) if err != nil { return "", err } @@ -99,33 +262,36 @@ func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (PublicSession // (a timestamp) but the suffix the (random) hash. // By reversing we move the hash to the front, making the comparison of // session ids "random". - id, err := reverseSessionId(encoded) - if err != nil { - return "", err - } + c.reverseSessionId(encoded) - return PublicSessionId(id), nil + defer c.bytesPool.Put(encoded) + return PublicSessionId(c.encodeToString(encoded)), nil } func (c *SessionIdCodec) DecodePrivate(encodedData PrivateSessionId) (*SessionIdData, error) { - var data SessionIdData - if err := c.cookie.Decode(privateSessionName, string(encodedData), &data); err != nil { - return nil, err + decoded, err := c.decodeFromString(string(encodedData)) + if err != nil { + return nil, fmt.Errorf("invalid session id: %w", err) } + defer c.bytesPool.Put(decoded) - return &data, nil + return c.decodeRaw(privateSessionName, decoded) } func (c *SessionIdCodec) DecodePublic(encodedData PublicSessionId) (*SessionIdData, error) { - reversed, err := reverseSessionId(string(encodedData)) + decoded, err := c.decodeFromString(string(encodedData)) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid session id: %w", err) } + defer c.bytesPool.Put(decoded) - var data SessionIdData - if err := c.cookie.Decode(publicSessionName, reversed, &data); err != nil { - return nil, err - } - - return &data, nil + c.reverseSessionId(decoded) + return c.decodeRaw(publicSessionName, decoded) +} + +func (c *SessionIdCodec) Put(data *SessionIdData) { + if data != nil { + data.Reset() + c.dataPool.Put(data) + } } diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 3bf405c..1176d3c 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -22,7 +22,6 @@ package signaling import ( - "encoding/base64" "testing" "time" @@ -33,24 +32,97 @@ import ( func TestReverseSessionId(t *testing.T) { assert := assert.New(t) require := require.New(t) - a := base64.URLEncoding.EncodeToString([]byte("12345")) - ar, err := reverseSessionId(a) + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) require.NoError(err) - require.NotEqual(a, ar) - b := base64.URLEncoding.EncodeToString([]byte("54321")) - br, err := reverseSessionId(b) - require.NoError(err) - require.NotEqual(b, br) - assert.Equal(b, ar) - assert.Equal(a, br) + a := []byte("12345") + codec.reverseSessionId(a) + assert.Equal([]byte("54321"), a) + b := []byte("4321") + codec.reverseSessionId(b) + assert.Equal([]byte("1234"), b) +} - // Invalid base64. - if s, err := reverseSessionId("hello world!"); !assert.Error(err) { - assert.Fail("should have failed", "received %s", s) +func Benchmark_EncodePrivateSessionId(b *testing.B) { + require := require.New(b) + backend := &Backend{ + id: "compat", } - // Invalid base64 length. - if s, err := reverseSessionId("123"); !assert.Error(err) { - assert.Fail("should have failed", "received %s", s) + data := &SessionIdData{ + Sid: 1, + Created: time.Now().UnixMicro(), + BackendId: backend.Id(), + } + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) + for b.Loop() { + if _, err := codec.EncodePrivate(data); err != nil { + b.Fatal(err) + } + } +} + +func Benchmark_DecodePrivateSessionId(b *testing.B) { + require := require.New(b) + backend := &Backend{ + id: "compat", + } + data := &SessionIdData{ + Sid: 1, + Created: time.Now().UnixMicro(), + BackendId: backend.Id(), + } + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) + sid, err := codec.EncodePrivate(data) + require.NoError(err) + for b.Loop() { + if decoded, err := codec.DecodePrivate(sid); err != nil { + b.Fatal(err) + } else { + codec.Put(decoded) + } + } +} + +func Benchmark_EncodePublicSessionId(b *testing.B) { + require := require.New(b) + backend := &Backend{ + id: "compat", + } + data := &SessionIdData{ + Sid: 1, + Created: time.Now().UnixMicro(), + BackendId: backend.Id(), + } + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) + for b.Loop() { + if _, err := codec.EncodePublic(data); err != nil { + b.Fatal(err) + } + } +} + +func Benchmark_DecodePublicSessionId(b *testing.B) { + require := require.New(b) + backend := &Backend{ + id: "compat", + } + data := &SessionIdData{ + Sid: 1, + Created: time.Now().UnixMicro(), + BackendId: backend.Id(), + } + codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + require.NoError(err) + sid, err := codec.EncodePublic(data) + require.NoError(err) + for b.Loop() { + if decoded, err := codec.DecodePublic(sid); err != nil { + b.Fatal(err) + } else { + codec.Put(decoded) + } } } @@ -63,17 +135,33 @@ func TestPublicPrivate(t *testing.T) { BackendId: "foo", } - codec := NewSessionIdCodec([]byte("0123456789012345"), []byte("0123456789012345")) + codec, err := NewSessionIdCodec([]byte("0123456789012345"), []byte("0123456789012345")) + require.NoError(err) private, err := codec.EncodePrivate(sd) require.NoError(err) public, err := codec.EncodePublic(sd) require.NoError(err) assert.NotEqual(private, public) + if data, err := codec.DecodePublic(public); assert.NoError(err) { + assert.Equal(sd.Sid, data.Sid) + assert.Equal(sd.Created, data.Created) + assert.Equal(sd.BackendId, data.BackendId) + codec.Put(data) + } + if data, err := codec.DecodePrivate(private); assert.NoError(err) { + assert.Equal(sd.Sid, data.Sid) + assert.Equal(sd.Created, data.Created) + assert.Equal(sd.BackendId, data.BackendId) + codec.Put(data) + } + if data, err := codec.DecodePublic(PublicSessionId(private)); !assert.Error(err) { assert.Fail("should have failed", "received %+v", data) + codec.Put(data) } if data, err := codec.DecodePrivate(PrivateSessionId(public)); !assert.Error(err) { assert.Fail("should have failed", "received %+v", data) + codec.Put(data) } } From a0b64b30e02f85cf29f3c8891331bf27c314f084 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 3 Dec 2025 07:59:35 +0100 Subject: [PATCH 325/549] Session ids are unique, no need to include type in cache key. --- hub.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/hub.go b/hub.go index 9e6c4b6..9bd53c1 100644 --- a/hub.go +++ b/hub.go @@ -44,6 +44,7 @@ import ( "sync" "sync/atomic" "time" + unsafe "unsafe" "github.com/dlintw/goconf" "github.com/golang-jwt/jwt/v5" @@ -621,45 +622,44 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { func (h *Hub) getDecodeCache(cache_key string) *LruCache[*SessionIdData] { hash := fnv.New32a() - hash.Write([]byte(cache_key)) // nolint + // Make sure we don't have a temporary allocation for the string -> []byte conversion. + hash.Write(unsafe.Slice(unsafe.StringData(cache_key), len(cache_key))) // nolint idx := hash.Sum32() % uint32(len(h.decodeCaches)) return h.decodeCaches[idx] } func (h *Hub) invalidatePublicSessionId(id PublicSessionId) { - h.invalidateSessionId(string(id), publicSessionName) + h.invalidateSessionId(string(id)) } func (h *Hub) invalidatePrivateSessionId(id PrivateSessionId) { - h.invalidateSessionId(string(id), privateSessionName) + h.invalidateSessionId(string(id)) } -func (h *Hub) invalidateSessionId(id string, sessionType string) { +func (h *Hub) invalidateSessionId(id string) { if len(id) == 0 { return } - cache_key := id + "|" + sessionType - cache := h.getDecodeCache(cache_key) - cache.Remove(cache_key) + cache := h.getDecodeCache(id) + cache.Remove(id) } func (h *Hub) setDecodedPublicSessionId(id PublicSessionId, data *SessionIdData) { - h.setDecodedSessionId(string(id), publicSessionName, data) + h.setDecodedSessionId(string(id), data) } func (h *Hub) setDecodedPrivateSessionId(id PrivateSessionId, data *SessionIdData) { - h.setDecodedSessionId(string(id), privateSessionName, data) + h.setDecodedSessionId(string(id), data) } -func (h *Hub) setDecodedSessionId(id string, sessionType string, data *SessionIdData) { +func (h *Hub) setDecodedSessionId(id string, data *SessionIdData) { if len(id) == 0 { return } - cache_key := id + "|" + sessionType - cache := h.getDecodeCache(cache_key) - cache.Set(cache_key, data) + cache := h.getDecodeCache(id) + cache.Set(id, data) } func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { @@ -667,7 +667,7 @@ func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { return nil } - cache_key := string(id + "|" + privateSessionName) + cache_key := string(id) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { return result @@ -687,7 +687,7 @@ func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { return nil } - cache_key := string(id + "|" + publicSessionName) + cache_key := string(id) cache := h.getDecodeCache(cache_key) if result := cache.Get(cache_key); result != nil { return result From ad150555152e2988eab980d5f8886e8f82131025 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 3 Dec 2025 09:27:06 +0100 Subject: [PATCH 326/549] CI: Check that version of tarball builds is not "unknown". --- .github/workflows/tarball.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 55330ad..a24eef6 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -72,6 +72,11 @@ jobs: run: | echo "Building with $(nproc) threads" make -C tmp build -j$(nproc) + UNKNOWN=$(./tmp/bin/signaling -version | grep unknown || true) + if [ -n "$UNKNOWN" ]; then \ + echo "Found unknown version: $UNKNOWN"; \ + exit 1; \ + fi test: strategy: From 5f58e335c8b51cc2cca5628b4b2f7a467bfb656e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 3 Dec 2025 09:29:16 +0100 Subject: [PATCH 327/549] make: Include "version.txt" in tarball. --- .github/workflows/tarball.yml | 6 ++++++ Makefile | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index a24eef6..b63eef3 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -67,6 +67,7 @@ jobs: mkdir -p tmp tar xvf nextcloud-spreed-signaling*.tar.gz --strip-components=1 -C tmp [ -d "tmp/vendor" ] || exit 1 + [ -f "tmp/version.txt" ] || exit 1 - name: Build run: | @@ -77,6 +78,11 @@ jobs: echo "Found unknown version: $UNKNOWN"; \ exit 1; \ fi + UNKNOWN=$(./tmp/bin/proxy -version | grep unknown || true) + if [ -n "$UNKNOWN" ]; then \ + echo "Found unknown version: $UNKNOWN"; \ + exit 1; \ + fi test: strategy: diff --git a/Makefile b/Makefile index 8ef1d2c..d511c32 100644 --- a/Makefile +++ b/Makefile @@ -194,7 +194,7 @@ vendor: go.mod go.sum rm -rf $(VENDORDIR) $(GO) mod vendor -tarball: vendor +tarball: vendor | $(TMPDIR) git archive \ --prefix=nextcloud-spreed-signaling-$(TARVERSION)/ \ -o nextcloud-spreed-signaling-$(TARVERSION).tar \ @@ -204,6 +204,12 @@ tarball: vendor --mtime="$(shell git log -1 --date=iso8601-strict --format=%cd HEAD)" \ --transform "s//nextcloud-spreed-signaling-$(TARVERSION)\//" \ vendor + echo "$(TARVERSION)" > "$(TMPDIR)/version.txt" + tar rf nextcloud-spreed-signaling-$(TARVERSION).tar \ + -C "$(TMPDIR)" \ + --mtime="$(shell git log -1 --date=iso8601-strict --format=%cd HEAD)" \ + --transform "s//nextcloud-spreed-signaling-$(TARVERSION)\//" \ + version.txt gzip --force nextcloud-spreed-signaling-$(TARVERSION).tar dist: tarball From 4586775afca2515c56ecdfaa7aadb819a6945185 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 3 Dec 2025 09:52:08 +0100 Subject: [PATCH 328/549] client: Include version, optimize JSON processing. --- .github/workflows/tarball.yml | 7 ++- client/main.go | 84 ++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index b63eef3..664103b 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -72,7 +72,7 @@ jobs: - name: Build run: | echo "Building with $(nproc) threads" - make -C tmp build -j$(nproc) + make -C tmp build client -j$(nproc) UNKNOWN=$(./tmp/bin/signaling -version | grep unknown || true) if [ -n "$UNKNOWN" ]; then \ echo "Found unknown version: $UNKNOWN"; \ @@ -83,6 +83,11 @@ jobs: echo "Found unknown version: $UNKNOWN"; \ exit 1; \ fi + UNKNOWN=$(./tmp/bin/client -version | grep unknown || true) + if [ -n "$UNKNOWN" ]; then \ + echo "Found unknown version: $UNKNOWN"; \ + exit 1; \ + fi test: strategy: diff --git a/client/main.go b/client/main.go index 942e94b..ab87a30 100644 --- a/client/main.go +++ b/client/main.go @@ -25,6 +25,7 @@ import ( "bytes" "encoding/json" "flag" + "fmt" "io" "log" pseudorand "math/rand" @@ -34,6 +35,7 @@ import ( "os" "os/signal" "runtime" + "runtime/pprof" "sync" "sync/atomic" "time" @@ -42,15 +44,25 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/mailru/easyjson" + "github.com/mailru/easyjson/jlexer" + "github.com/mailru/easyjson/jwriter" signaling "github.com/strukturag/nextcloud-spreed-signaling" ) var ( + version = "unreleased" + + showVersion = flag.Bool("version", false, "show version and quit") + addr = flag.String("addr", "localhost:28080", "http service address") config = flag.String("config", "server.conf", "config file to use") + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + + memprofile = flag.String("memprofile", "", "write memory profile to file") + maxClients = flag.Int("maxClients", 100, "number of client connections") backendSecret []byte @@ -77,6 +89,41 @@ type MessagePayload struct { Now time.Time `json:"now"` } +func (m *MessagePayload) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + w.RawByte('{') + w.RawString("\"now\":") + w.Raw(m.Now.MarshalJSON()) + w.RawByte('}') + return w.Buffer.BuildBytes(), w.Error +} + +func (m *MessagePayload) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + r.Delim('{') + for !r.IsDelim('}') { + key := r.UnsafeFieldName(false) + r.WantColon() + switch key { + case "now": + if r.IsNull() { + r.Skip() + } else { + if data := r.Raw(); r.Ok() { + r.AddError((m.Now).UnmarshalJSON(data)) + } + } + default: + r.SkipRecursive() + } + r.WantComma() + } + r.Delim('}') + r.Consumed() + + return r.Error() +} + type SignalingClient struct { readyWg *sync.WaitGroup // +checklocksignore: Only written to from constructor. @@ -198,7 +245,7 @@ func (c *SignalingClient) PublicSessionId() signaling.PublicSessionId { func (c *SignalingClient) processMessageMessage(message *signaling.ServerMessage) { var msg MessagePayload - if err := json.Unmarshal(message.Message.Data, &msg); err != nil { + if err := msg.UnmarshalJSON(message.Message.Data); err != nil { log.Println("Error in unmarshal", err) return } @@ -352,7 +399,7 @@ func (c *SignalingClient) SendMessages(clients []*SignalingClient) { msgdata := MessagePayload{ Now: now, } - data, _ := json.Marshal(msgdata) + data, _ := msgdata.MarshalJSON() msg := &signaling.ClientMessage{ Type: "message", Message: &signaling.MessageClientMessage{ @@ -453,6 +500,11 @@ func main() { flag.Parse() log.SetFlags(0) + if *showVersion { + fmt.Printf("nextcloud-spreed-signaling-client version %s/%s\n", version, runtime.Version()) + os.Exit(0) + } + config, err := goconf.ReadConfigFile(*config) if err != nil { log.Fatal("Could not read configuration: ", err) @@ -463,6 +515,34 @@ func main() { log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatalf("Error writing CPU profile to %s: %s", *cpuprofile, err) + } + log.Printf("Writing CPU profile to %s ...", *cpuprofile) + defer pprof.StopCPUProfile() + } + + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal(err) + } + + defer func() { + log.Printf("Writing Memory profile to %s ...", *memprofile) + runtime.GC() + if err := pprof.WriteHeapProfile(f); err != nil { + log.Printf("Error writing Memory profile to %s: %s", *memprofile, err) + } + }() + } + interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) From 5893fedef4862faebb0601e89ade2f83d1b2fb66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:01:54 +0000 Subject: [PATCH 329/549] Bump golangci/golangci-lint-action from 9.1.0 to 9.2.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.1.0 to 9.2.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v9.1.0...v9.2.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index facdd06..495b85f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,7 +33,7 @@ jobs: go-version: "1.24" - name: lint - uses: golangci/golangci-lint-action@v9.1.0 + uses: golangci/golangci-lint-action@v9.2.0 with: version: latest args: --timeout=2m0s From 5afa838ee8e24f85e15e5f6b431b14eb2d387984 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 20:01:43 +0000 Subject: [PATCH 330/549] Bump github.com/pion/ice/v4 from 4.0.12 to 4.0.13 Bumps [github.com/pion/ice/v4](https://github.com/pion/ice) from 4.0.12 to 4.0.13. - [Release notes](https://github.com/pion/ice/releases) - [Commits](https://github.com/pion/ice/compare/v4.0.12...v4.0.13) --- updated-dependencies: - dependency-name: github.com/pion/ice/v4 dependency-version: 4.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e6b08ff..069c9a1 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/ice/v4 v4.0.12 + github.com/pion/ice/v4 v4.0.13 github.com/pion/sdp/v3 v3.0.16 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 @@ -57,7 +57,7 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.0.7 // indirect + github.com/pion/dtls/v3 v3.0.8 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index de933cc..de47c5c 100644 --- a/go.sum +++ b/go.sum @@ -86,10 +86,10 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q= -github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8= -github.com/pion/ice/v4 v4.0.12 h1:vuI3h9OD5M0Z+V304qLC1/o16ahHVnDgqNCWbONv6s0= -github.com/pion/ice/v4 v4.0.12/go.mod h1:tAp574oAufhHRHr8EO1xgPmVKVDBROX+708WggYD6NE= +github.com/pion/dtls/v3 v3.0.8 h1:ZrPUrvPVDaTJDM8Vu1veatzXebLlsIWeT7Vaate/zwM= +github.com/pion/dtls/v3 v3.0.8/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os= +github.com/pion/ice/v4 v4.0.13 h1:1cdmd80gmLdnVTM2bXzw2CBebvXvkGNEaWi/CuDK9WQ= +github.com/pion/ice/v4 v4.0.13/go.mod h1:Xo5f5DBbEjQac+6pR7i83AGuwoGxnxwXkOOvHFVnfnM= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= From 55bafac6b74f78395acbab1b5094adbfa1ec1cfe Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:33:11 +0100 Subject: [PATCH 331/549] Fix spelling errors. --- capabilities_test.go | 2 +- grpc_client.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/capabilities_test.go b/capabilities_test.go index c4e7a7b..450da1b 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -354,7 +354,7 @@ func TestCapabilitiesShortCache(t *testing.T) { value = called.Load() assert.EqualValues(1, value) - // The capabilities are cached for a minumum duration. + // The capabilities are cached for a minimum duration. SetCapabilitiesGetNow(t, capabilities, func() time.Time { return time.Now().Add(minCapabilitiesCacheDuration / 2) }) diff --git a/grpc_client.go b/grpc_client.go index 64e45f8..daeedba 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -619,7 +619,7 @@ loop: c.closeClient(client) client.SetSelf(true) } else if version != c.version { - c.logger.Printf("WARNING: Node %s is runing different version %s than local node (%s)", client.Target(), version, c.version) + c.logger.Printf("WARNING: Node %s is running different version %s than local node (%s)", client.Target(), version, c.version) } else { c.logger.Printf("Checked GRPC server id of %s running version %s", client.Target(), version) } From b1c18c720772f548b7738d60078d93adc62bdf85 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:33:42 +0100 Subject: [PATCH 332/549] lint: Check spelling using "misspell". --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 68a75bd..6433e0b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,7 @@ version: "2" linters: enable: + - misspell - revive settings: govet: From 55d776d110f4faeb638a0242bec3c42ace9354be Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:36:03 +0100 Subject: [PATCH 333/549] Remove unnecessary nil check. --- mcu_janus_client.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 754ec2a..66ff7ac 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -247,9 +247,7 @@ func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelecti configure_msg := api.StringMap{ "request": "configure", } - if stream != nil { - stream.AddToMessage(configure_msg) - } + stream.AddToMessage(configure_msg) _, err := handle.Message(ctx, configure_msg, nil) if err != nil { callback(err, nil) From 98060d48cb56626015fc06f02275aa2bd02c7da0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:36:17 +0100 Subject: [PATCH 334/549] lint: Enable "nilness" check in govet. --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 6433e0b..63ddf4e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,6 +5,8 @@ linters: - revive settings: govet: + enable: + - nilness disable: - stdversion revive: From c581bc14d5c8f48bdd716ca05155f4db9b8d397d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:53:36 +0100 Subject: [PATCH 335/549] lint: Enable "errchkjson" and update json.Marshal error handling. --- .golangci.yml | 4 ++++ backend_server.go | 7 +------ capabilities_test.go | 3 ++- hub_test.go | 9 ++++----- proxy/proxy_server.go | 6 +----- proxy_config_etcd_test.go | 3 +-- 6 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 63ddf4e..e76cf27 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,9 +1,13 @@ version: "2" linters: enable: + - errchkjson - misspell - revive settings: + errchkjson: + check-error-free-encoding: true + report-no-exported: true govet: enable: - nilness diff --git a/backend_server.go b/backend_server.go index 23975be..27e75be 100644 --- a/backend_server.go +++ b/backend_server.go @@ -167,12 +167,7 @@ func (b *BackendServer) Start(r *mux.Router) error { "nextcloud-spreed-signaling": "Welcome", "version": b.version, } - welcomeMessage, err := json.Marshal(welcome) - if err != nil { - // Should never happen. - return err - } - + welcomeMessage, _ := json.Marshal(welcome) b.welcomeMessage = string(welcomeMessage) + "\n" if b.debug { diff --git a/capabilities_test.go b/capabilities_test.go index 450da1b..225691b 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -75,10 +75,11 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie config := api.StringMap{ "signaling": signaling, } - spreedCapa, _ := json.Marshal(api.StringMap{ + spreedCapa, err := json.Marshal(api.StringMap{ "features": features, "config": config, }) + require.NoError(err) emptyArray := []byte("[]") response := &CapabilitiesResponse{ Version: CapabilitiesVersion{ diff --git a/hub_test.go b/hub_test.go index 3a5ed91..657508e 100644 --- a/hub_test.go +++ b/hub_test.go @@ -414,8 +414,7 @@ func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re userdata := map[string]string{ "displayname": "Displayname " + params.UserId, } - data, err := json.Marshal(userdata) - require.NoError(err) + data, _ := json.Marshal(userdata) response.Auth.User = data return response } @@ -476,8 +475,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re data := map[string]string{ "userid": "userid-from-sessiondata", } - tmp, err := json.Marshal(data) - require.NoError(err) + tmp, _ := json.Marshal(data) response.Room.Session = tmp case "test-room-initial-permissions": permissions := []Permission{PERMISSION_MAY_PUBLISH_AUDIO} @@ -777,10 +775,11 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { signaling[ConfigKeyHelloV2TokenKey] = string(public) } } - spreedCapa, _ := json.Marshal(api.StringMap{ + spreedCapa, err := json.Marshal(api.StringMap{ "features": features, "config": config, }) + assert.NoError(t, err) response := &CapabilitiesResponse{ Version: CapabilitiesVersion{ Major: 20, diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 5c29077..24b48ea 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -289,11 +289,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * "nextcloud-spreed-signaling-proxy": "Welcome", "version": version, } - welcomeMessage, err := json.Marshal(welcome) - if err != nil { - // Should never happen. - return nil, err - } + welcomeMessage, _ := json.Marshal(welcome) tokenId, _ := config.GetString("app", "token_id") var tokenKey *rsa.PrivateKey diff --git a/proxy_config_etcd_test.go b/proxy_config_etcd_test.go index 814b9c3..ffb0dfd 100644 --- a/proxy_config_etcd_test.go +++ b/proxy_config_etcd_test.go @@ -54,8 +54,7 @@ func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*embed.Etcd, ProxyConfig) func SetEtcdProxy(t *testing.T, etcd *embed.Etcd, path string, proxy *TestProxyInformationEtcd) { t.Helper() - data, err := json.Marshal(proxy) - require.NoError(t, err) + data, _ := json.Marshal(proxy) SetEtcdValue(etcd, path, data) } From f795bf303d5ee7baac394ee76a8de333b4216639 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:54:56 +0100 Subject: [PATCH 336/549] lint: Enable "exptostd" to detect future std library features. --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index e76cf27..006f58d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,6 +2,7 @@ version: "2" linters: enable: - errchkjson + - exptostd - misspell - revive settings: From 694297a6f40bb86252e6e3ccf4173d6fcb5a0602 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 10:59:00 +0100 Subject: [PATCH 337/549] Fix ordering of case statements (found by "gocritic"). --- api/stringmap.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/stringmap.go b/api/stringmap.go index 749a3dc..de26597 100644 --- a/api/stringmap.go +++ b/api/stringmap.go @@ -68,10 +68,10 @@ func GetStringMapString[T ~string](m StringMap, key string) (T, bool) { } switch v := v.(type) { - case T: - return v, true case string: return T(v), true + case T: + return v, true default: return defaultValue, false } From 9d07a852a986aa0d603c18f2e07874da969354dd Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:06:37 +0100 Subject: [PATCH 338/549] Rewrite long if-else chains as switch statements. --- api/bandwidth.go | 9 +++++---- api_proxy.go | 9 +++++---- clientsession.go | 9 +++++---- mcu_janus_publisher.go | 9 +++++---- mcu_proxy.go | 28 ++++++++++++++++------------ 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/api/bandwidth.go b/api/bandwidth.go index 683dbac..ad5b3d9 100644 --- a/api/bandwidth.go +++ b/api/bandwidth.go @@ -50,13 +50,14 @@ func formatWithRemainder(value uint64, divisor uint64, format string) string { // String returns the formatted bandwidth. func (b Bandwidth) String() string { - if b >= Gigabit { + switch { + case b >= Gigabit: return formatWithRemainder(b.Bits(), Gigabit.Bits(), "Gbps") - } else if b >= Megabit { + case b >= Megabit: return formatWithRemainder(b.Bits(), Megabit.Bits(), "Mbps") - } else if b >= Kilobit { + case b >= Kilobit: return formatWithRemainder(b.Bits(), Kilobit.Bits(), "Kbps") - } else { + default: return fmt.Sprintf("%d bps", b) } } diff --git a/api_proxy.go b/api_proxy.go index 1c332a5..7761f6d 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -329,13 +329,14 @@ type EventProxyServerBandwidth struct { } func (b *EventProxyServerBandwidth) String() string { - if b.Incoming != nil && b.Outgoing != nil { + switch { + case b.Incoming != nil && b.Outgoing != nil: return fmt.Sprintf("bandwidth: incoming=%.3f%%, outgoing=%.3f%%", *b.Incoming, *b.Outgoing) - } else if b.Incoming != nil { + case b.Incoming != nil: return fmt.Sprintf("bandwidth: incoming=%.3f%%, outgoing=unlimited", *b.Incoming) - } else if b.Outgoing != nil { + case b.Outgoing != nil: return fmt.Sprintf("bandwidth: incoming=unlimited, outgoing=%.3f%%", *b.Outgoing) - } else { + default: return "bandwidth: incoming=unlimited, outgoing=unlimited" } } diff --git a/clientsession.go b/clientsession.go index ee0fbd2..6ff26a3 100644 --- a/clientsession.go +++ b/clientsession.go @@ -835,22 +835,23 @@ func (s *ClientSession) IsAllowedToSend(data *MessageClientMessageData) error { s.mu.Lock() defer s.mu.Unlock() - if data != nil && data.RoomType == "screen" { + switch { + case data != nil && data.RoomType == "screen": if s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { return nil } return &PermissionError{PERMISSION_MAY_PUBLISH_SCREEN} - } else if s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_MEDIA) { + case s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_MEDIA): // Client is allowed to publish any media (audio / video). return nil - } else if data != nil && data.Type == "offer" { + case data != nil && data.Type == "offer": // Check what user is trying to publish and check permissions accordingly. if _, err := s.isSdpAllowedToSendLocked(data.offerSdp); err != nil { return err } return nil - } else { + default: // Candidate or unknown event, check if client is allowed to publish any media. if s.hasAnyPermissionLocked(PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO) { return nil diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 225365b..7d907fd 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -319,7 +319,8 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, continue } - if strings.EqualFold(s.Type, "audio") { + switch { + case strings.EqualFold(s.Type, "audio"): s.Codec = answerCodec.Name if value, found := getFmtpValue(answerCodec.Fmtp, "useinbandfec"); found && value == "1" { s.Fec = true @@ -330,7 +331,7 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, if value, found := getFmtpValue(answerCodec.Fmtp, "stereo"); found && value == "1" { s.Stereo = true } - } else if strings.EqualFold(s.Type, "video") { + case strings.EqualFold(s.Type, "video"): s.Codec = answerCodec.Name // TODO: Determine if SVC is used. s.Svc = false @@ -384,9 +385,9 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, } } - } else if strings.EqualFold(s.Type, "data") { // nolint + case strings.EqualFold(s.Type, "data"): // Already handled above. - } else { + default: p.logger.Printf("Skip type %s", s.Type) continue } diff --git a/mcu_proxy.go b/mcu_proxy.go index 37ee0ff..c70c2a8 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -2086,18 +2086,20 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id Pu incoming_b = bw.Incoming } - if incoming_a == nil && incoming_b == nil { + switch { + case incoming_a == nil && incoming_b == nil: return 0 - } else if incoming_a == nil && incoming_b != nil { + case incoming_a == nil && incoming_b != nil: return -1 - } else if incoming_a != nil && incoming_b == nil { + case incoming_a != nil && incoming_b == nil: return -1 - } else if *incoming_a < *incoming_b { + case *incoming_a < *incoming_b: return -1 - } else if *incoming_a > *incoming_b { + case *incoming_a > *incoming_b: return 1 + default: + return 0 } - return 0 }) publisher = m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections2, func(c *mcuProxyConnection) bool { return true @@ -2349,18 +2351,20 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ outgoing_b = bw.Outgoing } - if outgoing_a == nil && outgoing_b == nil { + switch { + case outgoing_a == nil && outgoing_b == nil: return 0 - } else if outgoing_a == nil && outgoing_b != nil { + case outgoing_a == nil && outgoing_b != nil: return -1 - } else if outgoing_a != nil && outgoing_b == nil { + case outgoing_a != nil && outgoing_b == nil: return -1 - } else if *outgoing_a < *outgoing_b { + case *outgoing_a < *outgoing_b: return -1 - } else if *outgoing_a > *outgoing_b { + case *outgoing_a > *outgoing_b: return 1 + default: + return 0 } - return 0 }) subscriber = m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections2, func(c *mcuProxyConnection) bool { return true From 57b6b326c05a1ff848b648dde1ae7a966470d77d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:07:47 +0100 Subject: [PATCH 339/549] Simplify string concatenation. --- capabilities.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capabilities.go b/capabilities.go index 2f68bb9..0e66848 100644 --- a/capabilities.go +++ b/capabilities.go @@ -131,7 +131,7 @@ func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Tim if !strings.HasSuffix(capUrl.Path, "/") { capUrl.Path += "/" } - capUrl.Path = capUrl.Path + "ocs/v2.php/cloud/capabilities" + capUrl.Path += "ocs/v2.php/cloud/capabilities" } else if pos := strings.Index(capUrl.Path, "/ocs/v2.php/"); pos >= 0 { capUrl.Path = capUrl.Path[:pos+11] + "/cloud/capabilities" } From dd01d985535566ba219d914ec9231633870634e6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:08:31 +0100 Subject: [PATCH 340/549] Simplify and use range over integer. --- concurrentmap_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concurrentmap_test.go b/concurrentmap_test.go index 21ad085..c52ae7e 100644 --- a/concurrentmap_test.go +++ b/concurrentmap_test.go @@ -83,7 +83,7 @@ func TestConcurrentStringStringMap(t *testing.T) { key := "key-" + strconv.Itoa(x) rnd := newRandomString(32) - for y := 0; y < count; y = y + 1 { + for y := range count { value := rnd + "-" + strconv.Itoa(y) m.Set(key, value) if v, found := m.Get(key); !found { From f237458b35696cd54926a9a1d58083310b59d506 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:09:13 +0100 Subject: [PATCH 341/549] Don't capitalize variable name. --- proxy/proxy_server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 24b48ea..24be05c 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -153,11 +153,11 @@ type ProxyServer struct { remotePublishers map[string]map[*proxyRemotePublisher]bool } -func IsPublicIP(IP net.IP) bool { - if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() { +func IsPublicIP(ip net.IP) bool { + if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { return false } - if ip4 := IP.To4(); ip4 != nil { + if ip4 := ip.To4(); ip4 != nil { switch { case ip4[0] == 10: return false From 5a6dfa0516a42ef9c8559e119ad532c5228f1131 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:10:30 +0100 Subject: [PATCH 342/549] Fix comment format. --- internal/canonicalize_url_test.go | 2 +- janus_client.go | 2 +- mcu_janus_subscriber.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/canonicalize_url_test.go b/internal/canonicalize_url_test.go index 65d5fee..0cb3a5e 100644 --- a/internal/canonicalize_url_test.go +++ b/internal/canonicalize_url_test.go @@ -66,7 +66,7 @@ func TestCanonicalizeUrl(t *testing.T) { for idx, tc := range testcases { expectChanged := tc.url.String() != tc.expected.String() canonicalized, changed := CanonicalizeUrl(tc.url) - assert.Equal(tc.url, canonicalized) //urls will be changed inplace + assert.Equal(tc.url, canonicalized) // urls will be changed inplace if !expectChanged { assert.False(changed, "testcase %d should not have changed the url", idx) continue diff --git a/janus_client.go b/janus_client.go index 6ae6b88..e3f2803 100644 --- a/janus_client.go +++ b/janus_client.go @@ -719,7 +719,7 @@ type JanusHandle struct { // Type // pub or sub Type string - //User // Userid + // User // Userid User string // Events is a receive only channel that can be used to receive events diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 5fc1565..de9d369 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -261,7 +261,7 @@ retry: return } } - //p.logger.Println("Joined as listener", join_response) + // p.logger.Println("Joined as listener", join_response) p.session = join_response.Session callback(nil, join_response.Jsep) From bcdf9af5eb115843d62329234a7a853e9800f23b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:11:43 +0100 Subject: [PATCH 343/549] Remove trailing whitespace from key. --- geoip_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geoip_test.go b/geoip_test.go index 885749a..fecbc5a 100644 --- a/geoip_test.go +++ b/geoip_test.go @@ -103,11 +103,11 @@ func TestGeoLookupCaching(t *testing.T) { func TestGeoLookupContinent(t *testing.T) { tests := map[string][]string{ - "AU": {"OC"}, - "DE": {"EU"}, - "RU": {"EU"}, - "": nil, - "INVALID ": nil, + "AU": {"OC"}, + "DE": {"EU"}, + "RU": {"EU"}, + "": nil, + "INVALID": nil, } for country, expected := range tests { From 6d3ff0c5ba2a113a6448009ea766cf89728646e7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:13:12 +0100 Subject: [PATCH 344/549] Silence error about previous defer not running after "log.Fatal". --- client/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/main.go b/client/main.go index ab87a30..fdae23b 100644 --- a/client/main.go +++ b/client/main.go @@ -531,7 +531,7 @@ func main() { if *memprofile != "" { f, err := os.Create(*memprofile) if err != nil { - log.Fatal(err) + log.Fatal(err) // nolint (defer pprof.StopCPUProfile() will not run which is ok in case of errors) } defer func() { From 697f659083c9e6cc49490db58e9492b529e76602 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:13:27 +0100 Subject: [PATCH 345/549] lint: Enable "gocritic" linter. --- .golangci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 006f58d..5353c98 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,12 +3,21 @@ linters: enable: - errchkjson - exptostd + - gocritic - misspell - revive settings: errchkjson: check-error-free-encoding: true report-no-exported: true + gocritic: + disabled-checks: + - singleCaseSwitch + settings: + ifElseChain: + # Min number of if-else blocks that makes the warning trigger. + # Default: 2 + minThreshold: 3 govet: enable: - nilness From e761ea071bbd84ca02fa8880e20b072a2d4c1da1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:20:24 +0100 Subject: [PATCH 346/549] Use strings.Builder instead of looped string concatenation. --- stats_prometheus_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index 0516de9..abe83d7 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -77,18 +77,18 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 pc = pc[:n] frames := runtime.CallersFrames(pc) - stack := "" + var stack strings.Builder for { frame, more := frames.Next() if !strings.Contains(frame.File, "nextcloud-spreed-signaling") { break } - stack += fmt.Sprintf("%s:%d\n", frame.File, frame.Line) + fmt.Fprintf(&stack, "%s:%d\n", frame.File, frame.Line) if !more { break } } - assert.EqualValues(value, v, "Unexpected value for %s at\n%s", desc, stack) + assert.EqualValues(value, v, "Unexpected value for %s at\n%s", desc, stack.String()) } } From c3c3f0bf75c95d0239409c4234b9cc2914be01b1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 11:20:36 +0100 Subject: [PATCH 347/549] lint: Enable "modernize" linter. --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 5353c98..ef3f07d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,6 +5,7 @@ linters: - exptostd - gocritic - misspell + - modernize - revive settings: errchkjson: From 498612249320fe8fe1c0251cc94a87b73adac869 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 12:23:14 +0100 Subject: [PATCH 348/549] Run tests in parallel where possible. --- allowed_ips_test.go | 3 ++ api/stringmap_test.go | 3 ++ backend_configuration_test.go | 28 ++++++++++------ backend_server_test.go | 7 ++++ backend_storage_etcd_test.go | 2 +- backoff_test.go | 1 + channel_waiter_test.go | 1 + client/stats_test.go | 1 + client_test.go | 1 + clientsession_test.go | 54 +++++++++++++++++-------------- closer_test.go | 2 ++ concurrentmap_test.go | 1 + deferred_executor_test.go | 6 ++++ dns_monitor_test.go | 7 ++-- federation_test.go | 8 +++++ file_watcher_test.go | 11 +++++-- flags_test.go | 1 + geoip_test.go | 7 ++++ grpc_client_test.go | 8 ++--- grpc_server_test.go | 4 +-- hub_test.go | 38 ++++++++++++++++++++-- internal/canonicalize_url_test.go | 2 ++ internal/ips_test.go | 2 ++ lru_test.go | 2 ++ mcu_common_test.go | 1 + mcu_janus_events_handler_test.go | 1 + mcu_janus_publisher_test.go | 2 ++ mcu_janus_test.go | 19 +++++------ mcu_proxy_test.go | 8 ++++- natsclient_loopback_test.go | 8 ++--- natsclient_test.go | 10 +++--- notifier_test.go | 7 ++++ proxy/proxy_remote_test.go | 4 +++ proxy/proxy_server_test.go | 17 ++++++++++ proxy/proxy_tokens_etcd_test.go | 1 + proxy_config_static_test.go | 3 +- publisher_stats_counter_test.go | 2 +- room_ping_test.go | 4 +++ room_test.go | 1 + roomsessions_builtin_test.go | 1 + sessionid_codec_test.go | 2 ++ single_notifier_test.go | 7 ++++ throttle_test.go | 7 ++++ transient_data_test.go | 4 +++ 44 files changed, 240 insertions(+), 69 deletions(-) diff --git a/allowed_ips_test.go b/allowed_ips_test.go index da4f49b..2eb78de 100644 --- a/allowed_ips_test.go +++ b/allowed_ips_test.go @@ -30,6 +30,7 @@ import ( ) func TestAllowedIps(t *testing.T) { + t.Parallel() require := require.New(t) a, err := ParseAllowedIps("127.0.0.1, 192.168.0.1, 192.168.1.1/24") require.NoError(err) @@ -49,6 +50,7 @@ func TestAllowedIps(t *testing.T) { for _, addr := range allowed { t.Run(addr, func(t *testing.T) { + t.Parallel() assert := assert.New(t) if ip := net.ParseIP(addr); assert.NotNil(ip, "error parsing %s", addr) { assert.True(a.Allowed(ip), "should allow %s", addr) @@ -58,6 +60,7 @@ func TestAllowedIps(t *testing.T) { for _, addr := range notAllowed { t.Run(addr, func(t *testing.T) { + t.Parallel() assert := assert.New(t) if ip := net.ParseIP(addr); assert.NotNil(ip, "error parsing %s", addr) { assert.False(a.Allowed(ip), "should not allow %s", addr) diff --git a/api/stringmap_test.go b/api/stringmap_test.go index 941c7f9..f54e3db 100644 --- a/api/stringmap_test.go +++ b/api/stringmap_test.go @@ -28,6 +28,7 @@ import ( ) func TestConvertStringMap(t *testing.T) { + t.Parallel() assert := assert.New(t) d := map[string]any{ "foo": "bar", @@ -56,6 +57,7 @@ func TestConvertStringMap(t *testing.T) { } func TestGetStringMapString(t *testing.T) { + t.Parallel() assert := assert.New(t) type StringMapTestString string @@ -90,6 +92,7 @@ func TestGetStringMapString(t *testing.T) { } func TestGetStringMapStringMap(t *testing.T) { + t.Parallel() assert := assert.New(t) m := StringMap{ diff --git a/backend_configuration_test.go b/backend_configuration_test.go index e22d160..12e9fd1 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -37,6 +37,7 @@ import ( func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) { for _, u := range valid_urls { t.Run(u, func(t *testing.T) { + t.Parallel() assert := assert.New(t) parsed, err := url.ParseRequestURI(u) if !assert.NoError(err, "The url %s should be valid", u) { @@ -49,6 +50,7 @@ func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, i } for _, u := range invalid_urls { t.Run(u, func(t *testing.T) { + t.Parallel() assert := assert.New(t) parsed, _ := url.ParseRequestURI(u) assert.False(config.IsUrlAllowed(parsed), "The url %s should not be allowed", u) @@ -59,6 +61,7 @@ func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, i func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]string, invalid_urls []string) { for _, entry := range valid_urls { t.Run(entry[0], func(t *testing.T) { + t.Parallel() assert := assert.New(t) u := entry[0] parsed, err := url.ParseRequestURI(u) @@ -73,6 +76,7 @@ func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]str } for _, u := range invalid_urls { t.Run(u, func(t *testing.T) { + t.Parallel() assert := assert.New(t) parsed, _ := url.ParseRequestURI(u) assert.False(config.IsUrlAllowed(parsed), "The url %s should not be allowed", u) @@ -81,6 +85,7 @@ func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]str } func TestIsUrlAllowed_Compat(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) // Old-style configuration valid_urls := []string{ @@ -102,6 +107,7 @@ func TestIsUrlAllowed_Compat(t *testing.T) { } func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) // Old-style configuration, force HTTPS valid_urls := []string{ @@ -122,6 +128,7 @@ func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { } func TestIsUrlAllowed(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) valid_urls := [][]string{ {"https://domain.invalid/foo", string(testBackendSecret) + "-foo"}, @@ -166,6 +173,7 @@ func TestIsUrlAllowed(t *testing.T) { } func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) valid_urls := []string{} invalid_urls := []string{ @@ -182,6 +190,7 @@ func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { } func TestIsUrlAllowed_AllowAll(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) valid_urls := []string{ "http://domain.invalid", @@ -206,6 +215,7 @@ type ParseBackendIdsTestcase struct { } func TestParseBackendIds(t *testing.T) { + t.Parallel() testcases := []ParseBackendIdsTestcase{ {"", nil}, {"backend1", []string{"backend1"}}, @@ -223,7 +233,7 @@ func TestParseBackendIds(t *testing.T) { } } -func TestBackendReloadNoChange(t *testing.T) { +func TestBackendReloadNoChange(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -257,7 +267,7 @@ func TestBackendReloadNoChange(t *testing.T) { } } -func TestBackendReloadChangeExistingURL(t *testing.T) { +func TestBackendReloadChangeExistingURL(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -296,7 +306,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { } } -func TestBackendReloadChangeSecret(t *testing.T) { +func TestBackendReloadChangeSecret(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -331,7 +341,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { assert.Equal(t, n_cfg, o_cfg, "BackendConfiguration should be equal after Reload") } -func TestBackendReloadAddBackend(t *testing.T) { +func TestBackendReloadAddBackend(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -370,7 +380,7 @@ func TestBackendReloadAddBackend(t *testing.T) { } } -func TestBackendReloadRemoveHost(t *testing.T) { +func TestBackendReloadRemoveHost(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -406,7 +416,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { } } -func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { +func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -458,7 +468,7 @@ func mustParse(s string) *url.URL { return p } -func TestBackendConfiguration_EtcdCompat(t *testing.T) { +func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -619,7 +629,7 @@ func TestBackendCommonSecret(t *testing.T) { } } -func TestBackendChangeUrls(t *testing.T) { +func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) @@ -710,7 +720,7 @@ func TestBackendChangeUrls(t *testing.T) { assert.Nil(b1) } -func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { +func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsBackendsCurrent) logger := NewLoggerForTest(t) diff --git a/backend_server_test.go b/backend_server_test.go index 7a89a82..f6f3416 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -390,6 +390,7 @@ func TestBackendServer_UnsupportedRequest(t *testing.T) { } func TestBackendServer_RoomInvite(t *testing.T) { + t.Parallel() for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() @@ -457,6 +458,7 @@ func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { } func TestBackendServer_RoomDisinvite(t *testing.T) { + t.Parallel() for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() @@ -615,6 +617,7 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { } func TestBackendServer_RoomUpdate(t *testing.T) { + t.Parallel() for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() @@ -684,6 +687,7 @@ func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { } func TestBackendServer_RoomDelete(t *testing.T) { + t.Parallel() for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() @@ -750,6 +754,7 @@ func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) { } func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -1069,6 +1074,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { } func TestBackendServer_InCallAll(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -1315,6 +1321,7 @@ func TestBackendServer_TurnCredentials(t *testing.T) { } func TestBackendServer_StatsAllowedIps(t *testing.T) { + t.Parallel() config := goconf.NewConfigFile() config.AddOption("app", "trustedproxies", "1.2.3.4") config.AddOption("stats", "allowed_ips", "127.0.0.1, 192.168.0.1, 192.168.1.1/24") diff --git a/backend_storage_etcd_test.go b/backend_storage_etcd_test.go index 2c62520..5b4a21b 100644 --- a/backend_storage_etcd_test.go +++ b/backend_storage_etcd_test.go @@ -52,7 +52,7 @@ func (tl *testListener) EtcdClientCreated(client *EtcdClient) { close(tl.closed) } -func Test_BackendStorageEtcdNoLeak(t *testing.T) { +func Test_BackendStorageEtcdNoLeak(t *testing.T) { // nolint:paralleltest logger := NewLoggerForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { etcd, client := NewEtcdClientForTest(t) diff --git a/backoff_test.go b/backoff_test.go index 9882238..3ba3504 100644 --- a/backoff_test.go +++ b/backoff_test.go @@ -31,6 +31,7 @@ import ( ) func TestBackoff_Exponential(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) minWait := 100 * time.Millisecond diff --git a/channel_waiter_test.go b/channel_waiter_test.go index 9141642..cb6c961 100644 --- a/channel_waiter_test.go +++ b/channel_waiter_test.go @@ -28,6 +28,7 @@ import ( ) func TestChannelWaiters(t *testing.T) { + t.Parallel() var waiters ChannelWaiters ch1 := make(chan struct{}, 1) diff --git a/client/stats_test.go b/client/stats_test.go index 9ef132a..0bbafe6 100644 --- a/client/stats_test.go +++ b/client/stats_test.go @@ -30,6 +30,7 @@ import ( ) func TestStats(t *testing.T) { + t.Parallel() assert := assert.New(t) var stats Stats diff --git a/client_test.go b/client_test.go index 8afce5d..d5ee6fc 100644 --- a/client_test.go +++ b/client_test.go @@ -29,6 +29,7 @@ import ( ) func TestCounterWriter(t *testing.T) { + t.Parallel() assert := assert.New(t) var b bytes.Buffer diff --git a/clientsession_test.go b/clientsession_test.go index b0f0cb4..b5e1f82 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -84,24 +84,6 @@ func TestBandwidth_Client(t *testing.T) { func TestBandwidth_Backend(t *testing.T) { t.Parallel() - hub, _, _, server := CreateHubWithMultipleBackendsForTest(t) - - u, err := url.Parse(server.URL + "/one") - require.NoError(t, err) - backend := hub.backend.GetBackend(u) - require.NotNil(t, backend, "Could not get backend") - - backend.maxScreenBitrate = 1000 - backend.maxStreamBitrate = 2000 - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - mcu := NewTestMCU(t) - require.NoError(t, mcu.Start(ctx)) - defer mcu.Stop() - - hub.SetMcu(mcu) streamTypes := []StreamType{ StreamTypeVideo, @@ -110,8 +92,29 @@ func TestBandwidth_Backend(t *testing.T) { for _, streamType := range streamTypes { t.Run(string(streamType), func(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) + + hub, _, _, server := CreateHubWithMultipleBackendsForTest(t) + + u, err := url.Parse(server.URL + "/one") + require.NoError(err) + backend := hub.backend.GetBackend(u) + require.NotNil(t, backend, "Could not get backend") + + backend.maxScreenBitrate = 1000 + backend.maxStreamBitrate = 2000 + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mcu := NewTestMCU(t) + require.NoError(mcu.Start(ctx)) + defer mcu.Stop() + + hub.SetMcu(mcu) + client := NewTestClient(t, server, hub) defer client.CloseWithBye() @@ -243,13 +246,16 @@ func TestFeatureChatRelay(t *testing.T) { } } - t.Run("without-chat-relay", testFunc(false)) - t.Run("with-chat-relay", testFunc(true)) + t.Run("without-chat-relay", testFunc(false)) // nolint:paralleltest + t.Run("with-chat-relay", testFunc(true)) // nolint:paralleltest } func TestFeatureChatRelayFederation(t *testing.T) { + t.Parallel() + var testFunc = func(feature bool) func(t *testing.T) { return func(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) @@ -452,8 +458,8 @@ func TestFeatureChatRelayFederation(t *testing.T) { } } - t.Run("without-chat-relay", testFunc(false)) - t.Run("with-chat-relay", testFunc(true)) + t.Run("without-chat-relay", testFunc(false)) // nolint:paralleltest + t.Run("with-chat-relay", testFunc(true)) // nolint:paralleltest } func TestPermissionHideDisplayNames(t *testing.T) { @@ -566,6 +572,6 @@ func TestPermissionHideDisplayNames(t *testing.T) { } } - t.Run("without-hide-displaynames", testFunc(false)) - t.Run("with-hide-displaynames", testFunc(true)) + t.Run("without-hide-displaynames", testFunc(false)) // nolint:paralleltest + t.Run("with-hide-displaynames", testFunc(true)) // nolint:paralleltest } diff --git a/closer_test.go b/closer_test.go index f51f3f3..472c210 100644 --- a/closer_test.go +++ b/closer_test.go @@ -29,6 +29,7 @@ import ( ) func TestCloserMulti(t *testing.T) { + t.Parallel() closer := NewCloser() var wg sync.WaitGroup @@ -48,6 +49,7 @@ func TestCloserMulti(t *testing.T) { } func TestCloserCloseBeforeWait(t *testing.T) { + t.Parallel() closer := NewCloser() closer.Close() assert.True(t, closer.IsClosed()) diff --git a/concurrentmap_test.go b/concurrentmap_test.go index c52ae7e..ad20098 100644 --- a/concurrentmap_test.go +++ b/concurrentmap_test.go @@ -30,6 +30,7 @@ import ( ) func TestConcurrentStringStringMap(t *testing.T) { + t.Parallel() assert := assert.New(t) var m ConcurrentMap[string, string] assert.Equal(0, m.Len()) diff --git a/deferred_executor_test.go b/deferred_executor_test.go index 183b2ae..4ff6a60 100644 --- a/deferred_executor_test.go +++ b/deferred_executor_test.go @@ -29,6 +29,7 @@ import ( ) func TestDeferredExecutor_MultiClose(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() @@ -38,6 +39,7 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { } func TestDeferredExecutor_QueueSize(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { logger := NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) @@ -61,6 +63,7 @@ func TestDeferredExecutor_QueueSize(t *testing.T) { } func TestDeferredExecutor_Order(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -89,6 +92,7 @@ func TestDeferredExecutor_Order(t *testing.T) { } func TestDeferredExecutor_CloseFromFunc(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -103,6 +107,7 @@ func TestDeferredExecutor_CloseFromFunc(t *testing.T) { } func TestDeferredExecutor_DeferAfterClose(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -115,6 +120,7 @@ func TestDeferredExecutor_DeferAfterClose(t *testing.T) { } func TestDeferredExecutor_WaitForStopTwice(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() diff --git a/dns_monitor_test.go b/dns_monitor_test.go index 7d43dd7..77b2379 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -223,7 +223,7 @@ func (r *dnsMonitorReceiver) ExpectNone() { r.expected = expectNone } -func TestDnsMonitor(t *testing.T) { +func TestDnsMonitor(t *testing.T) { // nolint:paralleltest lookup := newMockDnsLookupForTest(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -292,6 +292,7 @@ func TestDnsMonitor(t *testing.T) { } func TestDnsMonitorIP(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -316,7 +317,7 @@ func TestDnsMonitorIP(t *testing.T) { time.Sleep(5 * interval) } -func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { +func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { // nolint:paralleltest interval := time.Millisecond monitor := newDnsMonitorForTest(t, interval) @@ -401,7 +402,7 @@ func (r *deadlockMonitorReceiver) Close() { r.wg.Wait() } -func TestDnsMonitorDeadlock(t *testing.T) { +func TestDnsMonitorDeadlock(t *testing.T) { // nolint:paralleltest lookup := newMockDnsLookupForTest(t) ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") diff --git a/federation_test.go b/federation_test.go index 92eabf0..0d6ab77 100644 --- a/federation_test.go +++ b/federation_test.go @@ -36,6 +36,7 @@ import ( ) func Test_FederationInvalidToken(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -73,6 +74,7 @@ func Test_FederationInvalidToken(t *testing.T) { } func Test_Federation(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -488,6 +490,7 @@ func Test_Federation(t *testing.T) { } func Test_FederationJoinRoomTwice(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -593,6 +596,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } func Test_FederationChangeRoom(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -700,6 +704,7 @@ func Test_FederationChangeRoom(t *testing.T) { } func Test_FederationMedia(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -803,6 +808,7 @@ func Test_FederationMedia(t *testing.T) { } func Test_FederationResume(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -922,6 +928,7 @@ func Test_FederationResume(t *testing.T) { } func Test_FederationResumeNewSession(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -1044,6 +1051,7 @@ func Test_FederationResumeNewSession(t *testing.T) { } func Test_FederationTransientData(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) diff --git a/file_watcher_test.go b/file_watcher_test.go index 5a29a73..f437193 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -36,6 +36,7 @@ var ( ) func TestFileWatcher_NotExist(t *testing.T) { + t.Parallel() assert := assert.New(t) tmpdir := t.TempDir() logger := NewLoggerForTest(t) @@ -46,7 +47,7 @@ func TestFileWatcher_NotExist(t *testing.T) { } } -func TestFileWatcher_File(t *testing.T) { +func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) assert := assert.New(t) @@ -88,7 +89,7 @@ func TestFileWatcher_File(t *testing.T) { }) } -func TestFileWatcher_CurrentDir(t *testing.T) { +func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) assert := assert.New(t) @@ -132,6 +133,7 @@ func TestFileWatcher_CurrentDir(t *testing.T) { } func TestFileWatcher_Rename(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() @@ -172,6 +174,7 @@ func TestFileWatcher_Rename(t *testing.T) { } func TestFileWatcher_Symlink(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() @@ -203,6 +206,7 @@ func TestFileWatcher_Symlink(t *testing.T) { } func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() @@ -239,6 +243,7 @@ func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { } func TestFileWatcher_OtherSymlink(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() @@ -272,6 +277,7 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { } func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() @@ -315,6 +321,7 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { } func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { + t.Parallel() // This mimics what k8s is doing with configmaps / secrets. require := require.New(t) assert := assert.New(t) diff --git a/flags_test.go b/flags_test.go index 4bc5d6d..de162de 100644 --- a/flags_test.go +++ b/flags_test.go @@ -30,6 +30,7 @@ import ( ) func TestFlags(t *testing.T) { + t.Parallel() assert := assert.New(t) var f Flags assert.EqualValues(0, f.Get()) diff --git a/geoip_test.go b/geoip_test.go index fecbc5a..3a03ed1 100644 --- a/geoip_test.go +++ b/geoip_test.go @@ -76,6 +76,7 @@ func GetGeoIpUrlForTest(t *testing.T) string { } func TestGeoLookup(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) require := require.New(t) reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) @@ -88,6 +89,7 @@ func TestGeoLookup(t *testing.T) { } func TestGeoLookupCaching(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) require := require.New(t) reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) @@ -102,6 +104,7 @@ func TestGeoLookupCaching(t *testing.T) { } func TestGeoLookupContinent(t *testing.T) { + t.Parallel() tests := map[string][]string{ "AU": {"OC"}, "DE": {"EU"}, @@ -112,6 +115,7 @@ func TestGeoLookupContinent(t *testing.T) { for country, expected := range tests { t.Run(country, func(t *testing.T) { + t.Parallel() continents := LookupContinents(country) if !assert.Equal(t, len(expected), len(continents), "Continents didn't match for %s: got %s, expected %s", country, continents, expected) { return @@ -126,6 +130,7 @@ func TestGeoLookupContinent(t *testing.T) { } func TestGeoLookupCloseEmpty(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) reader, err := NewGeoLookupFromUrl(logger, "ignore-url") require.NoError(t, err) @@ -133,6 +138,7 @@ func TestGeoLookupCloseEmpty(t *testing.T) { } func TestGeoLookupFromFile(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) require := require.New(t) geoIpUrl := GetGeoIpUrlForTest(t) @@ -196,6 +202,7 @@ func TestGeoLookupFromFile(t *testing.T) { } func TestIsValidContinent(t *testing.T) { + t.Parallel() for country, continents := range ContinentMap { for _, continent := range continents { assert.True(t, IsValidContinent(continent), "Continent %s of country %s is not valid", continent, country) diff --git a/grpc_client_test.go b/grpc_client_test.go index 031c1ba..825d982 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -110,7 +110,7 @@ func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { } } -func Test_GrpcClients_EtcdInitial(t *testing.T) { +func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) ensureNoGoroutinesLeak(t, func(t *testing.T) { @@ -219,7 +219,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { } } -func Test_GrpcClients_DnsDiscovery(t *testing.T) { +func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) ensureNoGoroutinesLeak(t, func(t *testing.T) { @@ -274,7 +274,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { }) } -func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { +func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { // nolint:paralleltest assert := assert.New(t) lookup := newMockDnsLookupForTest(t) target := "testgrpc:12345" @@ -303,7 +303,7 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { } } -func Test_GrpcClients_Encryption(t *testing.T) { +func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) diff --git a/grpc_server_test.go b/grpc_server_test.go index 87a036a..c476ac1 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -97,7 +97,7 @@ func NewGrpcServerForTest(t *testing.T) (server *GrpcServer, addr string) { return NewGrpcServerForTestWithConfig(t, config) } -func Test_GrpcServer_ReloadCerts(t *testing.T) { +func Test_GrpcServer_ReloadCerts(t *testing.T) { // nolint:paralleltest require := require.New(t) assert := assert.New(t) key, err := rsa.GenerateKey(rand.Reader, 1024) @@ -167,7 +167,7 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { } } -func Test_GrpcServer_ReloadCA(t *testing.T) { +func Test_GrpcServer_ReloadCA(t *testing.T) { // nolint:paralleltest logger := NewLoggerForTest(t) require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) diff --git a/hub_test.go b/hub_test.go index 657508e..be410d8 100644 --- a/hub_test.go +++ b/hub_test.go @@ -1000,8 +1000,10 @@ func TestClientHelloV1(t *testing.T) { } func TestClientHelloV2(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1036,8 +1038,10 @@ func TestClientHelloV2(t *testing.T) { } func TestClientHelloV2_IssuedInFuture(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1061,8 +1065,10 @@ func TestClientHelloV2_IssuedInFuture(t *testing.T) { } func TestClientHelloV2_IssuedFarInFuture(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1082,8 +1088,10 @@ func TestClientHelloV2_IssuedFarInFuture(t *testing.T) { } func TestClientHelloV2_Expired(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1103,8 +1111,10 @@ func TestClientHelloV2_Expired(t *testing.T) { } func TestClientHelloV2_IssuedAtMissing(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1124,8 +1134,10 @@ func TestClientHelloV2_IssuedAtMissing(t *testing.T) { } func TestClientHelloV2_ExpiresAtMissing(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1145,8 +1157,10 @@ func TestClientHelloV2_ExpiresAtMissing(t *testing.T) { } func TestClientHelloV2_CachedCapabilities(t *testing.T) { + t.Parallel() for _, algo := range testHelloV2Algorithms { t.Run(algo, func(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) @@ -1219,6 +1233,7 @@ func TestClientHelloAllowAll(t *testing.T) { } func TestClientHelloSessionLimit(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -1752,7 +1767,7 @@ func runGrpcProxyTest(t *testing.T, f func(hub1, hub2 *Hub, server1, server2 *ht f(hub1, hub2, server1, server2) } -func TestClientHelloResumeProxy(t *testing.T) { +func TestClientHelloResumeProxy(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) @@ -1802,7 +1817,7 @@ func TestClientHelloResumeProxy(t *testing.T) { }) } -func TestClientHelloResumeProxy_Takeover(t *testing.T) { +func TestClientHelloResumeProxy_Takeover(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) @@ -1856,7 +1871,7 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { }) } -func TestClientHelloResumeProxy_Disconnect(t *testing.T) { +func TestClientHelloResumeProxy_Disconnect(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) @@ -1951,6 +1966,7 @@ func TestClientHelloInternal(t *testing.T) { } func TestClientMessageToSessionId(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2015,6 +2031,7 @@ func TestClientMessageToSessionId(t *testing.T) { } func TestClientControlToSessionId(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2237,6 +2254,7 @@ func TestClientMessageToUserIdMultipleSessions(t *testing.T) { } func TestClientMessageToRoom(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2299,6 +2317,7 @@ func TestClientMessageToRoom(t *testing.T) { } func TestClientControlToRoom(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2361,6 +2380,7 @@ func TestClientControlToRoom(t *testing.T) { } func TestClientMessageToCall(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -2465,6 +2485,7 @@ func TestClientMessageToCall(t *testing.T) { } func TestClientControlToCall(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -3049,6 +3070,7 @@ func TestJoinRoomSwitchClient(t *testing.T) { } func TestGetRealUserIP(t *testing.T) { + t.Parallel() testcases := []struct { expected string headers http.Header @@ -3473,6 +3495,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { } func TestClientTakeoverRoomSession(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -3902,6 +3925,7 @@ loop: } func TestClientRequestOfferNotInRoom(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -4300,6 +4324,7 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { } func TestClientSendOffer(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -4442,6 +4467,7 @@ func TestClientUnshareScreen(t *testing.T) { } func TestVirtualClientSessions(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -4682,6 +4708,7 @@ func TestVirtualClientSessions(t *testing.T) { } func TestDuplicateVirtualSessions(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -5041,12 +5068,14 @@ func DoTestSwitchToOne(t *testing.T, details api.StringMap) { } func TestSwitchToOneMap(t *testing.T) { + t.Parallel() DoTestSwitchToOne(t, api.StringMap{ "foo": "bar", }) } func TestSwitchToOneList(t *testing.T) { + t.Parallel() DoTestSwitchToOne(t, nil) } @@ -5139,6 +5168,7 @@ func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.S } func TestSwitchToMultipleMap(t *testing.T) { + t.Parallel() DoTestSwitchToMultiple(t, api.StringMap{ "foo": "bar", }, api.StringMap{ @@ -5147,10 +5177,12 @@ func TestSwitchToMultipleMap(t *testing.T) { } func TestSwitchToMultipleList(t *testing.T) { + t.Parallel() DoTestSwitchToMultiple(t, nil, nil) } func TestSwitchToMultipleMixed(t *testing.T) { + t.Parallel() DoTestSwitchToMultiple(t, api.StringMap{ "foo": "bar", }, nil) diff --git a/internal/canonicalize_url_test.go b/internal/canonicalize_url_test.go index 0cb3a5e..9c9b1a1 100644 --- a/internal/canonicalize_url_test.go +++ b/internal/canonicalize_url_test.go @@ -30,6 +30,7 @@ import ( ) func TestCanonicalizeUrl(t *testing.T) { + t.Parallel() mustParse := func(s string) *url.URL { t.Helper() u, err := url.Parse(s) @@ -79,6 +80,7 @@ func TestCanonicalizeUrl(t *testing.T) { } func TestCanonicalizeUrlString(t *testing.T) { + t.Parallel() testcases := []struct { s string expected string diff --git a/internal/ips_test.go b/internal/ips_test.go index 94516ea..804e58f 100644 --- a/internal/ips_test.go +++ b/internal/ips_test.go @@ -28,6 +28,7 @@ import ( ) func TestIsLoopbackIP(t *testing.T) { + t.Parallel() loopback := []string{ "127.0.0.1", "127.1.0.1", @@ -51,6 +52,7 @@ func TestIsLoopbackIP(t *testing.T) { } func TestIsPrivateIP(t *testing.T) { + t.Parallel() private := []string{ "10.1.2.3", "172.20.21.22", diff --git a/lru_test.go b/lru_test.go index 572b5d5..52c672b 100644 --- a/lru_test.go +++ b/lru_test.go @@ -29,6 +29,7 @@ import ( ) func TestLruUnbound(t *testing.T) { + t.Parallel() assert := assert.New(t) lru := NewLruCache[int](0) count := 10 @@ -95,6 +96,7 @@ func TestLruUnbound(t *testing.T) { } func TestLruBound(t *testing.T) { + t.Parallel() assert := assert.New(t) size := 2 lru := NewLruCache[int](size) diff --git a/mcu_common_test.go b/mcu_common_test.go index 7632f92..0e4e839 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -28,6 +28,7 @@ import ( ) func TestCommonMcuStats(t *testing.T) { + t.Parallel() collectAndLint(t, commonMcuStats...) } diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index 55aed9c..1063023 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -498,6 +498,7 @@ func TestJanusEventsHandlerDifferentTypes(t *testing.T) { } func TestJanusEventsHandlerNotGrouped(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) diff --git a/mcu_janus_publisher_test.go b/mcu_janus_publisher_test.go index 31d3acf..3e409e6 100644 --- a/mcu_janus_publisher_test.go +++ b/mcu_janus_publisher_test.go @@ -34,6 +34,7 @@ import ( ) func TestGetFmtpValueH264(t *testing.T) { + t.Parallel() assert := assert.New(t) testcases := []struct { fmtp string @@ -70,6 +71,7 @@ func TestGetFmtpValueH264(t *testing.T) { } func TestGetFmtpValueVP9(t *testing.T) { + t.Parallel() assert := assert.New(t) testcases := []struct { fmtp string diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 99eab18..2e231e4 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -40,6 +40,7 @@ import ( ) func TestMcuJanusStats(t *testing.T) { + t.Parallel() collectAndLint(t, janusMcuStats...) } @@ -1080,7 +1081,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { } } -func Test_JanusPublisherSubscriber(t *testing.T) { +func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming")) ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing")) @@ -1395,7 +1396,7 @@ func Test_JanusRemotePublisher(t *testing.T) { assert.EqualValues(1, removed.Load()) } -func Test_JanusSubscriberNoSuchRoom(t *testing.T) { +func Test_JanusSubscriberNoSuchRoom(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) t.Cleanup(func() { if !t.Failed() { @@ -1494,7 +1495,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } -func test_JanusSubscriberAlreadyJoined(t *testing.T) { +func test_JanusSubscriberAlreadyJoined(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) t.Cleanup(func() { if !t.Failed() { @@ -1595,15 +1596,15 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } -func Test_JanusSubscriberAlreadyJoined(t *testing.T) { +func Test_JanusSubscriberAlreadyJoined(t *testing.T) { // nolint:paralleltest test_JanusSubscriberAlreadyJoined(t) } -func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { +func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { // nolint:paralleltest test_JanusSubscriberAlreadyJoined(t) } -func Test_JanusSubscriberTimeout(t *testing.T) { +func Test_JanusSubscriberTimeout(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) t.Cleanup(func() { if !t.Failed() { @@ -1706,7 +1707,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } -func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { +func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) t.Cleanup(func() { if !t.Failed() { @@ -1816,7 +1817,7 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { assert.Nil(handle, "subscriber should have been closed") } -func Test_JanusSubscriberRoomDestroyed(t *testing.T) { +func Test_JanusSubscriberRoomDestroyed(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) t.Cleanup(func() { if !t.Failed() { @@ -1926,7 +1927,7 @@ func Test_JanusSubscriberRoomDestroyed(t *testing.T) { assert.Nil(handle, "subscriber should have been closed") } -func Test_JanusSubscriberUpdateOffer(t *testing.T) { +func Test_JanusSubscriberUpdateOffer(t *testing.T) { // nolint:paralleltest ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) t.Cleanup(func() { if !t.Failed() { diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 258f92a..48a34c5 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -54,6 +54,7 @@ const ( ) func TestMcuProxyStats(t *testing.T) { + t.Parallel() collectAndLint(t, proxyMcuStats...) } @@ -64,6 +65,7 @@ func newProxyConnectionWithCountry(country string) *mcuProxyConnection { } func Test_sortConnectionsForCountry(t *testing.T) { + t.Parallel() conn_de := newProxyConnectionWithCountry("DE") conn_at := newProxyConnectionWithCountry("AT") conn_jp := newProxyConnectionWithCountry("JP") @@ -109,6 +111,7 @@ func Test_sortConnectionsForCountry(t *testing.T) { for country, test := range testcases { t.Run(country, func(t *testing.T) { + t.Parallel() sorted := sortConnectionsForCountry(test[0], country, nil) for idx, conn := range sorted { assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) @@ -118,6 +121,7 @@ func Test_sortConnectionsForCountry(t *testing.T) { } func Test_sortConnectionsForCountryWithOverride(t *testing.T) { + t.Parallel() conn_de := newProxyConnectionWithCountry("DE") conn_at := newProxyConnectionWithCountry("AT") conn_jp := newProxyConnectionWithCountry("JP") @@ -179,6 +183,7 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { } for country, test := range testcases { t.Run(country, func(t *testing.T) { + t.Parallel() sorted := sortConnectionsForCountry(test[0], country, continentMap) for idx, conn := range sorted { assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) @@ -1017,7 +1022,7 @@ func Test_ProxyAddRemoveConnections(t *testing.T) { assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") } -func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { +func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { // nolint:paralleltest assert := assert.New(t) require := require.New(t) @@ -2438,6 +2443,7 @@ func Test_ProxySubscriberTimeout(t *testing.T) { } func Test_ProxyReconnectAfter(t *testing.T) { + t.Parallel() reasons := []string{ "session_resumed", "session_expired", diff --git a/natsclient_loopback_test.go b/natsclient_loopback_test.go index 9a299ca..b403109 100644 --- a/natsclient_loopback_test.go +++ b/natsclient_loopback_test.go @@ -63,7 +63,7 @@ func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { return result } -func TestLoopbackNatsClient_Subscribe(t *testing.T) { +func TestLoopbackNatsClient_Subscribe(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { client := CreateLoopbackNatsClientForTest(t) @@ -71,7 +71,7 @@ func TestLoopbackNatsClient_Subscribe(t *testing.T) { }) } -func TestLoopbackClient_PublishAfterClose(t *testing.T) { +func TestLoopbackClient_PublishAfterClose(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { client := CreateLoopbackNatsClientForTest(t) @@ -79,7 +79,7 @@ func TestLoopbackClient_PublishAfterClose(t *testing.T) { }) } -func TestLoopbackClient_SubscribeAfterClose(t *testing.T) { +func TestLoopbackClient_SubscribeAfterClose(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { client := CreateLoopbackNatsClientForTest(t) @@ -87,7 +87,7 @@ func TestLoopbackClient_SubscribeAfterClose(t *testing.T) { }) } -func TestLoopbackClient_BadSubjects(t *testing.T) { +func TestLoopbackClient_BadSubjects(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { client := CreateLoopbackNatsClientForTest(t) diff --git a/natsclient_test.go b/natsclient_test.go index 766e123..a852b9f 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -110,7 +110,7 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { require.EqualValues(maxPublish, received.Load(), "Received wrong # of messages") } -func TestNatsClient_Subscribe(t *testing.T) { +func TestNatsClient_Subscribe(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -124,7 +124,7 @@ func testNatsClient_PublishAfterClose(t *testing.T, client NatsClient) { assert.ErrorIs(t, client.Publish("foo", "bar"), nats.ErrConnectionClosed) } -func TestNatsClient_PublishAfterClose(t *testing.T) { +func TestNatsClient_PublishAfterClose(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -140,7 +140,7 @@ func testNatsClient_SubscribeAfterClose(t *testing.T, client NatsClient) { assert.ErrorIs(t, err, nats.ErrConnectionClosed) } -func TestNatsClient_SubscribeAfterClose(t *testing.T) { +func TestNatsClient_SubscribeAfterClose(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -162,7 +162,7 @@ func testNatsClient_BadSubjects(t *testing.T, client NatsClient) { } } -func TestNatsClient_BadSubjects(t *testing.T) { +func TestNatsClient_BadSubjects(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) @@ -170,7 +170,7 @@ func TestNatsClient_BadSubjects(t *testing.T) { }) } -func TestNatsClient_MaxReconnects(t *testing.T) { +func TestNatsClient_MaxReconnects(t *testing.T) { // nolint:paralleltest ensureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) diff --git a/notifier_test.go b/notifier_test.go index ea61dcd..538a4f5 100644 --- a/notifier_test.go +++ b/notifier_test.go @@ -32,6 +32,7 @@ import ( ) func TestNotifierNoWaiter(t *testing.T) { + t.Parallel() var notifier Notifier // Notifications can be sent even if no waiter exists. @@ -39,6 +40,7 @@ func TestNotifierNoWaiter(t *testing.T) { } func TestNotifierSimple(t *testing.T) { + t.Parallel() var notifier Notifier var wg sync.WaitGroup @@ -59,6 +61,7 @@ func TestNotifierSimple(t *testing.T) { } func TestNotifierMultiNotify(t *testing.T) { + t.Parallel() var notifier Notifier waiter := notifier.NewWaiter("foo") @@ -70,6 +73,7 @@ func TestNotifierMultiNotify(t *testing.T) { } func TestNotifierWaitClosed(t *testing.T) { + t.Parallel() var notifier Notifier waiter := notifier.NewWaiter("foo") @@ -79,6 +83,7 @@ func TestNotifierWaitClosed(t *testing.T) { } func TestNotifierWaitClosedMulti(t *testing.T) { + t.Parallel() var notifier Notifier waiter1 := notifier.NewWaiter("foo") @@ -91,6 +96,7 @@ func TestNotifierWaitClosedMulti(t *testing.T) { } func TestNotifierResetWillNotify(t *testing.T) { + t.Parallel() var notifier Notifier var wg sync.WaitGroup @@ -111,6 +117,7 @@ func TestNotifierResetWillNotify(t *testing.T) { } func TestNotifierDuplicate(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { var notifier Notifier var done sync.WaitGroup diff --git a/proxy/proxy_remote_test.go b/proxy/proxy_remote_test.go index 75515f1..0c98002 100644 --- a/proxy/proxy_remote_test.go +++ b/proxy/proxy_remote_test.go @@ -74,6 +74,7 @@ func (c *RemoteConnection) WaitForDisconnect(ctx context.Context) error { } func Test_ProxyRemoteConnectionReconnect(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -101,6 +102,7 @@ func Test_ProxyRemoteConnectionReconnect(t *testing.T) { } func Test_ProxyRemoteConnectionReconnectUnknownSession(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -143,6 +145,7 @@ func Test_ProxyRemoteConnectionReconnectUnknownSession(t *testing.T) { } func Test_ProxyRemoteConnectionReconnectExpiredSession(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -178,6 +181,7 @@ func Test_ProxyRemoteConnectionReconnectExpiredSession(t *testing.T) { } func Test_ProxyRemoteConnectionCreatePublisher(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 76b9b3d..aef00ab 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -152,6 +152,7 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httpte } func TestTokenValid(t *testing.T) { + t.Parallel() proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -174,6 +175,7 @@ func TestTokenValid(t *testing.T) { } func TestTokenNotSigned(t *testing.T) { + t.Parallel() proxy, _, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -198,6 +200,7 @@ func TestTokenNotSigned(t *testing.T) { } func TestTokenUnknown(t *testing.T) { + t.Parallel() proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -222,6 +225,7 @@ func TestTokenUnknown(t *testing.T) { } func TestTokenInFuture(t *testing.T) { + t.Parallel() proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -246,6 +250,7 @@ func TestTokenInFuture(t *testing.T) { } func TestTokenExpired(t *testing.T) { + t.Parallel() proxy, key, _ := newProxyServerForTest(t) claims := &signaling.TokenClaims{ @@ -270,6 +275,7 @@ func TestTokenExpired(t *testing.T) { } func TestPublicIPs(t *testing.T) { + t.Parallel() assert := assert.New(t) public := []string{ "8.8.8.8", @@ -302,6 +308,7 @@ func TestPublicIPs(t *testing.T) { } func TestWebsocketFeatures(t *testing.T) { + t.Parallel() assert := assert.New(t) _, _, server := newProxyServerForTest(t) @@ -329,6 +336,7 @@ func TestWebsocketFeatures(t *testing.T) { } func TestProxyCreateSession(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) _, key, server := newProxyServerForTest(t) @@ -480,6 +488,7 @@ func NewPublisherTestMCU(t *testing.T) *PublisherTestMCU { } func TestProxyPublisherBandwidth(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -599,6 +608,7 @@ func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.M } func TestProxyCancelOnClose(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -677,6 +687,7 @@ func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.Mcu } func TestProxyCodecs(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -759,6 +770,7 @@ func NewStreamTestMCU(t *testing.T, streams []signaling.PublisherStream) *Stream } func TestProxyStreams(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -983,6 +995,7 @@ func (m *RemoteSubscriberTestMCU) NewRemoteSubscriber(ctx context.Context, liste } func TestProxyRemoteSubscriber(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1077,6 +1090,7 @@ func TestProxyRemoteSubscriber(t *testing.T) { } func TestProxyCloseRemoteOnSessionClose(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1239,6 +1253,7 @@ func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remo } func TestProxyUnpublishRemote(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1355,6 +1370,7 @@ func TestProxyUnpublishRemote(t *testing.T) { } func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) @@ -1486,6 +1502,7 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { } func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) proxy, key, server := newProxyServerForTest(t) diff --git a/proxy/proxy_tokens_etcd_test.go b/proxy/proxy_tokens_etcd_test.go index 4161d12..4d0b845 100644 --- a/proxy/proxy_tokens_etcd_test.go +++ b/proxy/proxy_tokens_etcd_test.go @@ -159,6 +159,7 @@ func generateAndSaveKey(t *testing.T, etcd *embed.Etcd, name string) *rsa.Privat } func TestProxyTokensEtcd(t *testing.T) { + t.Parallel() assert := assert.New(t) tokens, etcd := newTokensEtcdForTesting(t) diff --git a/proxy_config_static_test.go b/proxy_config_static_test.go index 5354d48..7c4de25 100644 --- a/proxy_config_static_test.go +++ b/proxy_config_static_test.go @@ -57,6 +57,7 @@ func updateProxyConfigStatic(t *testing.T, config ProxyConfig, dns bool, urls .. } func TestProxyConfigStaticSimple(t *testing.T) { + t.Parallel() proxy := newMcuProxyForConfig(t) config, _ := newProxyConfigStatic(t, proxy, false, "https://foo/") proxy.Expect("add", "https://foo/") @@ -72,7 +73,7 @@ func TestProxyConfigStaticSimple(t *testing.T) { updateProxyConfigStatic(t, config, false, "https://bar/", "https://baz/") } -func TestProxyConfigStaticDNS(t *testing.T) { +func TestProxyConfigStaticDNS(t *testing.T) { // nolint:paralleltest lookup := newMockDnsLookupForTest(t) proxy := newMcuProxyForConfig(t) config, dnsMonitor := newProxyConfigStatic(t, proxy, true, "https://foo/") diff --git a/publisher_stats_counter_test.go b/publisher_stats_counter_test.go index 975089b..be7a1ad 100644 --- a/publisher_stats_counter_test.go +++ b/publisher_stats_counter_test.go @@ -25,7 +25,7 @@ import ( "testing" ) -func TestPublisherStatsCounter(t *testing.T) { +func TestPublisherStatsCounter(t *testing.T) { // nolint:paralleltest RegisterJanusMcuStats() var c publisherStatsCounter diff --git a/room_ping_test.go b/room_ping_test.go index a89f536..b452da8 100644 --- a/room_ping_test.go +++ b/room_ping_test.go @@ -58,6 +58,7 @@ func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) } func TestSingleRoomPing(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) @@ -101,6 +102,7 @@ func TestSingleRoomPing(t *testing.T) { } func TestMultiRoomPing(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) @@ -140,6 +142,7 @@ func TestMultiRoomPing(t *testing.T) { } func TestMultiRoomPing_Separate(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) @@ -175,6 +178,7 @@ func TestMultiRoomPing_Separate(t *testing.T) { } func TestMultiRoomPing_DeleteRoom(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) diff --git a/room_test.go b/room_test.go index 8901d0b..5622cd6 100644 --- a/room_test.go +++ b/room_test.go @@ -37,6 +37,7 @@ import ( ) func TestRoom_InCall(t *testing.T) { + t.Parallel() type Testcase struct { Value any InCall bool diff --git a/roomsessions_builtin_test.go b/roomsessions_builtin_test.go index c69e346..02a6162 100644 --- a/roomsessions_builtin_test.go +++ b/roomsessions_builtin_test.go @@ -28,6 +28,7 @@ import ( ) func TestBuiltinRoomSessions(t *testing.T) { + t.Parallel() sessions, err := NewBuiltinRoomSessions(nil) require.NoError(t, err) diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 1176d3c..47317c8 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -30,6 +30,7 @@ import ( ) func TestReverseSessionId(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) @@ -127,6 +128,7 @@ func Benchmark_DecodePublicSessionId(b *testing.B) { } func TestPublicPrivate(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) sd := &SessionIdData{ diff --git a/single_notifier_test.go b/single_notifier_test.go index 55872cf..2041b25 100644 --- a/single_notifier_test.go +++ b/single_notifier_test.go @@ -32,6 +32,7 @@ import ( ) func TestSingleNotifierNoWaiter(t *testing.T) { + t.Parallel() var notifier SingleNotifier // Notifications can be sent even if no waiter exists. @@ -39,6 +40,7 @@ func TestSingleNotifierNoWaiter(t *testing.T) { } func TestSingleNotifierSimple(t *testing.T) { + t.Parallel() var notifier SingleNotifier var wg sync.WaitGroup @@ -59,6 +61,7 @@ func TestSingleNotifierSimple(t *testing.T) { } func TestSingleNotifierMultiNotify(t *testing.T) { + t.Parallel() var notifier SingleNotifier waiter := notifier.NewWaiter() @@ -70,6 +73,7 @@ func TestSingleNotifierMultiNotify(t *testing.T) { } func TestSingleNotifierWaitClosed(t *testing.T) { + t.Parallel() var notifier SingleNotifier waiter := notifier.NewWaiter() @@ -79,6 +83,7 @@ func TestSingleNotifierWaitClosed(t *testing.T) { } func TestSingleNotifierWaitClosedMulti(t *testing.T) { + t.Parallel() var notifier SingleNotifier waiter1 := notifier.NewWaiter() @@ -91,6 +96,7 @@ func TestSingleNotifierWaitClosedMulti(t *testing.T) { } func TestSingleNotifierResetWillNotify(t *testing.T) { + t.Parallel() var notifier SingleNotifier var wg sync.WaitGroup @@ -111,6 +117,7 @@ func TestSingleNotifierResetWillNotify(t *testing.T) { } func TestSingleNotifierDuplicate(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { var notifier SingleNotifier var done sync.WaitGroup diff --git a/throttle_test.go b/throttle_test.go index 07f3520..935aecf 100644 --- a/throttle_test.go +++ b/throttle_test.go @@ -67,6 +67,7 @@ func expectDelay(t *testing.T, f func(), delay time.Duration) { } func TestThrottler(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -101,6 +102,7 @@ func TestThrottler(t *testing.T) { } func TestThrottlerIPv6(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -138,6 +140,7 @@ func TestThrottlerIPv6(t *testing.T) { } func TestThrottler_Bruteforce(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -164,6 +167,7 @@ func TestThrottler_Bruteforce(t *testing.T) { } func TestThrottler_Cleanup(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) throttler := newMemoryThrottlerForTest(t) @@ -220,6 +224,7 @@ func TestThrottler_Cleanup(t *testing.T) { } func TestThrottler_ExpirePartial(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -252,6 +257,7 @@ func TestThrottler_ExpirePartial(t *testing.T) { } func TestThrottler_ExpireAll(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -284,6 +290,7 @@ func TestThrottler_ExpireAll(t *testing.T) { } func TestThrottler_Negative(t *testing.T) { + t.Parallel() SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) diff --git a/transient_data_test.go b/transient_data_test.go index 742d178..e2bee37 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -42,6 +42,7 @@ func (t *TransientData) SetTTLChannel(ch chan<- struct{}) { } func Test_TransientData(t *testing.T) { + t.Parallel() assert := assert.New(t) data := NewTransientData() assert.False(data.Set("foo", nil)) @@ -119,6 +120,7 @@ func (l *MockTransientListener) Close() { } func Test_TransientDataDeadlock(t *testing.T) { + t.Parallel() data := NewTransientData() listener := &MockTransientListener{ @@ -139,6 +141,7 @@ func Test_TransientDataDeadlock(t *testing.T) { } func Test_TransientMessages(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() @@ -313,6 +316,7 @@ func Test_TransientMessages(t *testing.T) { } func Test_TransientSessionData(t *testing.T) { + t.Parallel() for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() From f1781719e1d9d946185a7c8bfffe1f7b428c29d9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 12:23:28 +0100 Subject: [PATCH 349/549] lint: Enable "paralleltest" linter. --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index ef3f07d..6fe1782 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,6 +6,7 @@ linters: - gocritic - misspell - modernize + - paralleltest - revive settings: errchkjson: From e0da0529ecb5be7603e0802fda7654ff38d51f5e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 21:59:15 +0100 Subject: [PATCH 350/549] Don't call "t.Setenv" in synctest backport. --- synctest24_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/synctest24_test.go b/synctest24_test.go index dd7c194..a1bd191 100644 --- a/synctest24_test.go +++ b/synctest24_test.go @@ -33,9 +33,8 @@ func SynctestTest(t *testing.T, f func(t *testing.T)) { synctest.Run(func() { t.Run("synctest", func(t *testing.T) { - // synctest doesn't support t.Parallel - t.Setenv("PARALLEL_CHECK", "1") - + // synctest of Go 1.25 doesn't support "t.Parallel()" but we can't prevent + // this here. Invalid calls will be detected when running with Go 1.25. f(t) }) }) From 643c430e36a9c6ee0dae56ad3492eac9cfad5909 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Dec 2025 22:05:26 +0100 Subject: [PATCH 351/549] Use "testStorage" to store ping requests for parallel access. --- hub_test.go | 24 ++++++++---------------- testutils_test.go | 5 +++++ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/hub_test.go b/hub_test.go index be410d8..ef1bb6b 100644 --- a/hub_test.go +++ b/hub_test.go @@ -537,30 +537,22 @@ func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, return response } -var pingRequests map[*testing.T][]*BackendClientRequest +var ( + pingRequests testStorage[[]*BackendClientRequest] +) func getPingRequests(t *testing.T) []*BackendClientRequest { - return pingRequests[t] + entries, _ := pingRequests.Get(t) + return entries } func clearPingRequests(t *testing.T) { - delete(pingRequests, t) + pingRequests.Del(t) } func storePingRequest(t *testing.T, request *BackendClientRequest) { - if entries, found := pingRequests[t]; !found { - if pingRequests == nil { - pingRequests = make(map[*testing.T][]*BackendClientRequest) - } - pingRequests[t] = []*BackendClientRequest{ - request, - } - t.Cleanup(func() { - clearPingRequests(t) - }) - } else { - pingRequests[t] = append(entries, request) - } + entries, _ := pingRequests.Get(t) + pingRequests.Set(t, append(entries, request)) } func processPingRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { diff --git a/testutils_test.go b/testutils_test.go index e18146d..0addf6d 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -196,3 +196,8 @@ func (s *testStorage[T]) Get(t *testing.T) (T, bool) { var defaultValue T return defaultValue, false } + +func (s *testStorage[T]) Del(t *testing.T) { + key := t.Name() + s.cleanup(key) +} From f338a7b91e5016152adff148591d8065724a9323 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 09:16:30 +0100 Subject: [PATCH 352/549] Don't use string formatting without parameters, optimize simple cases. --- api_backend.go | 5 ++-- api_grpc.go | 4 +-- api_proxy.go | 31 +++++++++++------------ api_signaling.go | 50 +++++++++++++++++++------------------- api_signaling_test.go | 4 +-- backend_server.go | 4 +-- backend_storage_etcd.go | 5 ++-- backoff.go | 6 ++--- clientsession.go | 3 ++- dns_monitor_test.go | 2 +- geoip.go | 3 ++- grpc_client.go | 6 ++--- http_client_pool.go | 5 ++-- hub.go | 2 +- janus_client.go | 3 ++- lru_test.go | 22 ++++++++--------- mcu_common.go | 4 +-- mcu_janus.go | 2 +- mcu_proxy.go | 6 ++--- mcu_proxy_test.go | 9 ++++--- mcu_test.go | 8 +++--- natsclient.go | 4 +-- proxy/proxy_server.go | 10 ++++---- proxy/proxy_tokens_etcd.go | 3 ++- remotesession.go | 3 +-- roomsessions.go | 4 +-- test_helpers.go | 3 +-- testclient_test.go | 4 +-- 28 files changed, 109 insertions(+), 106 deletions(-) diff --git a/api_backend.go b/api_backend.go index 5929f7c..28b6462 100644 --- a/api_backend.go +++ b/api_backend.go @@ -28,6 +28,7 @@ import ( "crypto/subtle" "encoding/hex" "encoding/json" + "errors" "fmt" "net/http" "net/url" @@ -461,7 +462,7 @@ type BackendInformationEtcd struct { func (p *BackendInformationEtcd) CheckValid() (err error) { if p.Secret == "" { - return fmt.Errorf("secret missing") + return errors.New("secret missing") } if len(p.Urls) > 0 { @@ -504,7 +505,7 @@ func (p *BackendInformationEtcd) CheckValid() (err error) { p.Urls = append(p.Urls, p.Url) p.parsedUrls = append(p.parsedUrls, parsedUrl) } else { - return fmt.Errorf("urls missing") + return errors.New("urls missing") } return nil diff --git a/api_grpc.go b/api_grpc.go index 127e880..b67fe87 100644 --- a/api_grpc.go +++ b/api_grpc.go @@ -22,7 +22,7 @@ package signaling import ( - "fmt" + "errors" ) // Information on a GRPC target in the etcd cluster. @@ -33,7 +33,7 @@ type GrpcTargetInformationEtcd struct { func (p *GrpcTargetInformationEtcd) CheckValid() error { if l := len(p.Address); l == 0 { - return fmt.Errorf("address missing") + return errors.New("address missing") } else if p.Address[l-1] == '/' { p.Address = p.Address[:l-1] } diff --git a/api_proxy.go b/api_proxy.go index 7761f6d..68efa0e 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -23,6 +23,7 @@ package signaling import ( "encoding/json" + "errors" "fmt" "net/url" @@ -62,10 +63,10 @@ func (m *ProxyClientMessage) String() string { func (m *ProxyClientMessage) CheckValid() error { switch m.Type { case "": - return fmt.Errorf("type missing") + return errors.New("type missing") case "hello": if m.Hello == nil { - return fmt.Errorf("hello missing") + return errors.New("hello missing") } else if err := m.Hello.CheckValid(); err != nil { return err } @@ -78,13 +79,13 @@ func (m *ProxyClientMessage) CheckValid() error { } case "command": if m.Command == nil { - return fmt.Errorf("command missing") + return errors.New("command missing") } else if err := m.Command.CheckValid(); err != nil { return err } case "payload": if m.Payload == nil { - return fmt.Errorf("payload missing") + return errors.New("payload missing") } else if err := m.Payload.CheckValid(); err != nil { return err } @@ -166,7 +167,7 @@ func (m *HelloProxyClientMessage) CheckValid() error { } if m.ResumeId == "" { if m.Token == "" { - return fmt.Errorf("token missing") + return errors.New("token missing") } } return nil @@ -232,21 +233,21 @@ type CommandProxyClientMessage struct { func (m *CommandProxyClientMessage) CheckValid() error { switch m.Type { case "": - return fmt.Errorf("type missing") + return errors.New("type missing") case "create-publisher": if m.StreamType == "" { - return fmt.Errorf("stream type missing") + return errors.New("stream type missing") } case "create-subscriber": if m.PublisherId == "" { - return fmt.Errorf("publisher id missing") + return errors.New("publisher id missing") } if m.StreamType == "" { - return fmt.Errorf("stream type missing") + return errors.New("stream type missing") } if m.RemoteUrl != "" { if m.RemoteToken == "" { - return fmt.Errorf("remote token missing") + return errors.New("remote token missing") } remoteUrl, err := url.Parse(m.RemoteUrl) @@ -259,7 +260,7 @@ func (m *CommandProxyClientMessage) CheckValid() error { fallthrough case "delete-subscriber": if m.ClientId == "" { - return fmt.Errorf("client id missing") + return errors.New("client id missing") } } return nil @@ -287,14 +288,14 @@ type PayloadProxyClientMessage struct { func (m *PayloadProxyClientMessage) CheckValid() error { switch m.Type { case "": - return fmt.Errorf("type missing") + return errors.New("type missing") case "offer": fallthrough case "answer": fallthrough case "candidate": if len(m.Payload) == 0 { - return fmt.Errorf("payload missing") + return errors.New("payload missing") } case "endOfCandidates": fallthrough @@ -302,7 +303,7 @@ func (m *PayloadProxyClientMessage) CheckValid() error { // No payload required. } if m.ClientId == "" { - return fmt.Errorf("client id missing") + return errors.New("client id missing") } return nil } @@ -367,7 +368,7 @@ type ProxyInformationEtcd struct { func (p *ProxyInformationEtcd) CheckValid() error { if p.Address == "" { - return fmt.Errorf("address missing") + return errors.New("address missing") } if p.Address[len(p.Address)-1] != '/' { p.Address += "/" diff --git a/api_signaling.go b/api_signaling.go index 18c7df5..6efc3ed 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -105,10 +105,10 @@ type ClientMessage struct { func (m *ClientMessage) CheckValid() error { switch m.Type { case "": - return fmt.Errorf("type missing") + return errors.New("type missing") case "hello": if m.Hello == nil { - return fmt.Errorf("hello missing") + return errors.New("hello missing") } else if err := m.Hello.CheckValid(); err != nil { return err } @@ -116,31 +116,31 @@ func (m *ClientMessage) CheckValid() error { // No additional check required. case "room": if m.Room == nil { - return fmt.Errorf("room missing") + return errors.New("room missing") } else if err := m.Room.CheckValid(); err != nil { return err } case "message": if m.Message == nil { - return fmt.Errorf("message missing") + return errors.New("message missing") } else if err := m.Message.CheckValid(); err != nil { return err } case "control": if m.Control == nil { - return fmt.Errorf("control missing") + return errors.New("control missing") } else if err := m.Control.CheckValid(); err != nil { return err } case "internal": if m.Internal == nil { - return fmt.Errorf("internal missing") + return errors.New("internal missing") } else if err := m.Internal.CheckValid(); err != nil { return err } case "transient": if m.TransientData == nil { - return fmt.Errorf("transient missing") + return errors.New("transient missing") } else if err := m.TransientData.CheckValid(); err != nil { return err } @@ -362,7 +362,7 @@ type ClientTypeInternalAuthParams struct { func (p *ClientTypeInternalAuthParams) CheckValid() error { if p.Backend == "" { - return fmt.Errorf("backend missing") + return errors.New("backend missing") } if p.Backend[len(p.Backend)-1] != '/' { @@ -387,7 +387,7 @@ type HelloV2AuthParams struct { func (p *HelloV2AuthParams) CheckValid() error { if p.Token == "" { - return fmt.Errorf("token missing") + return errors.New("token missing") } return nil } @@ -414,7 +414,7 @@ type FederationAuthParams struct { func (p *FederationAuthParams) CheckValid() error { if p.Token == "" { - return fmt.Errorf("token missing") + return errors.New("token missing") } return nil } @@ -463,7 +463,7 @@ func (m *HelloClientMessage) CheckValid() error { } if m.ResumeId == "" { if m.Auth == nil || len(m.Auth.Params) == 0 { - return fmt.Errorf("params missing") + return errors.New("params missing") } if m.Auth.Type == "" { m.Auth.Type = HelloClientTypeClient @@ -473,7 +473,7 @@ func (m *HelloClientMessage) CheckValid() error { fallthrough case HelloClientTypeFederation: if m.Auth.Url == "" { - return fmt.Errorf("url missing") + return errors.New("url missing") } if m.Auth.Url[len(m.Auth.Url)-1] != '/' { @@ -520,7 +520,7 @@ func (m *HelloClientMessage) CheckValid() error { return err } default: - return fmt.Errorf("unsupported auth type") + return errors.New("unsupported auth type") } } return nil @@ -891,7 +891,7 @@ func FilterSDPCandidates(s *sdp.SessionDescription, allowed *AllowedIps, blocked func (m *MessageClientMessage) CheckValid() error { if len(m.Data) == 0 { - return fmt.Errorf("message empty") + return errors.New("message empty") } switch m.Recipient.Type { case RecipientTypeRoom: @@ -900,11 +900,11 @@ func (m *MessageClientMessage) CheckValid() error { // No additional checks required. case RecipientTypeSession: if m.Recipient.SessionId == "" { - return fmt.Errorf("session id missing") + return errors.New("session id missing") } case RecipientTypeUser: if m.Recipient.UserId == "" { - return fmt.Errorf("user id missing") + return errors.New("user id missing") } default: return fmt.Errorf("unsupported recipient type %v", m.Recipient.Type) @@ -957,10 +957,10 @@ type CommonSessionInternalClientMessage struct { func (m *CommonSessionInternalClientMessage) CheckValid() error { if m.SessionId == "" { - return fmt.Errorf("sessionid missing") + return errors.New("sessionid missing") } if m.RoomId == "" { - return fmt.Errorf("roomid missing") + return errors.New("roomid missing") } return nil } @@ -1080,31 +1080,31 @@ func (m *InternalClientMessage) CheckValid() error { return errors.New("type missing") case "addsession": if m.AddSession == nil { - return fmt.Errorf("addsession missing") + return errors.New("addsession missing") } else if err := m.AddSession.CheckValid(); err != nil { return err } case "updatesession": if m.UpdateSession == nil { - return fmt.Errorf("updatesession missing") + return errors.New("updatesession missing") } else if err := m.UpdateSession.CheckValid(); err != nil { return err } case "removesession": if m.RemoveSession == nil { - return fmt.Errorf("removesession missing") + return errors.New("removesession missing") } else if err := m.RemoveSession.CheckValid(); err != nil { return err } case "incall": if m.InCall == nil { - return fmt.Errorf("incall missing") + return errors.New("incall missing") } else if err := m.InCall.CheckValid(); err != nil { return err } case "dialout": if m.Dialout == nil { - return fmt.Errorf("dialout missing") + return errors.New("dialout missing") } else if err := m.Dialout.CheckValid(); err != nil { return err } @@ -1277,12 +1277,12 @@ func (m *TransientDataClientMessage) CheckValid() error { switch m.Type { case "set": if m.Key == "" { - return fmt.Errorf("key missing") + return errors.New("key missing") } // A "nil" value is allowed and will remove the key. case "remove": if m.Key == "" { - return fmt.Errorf("key missing") + return errors.New("key missing") } } return nil diff --git a/api_signaling_test.go b/api_signaling_test.go index d711860..eb0b300 100644 --- a/api_signaling_test.go +++ b/api_signaling_test.go @@ -23,7 +23,7 @@ package signaling import ( "encoding/json" - "fmt" + "errors" "slices" "strings" "testing" @@ -360,7 +360,7 @@ func TestErrorMessages(t *testing.T) { assert.Equal("error", err1.Type, "%+v", err1) assert.NotNil(err1.Error, "%+v", err1) - err2 := msg.NewWrappedErrorServerMessage(fmt.Errorf("test-error")) + err2 := msg.NewWrappedErrorServerMessage(errors.New("test-error")) assert.Equal(id, err2.Id, "%+v", err2) assert.Equal("error", err2.Type, "%+v", err2) if assert.NotNil(err2.Error, "%+v", err2) { diff --git a/backend_server.go b/backend_server.go index 27e75be..77c8253 100644 --- a/backend_server.go +++ b/backend_server.go @@ -93,10 +93,10 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, turnserverslist := slices.Collect(SplitEntries(turnservers, ",")) if len(turnserverslist) != 0 { if turnapikey == "" { - return nil, fmt.Errorf("need a TURN API key if TURN servers are configured") + return nil, errors.New("need a TURN API key if TURN servers are configured") } if turnsecret == "" { - return nil, fmt.Errorf("need a shared TURN secret if TURN servers are configured") + return nil, errors.New("need a shared TURN secret if TURN servers are configured") } logger.Printf("Using configured TURN API key") diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 769cb0b..4e210c8 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "net/url" "slices" "time" @@ -52,12 +51,12 @@ type backendStorageEtcd struct { func NewBackendStorageEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (BackendStorage, error) { if etcdClient == nil || !etcdClient.IsConfigured() { - return nil, fmt.Errorf("no etcd endpoints configured") + return nil, errors.New("no etcd endpoints configured") } keyPrefix, _ := config.GetString("backend", "backendprefix") if keyPrefix == "" { - return nil, fmt.Errorf("no backend prefix configured") + return nil, errors.New("no backend prefix configured") } initializedCtx, initializedFunc := context.WithCancel(context.Background()) diff --git a/backoff.go b/backoff.go index 3d59f51..ae55550 100644 --- a/backoff.go +++ b/backoff.go @@ -23,7 +23,7 @@ package signaling import ( "context" - "fmt" + "errors" "time" ) @@ -41,10 +41,10 @@ type exponentialBackoff struct { func NewExponentialBackoff(initial time.Duration, maxWait time.Duration) (Backoff, error) { if initial <= 0 { - return nil, fmt.Errorf("initial must be larger than 0") + return nil, errors.New("initial must be larger than 0") } if maxWait < initial { - return nil, fmt.Errorf("maxWait must be larger or equal to initial") + return nil, errors.New("maxWait must be larger or equal to initial") } return &exponentialBackoff{ diff --git a/clientsession.go b/clientsession.go index 6ff26a3..c9e38aa 100644 --- a/clientsession.go +++ b/clientsession.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "errors" "fmt" "maps" "net/url" @@ -857,7 +858,7 @@ func (s *ClientSession) IsAllowedToSend(data *MessageClientMessageData) error { return nil } - return fmt.Errorf("permission check failed") + return errors.New("permission check failed") } } diff --git a/dns_monitor_test.go b/dns_monitor_test.go index 77b2379..3feafde 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -77,7 +77,7 @@ func (m *mockDnsLookup) lookup(host string) ([]net.IP, error) { ips, found := m.ips[host] if !found { return nil, &net.DNSError{ - Err: fmt.Sprintf("could not resolve %s", host), + Err: "could not resolve " + host, Name: host, IsNotFound: true, } diff --git a/geoip.go b/geoip.go index f48092f..2feaa89 100644 --- a/geoip.go +++ b/geoip.go @@ -25,6 +25,7 @@ import ( "archive/tar" "compress/gzip" "context" + "errors" "fmt" "io" "net" @@ -40,7 +41,7 @@ import ( ) var ( - ErrDatabaseNotInitialized = fmt.Errorf("GeoIP database not initialized yet") + ErrDatabaseNotInitialized = errors.New("GeoIP database not initialized yet") ) func GetGeoIpDownloadUrl(license string) string { diff --git a/grpc_client.go b/grpc_client.go index daeedba..afdee7a 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -52,7 +52,7 @@ const ( ) var ( - ErrNoSuchResumeId = fmt.Errorf("unknown resume id") + ErrNoSuchResumeId = errors.New("unknown resume id") customResolverPrefix atomic.Uint64 ) @@ -802,12 +802,12 @@ func (c *GrpcClients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool defer c.mu.Unlock() if !c.etcdClient.IsConfigured() { - return fmt.Errorf("no etcd endpoints configured") + return errors.New("no etcd endpoints configured") } targetPrefix, _ := config.GetString("grpc", "targetprefix") if targetPrefix == "" { - return fmt.Errorf("no GRPC target prefix configured") + return errors.New("no GRPC target prefix configured") } c.targetPrefix = targetPrefix if c.targetInformation == nil { diff --git a/http_client_pool.go b/http_client_pool.go index b257962..9b2b2f9 100644 --- a/http_client_pool.go +++ b/http_client_pool.go @@ -25,7 +25,6 @@ import ( "context" "crypto/tls" "errors" - "fmt" "net/http" "net/url" "sync" @@ -60,7 +59,7 @@ func (p *Pool) Put(c *http.Client) { func newPool(host string, constructor func() *http.Client, size int) (*Pool, error) { if size <= 0 { - return nil, fmt.Errorf("can't create empty pool") + return nil, errors.New("can't create empty pool") } p := &Pool{ @@ -87,7 +86,7 @@ type HttpClientPool struct { func NewHttpClientPool(maxConcurrentRequestsPerHost int, skipVerify bool) (*HttpClientPool, error) { if maxConcurrentRequestsPerHost <= 0 { - return nil, fmt.Errorf("can't create empty pool") + return nil, errors.New("can't create empty pool") } tlsconfig := &tls.Config{ diff --git a/hub.go b/hub.go index 9bd53c1..6d1b38a 100644 --- a/hub.go +++ b/hub.go @@ -1460,7 +1460,7 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message keyData, _, found = h.backend.capabilities.GetStringConfig(backendCtx, url, ConfigGroupSignaling, ConfigKeyHelloV2TokenKey) } if !found { - return nil, fmt.Errorf("no key found for issuer") + return nil, errors.New("no key found for issuer") } } diff --git a/janus_client.go b/janus_client.go index e3f2803..b1db2f1 100644 --- a/janus_client.go +++ b/janus_client.go @@ -32,6 +32,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -372,7 +373,7 @@ func (gateway *JanusGateway) send(msg api.StringMap, t *transaction) (uint64, er if gateway.conn == nil { gateway.writeMu.Unlock() gateway.removeTransaction(id) - return 0, fmt.Errorf("not connected") + return 0, errors.New("not connected") } err = gateway.conn.WriteMessage(websocket.TextMessage, data) diff --git a/lru_test.go b/lru_test.go index 52c672b..e6660ab 100644 --- a/lru_test.go +++ b/lru_test.go @@ -22,7 +22,7 @@ package signaling import ( - "fmt" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -34,12 +34,12 @@ func TestLruUnbound(t *testing.T) { lru := NewLruCache[int](0) count := 10 for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) lru.Set(key, i) } assert.Equal(count, lru.Len()) for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) value := lru.Get(key) assert.EqualValues(i, value, "Failed for %s", key) } @@ -47,7 +47,7 @@ func TestLruUnbound(t *testing.T) { lru.RemoveOldest() assert.Equal(count-1, lru.Len()) for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) value := lru.Get(key) assert.EqualValues(i, value, "Failed for %s", key) } @@ -56,13 +56,13 @@ func TestLruUnbound(t *testing.T) { // Using the same keys will update the ordering. for i := count - 1; i >= 1; i-- { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) lru.Set(key, i) } assert.Equal(count-1, lru.Len()) // NOTE: The same ordering as the Set calls above. for i := count - 1; i >= 1; i-- { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) value := lru.Get(key) assert.EqualValues(i, value, "Failed for %s", key) } @@ -71,7 +71,7 @@ func TestLruUnbound(t *testing.T) { lru.RemoveOldest() assert.Equal(count-2, lru.Len()) for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) value := lru.Get(key) if i == 0 || i == count-1 { assert.EqualValues(0, value, "The value for key %s should have been removed", key) @@ -81,11 +81,11 @@ func TestLruUnbound(t *testing.T) { } // Remove an arbitrary key from the cache - key := fmt.Sprintf("%d", count/2) + key := strconv.Itoa(count / 2) lru.Remove(key) assert.Equal(count-3, lru.Len()) for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) value := lru.Get(key) if i == 0 || i == count-1 || i == count/2 { assert.EqualValues(0, value, "The value for key %s should have been removed", key) @@ -102,13 +102,13 @@ func TestLruBound(t *testing.T) { lru := NewLruCache[int](size) count := 10 for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) lru.Set(key, i) } assert.Equal(size, lru.Len()) // Only the last "size" entries have been stored. for i := range count { - key := fmt.Sprintf("%d", i) + key := strconv.Itoa(i) value := lru.Get(key) if i < count-size { assert.EqualValues(0, value, "The value for key %s should have been removed", key) diff --git a/mcu_common.go b/mcu_common.go index ba5a736..ab00386 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -23,7 +23,7 @@ package signaling import ( "context" - "fmt" + "errors" "sync/atomic" "time" @@ -43,7 +43,7 @@ var ( defaultMaxStreamBitrate = api.BandwidthFromMegabits(1) defaultMaxScreenBitrate = api.BandwidthFromMegabits(2) - ErrNotConnected = fmt.Errorf("not connected") + ErrNotConnected = errors.New("not connected") ) type MediaType int diff --git a/mcu_janus.go b/mcu_janus.go index 7e1e29b..96d15b4 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -466,7 +466,7 @@ func (m *mcuJanus) Start(ctx context.Context) error { m.logger.Printf("Used dependencies: %+v", info.Dependencies) if !info.DataChannels { - return fmt.Errorf("data channels are not supported") + return errors.New("data channels are not supported") } m.logger.Println("Data channels are supported") diff --git a/mcu_proxy.go b/mcu_proxy.go index c70c2a8..d49c45a 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1518,11 +1518,11 @@ func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient *Etc tokenId, _ := config.GetString("mcu", "token_id") if tokenId == "" { - return nil, fmt.Errorf("no token id configured") + return nil, errors.New("no token id configured") } tokenKeyFilename, _ := config.GetString("mcu", "token_key") if tokenKeyFilename == "" { - return nil, fmt.Errorf("no token key configured") + return nil, errors.New("no token key configured") } tokenKeyData, err := os.ReadFile(tokenKeyFilename) if err != nil { @@ -2108,7 +2108,7 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id Pu if publisher == nil { statsProxyNobackendAvailableTotal.WithLabelValues(string(streamType)).Inc() - return nil, fmt.Errorf("no MCU connection available") + return nil, errors.New("no MCU connection available") } return publisher, nil diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 48a34c5..0f035c5 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -35,6 +35,7 @@ import ( "net/url" "path" "slices" + "strconv" "strings" "sync" "sync/atomic" @@ -262,7 +263,7 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer key, found := c.server.tokens[claims.Issuer] if !assert.True(c.t, found) { - return nil, fmt.Errorf("no key found for issuer") + return nil, errors.New("no key found for issuer") } return key, nil @@ -371,7 +372,7 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( key, found := server.tokens[claims.Issuer] if !assert.True(c.t, found) { - return nil, fmt.Errorf("no key found for issuer") + return nil, errors.New("no key found for issuer") } return key, nil @@ -865,7 +866,7 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i if strings.Contains(t.Name(), "DnsDiscovery") { cfg.AddOption("mcu", "dnsdiscovery", "true") } - cfg.AddOption("mcu", "proxytimeout", fmt.Sprintf("%d", int(testTimeout.Seconds()))) + cfg.AddOption("mcu", "proxytimeout", strconv.Itoa(int(testTimeout.Seconds()))) var urls []string waitingMap := make(map[string]bool) if len(options.servers) == 0 { @@ -1061,7 +1062,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { // nolint:parall require.NotNil(dnsMonitor) server2 := NewProxyServerForTest(t, "DE") - l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.2:%s", port)) + l, err := net.Listen("tcp", "127.0.0.2:"+port) require.NoError(err) assert.NoError(server2.server.Listener.Close()) server2.server.Listener = l diff --git a/mcu_test.go b/mcu_test.go index 378a2ed..b6f8792 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -145,7 +145,7 @@ func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publi pub := m.publishers[publisher] if pub == nil { - return nil, fmt.Errorf("Waiting for publisher not implemented yet") + return nil, errors.New("Waiting for publisher not implemented yet") } id := newRandomString(8) @@ -220,7 +220,7 @@ func (p *TestMCUPublisher) SetMedia(mt MediaType) { func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { go func() { if p.isClosed() { - callback(fmt.Errorf("Already closed"), nil) + callback(errors.New("Already closed"), nil) return } @@ -276,7 +276,7 @@ func (s *TestMCUSubscriber) Publisher() PublicSessionId { func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { go func() { if s.isClosed() { - callback(fmt.Errorf("Already closed"), nil) + callback(errors.New("Already closed"), nil) return } @@ -286,7 +286,7 @@ func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageCli case "sendoffer": sdp := s.publisher.sdp if sdp == "" { - callback(fmt.Errorf("Publisher not sending (no SDP)"), nil) + callback(errors.New("Publisher not sending (no SDP)"), nil) return } diff --git a/natsclient.go b/natsclient.go index 10292e7..0054168 100644 --- a/natsclient.go +++ b/natsclient.go @@ -25,7 +25,7 @@ import ( "context" "encoding/base64" "encoding/json" - "fmt" + "errors" "net/url" "os" "os/signal" @@ -105,7 +105,7 @@ func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (Nat logger.Printf("Could not create connection (%s), will retry in %s", err, backoff.NextWait()) backoff.Wait(ctx) if ctx.Err() != nil { - return nil, fmt.Errorf("interrupted") + return nil, errors.New("interrupted") } client.conn, err = nats.Connect(url) diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 24be05c..dafd056 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -298,7 +298,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * if tokenId != "" { tokenKeyFilename, _ := config.GetString("app", "token_key") if tokenKeyFilename == "" { - return nil, fmt.Errorf("no token key configured") + return nil, errors.New("no token key configured") } tokenKeyData, err := os.ReadFile(tokenKeyFilename) if err != nil { @@ -424,7 +424,7 @@ func (s *ProxyServer) checkOrigin(r *http.Request) bool { func (s *ProxyServer) Start(config *goconf.ConfigFile) error { s.url, _ = signaling.GetStringOptionWithEnv(config, "mcu", "url") if s.url == "" { - return fmt.Errorf("no MCU server url configured") + return errors.New("no MCU server url configured") } mcuType, _ := config.GetString("mcu", "type") @@ -466,7 +466,7 @@ func (s *ProxyServer) Start(config *goconf.ConfigFile) error { s.logger.Printf("Could not initialize %s MCU at %s (%s) will retry in %s", mcuType, s.url, err, backoff.NextWait()) backoff.Wait(ctx) if ctx.Err() != nil { - return fmt.Errorf("cancelled") + return errors.New("cancelled") } } @@ -1429,7 +1429,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str if !ok { s.logger.Printf("Unsupported claims type: %+v", token.Claims) reason = "unsupported-claims" - return nil, fmt.Errorf("unsupported claims type") + return nil, errors.New("unsupported claims type") } tokenKey, err := s.tokens.Get(claims.Issuer) @@ -1442,7 +1442,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str if tokenKey == nil || tokenKey.key == nil { s.logger.Printf("Issuer %s is not supported", claims.Issuer) reason = "unsupported-issuer" - return nil, fmt.Errorf("no key found for issuer") + return nil, errors.New("no key found for issuer") } return tokenKey.key, nil diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index ccaa837..b24ddb8 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -24,6 +24,7 @@ package main import ( "bytes" "context" + "errors" "fmt" "strings" "sync/atomic" @@ -59,7 +60,7 @@ func NewProxyTokensEtcd(logger signaling.Logger, config *goconf.ConfigFile) (Pro } if !client.IsConfigured() { - return nil, fmt.Errorf("no etcd endpoints configured") + return nil, errors.New("no etcd endpoints configured") } result := &tokensEtcd{ diff --git a/remotesession.go b/remotesession.go index 0787785..e9911e2 100644 --- a/remotesession.go +++ b/remotesession.go @@ -25,7 +25,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "sync/atomic" "time" ) @@ -90,7 +89,7 @@ func (s *RemoteSession) OnProxyMessage(msg *ServerSessionMessage) error { } if !s.client.SendMessage(message) { - return fmt.Errorf("could not send message to client") + return errors.New("could not send message to client") } return nil diff --git a/roomsessions.go b/roomsessions.go index 3ff8b5e..090476c 100644 --- a/roomsessions.go +++ b/roomsessions.go @@ -23,11 +23,11 @@ package signaling import ( "context" - "fmt" + "errors" ) var ( - ErrNoSuchRoomSession = fmt.Errorf("unknown room session id") + ErrNoSuchRoomSession = errors.New("unknown room session id") ) type RoomSessions interface { diff --git a/test_helpers.go b/test_helpers.go index a0ca4ec..8ec27c9 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -23,7 +23,6 @@ package signaling import ( "bytes" - "fmt" "log" "sync" "testing" @@ -59,7 +58,7 @@ func NewLoggerForTest(t testing.TB) Logger { if !found { logger = log.New(&testLogWriter{ t: t, - }, fmt.Sprintf("%s: ", t.Name()), log.LstdFlags|log.Lmicroseconds|log.Lshortfile) + }, t.Name()+": ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) t.Cleanup(func() { testLoggersLock.Lock() diff --git a/testclient_test.go b/testclient_test.go index e3cc012..6f12250 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -49,7 +49,7 @@ var ( testBackendSecret = []byte("secret") testInternalSecret = []byte("internal-secret") - ErrNoMessageReceived = fmt.Errorf("no message was received by the server") + ErrNoMessageReceived = errors.New("no message was received by the server") testClientDialer = websocket.Dialer{ WriteBufferPool: &sync.Pool{}, @@ -337,7 +337,7 @@ func (c *TestClient) WaitForClientRemoved(ctx context.Context) error { func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId PublicSessionId) error { data := c.hub.decodePublicSessionId(sessionId) if data == nil { - return fmt.Errorf("Invalid session id passed") + return errors.New("Invalid session id passed") } c.hub.mu.Lock() From b8e0e5c2c1909efbbd23d808484eb111d440921e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 09:16:42 +0100 Subject: [PATCH 353/549] lint: Enable "perfsprint" linter. --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 6fe1782..be18bf7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,6 +7,7 @@ linters: - misspell - modernize - paralleltest + - perfsprint - revive settings: errchkjson: From d8d17734cbeb5c7ee577db36a78ee557569eaaaa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 09:48:52 +0100 Subject: [PATCH 354/549] Improve use of testify assertions. --- api_signaling_test.go | 8 +++--- backend_client_test.go | 10 +++++--- backend_server_test.go | 2 +- capabilities_test.go | 7 ++--- client_test.go | 8 +++--- clientsession_test.go | 2 +- federation_test.go | 8 +++--- geoip_test.go | 2 +- hub_test.go | 32 +++++++++++------------ lru_test.go | 18 ++++++------- mcu_janus_events_handler_test.go | 32 +++++++++++------------ mcu_janus_publisher_test.go | 4 +-- mcu_janus_test.go | 44 ++++++++++++++++++-------------- mcu_proxy_test.go | 4 +-- natsclient_test.go | 2 +- proxy/proxy_server_test.go | 26 +++++++++---------- room_test.go | 2 +- stats_prometheus_test.go | 6 ++--- testclient_test.go | 2 +- virtualsession_test.go | 10 ++++---- 20 files changed, 119 insertions(+), 110 deletions(-) diff --git a/api_signaling_test.go b/api_signaling_test.go index eb0b300..67bb4f8 100644 --- a/api_signaling_test.go +++ b/api_signaling_test.go @@ -524,7 +524,7 @@ func TestFilterSDPCandidates(t *testing.T) { } } - assert.EqualValues(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + assert.Equal(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) } blocked, err := ParseAllowedIps("192.0.0.0/24, 192.168.0.0/16") @@ -542,7 +542,7 @@ func TestFilterSDPCandidates(t *testing.T) { } } - assert.EqualValues(expectedAfter[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + assert.Equal(expectedAfter[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) } } @@ -574,7 +574,7 @@ func TestNoFilterSDPCandidates(t *testing.T) { } } - assert.EqualValues(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + assert.Equal(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) } blocked, err := ParseAllowedIps("192.0.0.0/24, 192.168.0.0/16") @@ -592,7 +592,7 @@ func TestNoFilterSDPCandidates(t *testing.T) { } } - assert.EqualValues(expectedAfter[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) + assert.Equal(expectedAfter[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) } } diff --git a/backend_client_test.go b/backend_client_test.go index 71c7824..344367d 100644 --- a/backend_client_test.go +++ b/backend_client_test.go @@ -74,12 +74,13 @@ func TestPostOnRedirect(t *testing.T) { http.Redirect(w, r, "/ocs/v2.php/two", http.StatusTemporaryRedirect) }) r.HandleFunc("/ocs/v2.php/two", func(w http.ResponseWriter, r *http.Request) { + assert := assert.New(t) body, err := io.ReadAll(r.Body) - require.NoError(err) + assert.NoError(err) var request map[string]string err = json.Unmarshal(body, &request) - require.NoError(err) + assert.NoError(err) returnOCS(t, w, body) }) @@ -160,9 +161,10 @@ func TestPostOnRedirectStatusFound(t *testing.T) { }) r.HandleFunc("/ocs/v2.php/two", func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) - require.NoError(err) + if assert.NoError(err) { + assert.Empty(string(body), "Should not have received any body, got %s", string(body)) + } - assert.Empty(string(body), "Should not have received any body, got %s", string(body)) returnOCS(t, w, []byte("{}")) }) server := httptest.NewServer(r) diff --git a/backend_server_test.go b/backend_server_test.go index f6f3416..8f2685c 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -1316,7 +1316,7 @@ func TestBackendServer_TurnCredentials(t *testing.T) { m.Write([]byte(cred.Username)) // nolint password := base64.StdEncoding.EncodeToString(m.Sum(nil)) assert.Equal(password, cred.Password) - assert.EqualValues((24 * time.Hour).Seconds(), cred.TTL) + assert.InEpsilon((24 * time.Hour).Seconds(), cred.TTL, 0.0001) assert.Equal(turnServers, cred.URIs) } diff --git a/capabilities_test.go b/capabilities_test.go index 225691b..d3f08c4 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -46,6 +46,7 @@ import ( func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*CapabilitiesResponse, http.ResponseWriter) error) (*url.URL, *Capabilities) { require := require.New(t) + assert := assert.New(t) pool, err := NewHttpClientPool(1, false) require.NoError(err) capabilities, err := NewCapabilities("0.0", pool) @@ -79,7 +80,7 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie "features": features, "config": config, }) - require.NoError(err) + assert.NoError(err) emptyArray := []byte("[]") response := &CapabilitiesResponse{ Version: CapabilitiesVersion{ @@ -92,7 +93,7 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie } data, err := json.Marshal(response) - assert.NoError(t, err, "Could not marshal %+v", response) + assert.NoError(err, "Could not marshal %+v", response) var ocs OcsResponse ocs.Ocs = &OcsBody{ @@ -104,7 +105,7 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie Data: data, } data, err = json.Marshal(ocs) - require.NoError(err) + assert.NoError(err) var cc []string if !strings.Contains(t.Name(), "NoCache") { diff --git a/client_test.go b/client_test.go index d5ee6fc..9b90e54 100644 --- a/client_test.go +++ b/client_test.go @@ -38,10 +38,10 @@ func TestCounterWriter(t *testing.T) { w: &b, counter: &written, } - if count, err := w.Write(nil); assert.NoError(err) && assert.EqualValues(0, count) { - assert.EqualValues(0, written) + if count, err := w.Write(nil); assert.NoError(err) && assert.Equal(0, count) { + assert.Equal(0, written) } - if count, err := w.Write([]byte("foo")); assert.NoError(err) && assert.EqualValues(3, count) { - assert.EqualValues(3, written) + if count, err := w.Write([]byte("foo")); assert.NoError(err) && assert.Equal(3, count) { + assert.Equal(3, written) } } diff --git a/clientsession_test.go b/clientsession_test.go index b5e1f82..cc46981 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -101,7 +101,7 @@ func TestBandwidth_Backend(t *testing.T) { u, err := url.Parse(server.URL + "/one") require.NoError(err) backend := hub.backend.GetBackend(u) - require.NotNil(t, backend, "Could not get backend") + require.NotNil(backend, "Could not get backend") backend.maxScreenBitrate = 1000 backend.maxStreamBitrate = 2000 diff --git a/federation_test.go b/federation_test.go index 0d6ab77..d92bbf7 100644 --- a/federation_test.go +++ b/federation_test.go @@ -208,7 +208,7 @@ func Test_Federation(t *testing.T) { // Leaving and re-joining a room as "direct" session will trigger correct events. if room, ok := client1.JoinRoom(ctx, ""); ok { - assert.Equal("", room.Room.RoomId) + assert.Empty(room.Room.RoomId) } client2.RunUntilLeft(ctx, hello1.Hello) @@ -225,7 +225,7 @@ func Test_Federation(t *testing.T) { // Leaving and re-joining a room as "federated" session will trigger correct events. if room, ok := client2.JoinRoom(ctx, ""); ok { - assert.Equal("", room.Room.RoomId) + assert.Empty(room.Room.RoomId) } client1.RunUntilLeft(ctx, &HelloServerMessage{ @@ -485,7 +485,7 @@ func Test_Federation(t *testing.T) { }, hello3.Hello, hello4.Hello) if room3, ok := client2.JoinRoom(ctx, ""); ok { - assert.Equal("", room3.Room.RoomId) + assert.Empty(room3.Room.RoomId) } } @@ -1043,7 +1043,7 @@ func Test_FederationResumeNewSession(t *testing.T) { // session and get "joined" events for all sessions in the room (including // its own). if message, ok := client2.RunUntilMessage(ctx); ok { - assert.Equal("", message.Id) + assert.Empty(message.Id) require.Equal("room", message.Type) require.Equal(federatedRoomId, message.Room.RoomId) } diff --git a/geoip_test.go b/geoip_test.go index 3a03ed1..41cbbec 100644 --- a/geoip_test.go +++ b/geoip_test.go @@ -117,7 +117,7 @@ func TestGeoLookupContinent(t *testing.T) { t.Run(country, func(t *testing.T) { t.Parallel() continents := LookupContinents(country) - if !assert.Equal(t, len(expected), len(continents), "Continents didn't match for %s: got %s, expected %s", country, continents, expected) { + if !assert.Len(t, continents, len(expected), "Continents didn't match for %s: got %s, expected %s", country, continents, expected) { return } for idx, c := range expected { diff --git a/hub_test.go b/hub_test.go index ef1bb6b..1cea5d2 100644 --- a/hub_test.go +++ b/hub_test.go @@ -341,22 +341,22 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Request, *BackendClientRequest) *BackendClientResponse) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - require := require.New(t) + assert := assert.New(t) body, err := io.ReadAll(r.Body) - require.NoError(err) + assert.NoError(err) rnd := r.Header.Get(HeaderBackendSignalingRandom) checksum := r.Header.Get(HeaderBackendSignalingChecksum) if rnd == "" || checksum == "" { - require.Fail("No checksum headers found", "request to %s", r.URL) + assert.Fail("No checksum headers found", "request to %s", r.URL) } if verify := CalculateBackendChecksum(rnd, body, testBackendSecret); verify != checksum { - require.Fail("Backend checksum verification failed", "request to %s", r.URL) + assert.Fail("Backend checksum verification failed", "request to %s", r.URL) } var request BackendClientRequest - require.NoError(json.Unmarshal(body, &request)) + assert.NoError(json.Unmarshal(body, &request)) response := f(w, r, &request) if response == nil { @@ -365,7 +365,7 @@ func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Req } data, err := json.Marshal(response) - require.NoError(err) + assert.NoError(err) if r.Header.Get("OCS-APIRequest") != "" { var ocs OcsResponse @@ -378,7 +378,7 @@ func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Req Data: data, } data, err = json.Marshal(ocs) - require.NoError(err) + assert.NoError(err) } w.Header().Set("Content-Type", "application/json") @@ -745,7 +745,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if (strings.Contains(t.Name(), "V2") && !skipV2) || strings.Contains(t.Name(), "Federation") { key := getPublicAuthToken(t) public, err := x509.MarshalPKIXPublicKey(key) - require.NoError(t, err) + assert.NoError(t, err) var pemType string if strings.Contains(t.Name(), "ECDSA") { pemType = "ECDSA PUBLIC KEY" @@ -794,7 +794,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { Data: data, } data, err = json.Marshal(ocs) - require.NoError(t, err) + assert.NoError(t, err) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(data) // nolint @@ -1370,7 +1370,7 @@ func TestSessionIdsUnordered(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - _, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + _, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) // nolint:testifylint assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello) assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello) @@ -2603,7 +2603,7 @@ func TestJoinRoom(t *testing.T) { // Leave room. roomMsg = MustSucceed2(t, client.JoinRoom, ctx, "") - require.Equal("", roomMsg.Room.RoomId) + require.Empty(roomMsg.Room.RoomId) } func TestJoinRoomBackendBandwidth(t *testing.T) { @@ -2849,7 +2849,7 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { // Leave room roomMsg = MustSucceed2(t, client.JoinRoom, ctx, "") - require.Equal("", roomMsg.Room.RoomId) + require.Empty(roomMsg.Room.RoomId) // Perform housekeeping in the future, this will cause the connection to // be terminated because the anonymous client didn't join a room. @@ -2894,7 +2894,7 @@ func TestJoinRoomChange(t *testing.T) { // Leave room. roomMsg = MustSucceed2(t, client.JoinRoom, ctx, "") - require.Equal("", roomMsg.Room.RoomId) + require.Empty(roomMsg.Room.RoomId) } func TestJoinMultiple(t *testing.T) { @@ -2928,13 +2928,13 @@ func TestJoinMultiple(t *testing.T) { // Leave room. roomMsg = MustSucceed2(t, client1.JoinRoom, ctx, "") - require.Equal("", roomMsg.Room.RoomId) + require.Empty(roomMsg.Room.RoomId) // The second client will now receive a "left" event client2.RunUntilLeft(ctx, hello1.Hello) roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, "") - require.Equal("", roomMsg.Room.RoomId) + require.Empty(roomMsg.Room.RoomId) } func TestJoinDisplaynamesPermission(t *testing.T) { @@ -3058,7 +3058,7 @@ func TestJoinRoomSwitchClient(t *testing.T) { // Leave room. roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, "") - require.Equal("", roomMsg.Room.RoomId) + require.Empty(roomMsg.Room.RoomId) } func TestGetRealUserIP(t *testing.T) { diff --git a/lru_test.go b/lru_test.go index e6660ab..35113dc 100644 --- a/lru_test.go +++ b/lru_test.go @@ -41,7 +41,7 @@ func TestLruUnbound(t *testing.T) { for i := range count { key := strconv.Itoa(i) value := lru.Get(key) - assert.EqualValues(i, value, "Failed for %s", key) + assert.Equal(i, value, "Failed for %s", key) } // The first key ("0") is now the oldest. lru.RemoveOldest() @@ -49,7 +49,7 @@ func TestLruUnbound(t *testing.T) { for i := range count { key := strconv.Itoa(i) value := lru.Get(key) - assert.EqualValues(i, value, "Failed for %s", key) + assert.Equal(i, value, "Failed for %s", key) } // NOTE: Key "0" no longer exists below, so make sure to not set it again. @@ -64,7 +64,7 @@ func TestLruUnbound(t *testing.T) { for i := count - 1; i >= 1; i-- { key := strconv.Itoa(i) value := lru.Get(key) - assert.EqualValues(i, value, "Failed for %s", key) + assert.Equal(i, value, "Failed for %s", key) } // The last key ("9") is now the oldest. @@ -74,9 +74,9 @@ func TestLruUnbound(t *testing.T) { key := strconv.Itoa(i) value := lru.Get(key) if i == 0 || i == count-1 { - assert.EqualValues(0, value, "The value for key %s should have been removed", key) + assert.Equal(0, value, "The value for key %s should have been removed", key) } else { - assert.EqualValues(i, value, "Failed for %s", key) + assert.Equal(i, value, "Failed for %s", key) } } @@ -88,9 +88,9 @@ func TestLruUnbound(t *testing.T) { key := strconv.Itoa(i) value := lru.Get(key) if i == 0 || i == count-1 || i == count/2 { - assert.EqualValues(0, value, "The value for key %s should have been removed", key) + assert.Equal(0, value, "The value for key %s should have been removed", key) } else { - assert.EqualValues(i, value, "Failed for %s", key) + assert.Equal(i, value, "Failed for %s", key) } } } @@ -111,9 +111,9 @@ func TestLruBound(t *testing.T) { key := strconv.Itoa(i) value := lru.Get(key) if i < count-size { - assert.EqualValues(0, value, "The value for key %s should have been removed", key) + assert.Equal(0, value, "The value for key %s should have been removed", key) } else { - assert.EqualValues(i, value, "Failed for %s", key) + assert.Equal(i, value, "Failed for %s", key) } } } diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index 1063023..3dffcf5 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -51,9 +51,9 @@ type TestJanusEventsServerHandler struct { func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.t.Helper() - require := require.New(h.t) + assert := assert.New(h.t) conn, err := h.upgrader.Upgrade(w, r, nil) - require.NoError(err) + assert.NoError(err) if conn.Subprotocol() == JanusEventsSubprotocol { addr := h.addr @@ -70,10 +70,10 @@ func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http. } deadline := time.Now().Add(time.Second) - require.NoError(conn.SetWriteDeadline(deadline)) - require.NoError(conn.WriteJSON(map[string]string{"error": "invalid_subprotocol"})) - require.NoError(conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseProtocolError, "invalid_subprotocol"), deadline)) - require.NoError(conn.Close()) + assert.NoError(conn.SetWriteDeadline(deadline)) + assert.NoError(conn.WriteJSON(map[string]string{"error": "invalid_subprotocol"})) + assert.NoError(conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseProtocolError, "invalid_subprotocol"), deadline)) + assert.NoError(conn.Close()) } func NewTestJanusEventsHandlerServer(t *testing.T) (*httptest.Server, string, *TestJanusEventsServerHandler) { @@ -121,7 +121,7 @@ func TestJanusEventsHandlerNoMcu(t *testing.T) { if mt, msg, err := conn.ReadMessage(); err == nil { assert.Fail("connection was not closed", "expected close error, got message %s with type %d", string(msg), mt) } else if assert.ErrorAs(err, &ce) { - assert.EqualValues(websocket.CloseInternalServerErr, ce.Code) + assert.Equal(websocket.CloseInternalServerErr, ce.Code) assert.Equal("no mcu configured", ce.Text) } } @@ -153,7 +153,7 @@ func TestJanusEventsHandlerInvalidMcu(t *testing.T) { if mt, msg, err := conn.ReadMessage(); err == nil { assert.Fail("connection was not closed", "expected close error, got message %s with type %d", string(msg), mt) } else if assert.ErrorAs(err, &ce) { - assert.EqualValues(websocket.CloseInternalServerErr, ce.Code) + assert.Equal(websocket.CloseInternalServerErr, ce.Code) assert.Equal("mcu does not support events", ce.Text) } } @@ -186,7 +186,7 @@ func TestJanusEventsHandlerPublicIP(t *testing.T) { if mt, msg, err := conn.ReadMessage(); err == nil { assert.Fail("connection was not closed", "expected close error, got message %s with type %d", string(msg), mt) } else if assert.ErrorAs(err, &ce) { - assert.EqualValues(websocket.ClosePolicyViolation, ce.Code) + assert.Equal(websocket.ClosePolicyViolation, ce.Code) assert.Equal("only loopback and private connections allowed", ce.Text) } } @@ -210,14 +210,14 @@ func (m *TestMcuWithEvents) UpdateBandwidth(handle uint64, media string, sent ap switch m.idx { case 1: assert.EqualValues(1, handle) - assert.EqualValues("audio", media) - assert.EqualValues(api.BandwidthFromBytes(100), sent) - assert.EqualValues(api.BandwidthFromBytes(200), received) + assert.Equal("audio", media) + assert.Equal(api.BandwidthFromBytes(100), sent) + assert.Equal(api.BandwidthFromBytes(200), received) case 2: assert.EqualValues(1, handle) - assert.EqualValues("video", media) - assert.EqualValues(api.BandwidthFromBytes(200), sent) - assert.EqualValues(api.BandwidthFromBytes(300), received) + assert.Equal("video", media) + assert.Equal(api.BandwidthFromBytes(200), sent) + assert.Equal(api.BandwidthFromBytes(300), received) default: assert.Fail("too many updates", "received update %d (handle=%d, media=%s, sent=%d, received=%d)", m.idx, handle, media, sent, received) } @@ -638,6 +638,6 @@ func TestValueCounter(t *testing.T) { assert.EqualValues(1, c.Update("foo", 11)) assert.EqualValues(10, c.Update("bar", 10)) assert.EqualValues(1, c.Update("bar", 11)) - assert.EqualValues(uint64(math.MaxUint64-10), c.Update("baz", math.MaxUint64-10)) + assert.Equal(uint64(math.MaxUint64-10), c.Update("baz", math.MaxUint64-10)) assert.EqualValues(20, c.Update("baz", 10)) } diff --git a/mcu_janus_publisher_test.go b/mcu_janus_publisher_test.go index 3e409e6..a4c7b43 100644 --- a/mcu_janus_publisher_test.go +++ b/mcu_janus_publisher_test.go @@ -122,10 +122,10 @@ func TestJanusPublisherRemote(t *testing.T) { assert.Equal(hostname, value) } if value, found := api.GetStringMapEntry[float64](body, "port"); assert.True(found) { - assert.EqualValues(port, value) + assert.InEpsilon(port, value, 0.0001) } if value, found := api.GetStringMapEntry[float64](body, "rtcp_port"); assert.True(found) { - assert.EqualValues(rtcpPort, value) + assert.InEpsilon(rtcpPort, value, 0.0001) } if value, found := api.GetStringMapString[string](body, "remote_id"); assert.True(found) { prev := remotePublishId.Swap(value) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 2e231e4..5fa93d1 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -104,11 +104,11 @@ func NewTestJanusGateway(t *testing.T) *TestJanusGateway { assert := assert.New(t) gateway.mu.Lock() defer gateway.mu.Unlock() - assert.Len(gateway.sessions, 0) - assert.Len(gateway.transactions, 0) - assert.Len(gateway.handles, 0) - assert.Len(gateway.rooms, 0) - assert.Len(gateway.handleRooms, 0) + assert.Empty(gateway.sessions) + assert.Empty(gateway.transactions) + assert.Empty(gateway.handles) + assert.Empty(gateway.rooms) + assert.Empty(gateway.handleRooms) }) return gateway @@ -352,7 +352,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan } } - assert.EqualValues(g.t, room.id, uint64(rid)) + assert.Equal(g.t, room.id, uint64(rid)) delete(g.rooms, uint64(rid)) for h, r := range g.handleRooms { if r.id == room.id { @@ -980,7 +980,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { stream := streams[0] assert.Equal("audio", stream.Type) assert.Equal("audio", stream.Mid) - assert.EqualValues(0, stream.Mindex) + assert.Equal(0, stream.Mindex) assert.False(stream.Disabled) assert.Equal("opus", stream.Codec) assert.False(stream.Stereo) @@ -1062,7 +1062,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { stream := streams[0] assert.Equal("audio", stream.Type) assert.Equal("audio", stream.Mid) - assert.EqualValues(0, stream.Mindex) + assert.Equal(0, stream.Mindex) assert.False(stream.Disabled) assert.Equal("opus", stream.Codec) assert.False(stream.Stereo) @@ -1072,7 +1072,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { stream = streams[1] assert.Equal("video", stream.Type) assert.Equal("video", stream.Mid) - assert.EqualValues(1, stream.Mindex) + assert.Equal(1, stream.Mindex) assert.False(stream.Disabled) assert.Equal("H264", stream.Codec) assert.Equal("4d0028", stream.ProfileH264) @@ -1121,12 +1121,12 @@ func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest assert.Nil(janusPub.Bandwidth()) mcu.UpdateBandwidth(janusPub.Handle(), "video", api.BandwidthFromBytes(1000), api.BandwidthFromBytes(2000)) if bw := janusPub.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(api.BandwidthFromBytes(1000), bw.Sent) - assert.EqualValues(api.BandwidthFromBytes(2000), bw.Received) + assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(2000), bw.Received) } if bw := mcu.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(api.BandwidthFromBytes(1000), bw.Sent) - assert.EqualValues(api.BandwidthFromBytes(2000), bw.Received) + assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(2000), bw.Received) } mcu.updateBandwidthStats() checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) @@ -1149,12 +1149,12 @@ func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest assert.Nil(janusSub.Bandwidth()) mcu.UpdateBandwidth(janusSub.Handle(), "video", api.BandwidthFromBytes(3000), api.BandwidthFromBytes(4000)) if bw := janusSub.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(api.BandwidthFromBytes(3000), bw.Sent) - assert.EqualValues(api.BandwidthFromBytes(4000), bw.Received) + assert.Equal(api.BandwidthFromBytes(3000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(4000), bw.Received) } if bw := mcu.Bandwidth(); assert.NotNil(bw) { - assert.EqualValues(api.BandwidthFromBytes(4000), bw.Sent) - assert.EqualValues(api.BandwidthFromBytes(6000), bw.Received) + assert.Equal(api.BandwidthFromBytes(4000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(6000), bw.Received) } checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 1000) @@ -1166,6 +1166,7 @@ func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest func Test_JanusSubscriberPublisher(t *testing.T) { t.Parallel() require := require.New(t) + assert := assert.New(t) mcu, gateway := newMcuJanusForTesting(t) gateway.registerHandlers(map[string]TestJanusHandler{}) @@ -1190,7 +1191,10 @@ func Test_JanusSubscriberPublisher(t *testing.T) { defer close(done) time.Sleep(100 * time.Millisecond) pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) + if !assert.NoError(err) { + return + } + defer func() { <-ready pub.Close(context.Background()) @@ -1271,7 +1275,9 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { "sdp": MockSdpOfferAudioAndVideo, }, } - require.NoError(data.CheckValid()) + if !assert.NoError(data.CheckValid()) { + return + } done := make(chan struct{}) pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 0f035c5..8c20f4d 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -318,9 +318,9 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( if assert.NotNil(c.t, msg.Command.PublisherSettings) { if assert.NotEqualValues(c.t, 0, msg.Command.PublisherSettings.Bitrate) { - assert.EqualValues(c.t, msg.Command.Bitrate, msg.Command.PublisherSettings.Bitrate) + assert.Equal(c.t, msg.Command.Bitrate, msg.Command.PublisherSettings.Bitrate) } - assert.EqualValues(c.t, msg.Command.MediaTypes, msg.Command.PublisherSettings.MediaTypes) + assert.Equal(c.t, msg.Command.MediaTypes, msg.Command.PublisherSettings.MediaTypes) if strings.Contains(c.t.Name(), "Codecs") { assert.Equal(c.t, "opus,g722", msg.Command.PublisherSettings.AudioCodec) assert.Equal(c.t, "vp9,vp8,av1", msg.Command.PublisherSettings.VideoCodec) diff --git a/natsclient_test.go b/natsclient_test.go index a852b9f..ace44f6 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -107,7 +107,7 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { } <-ch - require.EqualValues(maxPublish, received.Load(), "Received wrong # of messages") + require.Equal(maxPublish, received.Load(), "Received wrong # of messages") } func TestNatsClient_Subscribe(t *testing.T) { // nolint:paralleltest diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index aef00ab..270ab6e 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -537,10 +537,10 @@ func TestProxyPublisherBandwidth(t *testing.T) { assert.EqualValues(1, message.Event.Load) if bw := message.Event.Bandwidth; assert.NotNil(bw) { if assert.NotNil(bw.Incoming) { - assert.EqualValues(20, *bw.Incoming) + assert.InEpsilon(20, *bw.Incoming, 0.0001) } if assert.NotNil(bw.Outgoing) { - assert.EqualValues(10, *bw.Outgoing) + assert.InEpsilon(10, *bw.Outgoing, 0.0001) } } } @@ -893,7 +893,7 @@ func (p *TestRemotePublisher) MaxBitrate() api.Bandwidth { } func (p *TestRemotePublisher) Close(ctx context.Context) { - if count := p.refcnt.Add(-1); assert.True(p.t, count >= 0) && count == 0 { + if count := p.refcnt.Add(-1); assert.GreaterOrEqual(p.t, int(count), 0) && count == 0 { p.closeFunc() shortCtx, cancel := context.WithTimeout(ctx, time.Millisecond) defer cancel() @@ -1244,8 +1244,8 @@ func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remo assert.Equal(p.t, remoteId, p.remoteId) if remoteData := p.remoteData; assert.NotNil(p.t, remoteData) && assert.Equal(p.t, remoteData.hostname, hostname) && - assert.EqualValues(p.t, remoteData.port, port) && - assert.EqualValues(p.t, remoteData.rtcpPort, rtcpPort) { + assert.Equal(p.t, remoteData.port, port) && + assert.Equal(p.t, remoteData.rtcpPort, rtcpPort) { p.remoteId = "" p.remoteData = nil } @@ -1338,8 +1338,8 @@ func TestProxyUnpublishRemote(t *testing.T) { assert.Equal(hello2.Hello.SessionId, publisher.getRemoteId()) if remoteData := publisher.getRemoteData(); assert.NotNil(remoteData) { assert.Equal("remote-host", remoteData.hostname) - assert.EqualValues(10001, remoteData.port) - assert.EqualValues(10002, remoteData.rtcpPort) + assert.Equal(10001, remoteData.port) + assert.Equal(10002, remoteData.rtcpPort) } } @@ -1455,8 +1455,8 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { assert.Equal(hello2.Hello.SessionId, publisher.getRemoteId()) if remoteData := publisher.getRemoteData(); assert.NotNil(remoteData) { assert.Equal("remote-host", remoteData.hostname) - assert.EqualValues(10001, remoteData.port) - assert.EqualValues(10002, remoteData.rtcpPort) + assert.Equal(10001, remoteData.port) + assert.Equal(10002, remoteData.rtcpPort) } } @@ -1481,8 +1481,8 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { assert.Equal(hello2.Hello.SessionId, publisher.getRemoteId()) if remoteData := publisher.getRemoteData(); assert.NotNil(remoteData) { assert.Equal("remote-host", remoteData.hostname) - assert.EqualValues(10001, remoteData.port) - assert.EqualValues(10002, remoteData.rtcpPort) + assert.Equal(10001, remoteData.port) + assert.Equal(10002, remoteData.rtcpPort) } } @@ -1587,8 +1587,8 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { assert.Equal(hello2.Hello.SessionId, publisher.getRemoteId()) if remoteData := publisher.getRemoteData(); assert.NotNil(remoteData) { assert.Equal("remote-host", remoteData.hostname) - assert.EqualValues(10001, remoteData.port) - assert.EqualValues(10002, remoteData.rtcpPort) + assert.Equal(10001, remoteData.port) + assert.Equal(10002, remoteData.rtcpPort) } } diff --git a/room_test.go b/room_test.go index 5622cd6..d41fdbb 100644 --- a/room_test.go +++ b/room_test.go @@ -71,7 +71,7 @@ func TestRoom_InCall(t *testing.T) { } else { assert.False(t, ok, "%+v should not be valid", test.Value) } - assert.EqualValues(t, test.InCall, inCall, "conversion failed for %+v", test.Value) + assert.Equal(t, test.InCall, inCall, "conversion failed for %+v", test.Value) } } diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index abe83d7..bec5e10 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -54,7 +54,7 @@ func assertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta t.Helper() after := testutil.ToFloat64(collector) - assert.EqualValues(t, delta, after-before, "failed for %s", desc) + assert.InEpsilon(t, delta, after-before, 0.0001, "failed for %s", desc) }) } @@ -71,7 +71,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 pc := make([]uintptr, 10) n := runtime.Callers(2, pc) if n == 0 { - assert.EqualValues(value, v, "failed for %s", desc) + assert.InEpsilon(value, v, 0.0001, "failed for %s", desc) return } @@ -88,7 +88,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 break } } - assert.EqualValues(value, v, "Unexpected value for %s at\n%s", desc, stack.String()) + assert.InEpsilon(value, v, 0.0001, "Unexpected value for %s at\n%s", desc, stack.String()) } } diff --git a/testclient_test.go b/testclient_test.go index 6f12250..e9e8d52 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -1110,7 +1110,7 @@ func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data api assert := assert.New(t) return checkMessageType(t, message, "transient") && assert.Equal("initial", message.TransientData.Type, "invalid message type in %+v", message) && - assert.EqualValues(data, message.TransientData.Data, "invalid initial data in %+v", message) + assert.Equal(data, message.TransientData.Data, "invalid initial data in %+v", message) } func checkMessageInCallAll(t *testing.T, message *ServerMessage, roomId string, inCall int) bool { diff --git a/virtualsession_test.go b/virtualsession_test.go index cf01ae4..83b060a 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -135,7 +135,7 @@ func TestVirtualSession(t *testing.T) { if flagsMsg, ok := checkMessageParticipantFlags(t, msg4); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) - assert.EqualValues(newFlags, flagsMsg.Flags) + assert.Equal(newFlags, flagsMsg.Flags) } // A new client will receive the initial flags of the virtual session. @@ -162,7 +162,7 @@ func TestVirtualSession(t *testing.T) { if assert.Equal(roomId, msg.Event.Flags.RoomId) && assert.Equal(sessionId, msg.Event.Flags.SessionId) && - assert.EqualValues(newFlags, msg.Event.Flags.Flags) { + assert.Equal(newFlags, msg.Event.Flags.Flags) { gotFlags = true break } @@ -322,7 +322,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { if flagsMsg, ok := checkMessageParticipantFlags(t, msg4); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) - assert.EqualValues(newFlags, flagsMsg.Flags) + assert.Equal(newFlags, flagsMsg.Flags) } // A new client will receive the initial flags of the virtual session. @@ -349,7 +349,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { if assert.Equal(roomId, msg.Event.Flags.RoomId) && assert.Equal(sessionId, msg.Event.Flags.SessionId) && - assert.EqualValues(newFlags, msg.Event.Flags.Flags) { + assert.Equal(newFlags, msg.Event.Flags.Flags) { gotFlags = true break } @@ -414,7 +414,7 @@ func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sess } if value, found := api.GetStringMapEntry[float64](entry, "inCall"); !assert.True(found, "inCall not found or invalid in %+v", entry) || - !assert.EqualValues(value, inCall, "invalid inCall") { + !assert.InDelta(value, inCall, 0.0001, "invalid inCall") { return false } found = true From 8510ce45f3964b1ba35f5ddddd942833aa2694d1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 09:49:07 +0100 Subject: [PATCH 355/549] lint: Enable "testifylint" linter. --- .golangci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index be18bf7..c7c039b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,6 +9,7 @@ linters: - paralleltest - perfsprint - revive + - testifylint settings: errchkjson: check-error-free-encoding: true @@ -54,6 +55,9 @@ linters: - name: unreachable-code - name: use-any - name: redefines-builtin-id + testifylint: + disable: + - require-error exclusions: generated: lax presets: From 3178e0ee0801a96c7af99d1275e02e57183584b5 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 11:35:32 +0100 Subject: [PATCH 356/549] Move TestStorage to separate class for easier reuse. --- hub_test.go | 7 ++-- internal/test_storage.go | 78 +++++++++++++++++++++++++++++++++++ internal/test_storage_test.go | 64 ++++++++++++++++++++++++++++ test_helpers.go | 19 +++------ testutils_test.go | 51 ----------------------- 5 files changed, 151 insertions(+), 68 deletions(-) create mode 100644 internal/test_storage.go create mode 100644 internal/test_storage_test.go diff --git a/hub_test.go b/hub_test.go index 1cea5d2..9f9d39d 100644 --- a/hub_test.go +++ b/hub_test.go @@ -53,6 +53,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -538,7 +539,7 @@ func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, } var ( - pingRequests testStorage[[]*BackendClientRequest] + pingRequests internal.TestStorage[[]*BackendClientRequest] ) func getPingRequests(t *testing.T) []*BackendClientRequest { @@ -584,7 +585,7 @@ type testAuthToken struct { } var ( - authTokens testStorage[testAuthToken] + authTokens internal.TestStorage[testAuthToken] ) func ensureAuthTokens(t *testing.T) (string, string) { @@ -693,7 +694,7 @@ func registerBackendHandler(t *testing.T, router *mux.Router) { } var ( - skipV2Capabilities testStorage[bool] + skipV2Capabilities internal.TestStorage[bool] ) func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { diff --git a/internal/test_storage.go b/internal/test_storage.go new file mode 100644 index 0000000..b0007e3 --- /dev/null +++ b/internal/test_storage.go @@ -0,0 +1,78 @@ +/** + * 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 internal + +import ( + "sync" + "testing" +) + +type TestStorage[T any] struct { + mu sync.Mutex + // +checklocks:mu + entries map[string]T // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 +} + +func (s *TestStorage[T]) cleanup(key string) { + s.mu.Lock() + defer s.mu.Unlock() + + delete(s.entries, key) + if len(s.entries) == 0 { + s.entries = nil + } +} + +func (s *TestStorage[T]) Set(tb testing.TB, value T) { + s.mu.Lock() + defer s.mu.Unlock() + + key := tb.Name() + if _, found := s.entries[key]; !found { + tb.Cleanup(func() { + s.cleanup(key) + }) + } + + if s.entries == nil { + s.entries = make(map[string]T) + } + s.entries[key] = value +} + +func (s *TestStorage[T]) Get(tb testing.TB) (T, bool) { + s.mu.Lock() + defer s.mu.Unlock() + + key := tb.Name() + if value, found := s.entries[key]; found { + return value, true + } + + var defaultValue T + return defaultValue, false +} + +func (s *TestStorage[T]) Del(tb testing.TB) { + key := tb.Name() + s.cleanup(key) +} diff --git a/internal/test_storage_test.go b/internal/test_storage_test.go new file mode 100644 index 0000000..0e63cca --- /dev/null +++ b/internal/test_storage_test.go @@ -0,0 +1,64 @@ +/** + * 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 internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_TestStorage(t *testing.T) { + t.Parallel() + assert := assert.New(t) + var storage TestStorage[int] + + t.Cleanup(func() { + storage.mu.Lock() + defer storage.mu.Unlock() + + assert.Nil(storage.entries) + }) + + v, found := storage.Get(t) + assert.False(found, "expected missing value, got %d", v) + + storage.Set(t, 10) + v, found = storage.Get(t) + assert.True(found) + assert.Equal(10, v) + + storage.Set(t, 20) + v, found = storage.Get(t) + assert.True(found) + assert.Equal(20, v) + + storage.Del(t) + + v, found = storage.Get(t) + assert.False(found, "expected missing value, got %d", v) + + storage.Set(t, 30) + v, found = storage.Get(t) + assert.True(found) + assert.Equal(30, v) +} diff --git a/test_helpers.go b/test_helpers.go index 8ec27c9..c4ab4f6 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -26,6 +26,8 @@ import ( "log" "sync" "testing" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) type testLogWriter struct { @@ -44,30 +46,19 @@ func (w *testLogWriter) Write(b []byte) (int, error) { } var ( - // +checklocks:testLoggersLock - testLoggers = map[testing.TB]Logger{} - testLoggersLock sync.Mutex + testLoggers internal.TestStorage[Logger] ) func NewLoggerForTest(t testing.TB) Logger { t.Helper() - testLoggersLock.Lock() - defer testLoggersLock.Unlock() - logger, found := testLoggers[t] + logger, found := testLoggers.Get(t) if !found { logger = log.New(&testLogWriter{ t: t, }, t.Name()+": ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) - t.Cleanup(func() { - testLoggersLock.Lock() - defer testLoggersLock.Unlock() - - delete(testLoggers, t) - }) - - testLoggers[t] = logger + testLoggers.Set(t, logger) } return logger } diff --git a/testutils_test.go b/testutils_test.go index 0addf6d..60932af 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -150,54 +150,3 @@ func AssertEqualSerialized(t *testing.T, expected any, actual any, msgAndArgs .. return assert.Equal(t, string(a), string(e), msgAndArgs...) } - -type testStorage[T any] struct { - mu sync.Mutex - // +checklocks:mu - entries map[string]T // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 -} - -func (s *testStorage[T]) cleanup(key string) { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.entries, key) - if len(s.entries) == 0 { - s.entries = nil - } -} - -func (s *testStorage[T]) Set(t *testing.T, value T) { - s.mu.Lock() - defer s.mu.Unlock() - - key := t.Name() - if _, found := s.entries[key]; !found { - t.Cleanup(func() { - s.cleanup(key) - }) - } - - if s.entries == nil { - s.entries = make(map[string]T) - } - s.entries[key] = value -} - -func (s *testStorage[T]) Get(t *testing.T) (T, bool) { - s.mu.Lock() - defer s.mu.Unlock() - - key := t.Name() - if value, found := s.entries[key]; found { - return value, true - } - - var defaultValue T - return defaultValue, false -} - -func (s *testStorage[T]) Del(t *testing.T) { - key := t.Name() - s.cleanup(key) -} From efb90b4216afc42c2a0a23f6c713ec05c0f3de47 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 11:37:49 +0100 Subject: [PATCH 357/549] Add test for "internal.MakePtr". --- internal/make_ptr_test.go | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 internal/make_ptr_test.go diff --git a/internal/make_ptr_test.go b/internal/make_ptr_test.go new file mode 100644 index 0000000..1b5874e --- /dev/null +++ b/internal/make_ptr_test.go @@ -0,0 +1,41 @@ +/** + * 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 internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_MakePtr(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + if v := MakePtr(10); assert.NotNil(v) { + assert.Equal(10, *v) + } + + if v := MakePtr("foo"); assert.NotNil(v) { + assert.Equal("foo", *v) + } +} From 9363049e0fd034a99b69a18c3e124eb1e816422f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 12:02:33 +0100 Subject: [PATCH 358/549] Allow running tests with mock DNS discovery to run in parallel. --- backend_server_test.go | 4 +- dns_monitor.go | 23 ++++++----- dns_monitor_test.go | 31 +++++++------- grpc_client_test.go | 27 +++++++------ hub_test.go | 4 +- mcu_proxy_test.go | 81 +++++++++++++++++++------------------ proxy_config_static_test.go | 11 ++--- server/main.go | 2 +- 8 files changed, 95 insertions(+), 88 deletions(-) diff --git a/backend_server_test.go b/backend_server_test.go index 8f2685c..570b136 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -171,7 +171,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g defer cancel() assert.NoError(events1.Close(ctx)) }) - client1, _ := NewGrpcClientsForTest(t, addr2) + client1, _ := NewGrpcClientsForTest(t, addr2, nil) hub1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") require.NoError(err) @@ -196,7 +196,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g defer cancel() assert.NoError(events2.Close(ctx)) }) - client2, _ := NewGrpcClientsForTest(t, addr1) + client2, _ := NewGrpcClientsForTest(t, addr1, nil) hub2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") require.NoError(err) diff --git a/dns_monitor.go b/dns_monitor.go index 6906057..6d5465c 100644 --- a/dns_monitor.go +++ b/dns_monitor.go @@ -32,10 +32,6 @@ import ( "time" ) -var ( - lookupDnsMonitorIP = net.LookupIP -) - const ( defaultDnsMonitorInterval = time.Second ) @@ -157,9 +153,12 @@ func (e *dnsMonitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP } } +type DnsMonitorLookupFunc func(hostname string) ([]net.IP, error) + type DnsMonitor struct { - logger Logger - interval time.Duration + logger Logger + interval time.Duration + lookupFunc DnsMonitorLookupFunc stopCtx context.Context stopFunc func() @@ -176,15 +175,19 @@ type DnsMonitor struct { checkHostnames func() } -func NewDnsMonitor(logger Logger, interval time.Duration) (*DnsMonitor, error) { +func NewDnsMonitor(logger Logger, interval time.Duration, lookupFunc DnsMonitorLookupFunc) (*DnsMonitor, error) { if interval < 0 { interval = defaultDnsMonitorInterval } + if lookupFunc == nil { + lookupFunc = net.LookupIP + } stopCtx, stopFunc := context.WithCancel(context.Background()) monitor := &DnsMonitor{ - logger: logger, - interval: interval, + logger: logger, + interval: interval, + lookupFunc: lookupFunc, stopCtx: stopCtx, stopFunc: stopFunc, @@ -347,7 +350,7 @@ func (m *DnsMonitor) checkHostname(entry *dnsMonitorEntry) { return } - ips, err := lookupDnsMonitorIP(entry.hostname) + ips, err := m.lookupFunc(entry.hostname) if err != nil { m.logger.Printf("Could not lookup %s: %s", entry.hostname, err) return diff --git a/dns_monitor_test.go b/dns_monitor_test.go index 3feafde..68afc81 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -44,15 +44,9 @@ type mockDnsLookup struct { func newMockDnsLookupForTest(t *testing.T) *mockDnsLookup { t.Helper() - t.Setenv("PARALLEL_CHECK", "1") mock := &mockDnsLookup{ ips: make(map[string][]net.IP), } - prev := lookupDnsMonitorIP - t.Cleanup(func() { - lookupDnsMonitorIP = prev - }) - lookupDnsMonitorIP = mock.lookup return mock } @@ -86,12 +80,16 @@ func (m *mockDnsLookup) lookup(host string) ([]net.IP, error) { return append([]net.IP{}, ips...), nil } -func newDnsMonitorForTest(t *testing.T, interval time.Duration) *DnsMonitor { +func newDnsMonitorForTest(t *testing.T, interval time.Duration, lookup *mockDnsLookup) *DnsMonitor { t.Helper() require := require.New(t) logger := NewLoggerForTest(t) - monitor, err := NewDnsMonitor(logger, interval) + var lookupFunc DnsMonitorLookupFunc + if lookup != nil { + lookupFunc = lookup.lookup + } + monitor, err := NewDnsMonitor(logger, interval, lookupFunc) require.NoError(err) t.Cleanup(func() { @@ -223,13 +221,14 @@ func (r *dnsMonitorReceiver) ExpectNone() { r.expected = expectNone } -func TestDnsMonitor(t *testing.T) { // nolint:paralleltest +func TestDnsMonitor(t *testing.T) { + t.Parallel() lookup := newMockDnsLookupForTest(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval) + monitor := newDnsMonitorForTest(t, interval, lookup) ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.1.1") @@ -297,7 +296,7 @@ func TestDnsMonitorIP(t *testing.T) { defer cancel() interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval) + monitor := newDnsMonitorForTest(t, interval, nil) ip := "192.168.0.1" ips := []net.IP{ @@ -317,9 +316,10 @@ func TestDnsMonitorIP(t *testing.T) { time.Sleep(5 * interval) } -func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { // nolint:paralleltest +func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { + t.Parallel() interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval) + monitor := newDnsMonitorForTest(t, interval, nil) var checked atomic.Bool monitor.checkHostnames = func() { @@ -402,14 +402,15 @@ func (r *deadlockMonitorReceiver) Close() { r.wg.Wait() } -func TestDnsMonitorDeadlock(t *testing.T) { // nolint:paralleltest +func TestDnsMonitorDeadlock(t *testing.T) { + t.Parallel() lookup := newMockDnsLookupForTest(t) ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") lookup.Set("foo", []net.IP{ip1}) interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval) + monitor := newDnsMonitorForTest(t, interval, lookup) r := newDeadlockMonitorReceiver(t, monitor) r.Start() diff --git a/grpc_client_test.go b/grpc_client_test.go index 825d982..66100da 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -51,8 +51,8 @@ func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { return ch } -func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient *EtcdClient) (*GrpcClients, *DnsMonitor) { - dnsMonitor := newDnsMonitorForTest(t, time.Hour) // will be updated manually +func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient *EtcdClient, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { + dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := NewLoggerForTest(t) ctx := NewLoggerContext(t.Context(), logger) client, err := NewGrpcClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") @@ -64,15 +64,15 @@ func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, et return client, dnsMonitor } -func NewGrpcClientsForTest(t *testing.T, addr string) (*GrpcClients, *DnsMonitor) { +func NewGrpcClientsForTest(t *testing.T, addr string, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { config := goconf.NewConfigFile() config.AddOption("grpc", "targets", addr) config.AddOption("grpc", "dnsdiscovery", "true") - return NewGrpcClientsForTestWithConfig(t, config, nil) + return NewGrpcClientsForTestWithConfig(t, config, nil, lookup) } -func NewGrpcClientsWithEtcdForTest(t *testing.T, etcd *embed.Etcd) (*GrpcClients, *DnsMonitor) { +func NewGrpcClientsWithEtcdForTest(t *testing.T, etcd *embed.Etcd, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) @@ -86,7 +86,7 @@ func NewGrpcClientsWithEtcdForTest(t *testing.T, etcd *embed.Etcd) (*GrpcClients assert.NoError(t, etcdClient.Close()) }) - return NewGrpcClientsForTestWithConfig(t, config, etcdClient) + return NewGrpcClientsForTestWithConfig(t, config, etcdClient, lookup) } func drainWakeupChannel(ch <-chan struct{}) { @@ -122,7 +122,7 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - client, _ := NewGrpcClientsWithEtcdForTest(t, etcd) + client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() require.NoError(t, client.WaitForInitialized(ctx)) @@ -138,7 +138,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd := NewEtcdForTest(t) - client, _ := NewGrpcClientsWithEtcdForTest(t, etcd) + client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) ch := client.getWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) @@ -185,7 +185,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { ctx := NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd := NewEtcdForTest(t) - client, _ := NewGrpcClientsWithEtcdForTest(t, etcd) + client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) ch := client.getWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) @@ -232,7 +232,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) targetWithIp2 := fmt.Sprintf("%s (%s)", target, ip2) lookup.Set("testgrpc", []net.IP{ip1}) - client, dnsMonitor := NewGrpcClientsForTest(t, target) + client, dnsMonitor := NewGrpcClientsForTest(t, target, lookup) ch := client.getWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) @@ -274,13 +274,14 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest }) } -func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { // nolint:paralleltest +func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { + t.Parallel() assert := assert.New(t) lookup := newMockDnsLookupForTest(t) target := "testgrpc:12345" ip1 := net.ParseIP("192.168.0.1") targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) - client, dnsMonitor := NewGrpcClientsForTest(t, target) + client, dnsMonitor := NewGrpcClientsForTest(t, target, lookup) ch := client.getWakeupChannelForTesting() testCtx, testCtxCancel := context.WithTimeout(context.Background(), testTimeout) @@ -339,7 +340,7 @@ func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest clientConfig.AddOption("grpc", "clientcertificate", clientCertFile) clientConfig.AddOption("grpc", "clientkey", clientPrivkeyFile) clientConfig.AddOption("grpc", "serverca", serverCertFile) - clients, _ := NewGrpcClientsForTestWithConfig(t, clientConfig, nil) + clients, _ := NewGrpcClientsForTestWithConfig(t, clientConfig, nil, nil) ctx, cancel1 := context.WithTimeout(context.Background(), time.Second) defer cancel1() diff --git a/hub_test.go b/hub_test.go index 9f9d39d..27ded33 100644 --- a/hub_test.go +++ b/hub_test.go @@ -246,7 +246,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http }) config1, err := getConfigFunc(server1) require.NoError(err) - client1, _ := NewGrpcClientsForTest(t, addr2) + client1, _ := NewGrpcClientsForTest(t, addr2, nil) h1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") require.NoError(err) b1, err := NewBackendServer(ctx, config1, h1, "no-version") @@ -260,7 +260,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http }) config2, err := getConfigFunc(server2) require.NoError(err) - client2, _ := NewGrpcClientsForTest(t, addr1) + client2, _ := NewGrpcClientsForTest(t, addr1, nil) h2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") require.NoError(err) b2, err := NewBackendServer(ctx, config2, h2, "no-version") diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 8c20f4d..641f5d7 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -845,13 +845,13 @@ type proxyTestOptions struct { servers []*TestProxyServerHandler } -func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int) (*mcuProxy, *goconf.ConfigFile) { +func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int, lookup *mockDnsLookup) (*mcuProxy, *goconf.ConfigFile) { t.Helper() require := require.New(t) if options.etcd == nil { options.etcd = NewEtcdForTest(t) } - grpcClients, dnsMonitor := NewGrpcClientsWithEtcdForTest(t, options.etcd) + grpcClients, dnsMonitor := NewGrpcClientsWithEtcdForTest(t, options.etcd, lookup) tokenKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) @@ -933,20 +933,20 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i return proxy, cfg } -func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int) *mcuProxy { +func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int, lookup *mockDnsLookup) *mcuProxy { t.Helper() proxy, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: servers, - }, idx) + }, idx, lookup) return proxy } -func newMcuProxyForTest(t *testing.T, idx int) *mcuProxy { +func newMcuProxyForTest(t *testing.T, idx int, lookup *mockDnsLookup) *mcuProxy { t.Helper() server := NewProxyServerForTest(t, "DE") - return newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{server}, idx) + return newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{server}, idx, lookup) } func Test_ProxyAddRemoveConnections(t *testing.T) { @@ -961,7 +961,7 @@ func Test_ProxyAddRemoveConnections(t *testing.T) { servers: []*TestProxyServerHandler{ server1, }, - }, 0) + }, 0, nil) server2 := NewProxyServerForTest(t, "DE") server1.servers = append(server1.servers, server2) @@ -1023,7 +1023,8 @@ func Test_ProxyAddRemoveConnections(t *testing.T) { assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") } -func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { // nolint:paralleltest +func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { + t.Parallel() assert := assert.New(t) require := require.New(t) @@ -1052,7 +1053,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { // nolint:parall servers: []*TestProxyServerHandler{ server1, }, - }, 0) + }, 0, lookup) if connections := mcu.getConnections(); assert.Len(connections, 1) && assert.NotNil(connections[0].ip) { assert.True(ip1.Equal(connections[0].ip), "ip addresses differ: expected %s, got %s", ip1.String(), connections[0].ip.String()) @@ -1141,7 +1142,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { // nolint:parall func Test_ProxyPublisherSubscriber(t *testing.T) { t.Parallel() - mcu := newMcuProxyForTest(t, 0) + mcu := newMcuProxyForTest(t, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1176,7 +1177,7 @@ func Test_ProxyPublisherSubscriber(t *testing.T) { func Test_ProxyPublisherCodecs(t *testing.T) { t.Parallel() - mcu := newMcuProxyForTest(t, 0) + mcu := newMcuProxyForTest(t, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1202,7 +1203,7 @@ func Test_ProxyPublisherCodecs(t *testing.T) { func Test_ProxyWaitForPublisher(t *testing.T) { t.Parallel() - mcu := newMcuProxyForTest(t, 0) + mcu := newMcuProxyForTest(t, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1256,7 +1257,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ server1, server2, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1325,7 +1326,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ server1, server2, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1397,7 +1398,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ server1, server2, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1446,7 +1447,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1493,7 +1494,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1540,7 +1541,7 @@ func Test_ProxySubscriberCountry(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1583,7 +1584,7 @@ func Test_ProxySubscriberContinent(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1626,7 +1627,7 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1689,7 +1690,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ serverDE, serverUS, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -1824,7 +1825,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { server1, server2, }, - }, 1) + }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, @@ -1832,7 +1833,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { server1, server2, }, - }, 2) + }, 2, nil) hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) @@ -1909,7 +1910,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2, server3, }, - }, 1) + }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, @@ -1918,7 +1919,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2, server3, }, - }, 2) + }, 2, nil) hub2.proxy.Store(mcu2) mcu3, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, @@ -1927,7 +1928,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { server2, server3, }, - }, 3) + }, 3, nil) hub3.proxy.Store(mcu3) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) @@ -2009,7 +2010,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { server1, server2, }, - }, 1) + }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, @@ -2017,7 +2018,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { server1, server2, }, - }, 2) + }, 2, nil) hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) @@ -2103,14 +2104,14 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { servers: []*TestProxyServerHandler{ server1, }, - }, 1) + }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, }, - }, 2) + }, 2, nil) hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) @@ -2214,14 +2215,14 @@ func Test_ProxyConnectToken(t *testing.T) { servers: []*TestProxyServerHandler{ server1, }, - }, 1) + }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, }, - }, 2) + }, 2, nil) hub2.proxy.Store(mcu2) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) @@ -2296,14 +2297,14 @@ func Test_ProxyPublisherToken(t *testing.T) { servers: []*TestProxyServerHandler{ server1, }, - }, 1) + }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ etcd: etcd, servers: []*TestProxyServerHandler{ server2, }, - }, 2) + }, 2, nil) hub2.proxy.Store(mcu2) // Support remote subscribers for the tests. server1.servers = append(server1.servers, server2) @@ -2359,7 +2360,7 @@ func Test_ProxyPublisherTimeout(t *testing.T) { server := NewProxyServerForTest(t, "DE") mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -2399,7 +2400,7 @@ func Test_ProxySubscriberTimeout(t *testing.T) { server := NewProxyServerForTest(t, "DE") mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, - }, 0) + }, 0, nil) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() @@ -2459,7 +2460,7 @@ func Test_ProxyReconnectAfter(t *testing.T) { server := NewProxyServerForTest(t, "DE") mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, - }, 0) + }, 0, nil) connections := mcu.getSortedConnections(nil) require.Len(connections, 1) @@ -2499,7 +2500,7 @@ func Test_ProxyReconnectAfterShutdown(t *testing.T) { server := NewProxyServerForTest(t, "DE") mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, - }, 0) + }, 0, nil) connections := mcu.getSortedConnections(nil) require.Len(connections, 1) @@ -2538,7 +2539,7 @@ func Test_ProxyResume(t *testing.T) { server := NewProxyServerForTest(t, "DE") mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, - }, 0) + }, 0, nil) connections := mcu.getSortedConnections(nil) require.Len(connections, 1) @@ -2570,7 +2571,7 @@ func Test_ProxyResumeFail(t *testing.T) { server := NewProxyServerForTest(t, "DE") mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ servers: []*TestProxyServerHandler{server}, - }, 0) + }, 0, nil) connections := mcu.getSortedConnections(nil) require.Len(connections, 1) diff --git a/proxy_config_static_test.go b/proxy_config_static_test.go index 7c4de25..867b11f 100644 --- a/proxy_config_static_test.go +++ b/proxy_config_static_test.go @@ -31,13 +31,13 @@ import ( "github.com/stretchr/testify/require" ) -func newProxyConfigStatic(t *testing.T, proxy McuProxy, dns bool, urls ...string) (ProxyConfig, *DnsMonitor) { +func newProxyConfigStatic(t *testing.T, proxy McuProxy, dns bool, lookup *mockDnsLookup, urls ...string) (ProxyConfig, *DnsMonitor) { cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "url", strings.Join(urls, " ")) if dns { cfg.AddOption("mcu", "dnsdiscovery", "true") } - dnsMonitor := newDnsMonitorForTest(t, time.Hour) // will be updated manually + dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := NewLoggerForTest(t) p, err := NewProxyConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) @@ -59,7 +59,7 @@ func updateProxyConfigStatic(t *testing.T, config ProxyConfig, dns bool, urls .. func TestProxyConfigStaticSimple(t *testing.T) { t.Parallel() proxy := newMcuProxyForConfig(t) - config, _ := newProxyConfigStatic(t, proxy, false, "https://foo/") + config, _ := newProxyConfigStatic(t, proxy, false, nil, "https://foo/") proxy.Expect("add", "https://foo/") require.NoError(t, config.Start()) @@ -73,10 +73,11 @@ func TestProxyConfigStaticSimple(t *testing.T) { updateProxyConfigStatic(t, config, false, "https://bar/", "https://baz/") } -func TestProxyConfigStaticDNS(t *testing.T) { // nolint:paralleltest +func TestProxyConfigStaticDNS(t *testing.T) { + t.Parallel() lookup := newMockDnsLookupForTest(t) proxy := newMcuProxyForConfig(t) - config, dnsMonitor := newProxyConfigStatic(t, proxy, true, "https://foo/") + config, dnsMonitor := newProxyConfigStatic(t, proxy, true, lookup, "https://foo/") require.NoError(t, config.Start()) time.Sleep(time.Millisecond) diff --git a/server/main.go b/server/main.go index fd34249..9f74b07 100644 --- a/server/main.go +++ b/server/main.go @@ -192,7 +192,7 @@ func main() { } }() - dnsMonitor, err := signaling.NewDnsMonitor(logger, dnsMonitorInterval) + dnsMonitor, err := signaling.NewDnsMonitor(logger, dnsMonitorInterval, nil) if err != nil { logger.Fatal("Could not create DNS monitor: ", err) } From b86d05de088cfc78642977f19c2a96cbea5f2c8c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 5 Dec 2025 12:15:26 +0100 Subject: [PATCH 359/549] Allow running file watcher tests to run in parallel. --- certificate_reloader.go | 17 ++++++++++++++--- certificate_reloader_test.go | 13 ------------- file_watcher.go | 36 ++++++++++++++---------------------- file_watcher_test.go | 18 +++++++++--------- grpc_server_test.go | 8 ++++---- 5 files changed, 41 insertions(+), 51 deletions(-) diff --git a/certificate_reloader.go b/certificate_reloader.go index 373b503..b04429d 100644 --- a/certificate_reloader.go +++ b/certificate_reloader.go @@ -27,6 +27,7 @@ import ( "fmt" "os" "sync/atomic" + "testing" ) type CertificateReloader struct { @@ -49,17 +50,22 @@ func NewCertificateReloader(logger Logger, certFile string, keyFile string) (*Ce return nil, fmt.Errorf("could not load certificate / key: %w", err) } + deduplicate := defaultDeduplicateWatchEvents + if testing.Testing() { + deduplicate = 0 + } + reloader := &CertificateReloader{ logger: logger, certFile: certFile, keyFile: keyFile, } reloader.certificate.Store(&pair) - reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload) + reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload, deduplicate) if err != nil { return nil, err } - reloader.keyWatcher, err = NewFileWatcher(reloader.logger, keyFile, reloader.reload) + reloader.keyWatcher, err = NewFileWatcher(reloader.logger, keyFile, reloader.reload, deduplicate) if err != nil { reloader.certWatcher.Close() // nolint return nil, err @@ -132,12 +138,17 @@ func NewCertPoolReloader(logger Logger, certFile string) (*CertPoolReloader, err return nil, err } + deduplicate := defaultDeduplicateWatchEvents + if testing.Testing() { + deduplicate = 0 + } + reloader := &CertPoolReloader{ logger: logger, certFile: certFile, } reloader.pool.Store(pool) - reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload) + reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload, deduplicate) if err != nil { return nil, err } diff --git a/certificate_reloader_test.go b/certificate_reloader_test.go index 9e50fc6..f8be0a4 100644 --- a/certificate_reloader_test.go +++ b/certificate_reloader_test.go @@ -23,22 +23,9 @@ package signaling import ( "context" - "testing" "time" ) -func UpdateCertificateCheckIntervalForTest(t *testing.T, interval time.Duration) { - t.Helper() - // Make sure test is not executed with "t.Parallel()" - t.Setenv("PARALLEL_CHECK", "1") - old := deduplicateWatchEvents.Load() - t.Cleanup(func() { - deduplicateWatchEvents.Store(old) - }) - - deduplicateWatchEvents.Store(int64(interval)) -} - func (r *CertificateReloader) WaitForReload(ctx context.Context, counter uint64) error { for counter == r.GetReloadCounter() { if err := ctx.Err(); err != nil { diff --git a/file_watcher.go b/file_watcher.go index 489ed10..99592aa 100644 --- a/file_watcher.go +++ b/file_watcher.go @@ -29,7 +29,6 @@ import ( "path/filepath" "strings" "sync" - "sync/atomic" "time" "github.com/fsnotify/fsnotify" @@ -39,28 +38,21 @@ const ( defaultDeduplicateWatchEvents = 100 * time.Millisecond ) -var ( - deduplicateWatchEvents atomic.Int64 -) - -func init() { - deduplicateWatchEvents.Store(int64(defaultDeduplicateWatchEvents)) -} - type FileWatcherCallback func(filename string) type FileWatcher struct { - logger Logger - filename string - target string - callback FileWatcherCallback + logger Logger + filename string + target string + callback FileWatcherCallback + deduplicate time.Duration watcher *fsnotify.Watcher closeCtx context.Context closeFunc context.CancelFunc } -func NewFileWatcher(logger Logger, filename string, callback FileWatcherCallback) (*FileWatcher, error) { +func NewFileWatcher(logger Logger, filename string, callback FileWatcherCallback, deduplicate time.Duration) (*FileWatcher, error) { watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err @@ -74,10 +66,11 @@ func NewFileWatcher(logger Logger, filename string, callback FileWatcherCallback closeCtx, closeFunc := context.WithCancel(context.Background()) w := &FileWatcher{ - logger: logger, - filename: filename, - callback: callback, - watcher: watcher, + logger: logger, + filename: filename, + callback: callback, + deduplicate: deduplicate, + watcher: watcher, closeCtx: closeCtx, closeFunc: closeFunc, @@ -115,8 +108,7 @@ func (f *FileWatcher) run() { timers := make(map[string]*time.Timer) triggerEvent := func(event fsnotify.Event) { - deduplicate := time.Duration(deduplicateWatchEvents.Load()) - if deduplicate <= 0 { + if f.deduplicate <= 0 { f.callback(f.filename) return } @@ -128,7 +120,7 @@ func (f *FileWatcher) run() { t, found := timers[filename] mu.Unlock() if !found { - t = time.AfterFunc(deduplicate, func() { + t = time.AfterFunc(f.deduplicate, func() { f.callback(f.filename) mu.Lock() @@ -139,7 +131,7 @@ func (f *FileWatcher) run() { timers[filename] = t mu.Unlock() } else { - t.Reset(deduplicate) + t.Reset(f.deduplicate) } } diff --git a/file_watcher_test.go b/file_watcher_test.go index f437193..2749738 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -40,7 +40,7 @@ func TestFileWatcher_NotExist(t *testing.T) { assert := assert.New(t) tmpdir := t.TempDir() logger := NewLoggerForTest(t) - if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}); !assert.ErrorIs(err, os.ErrNotExist) { + if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}, defaultDeduplicateWatchEvents); !assert.ErrorIs(err, os.ErrNotExist) { if w != nil { assert.NoError(w.Close()) } @@ -59,7 +59,7 @@ func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -102,7 +102,7 @@ func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest modified := make(chan struct{}) w, err := NewFileWatcher(logger, "./"+path.Base(filename), func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -144,7 +144,7 @@ func TestFileWatcher_Rename(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -188,7 +188,7 @@ func TestFileWatcher_Symlink(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -223,7 +223,7 @@ func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -260,7 +260,7 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -291,7 +291,7 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -345,7 +345,7 @@ func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }) + }, defaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() diff --git a/grpc_server_test.go b/grpc_server_test.go index c476ac1..0db5b10 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -97,7 +97,8 @@ func NewGrpcServerForTest(t *testing.T) (server *GrpcServer, addr string) { return NewGrpcServerForTestWithConfig(t, config) } -func Test_GrpcServer_ReloadCerts(t *testing.T) { // nolint:paralleltest +func Test_GrpcServer_ReloadCerts(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) key, err := rsa.GenerateKey(rand.Reader, 1024) @@ -118,7 +119,6 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { // nolint:paralleltest config.AddOption("grpc", "servercertificate", certFile) config.AddOption("grpc", "serverkey", privkeyFile) - UpdateCertificateCheckIntervalForTest(t, 0) server, addr := NewGrpcServerForTestWithConfig(t, config) cp1 := x509.NewCertPool() @@ -167,7 +167,8 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { // nolint:paralleltest } } -func Test_GrpcServer_ReloadCA(t *testing.T) { // nolint:paralleltest +func Test_GrpcServer_ReloadCA(t *testing.T) { + t.Parallel() logger := NewLoggerForTest(t) require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) @@ -194,7 +195,6 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { // nolint:paralleltest config.AddOption("grpc", "serverkey", privkeyFile) config.AddOption("grpc", "clientca", caFile) - UpdateCertificateCheckIntervalForTest(t, 0) server, addr := NewGrpcServerForTestWithConfig(t, config) pool := x509.NewCertPool() From 958f50cec3dcc6c0de4b918bbf60dbe43f3604ff Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 09:10:28 +0100 Subject: [PATCH 360/549] Allow running backend configuration tests in parallel. --- backend_configuration.go | 44 +++++++- backend_configuration_test.go | 185 ++++++++++++++++++++-------------- backend_storage_etcd.go | 7 +- backend_storage_static.go | 11 +- 4 files changed, 158 insertions(+), 89 deletions(-) diff --git a/backend_configuration.go b/backend_configuration.go index d9be669..c7a485d 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -171,10 +171,19 @@ type BackendStorage interface { GetBackends() []*Backend } +type BackendStorageStats interface { + AddBackends(count int) + RemoveBackends(count int) + IncBackends() + DecBackends() +} + type backendStorageCommon struct { mu sync.RWMutex // +checklocks:mu backends map[string][]*Backend + + stats BackendStorageStats // +checklocksignore: Only written to from constructor } func (s *backendStorageCommon) GetBackends() []*Backend { @@ -224,21 +233,50 @@ type BackendConfiguration struct { storage BackendStorage } +type prometheusBackendStats struct{} + +func (s *prometheusBackendStats) AddBackends(count int) { + statsBackendsCurrent.Add(float64(count)) +} + +func (s *prometheusBackendStats) RemoveBackends(count int) { + statsBackendsCurrent.Sub(float64(count)) +} + +func (s *prometheusBackendStats) IncBackends() { + statsBackendsCurrent.Inc() +} + +func (s *prometheusBackendStats) DecBackends() { + statsBackendsCurrent.Dec() +} + +var ( + defaultBackendStats = &prometheusBackendStats{} +) + func NewBackendConfiguration(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (*BackendConfiguration, error) { + return NewBackendConfigurationWithStats(logger, config, etcdClient, nil) +} + +func NewBackendConfigurationWithStats(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (*BackendConfiguration, error) { backendType, _ := config.GetString("backend", "backendtype") if backendType == "" { backendType = DefaultBackendType } - RegisterBackendConfigurationStats() + if stats == nil { + RegisterBackendConfigurationStats() + stats = defaultBackendStats + } var storage BackendStorage var err error switch backendType { case BackendTypeStatic: - storage, err = NewBackendStorageStatic(logger, config) + storage, err = NewBackendStorageStatic(logger, config, stats) case BackendTypeEtcd: - storage, err = NewBackendStorageEtcd(logger, config, etcdClient) + storage, err = NewBackendStorageEtcd(logger, config, etcdClient, stats) default: err = fmt.Errorf("unknown backend type: %s", backendType) } diff --git a/backend_configuration_test.go b/backend_configuration_test.go index 12e9fd1..961b555 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -84,6 +84,26 @@ func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]str } } +type mockBackendStats struct { + value int +} + +func (s *mockBackendStats) AddBackends(count int) { + s.value += count +} + +func (s *mockBackendStats) RemoveBackends(count int) { + s.value -= count +} + +func (s *mockBackendStats) IncBackends() { + s.value++ +} + +func (s *mockBackendStats) DecBackends() { + s.value-- +} + func TestIsUrlAllowed_Compat(t *testing.T) { t.Parallel() logger := NewLoggerForTest(t) @@ -233,11 +253,13 @@ func TestParseBackendIds(t *testing.T) { } } -func TestBackendReloadNoChange(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendReloadNoChange(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) + assert := assert.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -245,9 +267,9 @@ func TestBackendReloadNoChange(t *testing.T) { // nolint:paralleltest original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(logger, original_config, nil) + o_cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") @@ -256,22 +278,24 @@ func TestBackendReloadNoChange(t *testing.T) { // nolint:paralleltest new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - n_cfg, err := NewBackendConfiguration(logger, new_config, nil) + n_cfg, err := NewBackendConfigurationWithStats(logger, new_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(4, stats.value) o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(4, stats.value) if !reflect.DeepEqual(n_cfg, o_cfg) { - assert.Fail(t, "BackendConfiguration should be equal after Reload") + assert.Fail("BackendConfiguration should be equal after Reload") } } -func TestBackendReloadChangeExistingURL(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendReloadChangeExistingURL(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) + assert := assert.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -279,10 +303,10 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { // nolint:paralleltest original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(logger, original_config, nil) + o_cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -291,26 +315,28 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { // nolint:paralleltest new_config.AddOption("backend1", "sessionlimit", "10") new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - n_cfg, err := NewBackendConfiguration(logger, new_config, nil) + n_cfg, err := NewBackendConfigurationWithStats(logger, new_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(4, stats.value) original_config.RemoveOption("backend1", "url") original_config.AddOption("backend1", "url", "http://domain3.invalid") original_config.AddOption("backend1", "sessionlimit", "10") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(4, stats.value) if !reflect.DeepEqual(n_cfg, o_cfg) { - assert.Fail(t, "BackendConfiguration should be equal after Reload") + assert.Fail("BackendConfiguration should be equal after Reload") } } -func TestBackendReloadChangeSecret(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendReloadChangeSecret(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) + assert := assert.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -318,10 +344,10 @@ func TestBackendReloadChangeSecret(t *testing.T) { // nolint:paralleltest original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(logger, original_config, nil) + o_cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -329,32 +355,34 @@ func TestBackendReloadChangeSecret(t *testing.T) { // nolint:paralleltest new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3") new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - n_cfg, err := NewBackendConfiguration(logger, new_config, nil) + n_cfg, err := NewBackendConfigurationWithStats(logger, new_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(4, stats.value) original_config.RemoveOption("backend1", "secret") original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, 4) - assert.Equal(t, n_cfg, o_cfg, "BackendConfiguration should be equal after Reload") + assert.Equal(4, stats.value) + assert.Equal(n_cfg, o_cfg, "BackendConfiguration should be equal after Reload") } -func TestBackendReloadAddBackend(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendReloadAddBackend(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) + assert := assert.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend1", "url", "http://domain1.invalid") original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") - o_cfg, err := NewBackendConfiguration(logger, original_config, nil) + o_cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -363,10 +391,10 @@ func TestBackendReloadAddBackend(t *testing.T) { // nolint:paralleltest new_config.AddOption("backend2", "url", "http://domain2.invalid") new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") new_config.AddOption("backend2", "sessionlimit", "10") - n_cfg, err := NewBackendConfiguration(logger, new_config, nil) + n_cfg, err := NewBackendConfigurationWithStats(logger, new_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 3) + assert.Equal(3, stats.value) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend2", "url", "http://domain2.invalid") @@ -374,17 +402,19 @@ func TestBackendReloadAddBackend(t *testing.T) { // nolint:paralleltest original_config.AddOption("backend2", "sessionlimit", "10") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, 4) + assert.Equal(4, stats.value) if !reflect.DeepEqual(n_cfg, o_cfg) { - assert.Fail(t, "BackendConfiguration should be equal after Reload") + assert.Fail("BackendConfiguration should be equal after Reload") } } -func TestBackendReloadRemoveHost(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendReloadRemoveHost(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) + assert := assert.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -392,35 +422,37 @@ func TestBackendReloadRemoveHost(t *testing.T) { // nolint:paralleltest original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(logger, original_config, nil) + o_cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend1", "url", "http://domain1.invalid") new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") - n_cfg, err := NewBackendConfiguration(logger, new_config, nil) + n_cfg, err := NewBackendConfigurationWithStats(logger, new_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 3) + assert.Equal(3, stats.value) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1") original_config.RemoveSection("backend2") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) if !reflect.DeepEqual(n_cfg, o_cfg) { - assert.Fail(t, "BackendConfiguration should be equal after Reload") + assert.Fail("BackendConfiguration should be equal after Reload") } } -func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) + assert := assert.New(t) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -428,27 +460,27 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { // nolint:para original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") original_config.AddOption("backend2", "url", "http://domain1.invalid/bar/") original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2") - o_cfg, err := NewBackendConfiguration(logger, original_config, nil) + o_cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend1", "url", "http://domain1.invalid/foo/") new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1") - n_cfg, err := NewBackendConfiguration(logger, new_config, nil) + n_cfg, err := NewBackendConfigurationWithStats(logger, new_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 3) + assert.Equal(3, stats.value) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1") original_config.RemoveSection("backend2") o_cfg.Reload(original_config) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) if !reflect.DeepEqual(n_cfg, o_cfg) { - assert.Fail(t, "BackendConfiguration should be equal after Reload") + assert.Fail("BackendConfiguration should be equal after Reload") } } @@ -468,8 +500,9 @@ func mustParse(s string) *url.URL { return p } -func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendConfiguration_EtcdCompat(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) @@ -486,9 +519,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest config.AddOption("backend", "backendtype", "etcd") config.AddOption("backend", "backendprefix", "/backends") - checkStatsValue(t, statsBackendsCurrent, 0) - - cfg, err := NewBackendConfiguration(logger, config, client) + cfg, err := NewBackendConfigurationWithStats(logger, config, client, stats) require.NoError(err) defer cfg.Close() @@ -511,7 +542,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+secret1+"\"}")) <-ch - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) { @@ -526,7 +557,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/2_two", []byte("{\"url\":\""+url2+"\",\"secret\":\""+secret2+"\"}")) <-ch - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) && @@ -545,7 +576,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/3_three", []byte("{\"url\":\""+url3+"\",\"secret\":\""+secret3+"\"}")) <-ch - checkStatsValue(t, statsBackendsCurrent, 3) + assert.Equal(3, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 3) && assert.Equal([]string{url1}, backends[0].urls) && assert.Equal(secret1, string(backends[0].secret)) && @@ -565,7 +596,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest drainWakeupChannel(ch) DeleteEtcdValue(etcd, "/backends/1_one") <-ch - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) { assert.Equal([]string{url2}, backends[0].urls) assert.Equal(secret2, string(backends[0].secret)) @@ -576,7 +607,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { // nolint:paralleltest drainWakeupChannel(ch) DeleteEtcdValue(etcd, "/backends/2_two") <-ch - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { assert.Equal([]string{url3}, backends[0].urls) assert.Equal(secret3, string(backends[0].secret)) @@ -629,8 +660,9 @@ func TestBackendCommonSecret(t *testing.T) { } } -func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendChangeUrls(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) @@ -645,12 +677,10 @@ func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest original_config.AddOption("backend1", "urls", u1.String()) original_config.AddOption("backend2", "urls", u2.String()) - checkStatsValue(t, statsBackendsCurrent, 0) - - cfg, err := NewBackendConfiguration(logger, original_config, nil) + cfg, err := NewBackendConfigurationWithStats(logger, original_config, nil, stats) require.NoError(err) - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret), string(b1.Secret())) @@ -670,7 +700,7 @@ func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest updated_config.AddOption("backend1", "urls", strings.Join([]string{u1.String(), u2.String()}, ",")) cfg.Reload(updated_config) - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) @@ -684,7 +714,7 @@ func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest // No change reload. cfg.Reload(updated_config) - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if b1 := cfg.GetBackend(u1); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret)+"-backend1", string(b1.Secret())) @@ -703,7 +733,7 @@ func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest updated_config.AddOption("backend1", "urls", u2.String()) cfg.Reload(updated_config) - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if b1 := cfg.GetBackend(u2); assert.NotNil(b1) { assert.Equal("backend1", b1.Id()) assert.Equal(string(testBackendSecret), string(b1.Secret())) @@ -715,13 +745,14 @@ func TestBackendChangeUrls(t *testing.T) { // nolint:paralleltest updated_config.AddOption("backend", "secret", string(testBackendSecret)) cfg.Reload(updated_config) - checkStatsValue(t, statsBackendsCurrent, 0) + assert.Equal(0, stats.value) b1 := cfg.GetBackend(u2) assert.Nil(b1) } -func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsBackendsCurrent) +func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { + t.Parallel() + stats := &mockBackendStats{} logger := NewLoggerForTest(t) require := require.New(t) @@ -738,9 +769,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:parallelt config.AddOption("backend", "backendtype", "etcd") config.AddOption("backend", "backendprefix", "/backends") - checkStatsValue(t, statsBackendsCurrent, 0) - - cfg, err := NewBackendConfiguration(logger, config, client) + cfg, err := NewBackendConfigurationWithStats(logger, config, client, stats) require.NoError(err) defer cfg.Close() @@ -752,7 +781,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:parallelt require.NoError(storage.WaitForInitialized(ctx)) - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal([]string{url1}, backends[0].Urls()) && assert.Equal(initialSecret1, string(backends[0].Secret())) { @@ -766,7 +795,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:parallelt drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/1_one", []byte("{\"urls\":[\""+url1+"\",\""+url2+"\"],\"secret\":\""+secret1+"\"}")) <-ch - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && assert.Equal([]string{url2, url1}, backends[0].Urls()) && assert.Equal(secret1, string(backends[0].Secret())) { @@ -786,7 +815,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:parallelt drainWakeupChannel(ch) SetEtcdValue(etcd, "/backends/3_three", []byte("{\"urls\":[\""+url3+"\",\""+url4+"\"],\"secret\":\""+secret3+"\"}")) <-ch - checkStatsValue(t, statsBackendsCurrent, 2) + assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && assert.Equal([]string{url2, url1}, backends[0].Urls()) && assert.Equal(secret1, string(backends[0].Secret())) && @@ -806,7 +835,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:parallelt drainWakeupChannel(ch) DeleteEtcdValue(etcd, "/backends/1_one") <-ch - checkStatsValue(t, statsBackendsCurrent, 1) + assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { assert.Equal([]string{url3, url4}, backends[0].Urls()) assert.Equal(secret3, string(backends[0].Secret())) @@ -816,7 +845,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { // nolint:parallelt DeleteEtcdValue(etcd, "/backends/3_three") <-ch - checkStatsValue(t, statsBackendsCurrent, 0) + assert.Equal(0, stats.value) storage.mu.RLock() _, found := storage.backends["domain1.invalid"] storage.mu.RUnlock() diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 4e210c8..9f7a841 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -49,7 +49,7 @@ type backendStorageEtcd struct { closeFunc context.CancelFunc } -func NewBackendStorageEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (BackendStorage, error) { +func NewBackendStorageEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (BackendStorage, error) { if etcdClient == nil || !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } @@ -64,6 +64,7 @@ func NewBackendStorageEtcd(logger Logger, config *goconf.ConfigFile, etcdClient result := &backendStorageEtcd{ backendStorageCommon: backendStorageCommon{ backends: make(map[string][]*Backend), + stats: stats, }, logger: logger, etcdClient: etcdClient, @@ -231,7 +232,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data } updateBackendStats(backend) if added { - statsBackendsCurrent.Inc() + s.stats.IncBackends() } s.wakeupForTesting() } @@ -275,7 +276,7 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev if !seen[entry.Id()] { seen[entry.Id()] = true updateBackendStats(entry) - statsBackendsCurrent.Dec() + s.stats.DecBackends() } continue } diff --git a/backend_storage_static.go b/backend_storage_static.go index dc90dfa..903e35a 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -43,7 +43,7 @@ type backendStorageStatic struct { compatBackend *Backend } -func NewBackendStorageStatic(logger Logger, config *goconf.ConfigFile) (BackendStorage, error) { +func NewBackendStorageStatic(logger Logger, config *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { allowAll, _ := config.GetBool("backend", "allowall") allowHttp, _ := config.GetBool("backend", "allowhttp") commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") @@ -157,10 +157,11 @@ func NewBackendStorageStatic(logger Logger, config *goconf.ConfigFile) (BackendS logger.Printf("WARNING: No backends configured, client connections will not be possible.") } - statsBackendsCurrent.Add(float64(numBackends)) + stats.AddBackends(numBackends) return &backendStorageStatic{ backendStorageCommon: backendStorageCommon{ backends: backends, + stats: stats, }, logger: logger, @@ -196,7 +197,7 @@ func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[strin backend.counted = false } } - statsBackendsCurrent.Sub(float64(deleted)) + s.stats.RemoveBackends(deleted) } delete(s.backends, host) } @@ -247,7 +248,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen if len(urls) == len(removed.urls) && removed.counted { deleteBackendStats(removed) delete(s.backendsById, removed.Id()) - statsBackendsCurrent.Dec() + s.stats.DecBackends() removed.counted = false } } @@ -276,7 +277,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen added.counted = true } } - statsBackendsCurrent.Add(float64(addedBackends)) + s.stats.AddBackends(addedBackends) } func getConfiguredBackendIDs(backendIds string) (ids []string) { From 964e9d2343cceb650f8205d4e07e7459f2ead85a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 09:43:03 +0100 Subject: [PATCH 361/549] Allow running publisher stats tests in parallel. --- publisher_stats_counter.go | 77 ++++++++++++-- publisher_stats_counter_test.go | 176 ++++++++++++++++++++++---------- 2 files changed, 190 insertions(+), 63 deletions(-) diff --git a/publisher_stats_counter.go b/publisher_stats_counter.go index 10257bb..bafe3f3 100644 --- a/publisher_stats_counter.go +++ b/publisher_stats_counter.go @@ -25,6 +25,45 @@ import ( "sync" ) +type publisherStatsCounterStats interface { + IncPublisherStream(streamType StreamType) + DecPublisherStream(streamType StreamType) + IncSubscriberStream(streamType StreamType) + DecSubscriberStream(streamType StreamType) + AddSubscriberStreams(streamType StreamType, count int) + SubSubscriberStreams(streamType StreamType, count int) +} + +type prometheusPublisherStats struct{} + +func (s *prometheusPublisherStats) IncPublisherStream(streamType StreamType) { + statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Inc() +} + +func (s *prometheusPublisherStats) DecPublisherStream(streamType StreamType) { + statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() +} + +func (s *prometheusPublisherStats) IncSubscriberStream(streamType StreamType) { + statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Inc() +} + +func (s *prometheusPublisherStats) DecSubscriberStream(streamType StreamType) { + statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() +} + +func (s *prometheusPublisherStats) AddSubscriberStreams(streamType StreamType, count int) { + statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Add(float64(count)) +} + +func (s *prometheusPublisherStats) SubSubscriberStreams(streamType StreamType, count int) { + statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Sub(float64(count)) +} + +var ( + defaultPublisherStats = &prometheusPublisherStats{} // +checklocksignore: Global readonly variable. +) + type publisherStatsCounter struct { mu sync.Mutex @@ -32,16 +71,23 @@ type publisherStatsCounter struct { streamTypes map[StreamType]bool // +checklocks:mu subscribers map[string]bool + // +checklocks:mu + stats publisherStatsCounterStats } func (c *publisherStatsCounter) Reset() { c.mu.Lock() defer c.mu.Unlock() + stats := c.stats + if stats == nil { + stats = defaultPublisherStats + } + count := len(c.subscribers) for streamType := range c.streamTypes { - statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() - statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Sub(float64(count)) + stats.DecPublisherStream(streamType) + stats.SubSubscriberStreams(streamType, count) } c.streamTypes = nil c.subscribers = nil @@ -55,17 +101,22 @@ func (c *publisherStatsCounter) EnableStream(streamType StreamType, enable bool) return } + stats := c.stats + if stats == nil { + stats = defaultPublisherStats + } + if enable { if c.streamTypes == nil { c.streamTypes = make(map[StreamType]bool) } c.streamTypes[streamType] = true - statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Inc() - statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Add(float64(len(c.subscribers))) + stats.IncPublisherStream(streamType) + stats.AddSubscriberStreams(streamType, len(c.subscribers)) } else { delete(c.streamTypes, streamType) - statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() - statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Sub(float64(len(c.subscribers))) + stats.DecPublisherStream(streamType) + stats.SubSubscriberStreams(streamType, len(c.subscribers)) } } @@ -77,12 +128,17 @@ func (c *publisherStatsCounter) AddSubscriber(id string) { return } + stats := c.stats + if stats == nil { + stats = defaultPublisherStats + } + if c.subscribers == nil { c.subscribers = make(map[string]bool) } c.subscribers[id] = true for streamType := range c.streamTypes { - statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Inc() + stats.IncSubscriberStream(streamType) } } @@ -94,8 +150,13 @@ func (c *publisherStatsCounter) RemoveSubscriber(id string) { return } + stats := c.stats + if stats == nil { + stats = defaultPublisherStats + } + delete(c.subscribers, id) for streamType := range c.streamTypes { - statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() + stats.DecSubscriberStream(streamType) } } diff --git a/publisher_stats_counter_test.go b/publisher_stats_counter_test.go index be7a1ad..d9ab73e 100644 --- a/publisher_stats_counter_test.go +++ b/publisher_stats_counter_test.go @@ -23,89 +23,155 @@ package signaling import ( "testing" + + "github.com/stretchr/testify/assert" ) -func TestPublisherStatsCounter(t *testing.T) { // nolint:paralleltest - RegisterJanusMcuStats() +type mockPublisherStats struct { + publishers map[StreamType]int + subscribers map[StreamType]int +} - var c publisherStatsCounter +func (s *mockPublisherStats) IncPublisherStream(streamType StreamType) { + if s.publishers == nil { + s.publishers = make(map[StreamType]int) + } + s.publishers[streamType]++ +} + +func (s *mockPublisherStats) DecPublisherStream(streamType StreamType) { + if s.publishers == nil { + s.publishers = make(map[StreamType]int) + } + s.publishers[streamType]-- +} + +func (s *mockPublisherStats) IncSubscriberStream(streamType StreamType) { + if s.subscribers == nil { + s.subscribers = make(map[StreamType]int) + } + s.subscribers[streamType]++ +} + +func (s *mockPublisherStats) DecSubscriberStream(streamType StreamType) { + if s.subscribers == nil { + s.subscribers = make(map[StreamType]int) + } + s.subscribers[streamType]-- +} + +func (s *mockPublisherStats) AddSubscriberStreams(streamType StreamType, count int) { + if s.subscribers == nil { + s.subscribers = make(map[StreamType]int) + } + s.subscribers[streamType] += count +} + +func (s *mockPublisherStats) SubSubscriberStreams(streamType StreamType, count int) { + if s.subscribers == nil { + s.subscribers = make(map[StreamType]int) + } + s.subscribers[streamType] -= count +} + +func (s *mockPublisherStats) Publishers(streamType StreamType) int { + return s.publishers[streamType] +} + +func (s *mockPublisherStats) Subscribers(streamType StreamType) int { + return s.subscribers[streamType] +} + +func TestPublisherStatsPrometheus(t *testing.T) { + t.Parallel() + + RegisterJanusMcuStats() + collectAndLint(t, commonMcuStats...) +} + +func TestPublisherStatsCounter(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + stats := &mockPublisherStats{} + c := publisherStatsCounter{ + stats: stats, + } c.Reset() - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) + assert.Equal(0, stats.Publishers("audio")) c.EnableStream("audio", false) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) + assert.Equal(0, stats.Publishers("audio")) c.EnableStream("audio", true) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) + assert.Equal(1, stats.Publishers("audio")) c.EnableStream("audio", true) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) + assert.Equal(1, stats.Publishers("audio")) c.EnableStream("video", true) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) c.EnableStream("audio", false) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(0, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) c.EnableStream("audio", false) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(0, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) c.AddSubscriber("1") - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(0, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(0, stats.Subscribers("audio")) + assert.Equal(1, stats.Subscribers("video")) c.EnableStream("audio", true) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(1, stats.Subscribers("audio")) + assert.Equal(1, stats.Subscribers("video")) c.AddSubscriber("1") - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(1, stats.Subscribers("audio")) + assert.Equal(1, stats.Subscribers("video")) c.AddSubscriber("2") - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 2) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 2) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(2, stats.Subscribers("audio")) + assert.Equal(2, stats.Subscribers("video")) c.RemoveSubscriber("3") - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 2) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 2) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(2, stats.Subscribers("audio")) + assert.Equal(2, stats.Subscribers("video")) c.RemoveSubscriber("1") - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 1) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(1, stats.Subscribers("audio")) + assert.Equal(1, stats.Subscribers("video")) c.AddSubscriber("1") - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 2) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 2) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(2, stats.Subscribers("audio")) + assert.Equal(2, stats.Subscribers("video")) c.EnableStream("audio", false) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 2) + assert.Equal(0, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(0, stats.Subscribers("audio")) + assert.Equal(2, stats.Subscribers("video")) c.EnableStream("audio", true) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 1) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 1) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 2) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 2) + assert.Equal(1, stats.Publishers("audio")) + assert.Equal(1, stats.Publishers("video")) + assert.Equal(2, stats.Subscribers("audio")) + assert.Equal(2, stats.Subscribers("video")) c.EnableStream("audio", false) c.EnableStream("video", false) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuPublisherStreamTypesCurrent.WithLabelValues("video"), 0) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("audio"), 0) - checkStatsValue(t, statsMcuSubscriberStreamTypesCurrent.WithLabelValues("video"), 0) - - collectAndLint(t, commonMcuStats...) + assert.Equal(0, stats.Publishers("audio")) + assert.Equal(0, stats.Publishers("video")) + assert.Equal(0, stats.Subscribers("audio")) + assert.Equal(0, stats.Subscribers("video")) } From 415a49e04bd9e19eb717f0b9337b802de004be39 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 09:51:38 +0100 Subject: [PATCH 362/549] Run Janus bandwidth tests in parallel. --- mcu_janus.go | 29 +++++++++++++++++++++++------ mcu_janus_test.go | 38 ++++++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/mcu_janus.go b/mcu_janus.go index 96d15b4..69e5f7a 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -333,7 +333,22 @@ func (m *mcuJanus) Bandwidth() (result *McuClientBandwidthInfo) { return } -func (m *mcuJanus) updateBandwidthStats() { +type janusBandwidthStats interface { + SetBandwidth(incoming uint64, outgoing uint64) +} + +type prometheusJanusBandwidthStats struct{} + +func (s *prometheusJanusBandwidthStats) SetBandwidth(incoming uint64, outgoing uint64) { + statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(float64(incoming)) + statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(float64(outgoing)) +} + +var ( + defaultJanusBandwidthStats = &prometheusJanusBandwidthStats{} +) + +func (m *mcuJanus) updateBandwidthStats(stats janusBandwidthStats) { if info := m.info.Load(); info != nil { if !info.EventHandlers { // Event handlers are disabled, no stats will be available. @@ -346,12 +361,14 @@ func (m *mcuJanus) updateBandwidthStats() { } } + if stats == nil { + stats = defaultJanusBandwidthStats + } + if bandwidth := m.Bandwidth(); bandwidth != nil { - statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(float64(bandwidth.Received.Bytes())) - statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(float64(bandwidth.Sent.Bytes())) + stats.SetBandwidth(bandwidth.Received.Bytes(), bandwidth.Sent.Bytes()) } else { - statsJanusBandwidthCurrent.WithLabelValues("incoming").Set(0) - statsJanusBandwidthCurrent.WithLabelValues("outgoing").Set(0) + stats.SetBandwidth(0, 0) } } @@ -524,7 +541,7 @@ loop: case <-ticker.C: m.sendKeepalive(context.Background()) case <-bandwidthTicker.C: - m.updateBandwidthStats() + m.updateBandwidthStats(nil) case <-m.closeChan: break loop } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 5fa93d1..f82e1da 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -1081,10 +1081,20 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { } } -func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming")) - ResetStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing")) +type mockBandwidthStats struct { + incoming uint64 + outgoing uint64 +} +func (s *mockBandwidthStats) SetBandwidth(incoming uint64, outgoing uint64) { + s.incoming = incoming + s.outgoing = outgoing +} + +func Test_JanusPublisherSubscriber(t *testing.T) { + t.Parallel() + + stats := &mockBandwidthStats{} require := require.New(t) assert := assert.New(t) @@ -1096,9 +1106,9 @@ func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest // Bandwidth for unknown handles is ignored. mcu.UpdateBandwidth(1234, "video", api.BandwidthFromBytes(100), api.BandwidthFromBytes(200)) - mcu.updateBandwidthStats() - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 0) - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 0) + mcu.updateBandwidthStats(stats) + assert.EqualValues(0, stats.incoming) + assert.EqualValues(0, stats.outgoing) pubId := PublicSessionId("publisher-id") listener1 := &TestMcuListener{ @@ -1128,9 +1138,9 @@ func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) assert.Equal(api.BandwidthFromBytes(2000), bw.Received) } - mcu.updateBandwidthStats() - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 1000) + mcu.updateBandwidthStats(stats) + assert.EqualValues(2000, stats.incoming) + assert.EqualValues(1000, stats.outgoing) listener2 := &TestMcuListener{ id: pubId, @@ -1156,11 +1166,11 @@ func Test_JanusPublisherSubscriber(t *testing.T) { // nolint:paralleltest assert.Equal(api.BandwidthFromBytes(4000), bw.Sent) assert.Equal(api.BandwidthFromBytes(6000), bw.Received) } - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 2000) - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 1000) - mcu.updateBandwidthStats() - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("incoming"), 6000) - checkStatsValue(t, statsJanusBandwidthCurrent.WithLabelValues("outgoing"), 4000) + assert.EqualValues(2000, stats.incoming) + assert.EqualValues(1000, stats.outgoing) + mcu.updateBandwidthStats(stats) + assert.EqualValues(6000, stats.incoming) + assert.EqualValues(4000, stats.outgoing) } func Test_JanusSubscriberPublisher(t *testing.T) { From 0960a714aaf16b27fe59939226a4b1ed98cffb9e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 10:17:36 +0100 Subject: [PATCH 363/549] Use "InDelta" to compare values where expected could be "0". --- stats_prometheus_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index bec5e10..5b1c0c3 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -71,7 +71,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 pc := make([]uintptr, 10) n := runtime.Callers(2, pc) if n == 0 { - assert.InEpsilon(value, v, 0.0001, "failed for %s", desc) + assert.InDelta(value, v, 0.0001, "failed for %s", desc) return } @@ -88,7 +88,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 break } } - assert.InEpsilon(value, v, 0.0001, "Unexpected value for %s at\n%s", desc, stack.String()) + assert.InDelta(value, v, 0.0001, "Unexpected value for %s at\n%s", desc, stack.String()) } } From 892dae684271a34d416e53cd374035b7f299a516 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 10:32:55 +0100 Subject: [PATCH 364/549] Run Janus subscriber stats tests in parallel. --- mcu_janus.go | 21 +++++- mcu_janus_subscriber.go | 4 +- mcu_janus_test.go | 162 ++++++++++++++++++++++++++++------------ 3 files changed, 134 insertions(+), 53 deletions(-) diff --git a/mcu_janus.go b/mcu_janus.go index 69e5f7a..3a092aa 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -213,6 +213,21 @@ func (s *mcuJanusSettings) Reload(config *goconf.ConfigFile) { } } +type mcuJanusStats interface { + IncSubscriber(streamType StreamType) + DecSubscriber(streamType StreamType) +} + +type prometheusJanusStats struct{} + +func (s *prometheusJanusStats) IncSubscriber(streamType StreamType) { + statsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() +} + +func (s *prometheusJanusStats) DecSubscriber(streamType StreamType) { + statsSubscribersCurrent.WithLabelValues(string(streamType)).Dec() +} + type mcuJanus struct { logger Logger @@ -220,6 +235,7 @@ type mcuJanus struct { mu sync.Mutex settings *mcuJanusSettings + stats mcuJanusStats createJanusGateway func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) @@ -265,6 +281,7 @@ func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mc logger: LoggerFromContext(ctx), url: url, settings: settings, + stats: &prometheusJanusStats{}, closeChan: make(chan struct{}, 1), clients: make(map[uint64]clientInterface), @@ -907,7 +924,7 @@ func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publ client.mcuJanusClient.handleMedia = client.handleMedia m.registerClient(client) go client.run(handle, client.closeChan) - statsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() + m.stats.IncSubscriber(streamType) statsSubscribersTotal.WithLabelValues(string(streamType)).Inc() return client, nil } @@ -1077,7 +1094,7 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener client.mcuJanusClient.handleMedia = client.handleMedia m.registerClient(client) go client.run(handle, client.closeChan) - statsSubscribersCurrent.WithLabelValues(string(publisher.StreamType())).Inc() + m.stats.IncSubscriber(publisher.StreamType()) statsSubscribersTotal.WithLabelValues(string(publisher.StreamType())).Inc() return client, nil } diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index de9d369..77c3241 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -140,7 +140,7 @@ func (p *mcuJanusSubscriber) closeClient(ctx context.Context) bool { return false } - statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Dec() + p.mcu.stats.DecSubscriber(p.streamType) return true } @@ -226,7 +226,7 @@ retry: p.sid = strconv.FormatUint(handle.Id, 10) p.listener.SubscriberSidUpdated(p) p.closeChan = make(chan struct{}, 1) - statsSubscribersCurrent.WithLabelValues(string(p.streamType)).Inc() + p.mcu.stats.IncSubscriber(p.streamType) go p.run(handle, p.closeChan) p.logger.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId.Load()) goto retry diff --git a/mcu_janus_test.go b/mcu_janus_test.go index f82e1da..b0cfe0d 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -1412,18 +1412,61 @@ func Test_JanusRemotePublisher(t *testing.T) { assert.EqualValues(1, removed.Load()) } -func Test_JanusSubscriberNoSuchRoom(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - t.Cleanup(func() { - if !t.Failed() { - checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) - } - }) +type mockJanusStats struct { + called atomic.Bool + mu sync.Mutex + // +checklocks:mu + value map[StreamType]int +} + +func (s *mockJanusStats) Value(streamType StreamType) int { + s.mu.Lock() + defer s.mu.Unlock() + + return s.value[streamType] +} + +func (s *mockJanusStats) IncSubscriber(streamType StreamType) { + s.called.Store(true) + + s.mu.Lock() + defer s.mu.Unlock() + + if s.value == nil { + s.value = make(map[StreamType]int) + } + s.value[streamType]++ +} + +func (s *mockJanusStats) DecSubscriber(streamType StreamType) { + s.called.Store(true) + + s.mu.Lock() + defer s.mu.Unlock() + + if s.value == nil { + s.value = make(map[StreamType]int) + } + s.value[streamType]-- +} + +func Test_JanusSubscriberNoSuchRoom(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + mcu, gateway := newMcuJanusForTesting(t) + mcu.stats = stats gateway.registerHandlers(map[string]TestJanusHandler{ "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) @@ -1511,18 +1554,21 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { // nolint:paralleltest client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } -func test_JanusSubscriberAlreadyJoined(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - t.Cleanup(func() { - if !t.Failed() { - checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) - } - }) - +func test_JanusSubscriberAlreadyJoined(t *testing.T) { require := require.New(t) assert := assert.New(t) + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + mcu, gateway := newMcuJanusForTesting(t) + mcu.stats = stats gateway.registerHandlers(map[string]TestJanusHandler{ "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) @@ -1612,26 +1658,32 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { // nolint:paralleltest client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } -func Test_JanusSubscriberAlreadyJoined(t *testing.T) { // nolint:paralleltest +func Test_JanusSubscriberAlreadyJoined(t *testing.T) { + t.Parallel() test_JanusSubscriberAlreadyJoined(t) } -func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { // nolint:paralleltest +func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { + t.Parallel() test_JanusSubscriberAlreadyJoined(t) } -func Test_JanusSubscriberTimeout(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - t.Cleanup(func() { - if !t.Failed() { - checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) - } - }) - +func Test_JanusSubscriberTimeout(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + mcu, gateway := newMcuJanusForTesting(t) + mcu.stats = stats gateway.registerHandlers(map[string]TestJanusHandler{ "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) @@ -1723,18 +1775,22 @@ func Test_JanusSubscriberTimeout(t *testing.T) { // nolint:paralleltest client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) } -func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - t.Cleanup(func() { - if !t.Failed() { - checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) - } - }) - +func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + mcu, gateway := newMcuJanusForTesting(t) + mcu.stats = stats gateway.registerHandlers(map[string]TestJanusHandler{ "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) @@ -1833,18 +1889,22 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { // nolint:paralleltes assert.Nil(handle, "subscriber should have been closed") } -func Test_JanusSubscriberRoomDestroyed(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - t.Cleanup(func() { - if !t.Failed() { - checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) - } - }) - +func Test_JanusSubscriberRoomDestroyed(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + mcu, gateway := newMcuJanusForTesting(t) + mcu.stats = stats gateway.registerHandlers(map[string]TestJanusHandler{ "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) @@ -1943,18 +2003,22 @@ func Test_JanusSubscriberRoomDestroyed(t *testing.T) { // nolint:paralleltest assert.Nil(handle, "subscriber should have been closed") } -func Test_JanusSubscriberUpdateOffer(t *testing.T) { // nolint:paralleltest - ResetStatsValue(t, statsSubscribersCurrent.WithLabelValues("video")) - t.Cleanup(func() { - if !t.Failed() { - checkStatsValue(t, statsSubscribersCurrent.WithLabelValues("video"), 0) - } - }) - +func Test_JanusSubscriberUpdateOffer(t *testing.T) { + t.Parallel() require := require.New(t) assert := assert.New(t) + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + mcu, gateway := newMcuJanusForTesting(t) + mcu.stats = stats gateway.registerHandlers(map[string]TestJanusHandler{ "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { assert.EqualValues(1, room.id) From f7b9224bda86432c23ec98704faee8066e8bbe7e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 10:40:54 +0100 Subject: [PATCH 365/549] The loopback NATS client can not leak goroutines. --- natsclient_loopback_test.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/natsclient_loopback_test.go b/natsclient_loopback_test.go index b403109..2109e0a 100644 --- a/natsclient_loopback_test.go +++ b/natsclient_loopback_test.go @@ -63,34 +63,30 @@ func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { return result } -func TestLoopbackNatsClient_Subscribe(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLoopbackNatsClientForTest(t) +func TestLoopbackNatsClient_Subscribe(t *testing.T) { + t.Parallel() - testNatsClient_Subscribe(t, client) - }) + client := CreateLoopbackNatsClientForTest(t) + testNatsClient_Subscribe(t, client) } -func TestLoopbackClient_PublishAfterClose(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLoopbackNatsClientForTest(t) +func TestLoopbackClient_PublishAfterClose(t *testing.T) { + t.Parallel() - testNatsClient_PublishAfterClose(t, client) - }) + client := CreateLoopbackNatsClientForTest(t) + testNatsClient_PublishAfterClose(t, client) } -func TestLoopbackClient_SubscribeAfterClose(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLoopbackNatsClientForTest(t) +func TestLoopbackClient_SubscribeAfterClose(t *testing.T) { + t.Parallel() - testNatsClient_SubscribeAfterClose(t, client) - }) + client := CreateLoopbackNatsClientForTest(t) + testNatsClient_SubscribeAfterClose(t, client) } -func TestLoopbackClient_BadSubjects(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { - client := CreateLoopbackNatsClientForTest(t) +func TestLoopbackClient_BadSubjects(t *testing.T) { + t.Parallel() - testNatsClient_BadSubjects(t, client) - }) + client := CreateLoopbackNatsClientForTest(t) + testNatsClient_BadSubjects(t, client) } From 6f35e021f97e9a484900d7e796c11cb7d1e27e24 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 10:44:16 +0100 Subject: [PATCH 366/549] Use "t.Chdir" to directory is restored after test. --- file_watcher_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file_watcher_test.go b/file_watcher_test.go index 2749738..241a5bd 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -94,7 +94,7 @@ func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() - require.NoError(os.Chdir(tmpdir)) + t.Chdir(tmpdir) filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) From c533b039b22f13e425dfb8ea4cb9a0aec0fc55fe Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 10:48:52 +0100 Subject: [PATCH 367/549] "checkStatsValue" is now unused, but keep if needed in future. --- stats_prometheus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index 5b1c0c3..dd2e169 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -58,7 +58,7 @@ func assertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta }) } -func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64) { +func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64) { // nolint:unused // Make sure test is not executed with "t.Parallel()" t.Setenv("PARALLEL_CHECK", "1") From 65edf5c03a278acca03c97ae05117dcbdd0bcbd7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 11:25:07 +0100 Subject: [PATCH 368/549] Wait for hub housekeeping to finish before continuing tests. --- hub_test.go | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/hub_test.go b/hub_test.go index 27ded33..5fb0693 100644 --- a/hub_test.go +++ b/hub_test.go @@ -809,16 +809,6 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { } } -func performHousekeeping(hub *Hub, now time.Time) *sync.WaitGroup { - var wg sync.WaitGroup - wg.Add(1) - go func() { - hub.performHousekeeping(now) - wg.Done() - }() - return &wg -} - func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { require := require.New(b) decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) @@ -940,7 +930,7 @@ func TestExpectClientHello(t *testing.T) { // Perform housekeeping in the future, this will cause the connection to // be terminated due to the missing "Hello" request. - performHousekeeping(hub, time.Now().Add(initialHelloTimeout+time.Second)) + hub.performHousekeeping(time.Now().Add(initialHelloTimeout + time.Second)) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1486,7 +1476,7 @@ func TestClientHelloResumeThrottle(t *testing.T) { // Perform housekeeping in the future, this will cause the session to be // cleaned up after it is expired. - performHousekeeping(hub, time.Now().Add(sessionExpireDuration+time.Second)).Wait() + hub.performHousekeeping(time.Now().Add(sessionExpireDuration + time.Second)) client = NewTestClient(t, server, hub) defer client.CloseWithBye() @@ -1523,7 +1513,7 @@ func TestClientHelloResumeExpired(t *testing.T) { // Perform housekeeping in the future, this will cause the session to be // cleaned up after it is expired. - performHousekeeping(hub, time.Now().Add(sessionExpireDuration+time.Second)).Wait() + hub.performHousekeeping(time.Now().Add(sessionExpireDuration + time.Second)) client = NewTestClient(t, server, hub) defer client.CloseWithBye() @@ -2803,7 +2793,7 @@ func TestExpectAnonymousJoinRoom(t *testing.T) { // Perform housekeeping in the future, this will cause the connection to // be terminated because the anonymous client didn't join a room. - performHousekeeping(hub, time.Now().Add(anonmyousJoinRoomTimeout+time.Second)) + hub.performHousekeeping(time.Now().Add(anonmyousJoinRoomTimeout + time.Second)) if message, ok := client.RunUntilMessage(ctx); ok { if checkMessageType(t, message, "bye") { @@ -2840,7 +2830,7 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { // Perform housekeeping in the future, this will keep the connection as the // session joined a room. - performHousekeeping(hub, time.Now().Add(anonmyousJoinRoomTimeout+time.Second)) + hub.performHousekeeping(time.Now().Add(anonmyousJoinRoomTimeout + time.Second)) // No message about the closing is sent to the new connection. ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) @@ -2854,7 +2844,7 @@ func TestExpectAnonymousJoinRoomAfterLeave(t *testing.T) { // Perform housekeeping in the future, this will cause the connection to // be terminated because the anonymous client didn't join a room. - performHousekeeping(hub, time.Now().Add(anonmyousJoinRoomTimeout+time.Second)) + hub.performHousekeeping(time.Now().Add(anonmyousJoinRoomTimeout + time.Second)) if message, ok := client.RunUntilMessage(ctx); ok { if checkMessageType(t, message, "bye") { @@ -5421,7 +5411,7 @@ func TestGracefulShutdownOnExpiration(t *testing.T) { case <-time.After(100 * time.Millisecond): } - performHousekeeping(hub, time.Now().Add(sessionExpireDuration+time.Second)) + hub.performHousekeeping(time.Now().Add(sessionExpireDuration + time.Second)) select { case <-hub.ShutdownChannel(): From e13bca696bd0d3c480c1082fb4ab0896cf5bb803 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 11:42:02 +0100 Subject: [PATCH 369/549] Close client connections and wait for server before terminating test. --- mcu_janus_events_handler_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index 3dffcf5..38552d4 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -47,10 +47,13 @@ type TestJanusEventsServerHandler struct { mcu Mcu addr string + wg sync.WaitGroup } func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.t.Helper() + h.wg.Add(1) + defer h.wg.Done() assert := assert.New(h.t) conn, err := h.upgrader.Upgrade(w, r, nil) assert.NoError(err) @@ -90,6 +93,8 @@ func NewTestJanusEventsHandlerServer(t *testing.T) (*httptest.Server, string, *T server := httptest.NewServer(handler) t.Cleanup(func() { server.Close() + server.CloseClientConnections() + handler.wg.Wait() }) url := strings.ReplaceAll(server.URL, "http://", "ws://") url = strings.ReplaceAll(url, "https://", "wss://") @@ -113,6 +118,9 @@ func TestJanusEventsHandlerNoMcu(t *testing.T) { } conn, response, err := dialer.DialContext(ctx, url, nil) require.NoError(err) + defer func() { + assert.NoError(conn.Close()) + }() assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) @@ -145,6 +153,9 @@ func TestJanusEventsHandlerInvalidMcu(t *testing.T) { } conn, response, err := dialer.DialContext(ctx, url, nil) require.NoError(err) + defer func() { + assert.NoError(conn.Close()) + }() assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) @@ -178,6 +189,9 @@ func TestJanusEventsHandlerPublicIP(t *testing.T) { } conn, response, err := dialer.DialContext(ctx, url, nil) require.NoError(err) + defer func() { + assert.NoError(conn.Close()) + }() assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) @@ -305,6 +319,9 @@ func TestJanusEventsHandlerDifferentTypes(t *testing.T) { } conn, response, err := dialer.DialContext(ctx, url, nil) require.NoError(err) + defer func() { + assert.NoError(conn.Close()) + }() assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) @@ -519,6 +536,9 @@ func TestJanusEventsHandlerNotGrouped(t *testing.T) { } conn, response, err := dialer.DialContext(ctx, url, nil) require.NoError(err) + defer func() { + assert.NoError(conn.Close()) + }() assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) @@ -595,6 +615,9 @@ func TestJanusEventsHandlerGrouped(t *testing.T) { } conn, response, err := dialer.DialContext(ctx, url, nil) require.NoError(err) + defer func() { + assert.NoError(conn.Close()) + }() assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) From adb391ab5adef8ebcba6cd570c1d70575799dbe1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 11:57:36 +0100 Subject: [PATCH 370/549] Stop transaction goroutine when removing. --- mcu_janus_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mcu_janus_test.go b/mcu_janus_test.go index b0cfe0d..f55e17a 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -575,7 +575,10 @@ func (g *TestJanusGateway) send(msg api.StringMap, t *transaction) (uint64, erro func (g *TestJanusGateway) removeTransaction(id uint64) { g.mu.Lock() defer g.mu.Unlock() - delete(g.transactions, id) + if t, found := g.transactions[id]; found { + delete(g.transactions, id) + t.quit() + } } func (g *TestJanusGateway) removeSession(session *JanusSession) { From 16c37cb0ed87a7a590107e40e867f3f7a5922f84 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 12:15:49 +0100 Subject: [PATCH 371/549] Ensure all client connections are closed when test ends. --- backend_server_test.go | 11 +++++++++++ clientsession_test.go | 3 +++ hub_test.go | 2 ++ testclient_test.go | 13 ++++++++++++- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend_server_test.go b/backend_server_test.go index 570b136..da09f8a 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -483,6 +483,7 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client.CloseWithBye() // Join room by id. roomId := "test-room" @@ -550,7 +551,9 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client1.CloseWithBye() client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client2.CloseWithBye() // Join room by id. roomId1 := "test-room1" @@ -780,7 +783,9 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + defer client1.CloseWithBye() client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") + defer client2.CloseWithBye() session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId) require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) @@ -863,6 +868,7 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client.CloseWithBye() session := hub.GetSessionByPublicId(hello.Hello.SessionId) assert.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) @@ -927,7 +933,9 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + defer client1.CloseWithBye() client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + defer client2.CloseWithBye() // Join room by id. roomId := "test-room" @@ -1100,7 +1108,9 @@ func TestBackendServer_InCallAll(t *testing.T) { defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + defer client1.CloseWithBye() client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") + defer client2.CloseWithBye() session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId) require.NotNil(session1, "Could not find session %s", hello1.Hello.SessionId) @@ -1258,6 +1268,7 @@ func TestBackendServer_RoomMessage(t *testing.T) { defer cancel() client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + defer client.CloseWithBye() // Join room by id. roomId := "test-room" diff --git a/clientsession_test.go b/clientsession_test.go index cc46981..1ccc062 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -51,6 +51,7 @@ func TestBandwidth_Client(t *testing.T) { hub.SetMcu(mcu) client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client.CloseWithBye() // Join room by id. roomId := "test-room" @@ -476,6 +477,7 @@ func TestPermissionHideDisplayNames(t *testing.T) { defer cancel() client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client.CloseWithBye() roomId := "test-room" roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) @@ -541,6 +543,7 @@ func TestPermissionHideDisplayNames(t *testing.T) { } client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + defer client2.CloseWithBye() roomMsg2 := MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg2.Room.RoomId) diff --git a/hub_test.go b/hub_test.go index 5fb0693..320e70d 100644 --- a/hub_test.go +++ b/hub_test.go @@ -5086,7 +5086,9 @@ func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.S defer cancel() client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") + defer client1.CloseWithBye() client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") + defer client2.CloseWithBye() roomSessionId1 := RoomSessionId("roomsession1") roomId1 := "test-room" diff --git a/testclient_test.go b/testclient_test.go index e9e8d52..2088fb8 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -221,7 +221,10 @@ func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Se messageChan := make(chan []byte) readErrorChan := make(chan error, 1) + closing := make(chan struct{}) + closed := make(chan struct{}) go func() { + defer close(closed) for { messageType, data, err := conn.ReadMessage() if err != nil { @@ -231,9 +234,17 @@ func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Se return } - messageChan <- data + select { + case messageChan <- data: + case <-closing: + return + } } }() + t.Cleanup(func() { + close(closing) + <-closed + }) return &TestClient{ t: t, From f52da04859b5acafa575ac6a262815d1b090f64f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 13:08:38 +0100 Subject: [PATCH 372/549] Use testing/synctest so Test_TransientData is not timing-dependent. --- transient_data.go | 22 ++++----- transient_data_test.go | 101 +++++++++++++++++++++-------------------- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/transient_data.go b/transient_data.go index a96bb3a..af3c640 100644 --- a/transient_data.go +++ b/transient_data.go @@ -95,8 +95,6 @@ type TransientData struct { listeners map[TransientListener]bool // +checklocks:mu timers map[string]*time.Timer - // +checklocks:mu - ttlCh chan<- struct{} } // NewTransientData creates a new transient data container. @@ -181,7 +179,10 @@ func (t *TransientData) RemoveListener(listener TransientListener) { // +checklocks:t.mu func (t *TransientData) updateTTL(key string, value any, ttl time.Duration) { if ttl <= 0 { - delete(t.timers, key) + if old, found := t.timers[key]; found { + old.Stop() + delete(t.timers, key) + } } else { t.removeAfterTTL(key, value, ttl) } @@ -189,25 +190,20 @@ func (t *TransientData) updateTTL(key string, value any, ttl time.Duration) { // +checklocks:t.mu func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) { - if ttl <= 0 { - return - } - if old, found := t.timers[key]; found { old.Stop() } + if ttl <= 0 { + delete(t.timers, key) + return + } + timer := time.AfterFunc(ttl, func() { t.mu.Lock() defer t.mu.Unlock() t.compareAndRemove(key, value) - if t.ttlCh != nil { - select { - case t.ttlCh <- struct{}{}: - default: - } - } }) if t.timers == nil { t.timers = make(map[string]*time.Timer) diff --git a/transient_data_test.go b/transient_data_test.go index e2bee37..e4aff9a 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -26,6 +26,7 @@ import ( "net/http/httptest" "sync" "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" @@ -34,59 +35,63 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" ) -func (t *TransientData) SetTTLChannel(ch chan<- struct{}) { - t.mu.Lock() - defer t.mu.Unlock() - - t.ttlCh = ch -} - func Test_TransientData(t *testing.T) { t.Parallel() - assert := assert.New(t) - data := NewTransientData() - assert.False(data.Set("foo", nil)) - assert.True(data.Set("foo", "bar")) - assert.False(data.Set("foo", "bar")) - assert.True(data.Set("foo", "baz")) - assert.False(data.CompareAndSet("foo", "bar", "lala")) - assert.True(data.CompareAndSet("foo", "baz", "lala")) - assert.False(data.CompareAndSet("test", nil, nil)) - assert.True(data.CompareAndSet("test", nil, "123")) - assert.False(data.CompareAndSet("test", nil, "456")) - assert.False(data.CompareAndRemove("test", "1234")) - assert.True(data.CompareAndRemove("test", "123")) - assert.False(data.Remove("lala")) - assert.True(data.Remove("foo")) + SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + data := NewTransientData() + assert.False(data.Set("foo", nil)) + assert.True(data.Set("foo", "bar")) + assert.False(data.Set("foo", "bar")) + assert.True(data.Set("foo", "baz")) + assert.False(data.CompareAndSet("foo", "bar", "lala")) + assert.True(data.CompareAndSet("foo", "baz", "lala")) + assert.False(data.CompareAndSet("test", nil, nil)) + assert.True(data.CompareAndSet("test", nil, "123")) + assert.False(data.CompareAndSet("test", nil, "456")) + assert.False(data.CompareAndRemove("test", "1234")) + assert.True(data.CompareAndRemove("test", "123")) + assert.False(data.Remove("lala")) + assert.True(data.Remove("foo")) - ttlCh := make(chan struct{}) - data.SetTTLChannel(ttlCh) - assert.True(data.SetTTL("test", "1234", time.Millisecond)) - assert.Equal("1234", data.GetData()["test"]) - // Data is removed after the TTL - <-ttlCh - assert.Nil(data.GetData()["test"]) + assert.True(data.SetTTL("test", "1234", time.Millisecond)) + assert.Equal("1234", data.GetData()["test"]) + // Data is removed after the TTL + start := time.Now() + time.Sleep(time.Millisecond) + synctest.Wait() + assert.Equal(time.Millisecond, time.Since(start)) + assert.Nil(data.GetData()["test"]) - assert.True(data.SetTTL("test", "1234", time.Millisecond)) - assert.Equal("1234", data.GetData()["test"]) - assert.True(data.SetTTL("test", "2345", 3*time.Millisecond)) - assert.Equal("2345", data.GetData()["test"]) - // Data is removed after the TTL only if the value still matches - time.Sleep(2 * time.Millisecond) - assert.Equal("2345", data.GetData()["test"]) - // Data is removed after the (second) TTL - <-ttlCh - assert.Nil(data.GetData()["test"]) + assert.True(data.SetTTL("test", "1234", time.Millisecond)) + assert.Equal("1234", data.GetData()["test"]) + assert.True(data.SetTTL("test", "2345", 3*time.Millisecond)) + assert.Equal("2345", data.GetData()["test"]) + start = time.Now() + // Data is removed after the TTL only if the value still matches + time.Sleep(2 * time.Millisecond) + synctest.Wait() + assert.Equal("2345", data.GetData()["test"]) + // Data is removed after the (second) TTL + time.Sleep(time.Millisecond) + synctest.Wait() + assert.Equal(3*time.Millisecond, time.Since(start)) + assert.Nil(data.GetData()["test"]) - // Setting existing key will update the TTL - assert.True(data.SetTTL("test", "1234", time.Millisecond)) - assert.False(data.SetTTL("test", "1234", 3*time.Millisecond)) - // Data still exists after the first TTL - time.Sleep(2 * time.Millisecond) - assert.Equal("1234", data.GetData()["test"]) - // Data is removed after the (updated) TTL - <-ttlCh - assert.Nil(data.GetData()["test"]) + // Setting existing key will update the TTL + assert.True(data.SetTTL("test", "1234", time.Millisecond)) + assert.False(data.SetTTL("test", "1234", 3*time.Millisecond)) + start = time.Now() + // Data still exists after the first TTL + time.Sleep(2 * time.Millisecond) + synctest.Wait() + assert.Equal("1234", data.GetData()["test"]) + // Data is removed after the (updated) TTL + time.Sleep(time.Millisecond) + synctest.Wait() + assert.Equal(3*time.Millisecond, time.Since(start)) + assert.Nil(data.GetData()["test"]) + }) } type MockTransientListener struct { From 62587796cef468e5d26a6ff9596b7f6139c0c1e4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 9 Dec 2025 15:06:05 +0100 Subject: [PATCH 373/549] Move logging code to separate package. --- .codecov.yml | 4 ++ async_events.go | 4 +- async_events_nats.go | 18 ++--- async_events_test.go | 10 +-- backend_client.go | 6 +- backend_client_test.go | 18 ++--- backend_configuration.go | 6 +- backend_configuration_test.go | 32 ++++----- backend_server.go | 11 ++-- backend_server_test.go | 69 ++++++++++---------- backend_storage_etcd.go | 6 +- backend_storage_etcd_test.go | 4 +- backend_storage_static.go | 8 ++- capabilities.go | 11 ++-- capabilities_test.go | 37 ++++++----- certificate_reloader.go | 10 +-- client.go | 6 +- clientsession.go | 7 +- deferred_executor.go | 6 +- deferred_executor_test.go | 14 ++-- dns_monitor.go | 6 +- dns_monitor_test.go | 4 +- etcd_client.go | 5 +- etcd_client_test.go | 22 ++++--- federation.go | 5 +- file_watcher.go | 6 +- file_watcher_test.go | 20 +++--- geoip.go | 10 +-- geoip_test.go | 10 +-- grpc_client.go | 12 ++-- grpc_client_test.go | 24 +++---- grpc_common.go | 4 +- grpc_remote_client.go | 4 +- grpc_server.go | 6 +- grpc_server_test.go | 8 ++- hub.go | 13 ++-- hub_test.go | 13 ++-- logging.go => log/logging.go | 2 +- log/logging_test.go | 68 +++++++++++++++++++ test_helpers.go => log/test_helpers.go | 2 +- test_helpers_24.go => log/test_helpers_24.go | 2 +- test_helpers_25.go => log/test_helpers_25.go | 2 +- mcu_common.go | 3 +- mcu_janus.go | 9 +-- mcu_janus_client.go | 3 +- mcu_janus_events_handler.go | 7 +- mcu_janus_events_handler_test.go | 5 +- mcu_janus_test.go | 5 +- mcu_proxy.go | 15 +++-- mcu_proxy_test.go | 6 +- mcu_test.go | 3 +- natsclient.go | 6 +- natsclient_loopback.go | 6 +- natsclient_loopback_test.go | 4 +- natsclient_test.go | 6 +- proxy/main.go | 3 +- proxy/proxy_remote.go | 3 +- proxy/proxy_server.go | 9 +-- proxy/proxy_server_test.go | 5 +- proxy/proxy_session.go | 3 +- proxy/proxy_tokens_etcd.go | 5 +- proxy/proxy_tokens_etcd_test.go | 4 +- proxy/proxy_tokens_static.go | 5 +- proxy_config_etcd.go | 6 +- proxy_config_etcd_test.go | 4 +- proxy_config_static.go | 6 +- proxy_config_static_test.go | 4 +- remotesession.go | 4 +- room.go | 9 +-- room_ping.go | 6 +- room_ping_test.go | 18 ++--- room_test.go | 22 ++++--- roomsessions_builtin.go | 4 +- server/main.go | 5 +- throttle.go | 6 +- throttle_test.go | 30 +++++---- virtualsession.go | 5 +- 77 files changed, 482 insertions(+), 297 deletions(-) rename logging.go => log/logging.go (98%) create mode 100644 log/logging_test.go rename test_helpers.go => log/test_helpers.go (98%) rename test_helpers_24.go => log/test_helpers_24.go (98%) rename test_helpers_25.go => log/test_helpers_25.go (98%) diff --git a/.codecov.yml b/.codecov.yml index 3acf08a..eff76ae 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -30,6 +30,10 @@ component_management: name: internal paths: - internal/** + - component_id: module_log + name: log + paths: + - log/** - component_id: module_proxy name: proxy paths: diff --git a/async_events.go b/async_events.go index bbb8aab..72c8b8f 100644 --- a/async_events.go +++ b/async_events.go @@ -24,6 +24,8 @@ package signaling import ( "context" "sync" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type AsyncBackendRoomEventListener interface { @@ -69,7 +71,7 @@ func NewAsyncEvents(ctx context.Context, url string) (AsyncEvents, error) { return nil, err } - return NewAsyncEventsNats(LoggerFromContext(ctx), client) + return NewAsyncEventsNats(log.LoggerFromContext(ctx), client) } type asyncBackendRoomSubscriber struct { diff --git a/async_events_nats.go b/async_events_nats.go index 55b3bf8..90b3839 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -27,6 +27,8 @@ import ( "time" "github.com/nats-io/nats.go" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func GetSubjectForBackendRoomId(roomId string, backend *Backend) string { @@ -60,7 +62,7 @@ func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string type asyncSubscriberNats struct { key string client NatsClient - logger Logger + logger log.Logger receiver chan *nats.Msg closeChan chan struct{} @@ -69,7 +71,7 @@ type asyncSubscriberNats struct { processMessage func(*nats.Msg) } -func newAsyncSubscriberNats(logger Logger, key string, client NatsClient) (*asyncSubscriberNats, error) { +func newAsyncSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncSubscriberNats, error) { receiver := make(chan *nats.Msg, 64) sub, err := client.Subscribe(key, receiver) if err != nil { @@ -117,7 +119,7 @@ type asyncBackendRoomSubscriberNats struct { asyncBackendRoomSubscriber } -func newAsyncBackendRoomSubscriberNats(logger Logger, key string, client NatsClient) (*asyncBackendRoomSubscriberNats, error) { +func newAsyncBackendRoomSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncBackendRoomSubscriberNats, error) { sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err @@ -146,7 +148,7 @@ type asyncRoomSubscriberNats struct { *asyncSubscriberNats } -func newAsyncRoomSubscriberNats(logger Logger, key string, client NatsClient) (*asyncRoomSubscriberNats, error) { +func newAsyncRoomSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncRoomSubscriberNats, error) { sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err @@ -175,7 +177,7 @@ type asyncUserSubscriberNats struct { asyncUserSubscriber } -func newAsyncUserSubscriberNats(logger Logger, key string, client NatsClient) (*asyncUserSubscriberNats, error) { +func newAsyncUserSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncUserSubscriberNats, error) { sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err @@ -204,7 +206,7 @@ type asyncSessionSubscriberNats struct { asyncSessionSubscriber } -func newAsyncSessionSubscriberNats(logger Logger, key string, client NatsClient) (*asyncSessionSubscriberNats, error) { +func newAsyncSessionSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncSessionSubscriberNats, error) { sub, err := newAsyncSubscriberNats(logger, key, client) if err != nil { return nil, err @@ -231,7 +233,7 @@ func (s *asyncSessionSubscriberNats) doProcessMessage(msg *nats.Msg) { type asyncEventsNats struct { mu sync.Mutex client NatsClient - logger Logger // +checklocksignore + logger log.Logger // +checklocksignore // +checklocks:mu backendRoomSubscriptions map[string]*asyncBackendRoomSubscriberNats @@ -243,7 +245,7 @@ type asyncEventsNats struct { sessionSubscriptions map[string]*asyncSessionSubscriberNats } -func NewAsyncEventsNats(logger Logger, client NatsClient) (AsyncEvents, error) { +func NewAsyncEventsNats(logger log.Logger, client NatsClient) (AsyncEvents, error) { events := &asyncEventsNats{ client: client, logger: logger, diff --git a/async_events_test.go b/async_events_test.go index ac14dfb..9cf2c5f 100644 --- a/async_events_test.go +++ b/async_events_test.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -54,8 +56,8 @@ func getAsyncEventsForTest(t *testing.T) AsyncEvents { } func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) server, _ := startLocalNatsServer(t) events, err := NewAsyncEvents(ctx, server.ClientURL()) if err != nil { @@ -65,8 +67,8 @@ func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { } func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) events, err := NewAsyncEvents(ctx, NatsLoopbackUrl) if err != nil { require.NoError(t, err) diff --git a/backend_client.go b/backend_client.go index 54ee18f..b521ca6 100644 --- a/backend_client.go +++ b/backend_client.go @@ -32,6 +32,8 @@ import ( "time" "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -57,7 +59,7 @@ type BackendClient struct { } func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) backends, err := NewBackendConfiguration(logger, config, etcdClient) if err != nil { return nil, err @@ -118,7 +120,7 @@ func isOcsRequest(u *url.URL) bool { // 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 { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) if u == nil { return fmt.Errorf("no url passed to perform JSON request %+v", request) } diff --git a/backend_client_test.go b/backend_client_test.go index 344367d..3f35d5d 100644 --- a/backend_client_test.go +++ b/backend_client_test.go @@ -34,6 +34,8 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { @@ -66,8 +68,8 @@ func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { func TestPostOnRedirect(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() r.HandleFunc("/ocs/v2.php/one", func(w http.ResponseWriter, r *http.Request) { @@ -114,8 +116,8 @@ func TestPostOnRedirect(t *testing.T) { func TestPostOnRedirectDifferentHost(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() r.HandleFunc("/ocs/v2.php/one", func(w http.ResponseWriter, r *http.Request) { @@ -151,8 +153,8 @@ func TestPostOnRedirectDifferentHost(t *testing.T) { func TestPostOnRedirectStatusFound(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) r := mux.NewRouter() @@ -194,8 +196,8 @@ func TestPostOnRedirectStatusFound(t *testing.T) { func TestHandleThrottled(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) r := mux.NewRouter() diff --git a/backend_configuration.go b/backend_configuration.go index c7a485d..a955eac 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -30,8 +30,10 @@ import ( "sync" "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -255,11 +257,11 @@ var ( defaultBackendStats = &prometheusBackendStats{} ) -func NewBackendConfiguration(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (*BackendConfiguration, error) { +func NewBackendConfiguration(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (*BackendConfiguration, error) { return NewBackendConfigurationWithStats(logger, config, etcdClient, nil) } -func NewBackendConfigurationWithStats(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (*BackendConfiguration, error) { +func NewBackendConfigurationWithStats(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (*BackendConfiguration, error) { backendType, _ := config.GetString("backend", "backendtype") if backendType == "" { backendType = DefaultBackendType diff --git a/backend_configuration_test.go b/backend_configuration_test.go index 961b555..74012a9 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -32,6 +32,8 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) { @@ -106,7 +108,7 @@ func (s *mockBackendStats) DecBackends() { func TestIsUrlAllowed_Compat(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) // Old-style configuration valid_urls := []string{ "http://domain.invalid", @@ -128,7 +130,7 @@ func TestIsUrlAllowed_Compat(t *testing.T) { func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) // Old-style configuration, force HTTPS valid_urls := []string{ "https://domain.invalid", @@ -149,7 +151,7 @@ func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { func TestIsUrlAllowed(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) valid_urls := [][]string{ {"https://domain.invalid/foo", string(testBackendSecret) + "-foo"}, {"https://domain.invalid/foo/", string(testBackendSecret) + "-foo"}, @@ -194,7 +196,7 @@ func TestIsUrlAllowed(t *testing.T) { func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) valid_urls := []string{} invalid_urls := []string{ "http://domain.invalid", @@ -211,7 +213,7 @@ func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { func TestIsUrlAllowed_AllowAll(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) valid_urls := []string{ "http://domain.invalid", "https://domain.invalid", @@ -257,7 +259,7 @@ func TestBackendReloadNoChange(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -293,7 +295,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -334,7 +336,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -371,7 +373,7 @@ func TestBackendReloadAddBackend(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -412,7 +414,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -450,7 +452,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -504,7 +506,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) @@ -621,7 +623,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { func TestBackendCommonSecret(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) u1, err := url.Parse("http://domain1.invalid") @@ -664,7 +666,7 @@ func TestBackendChangeUrls(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) u1, err := url.Parse("http://domain1.invalid/") @@ -754,7 +756,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) diff --git a/backend_server.go b/backend_server.go index 77c8253..a9b7351 100644 --- a/backend_server.go +++ b/backend_server.go @@ -49,6 +49,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -62,7 +63,7 @@ const ( ) type BackendServer struct { - logger Logger + logger log.Logger hub *Hub events AsyncEvents roomSessions RoomSessions @@ -83,7 +84,7 @@ type BackendServer struct { } func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) turnapikey, _ := GetStringOptionWithEnv(config, "turn", "apikey") turnsecret, _ := GetStringOptionWithEnv(config, "turn", "secret") turnservers, _ := config.GetString("turn", "servers") @@ -316,7 +317,7 @@ func (b *BackendServer) parseRequestBody(f func(context.Context, http.ResponseWr } defer b.buffers.Put(body) - ctx := NewLoggerContext(r.Context(), b.logger) + ctx := log.NewLoggerContext(r.Context(), b.logger) f(ctx, w, r, body.Bytes()) } } @@ -367,7 +368,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso } timeout := time.Second - ctx := NewLoggerContext(context.Background(), b.logger) + ctx := log.NewLoggerContext(context.Background(), b.logger) ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var wg sync.WaitGroup @@ -497,7 +498,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request if !request.InCall.All { timeout := time.Second - ctx := NewLoggerContext(context.Background(), b.logger) + ctx := log.NewLoggerContext(context.Background(), b.logger) ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var cache ConcurrentMap[RoomSessionId, PublicSessionId] diff --git a/backend_server_test.go b/backend_server_test.go index da09f8a..faee2aa 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -47,6 +47,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -99,8 +100,8 @@ func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFil config.AddOption("clients", "internalsecret", string(testInternalSecret)) config.AddOption("geoip", "url", "none") events := getAsyncEventsForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) hub, err := NewHub(ctx, config, events, nil, nil, nil, r, "no-version") require.NoError(err) b, err := NewBackendServer(ctx, config, hub, "no-version") @@ -161,8 +162,8 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config1.AddOption("clients", "internalsecret", string(testInternalSecret)) config1.AddOption("geoip", "url", "none") - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) events1, err := NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) @@ -394,8 +395,8 @@ func TestBackendServer_RoomInvite(t *testing.T) { for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomInvite(ctx, t) }) } @@ -462,8 +463,8 @@ func TestBackendServer_RoomDisinvite(t *testing.T) { for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomDisinvite(ctx, t) }) } @@ -541,8 +542,8 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -624,8 +625,8 @@ func TestBackendServer_RoomUpdate(t *testing.T) { for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomUpdate(ctx, t) }) } @@ -694,8 +695,8 @@ func TestBackendServer_RoomDelete(t *testing.T) { for _, backend := range eventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomDelete(ctx, t) }) } @@ -761,8 +762,8 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) var hub1 *Hub @@ -858,8 +859,8 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -923,8 +924,8 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1086,8 +1087,8 @@ func TestBackendServer_InCallAll(t *testing.T) { for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) var hub1 *Hub @@ -1258,8 +1259,8 @@ func TestBackendServer_InCallAll(t *testing.T) { func TestBackendServer_RoomMessage(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1428,8 +1429,8 @@ func Test_IsNumeric(t *testing.T) { func TestBackendServer_DialoutNoSipBridge(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1472,8 +1473,8 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { func TestBackendServer_DialoutAccepted(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1559,8 +1560,8 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1646,8 +1647,8 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { func TestBackendServer_DialoutRejected(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) @@ -1731,8 +1732,8 @@ func TestBackendServer_DialoutRejected(t *testing.T) { func TestBackendServer_DialoutFirstFailed(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 9f7a841..718eecf 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -31,12 +31,14 @@ import ( "github.com/dlintw/goconf" clientv3 "go.etcd.io/etcd/client/v3" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type backendStorageEtcd struct { backendStorageCommon - logger Logger + logger log.Logger etcdClient *EtcdClient keyPrefix string keyInfos map[string]*BackendInformationEtcd @@ -49,7 +51,7 @@ type backendStorageEtcd struct { closeFunc context.CancelFunc } -func NewBackendStorageEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (BackendStorage, error) { +func NewBackendStorageEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (BackendStorage, error) { if etcdClient == nil || !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } diff --git a/backend_storage_etcd_test.go b/backend_storage_etcd_test.go index 5b4a21b..2af55bb 100644 --- a/backend_storage_etcd_test.go +++ b/backend_storage_etcd_test.go @@ -27,6 +27,8 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func (s *backendStorageEtcd) getWakeupChannelForTesting() <-chan struct{} { @@ -53,7 +55,7 @@ func (tl *testListener) EtcdClientCreated(client *EtcdClient) { } func Test_BackendStorageEtcdNoLeak(t *testing.T) { // nolint:paralleltest - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) ensureNoGoroutinesLeak(t, func(t *testing.T) { etcd, client := NewEtcdClientForTest(t) tl := &testListener{ diff --git a/backend_storage_static.go b/backend_storage_static.go index 903e35a..9c8967f 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -27,14 +27,16 @@ import ( "strings" "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type backendStorageStatic struct { backendStorageCommon - logger Logger + logger log.Logger backendsById map[string]*Backend // Deprecated @@ -43,7 +45,7 @@ type backendStorageStatic struct { compatBackend *Backend } -func NewBackendStorageStatic(logger Logger, config *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { +func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { allowAll, _ := config.GetBool("backend", "allowall") allowHttp, _ := config.GetBool("backend", "allowhttp") commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") @@ -295,7 +297,7 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } -func getConfiguredHosts(logger Logger, backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { +func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { hosts = make(map[string][]*Backend) seenUrls := make(map[string]string) for _, id := range getConfiguredBackendIDs(backendIds) { diff --git a/capabilities.go b/capabilities.go index 0e66848..c609eb9 100644 --- a/capabilities.go +++ b/capabilities.go @@ -34,6 +34,7 @@ import ( "github.com/pquerna/cachecontrol/cacheobject" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -117,7 +118,7 @@ func (e *capabilitiesEntry) errorIfMustRevalidate(err error) (bool, error) { } func (e *capabilitiesEntry) update(ctx context.Context, u *url.URL, now time.Time) (bool, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) e.mu.Lock() defer e.mu.Unlock() @@ -351,7 +352,7 @@ func (c *Capabilities) loadCapabilities(ctx context.Context, u *url.URL) (api.St } func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, feature string) bool { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) caps, _, err := c.loadCapabilities(ctx, u) if err != nil { logger.Printf("Could not get capabilities for %s: %s", u, err) @@ -378,7 +379,7 @@ func (c *Capabilities) HasCapabilityFeature(ctx context.Context, u *url.URL, fea } func (c *Capabilities) getConfigGroup(ctx context.Context, u *url.URL, group string) (api.StringMap, bool, bool) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) caps, cached, err := c.loadCapabilities(ctx, u) if err != nil { logger.Printf("Could not get capabilities for %s: %s", u, err) @@ -429,7 +430,7 @@ func (c *Capabilities) GetIntegerConfig(ctx context.Context, u *url.URL, group, case float64: return int(value), cached, true default: - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) logger.Printf("Invalid config value for \"%s\" received from %s: %+v", key, u, value) } @@ -451,7 +452,7 @@ func (c *Capabilities) GetStringConfig(ctx context.Context, u *url.URL, group, k case string: return value, cached, true default: - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) logger.Printf("Invalid config value for \"%s\" received from %s: %+v", key, u, value) } diff --git a/capabilities_test.go b/capabilities_test.go index d3f08c4..a53d00f 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -42,6 +42,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*CapabilitiesResponse, http.ResponseWriter) error) (*url.URL, *Capabilities) { @@ -176,8 +177,8 @@ func SetCapabilitiesGetNow(t *testing.T, capabilities *Capabilities, f func() ti func TestCapabilities(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) url, capabilities := NewCapabilitiesForTest(t) @@ -220,8 +221,8 @@ func TestCapabilities(t *testing.T) { func TestInvalidateCapabilities(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -281,8 +282,8 @@ func TestInvalidateCapabilities(t *testing.T) { func TestCapabilitiesNoCache(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -326,8 +327,8 @@ func TestCapabilitiesNoCache(t *testing.T) { func TestCapabilitiesShortCache(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -381,8 +382,8 @@ func TestCapabilitiesShortCache(t *testing.T) { func TestCapabilitiesNoCacheETag(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -423,8 +424,8 @@ func TestCapabilitiesNoCacheETag(t *testing.T) { func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -464,8 +465,8 @@ func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -505,8 +506,8 @@ func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { @@ -544,8 +545,8 @@ func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { func TestConcurrentExpired(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error { diff --git a/certificate_reloader.go b/certificate_reloader.go index b04429d..cfc74a8 100644 --- a/certificate_reloader.go +++ b/certificate_reloader.go @@ -28,10 +28,12 @@ import ( "os" "sync/atomic" "testing" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type CertificateReloader struct { - logger Logger + logger log.Logger certFile string certWatcher *FileWatcher @@ -44,7 +46,7 @@ type CertificateReloader struct { reloadCounter atomic.Uint64 } -func NewCertificateReloader(logger Logger, certFile string, keyFile string) (*CertificateReloader, error) { +func NewCertificateReloader(logger log.Logger, certFile string, keyFile string) (*CertificateReloader, error) { pair, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, fmt.Errorf("could not load certificate / key: %w", err) @@ -108,7 +110,7 @@ func (r *CertificateReloader) GetReloadCounter() uint64 { } type CertPoolReloader struct { - logger Logger + logger log.Logger certFile string certWatcher *FileWatcher @@ -132,7 +134,7 @@ func loadCertPool(filename string) (*x509.CertPool, error) { return pool, nil } -func NewCertPoolReloader(logger Logger, certFile string) (*CertPoolReloader, error) { +func NewCertPoolReloader(logger log.Logger, certFile string) (*CertPoolReloader, error) { pool, err := loadCertPool(certFile) if err != nil { return nil, err diff --git a/client.go b/client.go index 46f979c..296319c 100644 --- a/client.go +++ b/client.go @@ -36,6 +36,8 @@ import ( "github.com/gorilla/websocket" "github.com/mailru/easyjson" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -121,7 +123,7 @@ type ClientGeoIpHandler interface { } type Client struct { - logger Logger + logger log.Logger ctx context.Context conn *websocket.Conn addr string @@ -164,7 +166,7 @@ func NewClient(ctx context.Context, conn *websocket.Conn, remoteAddress string, } func (c *Client) SetConn(ctx context.Context, conn *websocket.Conn, remoteAddress string, handler ClientHandler) { - c.logger = LoggerFromContext(ctx) + c.logger = log.LoggerFromContext(ctx) c.ctx = ctx c.conn = conn c.addr = remoteAddress diff --git a/clientsession.go b/clientsession.go index c9e38aa..5c5d2e9 100644 --- a/clientsession.go +++ b/clientsession.go @@ -36,6 +36,7 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -55,7 +56,7 @@ const ( type ResponseHandlerFunc func(message *ClientMessage) bool type ClientSession struct { - logger Logger + logger log.Logger hub *Hub events AsyncEvents privateId PrivateSessionId @@ -120,7 +121,7 @@ type ClientSession struct { } func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { - ctx := NewLoggerContext(context.Background(), hub.logger) + ctx := log.NewLoggerContext(context.Background(), hub.logger) ctx, closeFunc := context.WithCancel(ctx) s := &ClientSession{ logger: hub.logger, @@ -551,7 +552,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { if notify && room != nil && s.roomSessionId != "" && !s.roomSessionId.IsFederated() { // Notify go func(sid RoomSessionId) { - ctx := NewLoggerContext(context.Background(), s.logger) + ctx := log.NewLoggerContext(context.Background(), s.logger) request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) request.Room.Action = "leave" diff --git a/deferred_executor.go b/deferred_executor.go index 3162058..a6504a7 100644 --- a/deferred_executor.go +++ b/deferred_executor.go @@ -26,18 +26,20 @@ import ( "runtime" "runtime/debug" "sync" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) // DeferredExecutor will asynchronously execute functions while maintaining // their order. type DeferredExecutor struct { - logger Logger + logger log.Logger queue chan func() closed chan struct{} closeOnce sync.Once } -func NewDeferredExecutor(logger Logger, queueSize int) *DeferredExecutor { +func NewDeferredExecutor(logger log.Logger, queueSize int) *DeferredExecutor { if queueSize < 0 { queueSize = 0 } diff --git a/deferred_executor_test.go b/deferred_executor_test.go index 4ff6a60..60b1fd0 100644 --- a/deferred_executor_test.go +++ b/deferred_executor_test.go @@ -26,11 +26,13 @@ import ( "time" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func TestDeferredExecutor_MultiClose(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() @@ -41,7 +43,7 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { func TestDeferredExecutor_QueueSize(t *testing.T) { t.Parallel() SynctestTest(t, func(t *testing.T) { - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() defer e.Close() @@ -64,7 +66,7 @@ func TestDeferredExecutor_QueueSize(t *testing.T) { func TestDeferredExecutor_Order(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() defer e.Close() @@ -93,7 +95,7 @@ func TestDeferredExecutor_Order(t *testing.T) { func TestDeferredExecutor_CloseFromFunc(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -108,7 +110,7 @@ func TestDeferredExecutor_CloseFromFunc(t *testing.T) { func TestDeferredExecutor_DeferAfterClose(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -121,7 +123,7 @@ func TestDeferredExecutor_DeferAfterClose(t *testing.T) { func TestDeferredExecutor_WaitForStopTwice(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() diff --git a/dns_monitor.go b/dns_monitor.go index 6d5465c..33bc12b 100644 --- a/dns_monitor.go +++ b/dns_monitor.go @@ -30,6 +30,8 @@ import ( "sync" "sync/atomic" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -156,7 +158,7 @@ func (e *dnsMonitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP type DnsMonitorLookupFunc func(hostname string) ([]net.IP, error) type DnsMonitor struct { - logger Logger + logger log.Logger interval time.Duration lookupFunc DnsMonitorLookupFunc @@ -175,7 +177,7 @@ type DnsMonitor struct { checkHostnames func() } -func NewDnsMonitor(logger Logger, interval time.Duration, lookupFunc DnsMonitorLookupFunc) (*DnsMonitor, error) { +func NewDnsMonitor(logger log.Logger, interval time.Duration, lookupFunc DnsMonitorLookupFunc) (*DnsMonitor, error) { if interval < 0 { interval = defaultDnsMonitorInterval } diff --git a/dns_monitor_test.go b/dns_monitor_test.go index 68afc81..fdab473 100644 --- a/dns_monitor_test.go +++ b/dns_monitor_test.go @@ -33,6 +33,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type mockDnsLookup struct { @@ -84,7 +86,7 @@ func newDnsMonitorForTest(t *testing.T, interval time.Duration, lookup *mockDnsL t.Helper() require := require.New(t) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) var lookupFunc DnsMonitorLookupFunc if lookup != nil { lookupFunc = lookup.lookup diff --git a/etcd_client.go b/etcd_client.go index 15ce75e..2dbacdc 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -39,6 +39,7 @@ import ( "google.golang.org/grpc/connectivity" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type EtcdClientListener interface { @@ -52,7 +53,7 @@ type EtcdClientWatcher interface { } type EtcdClient struct { - logger Logger + logger log.Logger compatSection string mu sync.Mutex @@ -61,7 +62,7 @@ type EtcdClient struct { listeners map[EtcdClientListener]bool } -func NewEtcdClient(logger Logger, config *goconf.ConfigFile, compatSection string) (*EtcdClient, error) { +func NewEtcdClient(logger log.Logger, config *goconf.ConfigFile, compatSection string) (*EtcdClient, error) { result := &EtcdClient{ logger: logger, compatSection: compatSection, diff --git a/etcd_client_test.go b/etcd_client_test.go index 4a6fe8e..251a8d0 100644 --- a/etcd_client_test.go +++ b/etcd_client_test.go @@ -45,6 +45,8 @@ import ( "go.etcd.io/etcd/server/v3/lease" "go.uber.org/zap" "go.uber.org/zap/zaptest" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -154,7 +156,7 @@ func NewEtcdClientForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { config.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) config.AddOption("etcd", "loglevel", "error") - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) client, err := NewEtcdClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { @@ -173,7 +175,7 @@ func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { config.AddOption("etcd", "clientcert", certfile) config.AddOption("etcd", "cacert", certfile) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) client, err := NewEtcdClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { @@ -198,8 +200,8 @@ func DeleteEtcdValue(etcd *embed.Etcd, key string) { func Test_EtcdClient_Get(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) etcd, client := NewEtcdClientForTest(t) @@ -245,8 +247,8 @@ func Test_EtcdClient_Get(t *testing.T) { func Test_EtcdClientTLS_Get(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) etcd, client := NewEtcdClientWithTLSForTest(t) @@ -292,8 +294,8 @@ func Test_EtcdClientTLS_Get(t *testing.T) { func Test_EtcdClient_GetPrefix(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) @@ -404,8 +406,8 @@ func (l *EtcdClientTestListener) EtcdKeyDeleted(client *EtcdClient, key string, func Test_EtcdClient_Watch(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd, client := NewEtcdClientForTest(t) diff --git a/federation.go b/federation.go index b4f1650..dfbfe8b 100644 --- a/federation.go +++ b/federation.go @@ -39,6 +39,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -77,7 +78,7 @@ func getCloudUrl(s string) string { } type FederationClient struct { - logger Logger + logger log.Logger hub *Hub session *ClientSession message atomic.Pointer[ClientMessage] @@ -363,7 +364,7 @@ func (c *FederationClient) reconnect() { return } - ctx := NewLoggerContext(context.Background(), c.logger) + ctx := log.NewLoggerContext(context.Background(), c.logger) ctx, cancel := context.WithTimeout(ctx, time.Duration(c.hub.federationTimeout)) defer cancel() diff --git a/file_watcher.go b/file_watcher.go index 99592aa..dc1e824 100644 --- a/file_watcher.go +++ b/file_watcher.go @@ -32,6 +32,8 @@ import ( "time" "github.com/fsnotify/fsnotify" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -41,7 +43,7 @@ const ( type FileWatcherCallback func(filename string) type FileWatcher struct { - logger Logger + logger log.Logger filename string target string callback FileWatcherCallback @@ -52,7 +54,7 @@ type FileWatcher struct { closeFunc context.CancelFunc } -func NewFileWatcher(logger Logger, filename string, callback FileWatcherCallback, deduplicate time.Duration) (*FileWatcher, error) { +func NewFileWatcher(logger log.Logger, filename string, callback FileWatcherCallback, deduplicate time.Duration) (*FileWatcher, error) { watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err diff --git a/file_watcher_test.go b/file_watcher_test.go index 241a5bd..9978405 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -39,7 +41,7 @@ func TestFileWatcher_NotExist(t *testing.T) { t.Parallel() assert := assert.New(t) tmpdir := t.TempDir() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}, defaultDeduplicateWatchEvents); !assert.ErrorIs(err, os.ErrNotExist) { if w != nil { assert.NoError(w.Close()) @@ -55,7 +57,7 @@ func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -98,7 +100,7 @@ func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, "./"+path.Base(filename), func(filename string) { modified <- struct{}{} @@ -140,7 +142,7 @@ func TestFileWatcher_Rename(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -184,7 +186,7 @@ func TestFileWatcher_Symlink(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename, filename)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -219,7 +221,7 @@ func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename1, filename)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -256,7 +258,7 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename1, filename)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -287,7 +289,7 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.Symlink(sourceFilename1, filename)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -341,7 +343,7 @@ func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.Symlink("data/test.txt", filename)) - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} diff --git a/geoip.go b/geoip.go index 2feaa89..326e23f 100644 --- a/geoip.go +++ b/geoip.go @@ -38,6 +38,8 @@ import ( "github.com/dlintw/goconf" "github.com/oschwald/maxminddb-golang" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -57,7 +59,7 @@ func GetGeoIpDownloadUrl(license string) string { } type GeoLookup struct { - logger Logger + logger log.Logger url string isFile bool client http.Client @@ -68,7 +70,7 @@ type GeoLookup struct { reader atomic.Pointer[maxminddb.Reader] } -func NewGeoLookupFromUrl(logger Logger, url string) (*GeoLookup, error) { +func NewGeoLookupFromUrl(logger log.Logger, url string) (*GeoLookup, error) { geoip := &GeoLookup{ logger: logger, url: url, @@ -76,7 +78,7 @@ func NewGeoLookupFromUrl(logger Logger, url string) (*GeoLookup, error) { return geoip, nil } -func NewGeoLookupFromFile(logger Logger, filename string) (*GeoLookup, error) { +func NewGeoLookupFromFile(logger log.Logger, filename string) (*GeoLookup, error) { geoip := &GeoLookup{ logger: logger, url: filename, @@ -273,7 +275,7 @@ func IsValidContinent(continent string) bool { } func LoadGeoIPOverrides(ctx context.Context, config *goconf.ConfigFile, ignoreErrors bool) (map[*net.IPNet]string, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) options, _ := GetStringOptions(config, "geoip-overrides", true) if len(options) == 0 { return nil, nil diff --git a/geoip_test.go b/geoip_test.go index 41cbbec..de59b78 100644 --- a/geoip_test.go +++ b/geoip_test.go @@ -35,6 +35,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func testGeoLookupReader(t *testing.T, reader *GeoLookup) { @@ -77,7 +79,7 @@ func GetGeoIpUrlForTest(t *testing.T) string { func TestGeoLookup(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) require.NoError(err) @@ -90,7 +92,7 @@ func TestGeoLookup(t *testing.T) { func TestGeoLookupCaching(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) require.NoError(err) @@ -131,7 +133,7 @@ func TestGeoLookupContinent(t *testing.T) { func TestGeoLookupCloseEmpty(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) reader, err := NewGeoLookupFromUrl(logger, "ignore-url") require.NoError(t, err) reader.Close() @@ -139,7 +141,7 @@ func TestGeoLookupCloseEmpty(t *testing.T) { func TestGeoLookupFromFile(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) geoIpUrl := GetGeoIpUrlForTest(t) diff --git a/grpc_client.go b/grpc_client.go index afdee7a..dc0b94d 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -42,6 +42,8 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/resolver" status "google.golang.org/grpc/status" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -78,7 +80,7 @@ func newGrpcClientImpl(conn grpc.ClientConnInterface) *grpcClientImpl { } type GrpcClient struct { - logger Logger + logger log.Logger ip net.IP rawTarget string target string @@ -127,7 +129,7 @@ func (r *customIpResolver) Close() { // Noop } -func NewGrpcClient(logger Logger, target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClient, error) { +func NewGrpcClient(logger log.Logger, target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClient, error) { var conn *grpc.ClientConn var err error if ip != nil { @@ -370,7 +372,7 @@ type ProxySessionReceiver interface { } type SessionProxy struct { - logger Logger + logger log.Logger sessionId PublicSessionId receiver ProxySessionReceiver @@ -450,7 +452,7 @@ type grpcClientsList struct { type GrpcClients struct { mu sync.RWMutex version string - logger Logger + logger log.Logger // +checklocks:mu clientsMap map[string]*grpcClientsList @@ -482,7 +484,7 @@ func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient * closeCtx, closeFunc := context.WithCancel(context.Background()) result := &GrpcClients{ version: version, - logger: LoggerFromContext(ctx), + logger: log.LoggerFromContext(ctx), dnsMonitor: dnsMonitor, etcdClient: etcdClient, initializedCtx: initializedCtx, diff --git a/grpc_client_test.go b/grpc_client_test.go index 66100da..f6ef0c7 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -36,6 +36,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { @@ -53,8 +55,8 @@ func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient *EtcdClient, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) client, err := NewGrpcClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") require.NoError(t, err) t.Cleanup(func() { @@ -79,7 +81,7 @@ func NewGrpcClientsWithEtcdForTest(t *testing.T, etcd *embed.Etcd, lookup *mockD config.AddOption("grpc", "targettype", "etcd") config.AddOption("grpc", "targetprefix", "/grpctargets") - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) etcdClient, err := NewEtcdClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { @@ -111,8 +113,8 @@ func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { } func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) ensureNoGoroutinesLeak(t, func(t *testing.T) { _, addr1 := NewGrpcServerForTest(t) _, addr2 := NewGrpcServerForTest(t) @@ -134,8 +136,8 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest func Test_GrpcClients_EtcdUpdate(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd := NewEtcdForTest(t) client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) @@ -181,8 +183,8 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd := NewEtcdForTest(t) client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) @@ -220,8 +222,8 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { } func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) ensureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) diff --git a/grpc_common.go b/grpc_common.go index dfea756..5c49300 100644 --- a/grpc_common.go +++ b/grpc_common.go @@ -30,6 +30,8 @@ import ( "github.com/dlintw/goconf" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type reloadableCredentials struct { @@ -133,7 +135,7 @@ func (c *reloadableCredentials) Close() { } } -func NewReloadableCredentials(logger Logger, config *goconf.ConfigFile, server bool) (credentials.TransportCredentials, error) { +func NewReloadableCredentials(logger log.Logger, config *goconf.ConfigFile, server bool) (credentials.TransportCredentials, error) { var prefix string var caPrefix string if server { diff --git a/grpc_remote_client.go b/grpc_remote_client.go index 02133ec..9ba69e5 100644 --- a/grpc_remote_client.go +++ b/grpc_remote_client.go @@ -32,6 +32,8 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -48,7 +50,7 @@ func getMD(md metadata.MD, key string) string { // remoteGrpcClient is a remote client connecting from a GRPC proxy to a Hub. type remoteGrpcClient struct { - logger Logger + logger log.Logger hub *Hub client RpcSessions_ProxySessionServer diff --git a/grpc_server.go b/grpc_server.go index 27dc993..e8addcb 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -37,6 +37,8 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" status "google.golang.org/grpc/status" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -73,7 +75,7 @@ type GrpcServer struct { UnimplementedRpcMcuServer UnimplementedRpcSessionsServer - logger Logger + logger log.Logger version string creds credentials.TransportCredentials conn *grpc.Server @@ -93,7 +95,7 @@ func NewGrpcServer(ctx context.Context, config *goconf.ConfigFile, version strin } } - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) creds, err := NewReloadableCredentials(logger, config, true) if err != nil { return nil, err diff --git a/grpc_server_test.go b/grpc_server_test.go index 0db5b10..125107d 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -41,6 +41,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func (s *GrpcServer) WaitForCertificateReload(ctx context.Context, counter uint64) error { @@ -62,8 +64,8 @@ func (s *GrpcServer) WaitForCertPoolReload(ctx context.Context, counter uint64) } func NewGrpcServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *GrpcServer, addr string) { - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) for port := 50000; port < 50100; port++ { addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) config.AddOption("grpc", "listen", addr) @@ -169,7 +171,7 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { func Test_GrpcServer_ReloadCA(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) diff --git a/hub.go b/hub.go index 6d1b38a..a122de9 100644 --- a/hub.go +++ b/hub.go @@ -52,6 +52,7 @@ import ( "github.com/gorilla/websocket" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -139,7 +140,7 @@ func init() { type Hub struct { version string - logger Logger + logger log.Logger events AsyncEvents upgrader websocket.Upgrader sessionIds *SessionIdCodec @@ -220,7 +221,7 @@ type Hub struct { } func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) hashKey, _ := GetStringOptionWithEnv(config, "sessions", "hashkey") switch len(hashKey) { case 32: @@ -1270,7 +1271,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message } func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { - ctx := NewLoggerContext(client.Context(), h.logger) + ctx := log.NewLoggerContext(client.Context(), h.logger) resumeId := message.Hello.ResumeId if resumeId != "" { throttle, err := h.throttler.CheckBruteforce(ctx, client.RemoteAddr(), "HelloResume") @@ -1576,7 +1577,7 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) return } - ctx := NewLoggerContext(client.Context(), h.logger) + ctx := log.NewLoggerContext(client.Context(), h.logger) throttle, err := h.throttler.CheckBruteforce(ctx, client.RemoteAddr(), "HelloInternal") if err == ErrBruteforceDetected { client.SendMessage(message.NewErrorServerMessage(TooManyRequests)) @@ -1944,7 +1945,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { return 0, &wg } count := 0 - ctx := NewLoggerContext(context.Background(), h.logger) + ctx := log.NewLoggerContext(context.Background(), h.logger) for roomId, entries := range rooms { for u, e := range entries { wg.Add(1) @@ -3128,7 +3129,7 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { return } - ctx := NewLoggerContext(r.Context(), h.logger) + ctx := log.NewLoggerContext(r.Context(), h.logger) if conn.Subprotocol() == JanusEventsSubprotocol { RunJanusEventsHandler(ctx, h.mcu, conn, addr, agent) return diff --git a/hub_test.go b/hub_test.go index 320e70d..43fa737 100644 --- a/hub_test.go +++ b/hub_test.go @@ -54,6 +54,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -151,8 +152,8 @@ func getTestConfigWithMultipleUrls(server *httptest.Server) (*goconf.ConfigFile, } func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() registerBackendHandler(t, r) @@ -202,8 +203,8 @@ func CreateHubWithMultipleUrlsForTest(t *testing.T) (*Hub, AsyncEvents, *mux.Rou } func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, *Hub, *mux.Router, *mux.Router, *httptest.Server, *httptest.Server) { - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) r1 := mux.NewRouter() @@ -5200,8 +5201,8 @@ func TestGeoipOverrides(t *testing.T) { func TestDialoutStatus(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) _, _, _, hub, _, server := CreateBackendServerForTest(t) diff --git a/logging.go b/log/logging.go similarity index 98% rename from logging.go rename to log/logging.go index df87432..cf5cc78 100644 --- a/logging.go +++ b/log/logging.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 log import ( "context" diff --git a/log/logging_test.go b/log/logging_test.go new file mode 100644 index 0000000..c3bb64f --- /dev/null +++ b/log/logging_test.go @@ -0,0 +1,68 @@ +/** + * 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 log + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGlobalLogger(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + defer func() { + if err := recover(); assert.NotNil(err) { + assert.Equal("accessed global logger", err) + } + }() + + logger := LoggerFromContext(t.Context()) + assert.Fail("should have paniced", "got logger %+v", logger) +} + +func TestLoggerContext(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + testLogger := NewLoggerForTest(t) + testLogger.Printf("Hello %s!", "world") + + ctx := NewLoggerContext(t.Context(), testLogger) + logger2 := LoggerFromContext(ctx) + assert.Equal(testLogger, logger2) +} + +func TestNilLoggerContext(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + defer func() { + if err := recover(); assert.NotNil(err) { + assert.Equal("logger is nil", err) + } + }() + + ctx := NewLoggerContext(t.Context(), nil) + assert.Fail("should have paniced", "got context %+v", ctx) +} diff --git a/test_helpers.go b/log/test_helpers.go similarity index 98% rename from test_helpers.go rename to log/test_helpers.go index c4ab4f6..8eee5d6 100644 --- a/test_helpers.go +++ b/log/test_helpers.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 log import ( "bytes" diff --git a/test_helpers_24.go b/log/test_helpers_24.go similarity index 98% rename from test_helpers_24.go rename to log/test_helpers_24.go index f51c259..c55c540 100644 --- a/test_helpers_24.go +++ b/log/test_helpers_24.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package log import ( "testing" diff --git a/test_helpers_25.go b/log/test_helpers_25.go similarity index 98% rename from test_helpers_25.go rename to log/test_helpers_25.go index 06b0a82..74a0769 100644 --- a/test_helpers_25.go +++ b/log/test_helpers_25.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package log import ( "testing" diff --git a/mcu_common.go b/mcu_common.go index ab00386..f744227 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -30,6 +30,7 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -81,7 +82,7 @@ type McuSettings interface { } type mcuCommonSettings struct { - logger Logger + logger log.Logger maxStreamBitrate api.AtomicBandwidth maxScreenBitrate api.AtomicBandwidth diff --git a/mcu_janus.go b/mcu_janus.go index 3a092aa..ba440f0 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -36,6 +36,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -107,7 +108,7 @@ func convertIntValue(value any) (uint64, error) { } } -func getPluginIntValue(logger Logger, data janus.PluginData, pluginName string, key string) uint64 { +func getPluginIntValue(logger log.Logger, data janus.PluginData, pluginName string, key string) uint64 { val := getPluginValue(data, pluginName, key) if val == nil { return 0 @@ -156,7 +157,7 @@ type mcuJanusSettings struct { func newMcuJanusSettings(ctx context.Context, config *goconf.ConfigFile) (*mcuJanusSettings, error) { settings := &mcuJanusSettings{ mcuCommonSettings: mcuCommonSettings{ - logger: LoggerFromContext(ctx), + logger: log.LoggerFromContext(ctx), }, } if err := settings.load(config); err != nil { @@ -229,7 +230,7 @@ func (s *prometheusJanusStats) DecSubscriber(streamType StreamType) { } type mcuJanus struct { - logger Logger + logger log.Logger url string mu sync.Mutex @@ -278,7 +279,7 @@ func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mc } mcu := &mcuJanus{ - logger: LoggerFromContext(ctx), + logger: log.LoggerFromContext(ctx), url: url, settings: settings, stats: &prometheusJanusStats{}, diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 66ff7ac..27af7bb 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -31,10 +31,11 @@ import ( "github.com/notedit/janus-go" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type mcuJanusClient struct { - logger Logger + logger log.Logger mcu *mcuJanus listener McuListener mu sync.Mutex diff --git a/mcu_janus_events_handler.go b/mcu_janus_events_handler.go index f49ba7a..2044938 100644 --- a/mcu_janus_events_handler.go +++ b/mcu_janus_events_handler.go @@ -37,6 +37,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -620,7 +621,7 @@ func (h *handleStats) LostRemote(media string, lost uint64) { type JanusEventsHandler struct { mu sync.Mutex - logger Logger + logger log.Logger ctx context.Context mcu McuEventHandler // +checklocks:mu @@ -654,7 +655,7 @@ func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, a client, err := NewJanusEventsHandler(ctx, m, conn, addr, agent) if err != nil { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) logger.Printf("Could not create Janus events handler for %s: %s", addr, err) conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "error creating handler"), deadline) // nolint return @@ -665,7 +666,7 @@ func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, a func NewJanusEventsHandler(ctx context.Context, mcu McuEventHandler, conn *websocket.Conn, addr string, agent string) (*JanusEventsHandler, error) { handler := &JanusEventsHandler{ - logger: LoggerFromContext(ctx), + logger: log.LoggerFromContext(ctx), ctx: ctx, mcu: mcu, conn: conn, diff --git a/mcu_janus_events_handler_test.go b/mcu_janus_events_handler_test.go index 38552d4..afadd0d 100644 --- a/mcu_janus_events_handler_test.go +++ b/mcu_janus_events_handler_test.go @@ -39,6 +39,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type TestJanusEventsServerHandler struct { @@ -66,8 +67,8 @@ func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http. if host, _, err := net.SplitHostPort(addr); err == nil { addr = host } - logger := NewLoggerForTest(h.t) - ctx := NewLoggerContext(r.Context(), logger) + logger := log.NewLoggerForTest(h.t) + ctx := log.NewLoggerContext(r.Context(), logger) RunJanusEventsHandler(ctx, h.mcu, conn, addr, r.Header.Get("User-Agent")) return } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index f55e17a..b76e63e 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -37,6 +37,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func TestMcuJanusStats(t *testing.T) { @@ -594,8 +595,8 @@ func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { if strings.Contains(t.Name(), "Filter") { config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") } - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) mcu, err := NewMcuJanus(ctx, "", config) require.NoError(t, err) t.Cleanup(func() { diff --git a/mcu_proxy.go b/mcu_proxy.go index d49c45a..15027fb 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -47,6 +47,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -79,7 +80,7 @@ type McuProxy interface { } type mcuProxyPubSubCommon struct { - logger Logger + logger log.Logger sid string streamType StreamType @@ -149,7 +150,7 @@ type mcuProxyPublisher struct { settings NewPublisherSettings } -func newMcuProxyPublisher(logger Logger, id PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { +func newMcuProxyPublisher(logger log.Logger, id PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { return &mcuProxyPublisher{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ logger: logger, @@ -243,7 +244,7 @@ type mcuProxySubscriber struct { publisherConn *mcuProxyConnection } -func newMcuProxySubscriber(logger Logger, publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { +func newMcuProxySubscriber(logger log.Logger, publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { return &mcuProxySubscriber{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ logger: logger, @@ -344,7 +345,7 @@ func (s *mcuProxySubscriber) ProcessEvent(msg *EventProxyServerMessage) { type mcuProxyCallback func(response *ProxyServerMessage) type mcuProxyConnection struct { - logger Logger + logger log.Logger proxy *mcuProxy rawUrl string url *url.URL @@ -1449,7 +1450,7 @@ type mcuProxySettings struct { func newMcuProxySettings(ctx context.Context, config *goconf.ConfigFile) (McuSettings, error) { settings := &mcuProxySettings{ mcuCommonSettings: mcuCommonSettings{ - logger: LoggerFromContext(ctx), + logger: log.LoggerFromContext(ctx), }, } if err := settings.load(config); err != nil { @@ -1481,7 +1482,7 @@ func (s *mcuProxySettings) Reload(config *goconf.ConfigFile) { } type mcuProxy struct { - logger Logger + logger log.Logger urlType string tokenId string tokenKey *rsa.PrivateKey @@ -1510,7 +1511,7 @@ type mcuProxy struct { } func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients *GrpcClients, dnsMonitor *DnsMonitor) (Mcu, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) urlType, _ := config.GetString("mcu", "urltype") if urlType == "" { urlType = proxyUrlTypeStatic diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 641f5d7..8d738ed 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -48,6 +48,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -889,8 +891,8 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i etcdConfig.AddOption("etcd", "endpoints", options.etcd.Config().ListenClientUrls[0].String()) etcdConfig.AddOption("etcd", "loglevel", "error") - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) etcdClient, err := NewEtcdClient(logger, etcdConfig, "") require.NoError(err) t.Cleanup(func() { diff --git a/mcu_test.go b/mcu_test.go index b6f8792..321ee33 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -33,6 +33,7 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -188,7 +189,7 @@ func (c *TestMCUClient) MaxBitrate() api.Bandwidth { func (c *TestMCUClient) Close(ctx context.Context) { if c.closed.CompareAndSwap(false, true) { - logger := NewLoggerForTest(c.t) + logger := log.NewLoggerForTest(c.t) logger.Printf("Close MCU client %s", c.id) } } diff --git a/natsclient.go b/natsclient.go index 0054168..6fec9a4 100644 --- a/natsclient.go +++ b/natsclient.go @@ -33,6 +33,8 @@ import ( "time" "github.com/nats-io/nats.go" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -63,13 +65,13 @@ func GetEncodedSubject(prefix string, suffix string) string { } type natsClient struct { - logger Logger + logger log.Logger conn *nats.Conn closed chan struct{} } func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (NatsClient, error) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) if url == ":loopback:" { logger.Printf("WARNING: events url %s is deprecated, please use %s instead", url, NatsLoopbackUrl) url = NatsLoopbackUrl diff --git a/natsclient_loopback.go b/natsclient_loopback.go index a478beb..09dca16 100644 --- a/natsclient_loopback.go +++ b/natsclient_loopback.go @@ -29,10 +29,12 @@ import ( "sync" "github.com/nats-io/nats.go" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type LoopbackNatsClient struct { - logger Logger + logger log.Logger mu sync.Mutex closed chan struct{} @@ -46,7 +48,7 @@ type LoopbackNatsClient struct { incoming list.List } -func NewLoopbackNatsClient(logger Logger) (NatsClient, error) { +func NewLoopbackNatsClient(logger log.Logger) (NatsClient, error) { client := &LoopbackNatsClient{ logger: logger, closed: make(chan struct{}), diff --git a/natsclient_loopback_test.go b/natsclient_loopback_test.go index 2109e0a..bfb3bf0 100644 --- a/natsclient_loopback_test.go +++ b/natsclient_loopback_test.go @@ -28,6 +28,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func (c *LoopbackNatsClient) waitForSubscriptionsEmpty(ctx context.Context, t *testing.T) { @@ -52,7 +54,7 @@ func (c *LoopbackNatsClient) waitForSubscriptionsEmpty(ctx context.Context, t *t } func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) result, err := NewLoopbackNatsClient(logger) require.NoError(t, err) t.Cleanup(func() { diff --git a/natsclient_test.go b/natsclient_test.go index ace44f6..5f9a22f 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -33,6 +33,8 @@ import ( "github.com/nats-io/nats-server/v2/server" natsserver "github.com/nats-io/nats-server/v2/test" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func startLocalNatsServer(t *testing.T) (*server.Server, int) { @@ -56,8 +58,8 @@ func startLocalNatsServerPort(t *testing.T, port int) (*server.Server, int) { func CreateLocalNatsClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, NatsClient) { t.Helper() server, port := startLocalNatsServer(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) result, err := NewNatsClient(ctx, server.ClientURL(), options...) require.NoError(t, err) t.Cleanup(func() { diff --git a/proxy/main.go b/proxy/main.go index acc024e..7b05cb7 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -38,6 +38,7 @@ import ( "github.com/gorilla/mux" signaling "github.com/strukturag/nextcloud-spreed-signaling" + signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -72,7 +73,7 @@ func main() { defer stop() logger := log.Default() - stopCtx = signaling.NewLoggerContext(stopCtx, logger) + stopCtx = signalinglog.NewLoggerContext(stopCtx, logger) logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 3c16da2..5d9df6e 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -40,6 +40,7 @@ import ( "github.com/gorilla/websocket" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -61,7 +62,7 @@ var ( ) type RemoteConnection struct { - logger signaling.Logger + logger log.Logger mu sync.Mutex p *ProxyServer url *url.URL diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index dafd056..43882c9 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -51,6 +51,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -109,7 +110,7 @@ type ProxyServer struct { welcomeMsg *signaling.WelcomeServerMessage config *goconf.ConfigFile mcuTimeout time.Duration - logger signaling.Logger + logger log.Logger url string mcu signaling.Mcu @@ -188,7 +189,7 @@ func GetLocalIP() (string, error) { return "", nil } -func getTargetBandwidths(logger signaling.Logger, config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidth) { +func getTargetBandwidths(logger log.Logger, config *goconf.ConfigFile) (api.Bandwidth, api.Bandwidth) { maxIncomingValue, _ := config.GetInt("bandwidth", "incoming") if maxIncomingValue < 0 { maxIncomingValue = 0 @@ -215,7 +216,7 @@ func getTargetBandwidths(logger signaling.Logger, config *goconf.ConfigFile) (ap } func NewProxyServer(ctx context.Context, r *mux.Router, version string, config *goconf.ConfigFile) (*ProxyServer, error) { - logger := signaling.LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) hashKey := make([]byte, 64) if _, err := rand.Read(hashKey); err != nil { return nil, fmt.Errorf("could not generate random hash key: %s", err) @@ -677,7 +678,7 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { return } - ctx := signaling.NewLoggerContext(r.Context(), s.logger) + ctx := log.NewLoggerContext(r.Context(), s.logger) if conn.Subprotocol() == signaling.JanusEventsSubprotocol { agent := r.Header.Get("User-Agent") signaling.RunJanusEventsHandler(ctx, s.mcu, conn, addr, agent) diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 270ab6e..148c2ec 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -46,6 +46,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -138,8 +139,8 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httpte config := goconf.NewConfigFile() config.AddOption("tokens", TokenIdForTest, pubkey.Name()) - logger := signaling.NewLoggerForTest(t) - ctx := signaling.NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) proxy, err = NewProxyServer(ctx, r, "0.0", config) require.NoError(err) diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 750ac2a..82ed8f5 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -30,6 +30,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -45,7 +46,7 @@ type remotePublisherData struct { } type ProxySession struct { - logger signaling.Logger + logger log.Logger proxy *ProxyServer id signaling.PublicSessionId sid uint64 diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index b24ddb8..77ffc18 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -34,6 +34,7 @@ import ( "github.com/golang-jwt/jwt/v5" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -46,14 +47,14 @@ type tokenCacheEntry struct { } type tokensEtcd struct { - logger signaling.Logger + logger log.Logger client *signaling.EtcdClient tokenFormats atomic.Value tokenCache *signaling.LruCache[*tokenCacheEntry] } -func NewProxyTokensEtcd(logger signaling.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { +func NewProxyTokensEtcd(logger log.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { client, err := signaling.NewEtcdClient(logger, config, "tokens") if err != nil { return nil, err diff --git a/proxy/proxy_tokens_etcd_test.go b/proxy/proxy_tokens_etcd_test.go index 4d0b845..03da903 100644 --- a/proxy/proxy_tokens_etcd_test.go +++ b/proxy/proxy_tokens_etcd_test.go @@ -44,7 +44,7 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest" - signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -118,7 +118,7 @@ func newTokensEtcdForTesting(t *testing.T) (*tokensEtcd, *embed.Etcd) { cfg.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) cfg.AddOption("tokens", "keyformat", "/%s, /testing/%s/key") - logger := signaling.NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) tokens, err := NewProxyTokensEtcd(logger, cfg) require.NoError(t, err) t.Cleanup(func() { diff --git a/proxy/proxy_tokens_static.go b/proxy/proxy_tokens_static.go index 8dc2118..ab28f04 100644 --- a/proxy/proxy_tokens_static.go +++ b/proxy/proxy_tokens_static.go @@ -31,14 +31,15 @@ import ( "github.com/golang-jwt/jwt/v5" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type tokensStatic struct { - logger signaling.Logger + logger log.Logger tokenKeys atomic.Value } -func NewProxyTokensStatic(logger signaling.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { +func NewProxyTokensStatic(logger log.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { result := &tokensStatic{ logger: logger, } diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index 94365c4..35d6c33 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -30,10 +30,12 @@ import ( "github.com/dlintw/goconf" clientv3 "go.etcd.io/etcd/client/v3" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type proxyConfigEtcd struct { - logger Logger + logger log.Logger mu sync.Mutex proxy McuProxy // +checklocksignore: Only written to from constructor. @@ -48,7 +50,7 @@ type proxyConfigEtcd struct { closeFunc context.CancelFunc } -func NewProxyConfigEtcd(logger Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, proxy McuProxy) (ProxyConfig, error) { +func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, proxy McuProxy) (ProxyConfig, error) { if !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } diff --git a/proxy_config_etcd_test.go b/proxy_config_etcd_test.go index ffb0dfd..938ae9c 100644 --- a/proxy_config_etcd_test.go +++ b/proxy_config_etcd_test.go @@ -30,6 +30,8 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type TestProxyInformationEtcd struct { @@ -43,7 +45,7 @@ func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*embed.Etcd, ProxyConfig) etcd, client := NewEtcdClientForTest(t) cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "keyprefix", "proxies/") - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) p, err := NewProxyConfigEtcd(logger, cfg, client, proxy) require.NoError(t, err) t.Cleanup(func() { diff --git a/proxy_config_static.go b/proxy_config_static.go index 0dcaa55..577fc39 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -29,6 +29,8 @@ import ( "sync" "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type ipList struct { @@ -39,7 +41,7 @@ type ipList struct { } type proxyConfigStatic struct { - logger Logger + logger log.Logger mu sync.Mutex proxy McuProxy @@ -51,7 +53,7 @@ type proxyConfigStatic struct { connectionsMap map[string]*ipList } -func NewProxyConfigStatic(logger Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *DnsMonitor) (ProxyConfig, error) { +func NewProxyConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *DnsMonitor) (ProxyConfig, error) { result := &proxyConfigStatic{ logger: logger, proxy: proxy, diff --git a/proxy_config_static_test.go b/proxy_config_static_test.go index 867b11f..484045b 100644 --- a/proxy_config_static_test.go +++ b/proxy_config_static_test.go @@ -29,6 +29,8 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func newProxyConfigStatic(t *testing.T, proxy McuProxy, dns bool, lookup *mockDnsLookup, urls ...string) (ProxyConfig, *DnsMonitor) { @@ -38,7 +40,7 @@ func newProxyConfigStatic(t *testing.T, proxy McuProxy, dns bool, lookup *mockDn cfg.AddOption("mcu", "dnsdiscovery", "true") } dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually - logger := NewLoggerForTest(t) + logger := log.NewLoggerForTest(t) p, err := NewProxyConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) t.Cleanup(func() { diff --git a/remotesession.go b/remotesession.go index e9911e2..a5a8ed5 100644 --- a/remotesession.go +++ b/remotesession.go @@ -27,10 +27,12 @@ import ( "errors" "sync/atomic" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type RemoteSession struct { - logger Logger + logger log.Logger hub *Hub client *Client remoteClient *GrpcClient diff --git a/room.go b/room.go index 69de4bd..235b20b 100644 --- a/room.go +++ b/room.go @@ -36,6 +36,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -64,7 +65,7 @@ func init() { type Room struct { id string - logger Logger + logger log.Logger hub *Hub events AsyncEvents backend *Backend @@ -619,7 +620,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSession r.mu.RUnlock() defer r.mu.RLock() - ctx := NewLoggerContext(context.Background(), r.logger) + ctx := log.NewLoggerContext(context.Background(), r.logger) ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() @@ -1103,7 +1104,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { return 0, &wg } var count int - ctx := NewLoggerContext(context.Background(), r.logger) + ctx := log.NewLoggerContext(context.Background(), r.logger) for u, e := range entries { wg.Add(1) count += len(e) @@ -1285,7 +1286,7 @@ func (r *Room) fetchInitialTransientData() { return } - ctx := NewLoggerContext(context.Background(), r.logger) + ctx := log.NewLoggerContext(context.Background(), r.logger) ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() diff --git a/room_ping.go b/room_ping.go index 7958370..cb4bc14 100644 --- a/room_ping.go +++ b/room_ping.go @@ -27,6 +27,8 @@ import ( "slices" "sync" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type pingEntries struct { @@ -121,7 +123,7 @@ func (p *RoomPing) publishEntries(ctx context.Context, entries *pingEntries, tim if !found || limit <= 0 { // Limit disabled while waiting for the next iteration, fallback to sending // one request per room. - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) for roomId, e := range entries.entries { ctx2, cancel2 := context.WithTimeout(context.WithoutCancel(ctx), timeout) defer cancel2() @@ -167,7 +169,7 @@ func (p *RoomPing) sendPingsDirect(ctx context.Context, roomId string, url *url. } func (p *RoomPing) sendPingsCombined(ctx context.Context, url *url.URL, entries []BackendPingEntry, limit int, timeout time.Duration) { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) for tosend := range slices.Chunk(entries, limit) { subCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() diff --git a/room_ping_test.go b/room_ping_test.go index b452da8..f42693f 100644 --- a/room_ping_test.go +++ b/room_ping_test.go @@ -30,6 +30,8 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) { @@ -59,8 +61,8 @@ func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) func TestSingleRoomPing(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) @@ -103,8 +105,8 @@ func TestSingleRoomPing(t *testing.T) { func TestMultiRoomPing(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) @@ -143,8 +145,8 @@ func TestMultiRoomPing(t *testing.T) { func TestMultiRoomPing_Separate(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) @@ -179,8 +181,8 @@ func TestMultiRoomPing_Separate(t *testing.T) { func TestMultiRoomPing_DeleteRoom(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) diff --git a/room_test.go b/room_test.go index d41fdbb..022a545 100644 --- a/room_test.go +++ b/room_test.go @@ -34,6 +34,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func TestRoom_InCall(t *testing.T) { @@ -77,8 +79,8 @@ func TestRoom_InCall(t *testing.T) { func TestRoom_Update(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) @@ -172,8 +174,8 @@ loop: func TestRoom_Delete(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) @@ -270,8 +272,8 @@ loop: func TestRoom_RoomJoinFeatures(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) @@ -308,8 +310,8 @@ func TestRoom_RoomJoinFeatures(t *testing.T) { func TestRoom_RoomSessionData(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) @@ -352,8 +354,8 @@ func TestRoom_RoomSessionData(t *testing.T) { func TestRoom_InCallAll(t *testing.T) { t.Parallel() - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) hub, _, router, server := CreateHubForTest(t) diff --git a/roomsessions_builtin.go b/roomsessions_builtin.go index 9c54bf5..85bdf92 100644 --- a/roomsessions_builtin.go +++ b/roomsessions_builtin.go @@ -26,6 +26,8 @@ import ( "errors" "sync" "sync/atomic" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type BuiltinRoomSessions struct { @@ -116,7 +118,7 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId var wg sync.WaitGroup var result atomic.Value - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) for _, client := range clients { wg.Add(1) go func(client *GrpcClient) { diff --git a/server/main.go b/server/main.go index 9f74b07..5e11579 100644 --- a/server/main.go +++ b/server/main.go @@ -43,6 +43,7 @@ import ( "github.com/nats-io/nats.go" signaling "github.com/strukturag/nextcloud-spreed-signaling" + signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" ) var ( @@ -93,7 +94,7 @@ func createTLSListener(addr string, certFile, keyFile string) (net.Listener, err } type Listeners struct { - logger signaling.Logger // +checklocksignore + logger signalinglog.Logger // +checklocksignore mu sync.Mutex // +checklocks:mu listeners []net.Listener @@ -134,7 +135,7 @@ func main() { defer stop() logger := log.Default() - stopCtx = signaling.NewLoggerContext(stopCtx, logger) + stopCtx = signalinglog.NewLoggerContext(stopCtx, logger) if *cpuprofile != "" { f, err := os.Create(*cpuprofile) diff --git a/throttle.go b/throttle.go index a19aa25..27bce25 100644 --- a/throttle.go +++ b/throttle.go @@ -28,6 +28,8 @@ import ( "strconv" "sync" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -276,7 +278,7 @@ func (t *memoryThrottler) CheckBruteforce(ctx context.Context, client string, ac if l >= maxBruteforceAttempts { delta := now.Sub(entries[l-maxBruteforceAttempts].ts) if delta <= maxBruteforceDurationThreshold { - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) logger.Printf("Detected bruteforce attempt on \"%s\" from %s", action, client) statsThrottleBruteforceTotal.WithLabelValues(action).Inc() return doThrottle, ErrBruteforceDetected @@ -301,7 +303,7 @@ func (t *memoryThrottler) throttle(ctx context.Context, client string, action st } count := t.addEntry(client, action, entry) delay := t.getDelay(count - 1) - logger := LoggerFromContext(ctx) + logger := log.LoggerFromContext(ctx) logger.Printf("Failed attempt on \"%s\" from %s, throttling by %s", action, client, delay) statsThrottleDelayedTotal.WithLabelValues(action, strconv.FormatInt(delay.Milliseconds(), 10)).Inc() t.doDelay(ctx, delay) diff --git a/throttle_test.go b/throttle_test.go index 935aecf..ffb2b22 100644 --- a/throttle_test.go +++ b/throttle_test.go @@ -28,6 +28,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" ) func newMemoryThrottlerForTest(t *testing.T) Throttler { @@ -72,8 +74,8 @@ func TestThrottler(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -107,8 +109,8 @@ func TestThrottlerIPv6(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) // Make sure full /64 subnets are throttled for IPv6. throttle1, err := th.CheckBruteforce(ctx, "2001:db8:abcd:0012::1", "action1") @@ -145,8 +147,8 @@ func TestThrottler_Bruteforce(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) delay := 100 * time.Millisecond for range maxBruteforceAttempts { @@ -174,8 +176,8 @@ func TestThrottler_Cleanup(t *testing.T) { th, ok := throttler.(*memoryThrottler) require.True(t, ok, "required memoryThrottler, got %T", throttler) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -229,8 +231,8 @@ func TestThrottler_ExpirePartial(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -262,8 +264,8 @@ func TestThrottler_ExpireAll(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") assert.NoError(err) @@ -295,8 +297,8 @@ func TestThrottler_Negative(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := NewLoggerForTest(t) - ctx := NewLoggerContext(t.Context(), logger) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) delay := 100 * time.Millisecond for range maxBruteforceAttempts * 10 { diff --git a/virtualsession.go b/virtualsession.go index 57ef6aa..ec98032 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -28,6 +28,7 @@ import ( "sync/atomic" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) const ( @@ -37,7 +38,7 @@ const ( ) type VirtualSession struct { - logger Logger + logger log.Logger hub *Hub session *ClientSession privateId PrivateSessionId @@ -192,7 +193,7 @@ func (s *VirtualSession) CloseWithFeedback(session Session, message *ClientMessa } func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, message *ClientMessage) { - ctx := NewLoggerContext(context.Background(), s.logger) + ctx := log.NewLoggerContext(context.Background(), s.logger) ctx, cancel := context.WithTimeout(ctx, s.hub.backendTimeout) defer cancel() From 8371fbe9bfec28116fa2b3fad597b1c53e72b844 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 08:51:58 +0100 Subject: [PATCH 374/549] Close subscriber synchronously on errors. Otherwise it could happen that a re-subscribe request was still processed by the (closing) old subscriber for which the deferred processing stopped. Such requests would then timeout. Could be triggered by the CI tests under load (e.g. Test_JanusSubscriberTimeout). --- mcu_janus_subscriber.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 77c3241..945dca8 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -236,7 +236,7 @@ retry: switch error_code { case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: p.logger.Printf("Publisher %s not created yet for %s, not joining room %d as subscriber", p.publisher, p.streamType, p.roomId) - go p.Close(context.Background()) + p.Close(context.Background()) callback(fmt.Errorf("Publisher %s not created yet for %s", p.publisher, p.streamType), nil) return case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: @@ -249,7 +249,7 @@ retry: } if err := waiter.Wait(ctx); err != nil { - go p.Close(context.Background()) + p.Close(context.Background()) callback(err, nil) return } From 78d74ea3ee5fd748a215397d02b23fbf06402130 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 09:58:36 +0100 Subject: [PATCH 375/549] Start timer for anonymous sessions to join room before sending response. Fixes flaky "TestExpectAnonymousJoinRoomAfterLeave" under load. --- hub.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hub.go b/hub.go index a122de9..5a33bde 100644 --- a/hub.go +++ b/hub.go @@ -1719,11 +1719,11 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if roomId == "" { // We can handle leaving a room directly. 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 { h.startWaitAnonymousSessionRoom(session) } + // User was in a room before, so need to notify about leaving it. + h.sendRoom(session, message, nil) } return From 18e41f243a83c88cbcdb702b16dbc70578ae1a86 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 09:59:27 +0100 Subject: [PATCH 376/549] Also ignore "participants" events sent before room was joined. Fix flaky "TestVirtualSessionCustomInCall" under load. --- clientsession.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clientsession.go b/clientsession.go index 5c5d2e9..fa414ec 100644 --- a/clientsession.go +++ b/clientsession.go @@ -1424,7 +1424,7 @@ func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { } } case "event": - if msg.Message.Event.Target == "room" { + if msg.Message.Event.Target == "room" || msg.Message.Event.Target == "participants" { // Can happen mostly during tests where an older room async message // could be received by a subscriber that joined after it was sent. if joined := s.getRoomJoinTime(); joined.IsZero() || msg.SendTime.Before(joined) { From 550e40f322e95d07f686691ed436ce81e340e7a9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 11:00:59 +0100 Subject: [PATCH 377/549] Wait for events to be processed in tests before sending between sessions. Fixes flaky "TestClientControlToSessionId" under load which could send to a session before the events subscription was processed completely. --- async_events_test.go | 18 ++++++++++++++++++ hub_test.go | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/async_events_test.go b/async_events_test.go index 9cf2c5f..107a41d 100644 --- a/async_events_test.go +++ b/async_events_test.go @@ -83,3 +83,21 @@ func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { }) return events } + +func waitForAsyncEventsFlushed(ctx context.Context, t *testing.T, events AsyncEvents) { + t.Helper() + + nats, ok := (events.(*asyncEventsNats)) + if !ok { + // Only can wait for NATS events. + return + } + + client, ok := nats.client.(*natsClient) + if !ok { + // The loopback NATS clients is executing all events synchronously. + return + } + + assert.NoError(t, client.conn.FlushWithContext(ctx)) +} diff --git a/hub_test.go b/hub_test.go index 43fa737..e8ec61a 100644 --- a/hub_test.go +++ b/hub_test.go @@ -1985,6 +1985,10 @@ func TestClientMessageToSessionId(t *testing.T) { client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + // Make sure the session subscription events are processed. + waitForAsyncEventsFlushed(ctx, t, hub1.events) + waitForAsyncEventsFlushed(ctx, t, hub2.events) + recipient1 := MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, @@ -2042,6 +2046,10 @@ func TestClientControlToSessionId(t *testing.T) { client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + // Make sure the session subscription events are processed. + waitForAsyncEventsFlushed(ctx, t, hub1.events) + waitForAsyncEventsFlushed(ctx, t, hub2.events) + recipient1 := MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, From 47b51e804f9714d3d9b39eb06c4581b842bd318f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:01:40 +0000 Subject: [PATCH 378/549] Bump the artifacts group with 2 updates Bumps the artifacts group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/download-artifact` from 6 to 7 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts ... Signed-off-by: dependabot[bot] --- .github/workflows/tarball.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 664103b..1e54cc2 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -39,7 +39,7 @@ jobs: make tarball - name: Upload tarball - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: tarball-${{ matrix.go-version }} path: nextcloud-spreed-signaling*.tar.gz @@ -58,7 +58,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Download tarball - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: tarball-${{ matrix.go-version }} @@ -103,7 +103,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Download tarball - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: tarball-${{ matrix.go-version }} From 2f5af4d4a1e7122ecbb5852c77dca97a31147cdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:01:43 +0000 Subject: [PATCH 379/549] Bump github.com/pion/ice/v4 from 4.0.13 to 4.1.0 Bumps [github.com/pion/ice/v4](https://github.com/pion/ice) from 4.0.13 to 4.1.0. - [Release notes](https://github.com/pion/ice/releases) - [Commits](https://github.com/pion/ice/compare/v4.0.13...v4.1.0) --- updated-dependencies: - dependency-name: github.com/pion/ice/v4 dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 069c9a1..10fb47c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/nats-io/nats.go v1.47.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/ice/v4 v4.0.13 + github.com/pion/ice/v4 v4.1.0 github.com/pion/sdp/v3 v3.0.16 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 @@ -57,11 +57,11 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.0.8 // indirect + github.com/pion/dtls/v3 v3.0.9 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/stun/v3 v3.0.1 // indirect + github.com/pion/stun/v3 v3.0.2 // indirect github.com/pion/transport/v3 v3.1.1 // indirect github.com/pion/turn/v4 v4.1.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index de47c5c..8fb8edd 100644 --- a/go.sum +++ b/go.sum @@ -86,10 +86,10 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.0.8 h1:ZrPUrvPVDaTJDM8Vu1veatzXebLlsIWeT7Vaate/zwM= -github.com/pion/dtls/v3 v3.0.8/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os= -github.com/pion/ice/v4 v4.0.13 h1:1cdmd80gmLdnVTM2bXzw2CBebvXvkGNEaWi/CuDK9WQ= -github.com/pion/ice/v4 v4.0.13/go.mod h1:Xo5f5DBbEjQac+6pR7i83AGuwoGxnxwXkOOvHFVnfnM= +github.com/pion/dtls/v3 v3.0.9 h1:4AijfFRm8mAjd1gfdlB1wzJF3fjjR/VPIpJgkEtvYmM= +github.com/pion/dtls/v3 v3.0.9/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os= +github.com/pion/ice/v4 v4.1.0 h1:YlxIii2bTPWyC08/4hdmtYq4srbrY0T9xcTsTjldGqU= +github.com/pion/ice/v4 v4.1.0/go.mod h1:5gPbzYxqenvn05k7zKPIZFuSAufolygiy6P1U9HzvZ4= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= @@ -98,8 +98,8 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= -github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA= -github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw= +github.com/pion/stun/v3 v3.0.2 h1:BJuGEN2oLrJisiNEJtUTJC4BGbzbfp37LizfqswblFU= +github.com/pion/stun/v3 v3.0.2/go.mod h1:JFJKfIWvt178MCF5H/YIgZ4VX3LYE77vca4b9HP60SA= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/turn/v4 v4.1.3 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA= From f0b2fc6c4ff2d30b97c45c1bdcf2d2a6d8504880 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:01:48 +0000 Subject: [PATCH 380/549] Bump google.golang.org/protobuf from 1.36.10 to 1.36.11 Bumps google.golang.org/protobuf from 1.36.10 to 1.36.11. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 069c9a1..5885660 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/zap v1.27.1 google.golang.org/grpc v1.77.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/protobuf v1.36.11 ) require ( diff --git a/go.sum b/go.sum index de47c5c..bd2f1fb 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,8 @@ google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 5dd4c91fff7f163f2966f10de907e16940b48686 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:01:49 +0000 Subject: [PATCH 381/549] Bump actions/cache from 4 to 5 Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploydocker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 3b8510d..fe77ff4 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -46,7 +46,7 @@ jobs: type=semver,pattern={{major}} type=sha,prefix= - name: Cache Docker layers - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -116,7 +116,7 @@ jobs: type=semver,pattern={{major}} type=sha,prefix= - name: Cache Docker layers - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} From f975ce1494f8bda0a7f7bb03a8aa3bc695dc5e43 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 10:13:31 +0100 Subject: [PATCH 382/549] CI: Also upload images to quay.io/strukturag/nextcloud-spreed-signaling --- .github/workflows/deploydocker.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 3b8510d..87eb409 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -1,4 +1,4 @@ -name: Deploy to Docker Hub / GHCR +name: Deploy to Docker Hub / GHCR / quay.io on: pull_request: @@ -39,6 +39,7 @@ jobs: images: | strukturag/nextcloud-spreed-signaling ghcr.io/strukturag/nextcloud-spreed-signaling + quay.io/strukturag/nextcloud-spreed-signaling tags: | type=ref,event=branch type=semver,pattern={{version}} @@ -67,6 +68,14 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to quay.io + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ secrets.QUAY_IO_USERNAME }} + password: ${{ secrets.QUAY_IO_ACCESS_TOKEN }} + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 @@ -104,6 +113,7 @@ jobs: images: | strukturag/nextcloud-spreed-signaling ghcr.io/strukturag/nextcloud-spreed-signaling + quay.io/strukturag/nextcloud-spreed-signaling labels: | org.opencontainers.image.title=nextcloud-spreed-signaling-proxy org.opencontainers.image.description=Signaling proxy for the standalone signaling server for Nextcloud Talk. @@ -137,6 +147,14 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to quay.io + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ secrets.QUAY_IO_USERNAME }} + password: ${{ secrets.QUAY_IO_ACCESS_TOKEN }} + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 From a620ecca8be7ebb0e32d5e4a5b47b5344329aba2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 12:58:39 +0100 Subject: [PATCH 383/549] CI: Run "modernize" with Go 1.25 --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 495b85f..d97f6e5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,11 +49,11 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: "1.24" + go-version: "1.25" - name: moderize run: | - GOEXPERIMENT=synctest go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... + go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... checklocks: name: checklocks From b22332e1d714fc42c149c337f87cab41bc940ea2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 13:10:34 +0100 Subject: [PATCH 384/549] CI: Skip some modernize checks that fail on GRPC stubs. --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d97f6e5..b8bc3b8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -53,7 +53,7 @@ jobs: - name: moderize run: | - go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... + go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -any=false -reflecttypefor=false -test ./... checklocks: name: checklocks From ff753051f776821596d9b3fc83a6f30ba0319311 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 15:34:48 +0100 Subject: [PATCH 385/549] CI: Always use latest patch release for govuln checks. --- .github/workflows/govuln.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index 8c6de48..fe322cc 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -31,6 +31,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} + check-latest: true - run: date From 8ae1e4eafd0ea419f08ae56fc4fb0bd88e762158 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:01:50 +0000 Subject: [PATCH 386/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.6 to 3.6.7 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.6...v3.6.7) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.6 to 3.6.7 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.6...v3.6.7) Updates `go.etcd.io/etcd/client/v3` from 3.6.6 to 3.6.7 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.6...v3.6.7) Updates `go.etcd.io/etcd/server/v3` from 3.6.6 to 3.6.7 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.6...v3.6.7) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index a0b4eec..deefeac 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/api/v3 v3.6.6 - go.etcd.io/etcd/client/pkg/v3 v3.6.6 - go.etcd.io/etcd/client/v3 v3.6.6 - go.etcd.io/etcd/server/v3 v3.6.6 + go.etcd.io/etcd/api/v3 v3.6.7 + go.etcd.io/etcd/client/pkg/v3 v3.6.7 + go.etcd.io/etcd/client/v3 v3.6.7 + go.etcd.io/etcd/server/v3 v3.6.7 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.77.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 @@ -75,7 +75,7 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.7 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index 504dbf0..e1f1cbd 100644 --- a/go.sum +++ b/go.sum @@ -139,16 +139,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= -go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= -go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= -go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= -go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= -go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= -go.etcd.io/etcd/pkg/v3 v3.6.6 h1:wylOivS/UxXTZ0Le5fOdxCjatW5ql9dcWEggQQHSorw= -go.etcd.io/etcd/pkg/v3 v3.6.6/go.mod h1:9TKZL7WUEVHXYM3srP3ESZfIms34s1G72eNtWA9YKg4= -go.etcd.io/etcd/server/v3 v3.6.6 h1:YSRWGJPzU+lIREwUQI4MfyLZrkUyzjJOVpMxJvZePaY= -go.etcd.io/etcd/server/v3 v3.6.6/go.mod h1:A1OQ1x3PaiENDLywMjCiMwV1pwJSpb0h9Z5ORP2dv6I= +go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0= +go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI= +go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs= +go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q= +go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U= +go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE= +go.etcd.io/etcd/pkg/v3 v3.6.7 h1:qIxdSI+LAmKFAjMy42yHQzSNqG/sWES4QjhFSGsMDpY= +go.etcd.io/etcd/pkg/v3 v3.6.7/go.mod h1:nPbpIExp9Q6tR/EVI2aZe0VBlflLys5VGFWSCmqUOyk= +go.etcd.io/etcd/server/v3 v3.6.7 h1:8dEGQ877tj0cQJFEfD2bDoZDA76qbS2OkvCNjwAyrSo= +go.etcd.io/etcd/server/v3 v3.6.7/go.mod h1:LEM328bPA2uVMhN0+Ht/vAsADW127QS1oM7EuHrOTy0= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= From 2b87c5f5c237c64ce07bd87cc6c65c43b4f03e12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:01:55 +0000 Subject: [PATCH 387/549] Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.47.0 to 1.48.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.47.0...v1.48.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.48.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a0b4eec..9875bf4 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.12.2 - github.com/nats-io/nats.go v1.47.0 + github.com/nats-io/nats.go v1.48.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.1.0 diff --git a/go.sum b/go.sum index 504dbf0..9aaf90a 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.12.2 h1:4TEQd0Y4zvcW0IsVxjlXnRso1hBkQl3TS0BI+SxgPhE= github.com/nats-io/nats-server/v2 v2.12.2/go.mod h1:j1AAttYeu7WnvD8HLJ+WWKNMSyxsqmZ160pNtCQRMyE= -github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM= -github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= +github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From 3f278d200502e46caff61c8528851d16c3b9630a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:14:16 +0000 Subject: [PATCH 388/549] Bump github.com/nats-io/nats-server/v2 from 2.12.2 to 2.12.3 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.2 to 2.12.3. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Commits](https://github.com/nats-io/nats-server/compare/v2.12.2...v2.12.3) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 9875bf4..477cf20 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 - github.com/nats-io/nats-server/v2 v2.12.2 + github.com/nats-io/nats-server/v2 v2.12.3 github.com/nats-io/nats.go v1.48.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -30,7 +30,7 @@ require ( ) require ( - github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op // indirect + github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -44,18 +44,18 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-tpm v0.9.6 // indirect + github.com/google/go-tpm v0.9.7 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.1 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/jwt/v2 v2.8.0 // indirect - github.com/nats-io/nkeys v0.4.11 // indirect + github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pion/dtls/v3 v3.0.9 // indirect github.com/pion/logging v0.2.4 // indirect @@ -88,10 +88,10 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.45.0 // indirect + golang.org/x/crypto v0.46.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect diff --git a/go.sum b/go.sum index 9aaf90a..4720f08 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= -github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op h1:Ucf+QxEKMbPogRO5guBNe5cgd9uZgfoJLOYs8WWhtjM= +github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -38,8 +38,8 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA= -github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA= +github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -58,8 +58,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -74,12 +74,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.2 h1:4TEQd0Y4zvcW0IsVxjlXnRso1hBkQl3TS0BI+SxgPhE= -github.com/nats-io/nats-server/v2 v2.12.2/go.mod h1:j1AAttYeu7WnvD8HLJ+WWKNMSyxsqmZ160pNtCQRMyE= +github.com/nats-io/nats-server/v2 v2.12.3 h1:KRv+1n7lddMVgkJPQer+pt36TcO0ENxjilBmeWdjcHs= +github.com/nats-io/nats-server/v2 v2.12.3/go.mod h1:MQXjG9WjyXKz9koWzUc3jYUMKD8x3CLmTNy91IQQz3Y= github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= -github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= -github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= +github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc= +github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8iFMHvQleYlF5M3PY6zpAbxsngImjE= @@ -182,8 +182,8 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -196,19 +196,19 @@ golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From f2eac4c3b390b2bceaba55be920cea1cb0134bbc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 22 Dec 2025 15:18:03 +0100 Subject: [PATCH 389/549] Process all NATS messages for same target from single goroutine. --- async_events.go | 184 +++----------------- async_events_nats.go | 373 ++++++++++------------------------------- backend_server_test.go | 42 +++-- clientsession.go | 52 ++++-- clientsession_test.go | 6 +- hub_test.go | 4 +- natsclient.go | 4 +- natsclient_loopback.go | 4 - room.go | 33 +++- virtualsession.go | 58 ++++++- 10 files changed, 256 insertions(+), 504 deletions(-) diff --git a/async_events.go b/async_events.go index 72c8b8f..c8e2284 100644 --- a/async_events.go +++ b/async_events.go @@ -23,41 +23,41 @@ package signaling import ( "context" - "sync" + "errors" + + "github.com/nats-io/nats.go" "github.com/strukturag/nextcloud-spreed-signaling/log" ) -type AsyncBackendRoomEventListener interface { - ProcessBackendRoomRequest(message *AsyncMessage) -} +var ( + ErrAlreadyRegistered = errors.New("already registered") // +checklocksignore: Global readonly variable. +) -type AsyncRoomEventListener interface { - ProcessAsyncRoomMessage(message *AsyncMessage) -} +const ( + DefaultAsyncChannelSize = 64 +) -type AsyncUserEventListener interface { - ProcessAsyncUserMessage(message *AsyncMessage) -} +type AsyncChannel chan *nats.Msg -type AsyncSessionEventListener interface { - ProcessAsyncSessionMessage(message *AsyncMessage) +type AsyncEventListener interface { + AsyncChannel() AsyncChannel } type AsyncEvents interface { Close(ctx context.Context) error - RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) error - UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) + RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error + UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error - RegisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) error - UnregisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) + RegisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error + UnregisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error - RegisterUserListener(userId string, backend *Backend, listener AsyncUserEventListener) error - UnregisterUserListener(userId string, backend *Backend, listener AsyncUserEventListener) + RegisterUserListener(userId string, backend *Backend, listener AsyncEventListener) error + UnregisterUserListener(userId string, backend *Backend, listener AsyncEventListener) error - RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) error - UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) + RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error + UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error PublishBackendRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error PublishRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error @@ -73,147 +73,3 @@ func NewAsyncEvents(ctx context.Context, url string) (AsyncEvents, error) { return NewAsyncEventsNats(log.LoggerFromContext(ctx), client) } - -type asyncBackendRoomSubscriber struct { - mu sync.Mutex - - // +checklocks:mu - listeners map[AsyncBackendRoomEventListener]bool -} - -func (s *asyncBackendRoomSubscriber) processBackendRoomRequest(message *AsyncMessage) { - s.mu.Lock() - defer s.mu.Unlock() - - for listener := range s.listeners { - s.mu.Unlock() - listener.ProcessBackendRoomRequest(message) - s.mu.Lock() - } -} - -func (s *asyncBackendRoomSubscriber) addListener(listener AsyncBackendRoomEventListener) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.listeners == nil { - s.listeners = make(map[AsyncBackendRoomEventListener]bool) - } - s.listeners[listener] = true -} - -func (s *asyncBackendRoomSubscriber) removeListener(listener AsyncBackendRoomEventListener) bool { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.listeners, listener) - return len(s.listeners) > 0 -} - -type asyncRoomSubscriber struct { - mu sync.Mutex - - // +checklocks:mu - listeners map[AsyncRoomEventListener]bool -} - -func (s *asyncRoomSubscriber) processAsyncRoomMessage(message *AsyncMessage) { - s.mu.Lock() - defer s.mu.Unlock() - - for listener := range s.listeners { - s.mu.Unlock() - listener.ProcessAsyncRoomMessage(message) - s.mu.Lock() - } -} - -func (s *asyncRoomSubscriber) addListener(listener AsyncRoomEventListener) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.listeners == nil { - s.listeners = make(map[AsyncRoomEventListener]bool) - } - s.listeners[listener] = true -} - -func (s *asyncRoomSubscriber) removeListener(listener AsyncRoomEventListener) bool { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.listeners, listener) - return len(s.listeners) > 0 -} - -type asyncUserSubscriber struct { - mu sync.Mutex - - // +checklocks:mu - listeners map[AsyncUserEventListener]bool -} - -func (s *asyncUserSubscriber) processAsyncUserMessage(message *AsyncMessage) { - s.mu.Lock() - defer s.mu.Unlock() - - for listener := range s.listeners { - s.mu.Unlock() - listener.ProcessAsyncUserMessage(message) - s.mu.Lock() - } -} - -func (s *asyncUserSubscriber) addListener(listener AsyncUserEventListener) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.listeners == nil { - s.listeners = make(map[AsyncUserEventListener]bool) - } - s.listeners[listener] = true -} - -func (s *asyncUserSubscriber) removeListener(listener AsyncUserEventListener) bool { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.listeners, listener) - return len(s.listeners) > 0 -} - -type asyncSessionSubscriber struct { - mu sync.Mutex - - // +checklocks:mu - listeners map[AsyncSessionEventListener]bool -} - -func (s *asyncSessionSubscriber) processAsyncSessionMessage(message *AsyncMessage) { - s.mu.Lock() - defer s.mu.Unlock() - - for listener := range s.listeners { - s.mu.Unlock() - listener.ProcessAsyncSessionMessage(message) - s.mu.Lock() - } -} - -func (s *asyncSessionSubscriber) addListener(listener AsyncSessionEventListener) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.listeners == nil { - s.listeners = make(map[AsyncSessionEventListener]bool) - } - s.listeners[listener] = true -} - -func (s *asyncSessionSubscriber) removeListener(listener AsyncSessionEventListener) bool { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.listeners, listener) - return len(s.listeners) > 0 -} diff --git a/async_events_nats.go b/async_events_nats.go index 90b3839..b89a4ab 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -23,6 +23,7 @@ package signaling import ( "context" + "errors" "sync" "time" @@ -59,176 +60,7 @@ func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string return string("session." + sessionId) } -type asyncSubscriberNats struct { - key string - client NatsClient - logger log.Logger - - receiver chan *nats.Msg - closeChan chan struct{} - subscription NatsSubscription - - processMessage func(*nats.Msg) -} - -func newAsyncSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncSubscriberNats, error) { - receiver := make(chan *nats.Msg, 64) - sub, err := client.Subscribe(key, receiver) - if err != nil { - return nil, err - } - - result := &asyncSubscriberNats{ - key: key, - client: client, - logger: logger, - - receiver: receiver, - closeChan: make(chan struct{}), - subscription: sub, - } - return result, nil -} - -func (s *asyncSubscriberNats) run() { - defer func() { - if err := s.subscription.Unsubscribe(); err != nil { - s.logger.Printf("Error unsubscribing %s: %s", s.key, err) - } - }() - - for { - select { - case msg := <-s.receiver: - s.processMessage(msg) - for count := len(s.receiver); count > 0; count-- { - s.processMessage(<-s.receiver) - } - case <-s.closeChan: - return - } - } -} - -func (s *asyncSubscriberNats) close() { - close(s.closeChan) -} - -type asyncBackendRoomSubscriberNats struct { - *asyncSubscriberNats - asyncBackendRoomSubscriber -} - -func newAsyncBackendRoomSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncBackendRoomSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(logger, key, client) - if err != nil { - return nil, err - } - - result := &asyncBackendRoomSubscriberNats{ - asyncSubscriberNats: sub, - } - result.processMessage = result.doProcessMessage - go result.run() - return result, nil -} - -func (s *asyncBackendRoomSubscriberNats) doProcessMessage(msg *nats.Msg) { - var message AsyncMessage - if err := s.client.Decode(msg, &message); err != nil { - s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) - return - } - - s.processBackendRoomRequest(&message) -} - -type asyncRoomSubscriberNats struct { - asyncRoomSubscriber - *asyncSubscriberNats -} - -func newAsyncRoomSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncRoomSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(logger, key, client) - if err != nil { - return nil, err - } - - result := &asyncRoomSubscriberNats{ - asyncSubscriberNats: sub, - } - result.processMessage = result.doProcessMessage - go result.run() - return result, nil -} - -func (s *asyncRoomSubscriberNats) doProcessMessage(msg *nats.Msg) { - var message AsyncMessage - if err := s.client.Decode(msg, &message); err != nil { - s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) - return - } - - s.processAsyncRoomMessage(&message) -} - -type asyncUserSubscriberNats struct { - *asyncSubscriberNats - asyncUserSubscriber -} - -func newAsyncUserSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncUserSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(logger, key, client) - if err != nil { - return nil, err - } - - result := &asyncUserSubscriberNats{ - asyncSubscriberNats: sub, - } - result.processMessage = result.doProcessMessage - go result.run() - return result, nil -} - -func (s *asyncUserSubscriberNats) doProcessMessage(msg *nats.Msg) { - var message AsyncMessage - if err := s.client.Decode(msg, &message); err != nil { - s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) - return - } - - s.processAsyncUserMessage(&message) -} - -type asyncSessionSubscriberNats struct { - *asyncSubscriberNats - asyncSessionSubscriber -} - -func newAsyncSessionSubscriberNats(logger log.Logger, key string, client NatsClient) (*asyncSessionSubscriberNats, error) { - sub, err := newAsyncSubscriberNats(logger, key, client) - if err != nil { - return nil, err - } - - result := &asyncSessionSubscriberNats{ - asyncSubscriberNats: sub, - } - result.processMessage = result.doProcessMessage - go result.run() - return result, nil -} - -func (s *asyncSessionSubscriberNats) doProcessMessage(msg *nats.Msg) { - var message AsyncMessage - if err := s.client.Decode(msg, &message); err != nil { - s.logger.Printf("Could not decode NATS message %+v, %s", msg, err) - return - } - - s.processAsyncSessionMessage(&message) -} +type asyncEventsNatsSubscriptions map[string]map[AsyncEventListener]NatsSubscription type asyncEventsNats struct { mu sync.Mutex @@ -236,13 +68,13 @@ type asyncEventsNats struct { logger log.Logger // +checklocksignore // +checklocks:mu - backendRoomSubscriptions map[string]*asyncBackendRoomSubscriberNats + backendRoomSubscriptions asyncEventsNatsSubscriptions // +checklocks:mu - roomSubscriptions map[string]*asyncRoomSubscriberNats + roomSubscriptions asyncEventsNatsSubscriptions // +checklocks:mu - userSubscriptions map[string]*asyncUserSubscriberNats + userSubscriptions asyncEventsNatsSubscriptions // +checklocks:mu - sessionSubscriptions map[string]*asyncSessionSubscriberNats + sessionSubscriptions asyncEventsNatsSubscriptions } func NewAsyncEventsNats(logger log.Logger, client NatsClient) (AsyncEvents, error) { @@ -250,10 +82,10 @@ func NewAsyncEventsNats(logger log.Logger, client NatsClient) (AsyncEvents, erro client: client, logger: logger, - backendRoomSubscriptions: make(map[string]*asyncBackendRoomSubscriberNats), - roomSubscriptions: make(map[string]*asyncRoomSubscriberNats), - userSubscriptions: make(map[string]*asyncUserSubscriberNats), - sessionSubscriptions: make(map[string]*asyncSessionSubscriberNats), + backendRoomSubscriptions: make(asyncEventsNatsSubscriptions), + roomSubscriptions: make(asyncEventsNatsSubscriptions), + userSubscriptions: make(asyncEventsNatsSubscriptions), + sessionSubscriptions: make(asyncEventsNatsSubscriptions), } return events, nil } @@ -283,186 +115,153 @@ func (e *asyncEventsNats) GetServerInfoNats() *BackendServerInfoNats { return nats } +func closeSubscriptions(logger log.Logger, wg *sync.WaitGroup, subscriptions asyncEventsNatsSubscriptions) { + defer wg.Done() + + for subject, subs := range subscriptions { + for _, sub := range subs { + if err := sub.Unsubscribe(); err != nil && !errors.Is(err, nats.ErrConnectionClosed) { + logger.Printf("Error unsubscribing %s: %s", subject, err) + } + } + } +} + func (e *asyncEventsNats) Close(ctx context.Context) error { e.mu.Lock() defer e.mu.Unlock() var wg sync.WaitGroup wg.Add(1) - go func(subscriptions map[string]*asyncBackendRoomSubscriberNats) { - defer wg.Done() - for _, sub := range subscriptions { - sub.close() - } - }(e.backendRoomSubscriptions) + go closeSubscriptions(e.logger, &wg, e.backendRoomSubscriptions) wg.Add(1) - go func(subscriptions map[string]*asyncRoomSubscriberNats) { - defer wg.Done() - for _, sub := range subscriptions { - sub.close() - } - }(e.roomSubscriptions) + go closeSubscriptions(e.logger, &wg, e.roomSubscriptions) wg.Add(1) - go func(subscriptions map[string]*asyncUserSubscriberNats) { - defer wg.Done() - for _, sub := range subscriptions { - sub.close() - } - }(e.userSubscriptions) + go closeSubscriptions(e.logger, &wg, e.userSubscriptions) wg.Add(1) - go func(subscriptions map[string]*asyncSessionSubscriberNats) { - defer wg.Done() - for _, sub := range subscriptions { - sub.close() - } - }(e.sessionSubscriptions) + go closeSubscriptions(e.logger, &wg, e.sessionSubscriptions) // Can't use clear(...) here as the maps are processed asynchronously by the // goroutines above. - e.backendRoomSubscriptions = make(map[string]*asyncBackendRoomSubscriberNats) - e.roomSubscriptions = make(map[string]*asyncRoomSubscriberNats) - e.userSubscriptions = make(map[string]*asyncUserSubscriberNats) - e.sessionSubscriptions = make(map[string]*asyncSessionSubscriberNats) + e.backendRoomSubscriptions = make(asyncEventsNatsSubscriptions) + e.roomSubscriptions = make(asyncEventsNatsSubscriptions) + e.userSubscriptions = make(asyncEventsNatsSubscriptions) + e.sessionSubscriptions = make(asyncEventsNatsSubscriptions) wg.Wait() return e.client.Close(ctx) } -func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) error { +// +checklocks:e.mu +func (e *asyncEventsNats) registerListener(key string, subscriptions asyncEventsNatsSubscriptions, listener AsyncEventListener) error { + subs, found := subscriptions[key] + if !found { + subs = make(map[AsyncEventListener]NatsSubscription) + subscriptions[key] = subs + } else if _, found := subs[listener]; found { + return ErrAlreadyRegistered + } + + sub, err := e.client.Subscribe(key, listener.AsyncChannel()) + if err != nil { + return err + } + + subs[listener] = sub + return nil +} + +// +checklocks:e.mu +func (e *asyncEventsNats) unregisterListener(key string, subscriptions asyncEventsNatsSubscriptions, listener AsyncEventListener) error { + subs, found := subscriptions[key] + if !found { + return nil + } + + sub, found := subs[listener] + if !found { + return nil + } + + delete(subs, listener) + if len(subs) == 0 { + delete(subscriptions, key) + } + + return sub.Unsubscribe() +} + +func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForBackendRoomId(roomId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.backendRoomSubscriptions[key] - if !found { - var err error - if sub, err = newAsyncBackendRoomSubscriberNats(e.logger, key, e.client); err != nil { - return err - } - e.backendRoomSubscriptions[key] = sub - } - sub.addListener(listener) - return nil + return e.registerListener(key, e.backendRoomSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) { +func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForBackendRoomId(roomId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.backendRoomSubscriptions[key] - if !found { - return - } - if !sub.removeListener(listener) { - delete(e.backendRoomSubscriptions, key) - sub.close() - } + return e.unregisterListener(key, e.backendRoomSubscriptions, listener) } -func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) error { +func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForRoomId(roomId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.roomSubscriptions[key] - if !found { - var err error - if sub, err = newAsyncRoomSubscriberNats(e.logger, key, e.client); err != nil { - return err - } - e.roomSubscriptions[key] = sub - } - sub.addListener(listener) - return nil + return e.registerListener(key, e.roomSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) { +func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForRoomId(roomId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.roomSubscriptions[key] - if !found { - return - } - if !sub.removeListener(listener) { - delete(e.roomSubscriptions, key) - sub.close() - } + return e.unregisterListener(key, e.roomSubscriptions, listener) } -func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *Backend, listener AsyncUserEventListener) error { +func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForUserId(roomId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.userSubscriptions[key] - if !found { - var err error - if sub, err = newAsyncUserSubscriberNats(e.logger, key, e.client); err != nil { - return err - } - e.userSubscriptions[key] = sub - } - sub.addListener(listener) - return nil + return e.registerListener(key, e.userSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend, listener AsyncUserEventListener) { +func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForUserId(roomId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.userSubscriptions[key] - if !found { - return - } - if !sub.removeListener(listener) { - delete(e.userSubscriptions, key) - sub.close() - } + return e.unregisterListener(key, e.userSubscriptions, listener) } -func (e *asyncEventsNats) RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) error { +func (e *asyncEventsNats) RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.sessionSubscriptions[key] - if !found { - var err error - if sub, err = newAsyncSessionSubscriberNats(e.logger, key, e.client); err != nil { - return err - } - e.sessionSubscriptions[key] = sub - } - sub.addListener(listener) - return nil + return e.registerListener(key, e.sessionSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncSessionEventListener) { +func (e *asyncEventsNats) UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() defer e.mu.Unlock() - sub, found := e.sessionSubscriptions[key] - if !found { - return - } - if !sub.removeListener(listener) { - delete(e.sessionSubscriptions, key) - sub.close() - } + return e.unregisterListener(key, e.sessionSubscriptions, listener) } func (e *asyncEventsNats) publish(subject string, message *AsyncMessage) error { - message.SendTime = time.Now() + message.SendTime = time.Now().Truncate(time.Microsecond) return e.client.Publish(subject, message) } diff --git a/backend_server_test.go b/backend_server_test.go index faee2aa..aaddcf5 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -241,13 +241,15 @@ func performBackendRequest(requestUrl string, body []byte) (*http.Response, erro return client.Do(request) } -func expectRoomlistEvent(t *testing.T, ch chan *AsyncMessage, msgType string) (*EventServerMessage, bool) { +func expectRoomlistEvent(t *testing.T, ch AsyncChannel, msgType string) (*EventServerMessage, bool) { assert := assert.New(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() select { - case message := <-ch: - if !assert.Equal("message", message.Type, "invalid message type, got %+v", message) || + case natsMsg := <-ch: + var message AsyncMessage + if !assert.NoError(NatsDecode(natsMsg, &message)) || + !assert.Equal("message", message.Type, "invalid message type, got %+v", message) || !assert.NotNil(message.Message, "message missing, got %+v", message) { return nil, false } @@ -403,11 +405,11 @@ func TestBackendServer_RoomInvite(t *testing.T) { } type channelEventListener struct { - ch chan *AsyncMessage + ch AsyncChannel } -func (l *channelEventListener) ProcessAsyncUserMessage(message *AsyncMessage) { - l.ch <- message +func (l *channelEventListener) AsyncChannel() AsyncChannel { + return l.ch } func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { @@ -422,13 +424,14 @@ func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { roomProperties := json.RawMessage("{\"foo\":\"bar\"}") backend := hub.backend.GetBackend(u) - eventsChan := make(chan *AsyncMessage, 1) + eventsChan := make(AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } require.NoError(events.RegisterUserListener(userid, backend, listener)) - defer events.UnregisterUserListener(userid, backend, listener) - + defer func() { + assert.NoError(events.UnregisterUserListener(userid, backend, listener)) + }() msg := &BackendServerRoomRequest{ Type: "invite", Invite: &BackendRoomInviteRequest{ @@ -497,13 +500,14 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { roomProperties := json.RawMessage("{\"foo\":\"bar\"}") - eventsChan := make(chan *AsyncMessage, 1) + eventsChan := make(AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } require.NoError(events.RegisterUserListener(testDefaultUserId, backend, listener)) - defer events.UnregisterUserListener(testDefaultUserId, backend, listener) - + defer func() { + assert.NoError(events.UnregisterUserListener(testDefaultUserId, backend, listener)) + }() msg := &BackendServerRoomRequest{ Type: "disinvite", Disinvite: &BackendRoomDisinviteRequest{ @@ -651,13 +655,14 @@ func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { userid := "test-userid" roomProperties := json.RawMessage("{\"foo\":\"bar\"}") - eventsChan := make(chan *AsyncMessage, 1) + eventsChan := make(AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } require.NoError(events.RegisterUserListener(userid, backend, listener)) - defer events.UnregisterUserListener(userid, backend, listener) - + defer func() { + assert.NoError(events.UnregisterUserListener(userid, backend, listener)) + }() msg := &BackendServerRoomRequest{ Type: "update", Update: &BackendRoomUpdateRequest{ @@ -718,13 +723,14 @@ func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) { require.NoError(err) userid := "test-userid" - eventsChan := make(chan *AsyncMessage, 1) + eventsChan := make(AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } require.NoError(events.RegisterUserListener(userid, backend, listener)) - defer events.UnregisterUserListener(userid, backend, listener) - + defer func() { + assert.NoError(events.UnregisterUserListener(userid, backend, listener)) + }() msg := &BackendServerRoomRequest{ Type: "delete", Delete: &BackendRoomDeleteRequest{ diff --git a/clientsession.go b/clientsession.go index fa414ec..16480c3 100644 --- a/clientsession.go +++ b/clientsession.go @@ -33,6 +33,7 @@ import ( "sync/atomic" "time" + "github.com/nats-io/nats.go" "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" @@ -82,7 +83,8 @@ type ClientSession struct { backendUrl string parsedBackendUrl *url.URL - mu sync.Mutex + mu sync.Mutex + asyncCh AsyncChannel // +checklocks:mu client HandlerClient @@ -140,6 +142,7 @@ func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessi parseUserData: parseUserData(auth.User), backend: backend, + asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), } if s.clientType == HelloClientTypeInternal { s.backendUrl = hello.Auth.internalParams.Backend @@ -155,6 +158,7 @@ func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessi if err := s.SubscribeEvents(); err != nil { return nil, err } + go s.run() return s, nil } @@ -391,6 +395,24 @@ func (s *ClientSession) releaseMcuObjects() { } } +func (s *ClientSession) AsyncChannel() AsyncChannel { + return s.asyncCh +} + +func (s *ClientSession) run() { + for { + select { + case <-s.ctx.Done(): + return + case msg := <-s.asyncCh: + s.processAsyncNatsMessage(msg) + for count := len(s.asyncCh); count > 0; count-- { + s.processAsyncNatsMessage(<-s.asyncCh) + } + } + } +} + func (s *ClientSession) Close() { s.closeAndWait(true) } @@ -406,9 +428,13 @@ func (s *ClientSession) closeAndWait(wait bool) { s.mu.Lock() defer s.mu.Unlock() if s.userId != "" { - s.events.UnregisterUserListener(s.userId, s.backend, s) + if err := s.events.UnregisterUserListener(s.userId, s.backend, s); err != nil && !errors.Is(err, nats.ErrConnectionClosed) { + s.logger.Printf("Error unsubscribing user listener for %s in session %s: %s", s.userId, s.publicId, err) + } + } + if err := s.events.UnregisterSessionListener(s.publicId, s.backend, s); err != nil && !errors.Is(err, nats.ErrConnectionClosed) { + s.logger.Printf("Error unsubscribing listener in session %s: %s", s.publicId, err) } - s.events.UnregisterSessionListener(s.publicId, s.backend, s) go func(virtualSessions map[*VirtualSession]bool) { for session := range virtualSessions { session.Close() @@ -543,7 +569,9 @@ func (s *ClientSession) UnsubscribeRoomEvents() { func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { room := s.GetRoom() if room != nil { - s.events.UnregisterRoomListener(room.Id(), s.Backend(), s) + if err := s.events.UnregisterRoomListener(room.Id(), s.Backend(), s); err != nil && !errors.Is(err, nats.ErrConnectionClosed) { + s.logger.Printf("Error unsubscribing room listener for %s in session %s: %s", room.Id(), s.publicId, err) + } } s.hub.roomSessions.DeleteRoomSession(s) @@ -1039,16 +1067,14 @@ func (s *ClientSession) GetSubscriber(id PublicSessionId, streamType StreamType) return s.subscribers[getStreamId(id, streamType)] } -func (s *ClientSession) ProcessAsyncRoomMessage(message *AsyncMessage) { - s.processAsyncMessage(message) -} +func (s *ClientSession) processAsyncNatsMessage(msg *nats.Msg) { + var message AsyncMessage + if err := NatsDecode(msg, &message); err != nil { + s.logger.Printf("Could not decode NATS message %+v: %s", msg, err) + return + } -func (s *ClientSession) ProcessAsyncUserMessage(message *AsyncMessage) { - s.processAsyncMessage(message) -} - -func (s *ClientSession) ProcessAsyncSessionMessage(message *AsyncMessage) { - s.processAsyncMessage(message) + s.processAsyncMessage(&message) } func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { diff --git a/clientsession_test.go b/clientsession_test.go index 1ccc062..d236458 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -216,7 +216,7 @@ func TestFeatureChatRelay(t *testing.T) { require.NoError(err) // Simulate request from the backend. - room.ProcessBackendRoomRequest(&AsyncMessage{ + room.processAsyncMessage(&AsyncMessage{ Type: "room", Room: &BackendServerRoomRequest{ Type: "message", @@ -413,7 +413,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { require.NoError(err) // Simulate request from the backend. - room.ProcessBackendRoomRequest(&AsyncMessage{ + room.processAsyncMessage(&AsyncMessage{ Type: "room", Room: &BackendServerRoomRequest{ Type: "message", @@ -513,7 +513,7 @@ func TestPermissionHideDisplayNames(t *testing.T) { require.NoError(err) // Simulate request from the backend. - room.ProcessBackendRoomRequest(&AsyncMessage{ + room.processAsyncMessage(&AsyncMessage{ Type: "room", Room: &BackendServerRoomRequest{ Type: "message", diff --git a/hub_test.go b/hub_test.go index e8ec61a..9455ad9 100644 --- a/hub_test.go +++ b/hub_test.go @@ -3361,7 +3361,7 @@ func TestCombineChatRefreshWhileDisconnected(t *testing.T) { require.NoError(json.Unmarshal([]byte(chat_refresh), &data)) // Simulate requests from the backend. - room.ProcessBackendRoomRequest(&AsyncMessage{ + room.processAsyncMessage(&AsyncMessage{ Type: "room", Room: &BackendServerRoomRequest{ Type: "message", @@ -3370,7 +3370,7 @@ func TestCombineChatRefreshWhileDisconnected(t *testing.T) { }, }, }) - room.ProcessBackendRoomRequest(&AsyncMessage{ + room.processAsyncMessage(&AsyncMessage{ Type: "room", Room: &BackendServerRoomRequest{ Type: "message", diff --git a/natsclient.go b/natsclient.go index 6fec9a4..0c92b03 100644 --- a/natsclient.go +++ b/natsclient.go @@ -53,8 +53,6 @@ type NatsClient interface { Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) Publish(subject string, message any) error - - Decode(msg *nats.Msg, v any) error } // The NATS client doesn't work if a subject contains spaces. As the room id @@ -156,7 +154,7 @@ func (c *natsClient) Publish(subject string, message any) error { return c.conn.Publish(subject, data) } -func (c *natsClient) Decode(msg *nats.Msg, vPtr any) (err error) { +func NatsDecode(msg *nats.Msg, vPtr any) (err error) { switch arg := vPtr.(type) { case *string: // If they want a string and it is a JSON string, strip quotes diff --git a/natsclient_loopback.go b/natsclient_loopback.go index 09dca16..bd2deef 100644 --- a/natsclient_loopback.go +++ b/natsclient_loopback.go @@ -192,7 +192,3 @@ func (c *LoopbackNatsClient) Publish(subject string, message any) error { c.wakeup.Signal() return nil } - -func (c *LoopbackNatsClient) Decode(msg *nats.Msg, v any) error { - return json.Unmarshal(msg.Data, v) -} diff --git a/room.go b/room.go index 235b20b..d2e089d 100644 --- a/room.go +++ b/room.go @@ -25,6 +25,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "maps" "net/url" @@ -32,6 +33,7 @@ import ( "sync" "time" + "github.com/nats-io/nats.go" "github.com/prometheus/client_golang/prometheus" "github.com/strukturag/nextcloud-spreed-signaling/api" @@ -73,8 +75,9 @@ type Room struct { // +checklocks:mu properties json.RawMessage - closer *Closer - mu *sync.RWMutex + closer *Closer + mu *sync.RWMutex + asyncCh AsyncChannel // +checklocks:mu sessions map[PublicSessionId]Session // +checklocks:mu @@ -118,6 +121,7 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEv closer: NewCloser(), mu: &sync.RWMutex{}, + asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), sessions: make(map[PublicSessionId]Session), internalSessions: make(map[*ClientSession]bool), @@ -180,6 +184,10 @@ func (r *Room) IsEqual(other *Room) bool { return b1.Id() == b2.Id() } +func (r *Room) AsyncChannel() AsyncChannel { + return r.asyncCh +} + func (r *Room) run() { ticker := time.NewTicker(updateActiveSessionsInterval) loop: @@ -187,6 +195,11 @@ loop: select { case <-r.closer.C: break loop + case msg := <-r.asyncCh: + r.processAsyncNatsMessage(msg) + for count := len(r.asyncCh); count > 0; count-- { + r.processAsyncNatsMessage(<-r.asyncCh) + } case <-ticker.C: r.publishActiveSessions() } @@ -198,7 +211,9 @@ func (r *Room) doClose() { } func (r *Room) unsubscribeBackend() { - r.events.UnregisterBackendRoomListener(r.id, r.backend, r) + if err := r.events.UnregisterBackendRoomListener(r.id, r.backend, r); err != nil && !errors.Is(err, nats.ErrConnectionClosed) { + r.logger.Printf("Error unsubscribing room listener in %s: %s", r.id, err) + } } func (r *Room) Close() []Session { @@ -218,7 +233,17 @@ func (r *Room) Close() []Session { return result } -func (r *Room) ProcessBackendRoomRequest(message *AsyncMessage) { +func (r *Room) processAsyncNatsMessage(msg *nats.Msg) { + var message AsyncMessage + if err := NatsDecode(msg, &message); err != nil { + r.logger.Printf("Could not decode NATS message %+v: %s", msg, err) + return + } + + r.processAsyncMessage(&message) +} + +func (r *Room) processAsyncMessage(message *AsyncMessage) { switch message.Type { case "room": r.processBackendRoomRequestRoom(message.Room) diff --git a/virtualsession.go b/virtualsession.go index ec98032..536655a 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -24,9 +24,11 @@ package signaling import ( "context" "encoding/json" + "errors" "net/url" "sync/atomic" + "github.com/nats-io/nats.go" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -44,6 +46,8 @@ type VirtualSession struct { privateId PrivateSessionId publicId PublicSessionId data *SessionIdData + ctx context.Context + closeFunc context.CancelFunc room atomic.Pointer[Room] sessionId PublicSessionId @@ -54,6 +58,8 @@ type VirtualSession struct { options *AddSessionOptions parseUserData func() (api.StringMap, error) + + asyncCh AsyncChannel } func GetVirtualSessionId(session Session, sessionId PublicSessionId) PublicSessionId { @@ -61,6 +67,9 @@ func GetVirtualSessionId(session Session, sessionId PublicSessionId) PublicSessi } func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, msg *AddSessionInternalClientMessage) (*VirtualSession, error) { + ctx := log.NewLoggerContext(session.Context(), session.hub.logger) + ctx, closeFunc := context.WithCancel(ctx) + result := &VirtualSession{ logger: session.hub.logger, hub: session.hub, @@ -68,12 +77,16 @@ func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publi privateId: privateId, publicId: publicId, data: data, + ctx: ctx, + closeFunc: closeFunc, sessionId: msg.SessionId, userId: msg.UserId, userData: msg.User, parseUserData: parseUserData(msg.User), options: msg.Options, + + asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), } if err := session.events.RegisterSessionListener(publicId, session.Backend(), result); err != nil { @@ -89,11 +102,12 @@ func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publi result.SetFlags(msg.Flags) } + go result.run() return result, nil } func (s *VirtualSession) Context() context.Context { - return s.session.Context() + return s.ctx } func (s *VirtualSession) PrivateId() PrivateSessionId { @@ -178,18 +192,40 @@ func (s *VirtualSession) LeaveRoom(notify bool) *Room { return room } +func (s *VirtualSession) AsyncChannel() AsyncChannel { + return s.asyncCh +} + +func (s *VirtualSession) run() { + for { + select { + case <-s.ctx.Done(): + return + case msg := <-s.asyncCh: + s.processAsyncNatsMessage(msg) + for count := len(s.asyncCh); count > 0; count-- { + s.processAsyncNatsMessage(<-s.asyncCh) + } + } + } +} + func (s *VirtualSession) Close() { s.CloseWithFeedback(nil, nil) } func (s *VirtualSession) CloseWithFeedback(session Session, message *ClientMessage) { + s.closeFunc() + room := s.GetRoom() s.session.RemoveVirtualSession(s) removed := s.session.hub.removeSession(s) if removed && room != nil { go s.notifyBackendRemoved(room, session, message) } - s.session.events.UnregisterSessionListener(s.PublicId(), s.session.Backend(), s) + if err := s.session.events.UnregisterSessionListener(s.PublicId(), s.session.Backend(), s); err != nil && !errors.Is(err, nats.ErrConnectionClosed) { + s.logger.Printf("Error unsubscribing listener for session %s: %s", s.publicId, err) + } } func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, message *ClientMessage) { @@ -272,7 +308,17 @@ func (s *VirtualSession) Options() *AddSessionOptions { return s.options } -func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { +func (s *VirtualSession) processAsyncNatsMessage(msg *nats.Msg) { + var message AsyncMessage + if err := NatsDecode(msg, &message); err != nil { + s.logger.Printf("Could not decode NATS message %+v: %s", msg, err) + return + } + + s.processAsyncMessage(&message) +} + +func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { if message.Type == "message" && message.Message != nil { switch message.Message.Type { case "message": @@ -286,7 +332,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { SessionId: s.SessionId(), UserId: s.UserId(), } - s.session.ProcessAsyncSessionMessage(message) + s.session.processAsyncMessage(message) } case "event": if room := s.GetRoom(); room != nil && @@ -307,7 +353,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { return } - s.session.ProcessAsyncSessionMessage(&AsyncMessage{ + s.session.processAsyncMessage(&AsyncMessage{ Type: "message", SendTime: message.SendTime, Message: &ServerMessage{ @@ -334,7 +380,7 @@ func (s *VirtualSession) ProcessAsyncSessionMessage(message *AsyncMessage) { SessionId: s.SessionId(), UserId: s.UserId(), } - s.session.ProcessAsyncSessionMessage(message) + s.session.processAsyncMessage(message) } } } From eab1d4392af6f2aa06124d92fa6fa116d1eaa610 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 20:01:36 +0000 Subject: [PATCH 390/549] Bump github.com/pion/sdp/v3 from 3.0.16 to 3.0.17 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.16 to 3.0.17. - [Release notes](https://github.com/pion/sdp/releases) - [Commits](https://github.com/pion/sdp/compare/v3.0.16...v3.0.17) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0c2bcb8..9f503e3 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.1.0 - github.com/pion/sdp/v3 v3.0.16 + github.com/pion/sdp/v3 v3.0.17 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index e791ebe..de89488 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= -github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= +github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo= +github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= github.com/pion/stun/v3 v3.0.2 h1:BJuGEN2oLrJisiNEJtUTJC4BGbzbfp37LizfqswblFU= github.com/pion/stun/v3 v3.0.2/go.mod h1:JFJKfIWvt178MCF5H/YIgZ4VX3LYE77vca4b9HP60SA= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= From ee908528d4e20a3c896a7401a98a9683143d9857 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:01:37 +0000 Subject: [PATCH 391/549] Bump google.golang.org/grpc from 1.77.0 to 1.78.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.77.0 to 1.78.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.77.0...v1.78.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.78.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0c2bcb8..de3b80d 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.7 go.etcd.io/etcd/server/v3 v3.6.7 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.77.0 + google.golang.org/grpc v1.78.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 google.golang.org/protobuf v1.36.11 ) @@ -93,8 +93,8 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index e791ebe..04f0810 100644 --- a/go.sum +++ b/go.sum @@ -221,12 +221,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE= +google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From 18174c64703c08817696e92fbcf6cbeb79466036 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 10:08:55 +0100 Subject: [PATCH 392/549] CI: Process files in all folders with licensecheck. --- .github/workflows/licensecheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/licensecheck.yml b/.github/workflows/licensecheck.yml index b2d2de7..55f52a3 100644 --- a/.github/workflows/licensecheck.yml +++ b/.github/workflows/licensecheck.yml @@ -33,7 +33,7 @@ jobs: run: | { echo 'CHECK_RESULT<> "$GITHUB_ENV" From 0b89140ef19496fa5c30e2aea5bc12bd544694df Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 10:21:49 +0100 Subject: [PATCH 393/549] CI: Run checklocks with Go 1.25 --- .github/workflows/lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b8bc3b8..f528ff4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -63,7 +63,8 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: "1.24" + go-version: "1.25" + check-latest: true - name: checklocks run: | From 8b2cb0fcffdb846e4f29ec6ae04ee0c9ae6da430 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 14:42:44 +0100 Subject: [PATCH 394/549] Move synctest helper to test package. --- .codecov.yml | 4 +++ .github/workflows/govuln.yml | 2 +- backoff_test.go | 4 ++- deferred_executor_test.go | 3 +- notifier_test.go | 4 ++- single_notifier_test.go | 4 ++- synctest24_test.go => test/synctest24.go | 2 +- synctest25_test.go => test/synctest25.go | 2 +- test/synctest_test.go | 38 ++++++++++++++++++++++++ throttle_test.go | 15 +++++----- transient_data_test.go | 3 +- 11 files changed, 66 insertions(+), 15 deletions(-) rename synctest24_test.go => test/synctest24.go (98%) rename synctest25_test.go => test/synctest25.go (98%) create mode 100644 test/synctest_test.go diff --git a/.codecov.yml b/.codecov.yml index eff76ae..71b2baf 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -42,3 +42,7 @@ component_management: name: server paths: - server/** + - component_id: module_test + name: test + paths: + - test/** diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index fe322cc..ef2990a 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -39,4 +39,4 @@ jobs: run: | set -euo pipefail go install golang.org/x/vuln/cmd/govulncheck@latest - govulncheck ./... + GOEXPERIMENT=synctest govulncheck ./... diff --git a/backoff_test.go b/backoff_test.go index 3ba3504..28d79e8 100644 --- a/backoff_test.go +++ b/backoff_test.go @@ -28,11 +28,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestBackoff_Exponential(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) minWait := 100 * time.Millisecond backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) diff --git a/deferred_executor_test.go b/deferred_executor_test.go index 60b1fd0..a9a02fb 100644 --- a/deferred_executor_test.go +++ b/deferred_executor_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestDeferredExecutor_MultiClose(t *testing.T) { @@ -42,7 +43,7 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { func TestDeferredExecutor_QueueSize(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { logger := log.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() diff --git a/notifier_test.go b/notifier_test.go index 538a4f5..aae7c58 100644 --- a/notifier_test.go +++ b/notifier_test.go @@ -29,6 +29,8 @@ import ( "time" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestNotifierNoWaiter(t *testing.T) { @@ -118,7 +120,7 @@ func TestNotifierResetWillNotify(t *testing.T) { func TestNotifierDuplicate(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { var notifier Notifier var done sync.WaitGroup diff --git a/single_notifier_test.go b/single_notifier_test.go index 2041b25..5a90931 100644 --- a/single_notifier_test.go +++ b/single_notifier_test.go @@ -29,6 +29,8 @@ import ( "time" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestSingleNotifierNoWaiter(t *testing.T) { @@ -118,7 +120,7 @@ func TestSingleNotifierResetWillNotify(t *testing.T) { func TestSingleNotifierDuplicate(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { var notifier SingleNotifier var done sync.WaitGroup diff --git a/synctest24_test.go b/test/synctest24.go similarity index 98% rename from synctest24_test.go rename to test/synctest24.go index a1bd191..47be565 100644 --- a/synctest24_test.go +++ b/test/synctest24.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package test import ( "testing" diff --git a/synctest25_test.go b/test/synctest25.go similarity index 98% rename from synctest25_test.go rename to test/synctest25.go index c79163b..e0e6da2 100644 --- a/synctest25_test.go +++ b/test/synctest25.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package test import ( "testing/synctest" diff --git a/test/synctest_test.go b/test/synctest_test.go new file mode 100644 index 0000000..de90478 --- /dev/null +++ b/test/synctest_test.go @@ -0,0 +1,38 @@ +/** + * 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 test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestSynctest(t *testing.T) { + t.Parallel() + SynctestTest(t, func(t *testing.T) { + start := time.Now() + time.Sleep(time.Second) + assert.Equal(t, time.Second, time.Since(start)) + }) +} diff --git a/throttle_test.go b/throttle_test.go index ffb2b22..5bd64d5 100644 --- a/throttle_test.go +++ b/throttle_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func newMemoryThrottlerForTest(t *testing.T) Throttler { @@ -70,7 +71,7 @@ func expectDelay(t *testing.T, f func(), delay time.Duration) { func TestThrottler(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -105,7 +106,7 @@ func TestThrottler(t *testing.T) { func TestThrottlerIPv6(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -143,7 +144,7 @@ func TestThrottlerIPv6(t *testing.T) { func TestThrottler_Bruteforce(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -170,7 +171,7 @@ func TestThrottler_Bruteforce(t *testing.T) { func TestThrottler_Cleanup(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) throttler := newMemoryThrottlerForTest(t) th, ok := throttler.(*memoryThrottler) @@ -227,7 +228,7 @@ func TestThrottler_Cleanup(t *testing.T) { func TestThrottler_ExpirePartial(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -260,7 +261,7 @@ func TestThrottler_ExpirePartial(t *testing.T) { func TestThrottler_ExpireAll(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -293,7 +294,7 @@ func TestThrottler_ExpireAll(t *testing.T) { func TestThrottler_Negative(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) diff --git a/transient_data_test.go b/transient_data_test.go index e4aff9a..ba2ec6d 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -33,11 +33,12 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func Test_TransientData(t *testing.T) { t.Parallel() - SynctestTest(t, func(t *testing.T) { + test.SynctestTest(t, func(t *testing.T) { assert := assert.New(t) data := NewTransientData() assert.False(data.Set("foo", nil)) From 98a8465e1214b17f710a50bcaa652cd2f2a59d2f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 14:47:31 +0100 Subject: [PATCH 395/549] Move Goroutines helpers to test package. --- backend_storage_etcd_test.go | 3 +- file_watcher_test.go | 5 +- grpc_client_test.go | 7 ++- hub_test.go | 9 +-- natsclient_test.go | 11 ++-- test/goroutines.go | 105 +++++++++++++++++++++++++++++++++++ test/goroutines_test.go | 60 ++++++++++++++++++++ testutils_test.go | 71 ----------------------- 8 files changed, 185 insertions(+), 86 deletions(-) create mode 100644 test/goroutines.go create mode 100644 test/goroutines_test.go diff --git a/backend_storage_etcd_test.go b/backend_storage_etcd_test.go index 2af55bb..255882a 100644 --- a/backend_storage_etcd_test.go +++ b/backend_storage_etcd_test.go @@ -29,6 +29,7 @@ import ( "go.etcd.io/etcd/server/v3/embed" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func (s *backendStorageEtcd) getWakeupChannelForTesting() <-chan struct{} { @@ -56,7 +57,7 @@ func (tl *testListener) EtcdClientCreated(client *EtcdClient) { func Test_BackendStorageEtcdNoLeak(t *testing.T) { // nolint:paralleltest logger := log.NewLoggerForTest(t) - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { etcd, client := NewEtcdClientForTest(t) tl := &testListener{ etcd: etcd, diff --git a/file_watcher_test.go b/file_watcher_test.go index 9978405..a8e96e3 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) var ( @@ -50,7 +51,7 @@ func TestFileWatcher_NotExist(t *testing.T) { } func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() @@ -92,7 +93,7 @@ func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest } func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) assert := assert.New(t) tmpdir := t.TempDir() diff --git a/grpc_client_test.go b/grpc_client_test.go index f6ef0c7..61c0b15 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -38,6 +38,7 @@ import ( "go.etcd.io/etcd/server/v3/embed" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { @@ -115,7 +116,7 @@ func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { _, addr1 := NewGrpcServerForTest(t) _, addr2 := NewGrpcServerForTest(t) @@ -224,7 +225,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) lookup := newMockDnsLookupForTest(t) @@ -307,7 +308,7 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { } func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) diff --git a/hub_test.go b/hub_test.go index 9455ad9..1e0df42 100644 --- a/hub_test.go +++ b/hub_test.go @@ -55,6 +55,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) const ( @@ -320,7 +321,7 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { case <-ctx.Done(): h.mu.Lock() h.ru.Lock() - dumpGoroutines("", os.Stderr) + test.DumpGoroutines("", os.Stderr) assert.Fail(t, "Error waiting for hub to terminate", "clients %+v / rooms %+v / sessions %+v / remoteSessions %v / federatedSessions %v / federationClients %v / %d read / %d write: %s", h.clients, h.rooms, @@ -1752,7 +1753,7 @@ func runGrpcProxyTest(t *testing.T, f func(hub1, hub2 *Hub, server1, server2 *ht } func TestClientHelloResumeProxy(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) assert := assert.New(t) @@ -1802,7 +1803,7 @@ func TestClientHelloResumeProxy(t *testing.T) { // nolint:paralleltest } func TestClientHelloResumeProxy_Takeover(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) assert := assert.New(t) @@ -1856,7 +1857,7 @@ func TestClientHelloResumeProxy_Takeover(t *testing.T) { // nolint:paralleltest } func TestClientHelloResumeProxy_Disconnect(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { runGrpcProxyTest(t, func(hub1, hub2 *Hub, server1, server2 *httptest.Server) { require := require.New(t) assert := assert.New(t) diff --git a/natsclient_test.go b/natsclient_test.go index 5f9a22f..e4ae1ae 100644 --- a/natsclient_test.go +++ b/natsclient_test.go @@ -35,6 +35,7 @@ import ( natsserver "github.com/nats-io/nats-server/v2/test" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func startLocalNatsServer(t *testing.T) (*server.Server, int) { @@ -113,7 +114,7 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { } func TestNatsClient_Subscribe(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_Subscribe(t, client) @@ -127,7 +128,7 @@ func testNatsClient_PublishAfterClose(t *testing.T, client NatsClient) { } func TestNatsClient_PublishAfterClose(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_PublishAfterClose(t, client) @@ -143,7 +144,7 @@ func testNatsClient_SubscribeAfterClose(t *testing.T, client NatsClient) { } func TestNatsClient_SubscribeAfterClose(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_SubscribeAfterClose(t, client) @@ -165,7 +166,7 @@ func testNatsClient_BadSubjects(t *testing.T, client NatsClient) { } func TestNatsClient_BadSubjects(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { _, _, client := CreateLocalNatsClientForTest(t) testNatsClient_BadSubjects(t, client) @@ -173,7 +174,7 @@ func TestNatsClient_BadSubjects(t *testing.T) { // nolint:paralleltest } func TestNatsClient_MaxReconnects(t *testing.T) { // nolint:paralleltest - ensureNoGoroutinesLeak(t, func(t *testing.T) { + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) reconnectWait := time.Millisecond diff --git a/test/goroutines.go b/test/goroutines.go new file mode 100644 index 0000000..e062e24 --- /dev/null +++ b/test/goroutines.go @@ -0,0 +1,105 @@ +/** + * 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 test + +import ( + "bytes" + "io" + "os" + "os/signal" + "runtime/pprof" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +var listenSignalOnce sync.Once + +func EnsureNoGoroutinesLeak(t *testing.T, f func(t *testing.T)) { + t.Helper() + + ensureNoGoroutinesLeak(t, f, false) +} + +func ensureNoGoroutinesLeak(t *testing.T, f func(t *testing.T), fromTest bool) (int, int) { + t.Helper() + // Make sure test is not executed with "t.Parallel()" + t.Setenv("PARALLEL_CHECK", "1") + + // The signal package will start a goroutine the first time "signal.Notify" + // is called. Do so outside the function under test so the signal goroutine + // will not be shown as "leaking". + listenSignalOnce.Do(func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + go func() { + for { + <-ch + } + }() + }) + + profile := pprof.Lookup("goroutine") + // Give time for things to settle before capturing the number of + // go routines + var before int + timeout := time.Now().Add(time.Second) + for time.Now().Before(timeout) { + before = profile.Count() + time.Sleep(10 * time.Millisecond) + if profile.Count() == before { + break + } + } + var prev bytes.Buffer + DumpGoroutines("Before:", &prev) + + t.Run("leakcheck", f) + + var after int + // Give time for things to settle before capturing the number of + // go routines + timeout = time.Now().Add(time.Second) + for time.Now().Before(timeout) { + after = profile.Count() + if after == before { + break + } + } + + if after != before && !fromTest { + io.Copy(os.Stderr, &prev) // nolint + DumpGoroutines("After:", os.Stderr) + require.Equal(t, before, after, "Number of Go routines has changed") + } + return before, after +} + +func DumpGoroutines(prefix string, w io.Writer) { + if prefix != "" { + io.WriteString(w, prefix+"\n") // nolint + } + profile := pprof.Lookup("goroutine") + profile.WriteTo(w, 2) // nolint +} diff --git a/test/goroutines_test.go b/test/goroutines_test.go new file mode 100644 index 0000000..65cf390 --- /dev/null +++ b/test/goroutines_test.go @@ -0,0 +1,60 @@ +/** + * 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 test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNoGoroutineLeak(t *testing.T) { // nolint:paralleltest + EnsureNoGoroutinesLeak(t, func(t *testing.T) { + stop := make(chan struct{}) + stopped := make(chan struct{}) + + go func() { + defer close(stopped) + <-stop + }() + + close(stop) + <-stopped + }) +} + +func TestLeakGoroutine(t *testing.T) { // nolint:paralleltest + stop := make(chan struct{}) + stopped := make(chan struct{}) + + before, after := ensureNoGoroutinesLeak(t, func(t *testing.T) { + go func() { + defer close(stopped) + <-stop + }() + + }, true) + close(stop) + <-stopped + + assert.Equal(t, 1, after-before, "wrong number of leaked goroutines") +} diff --git a/testutils_test.go b/testutils_test.go index 60932af..4571012 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -22,84 +22,13 @@ package signaling import ( - "bytes" "context" "encoding/json" - "io" - "os" - "os/signal" - "runtime/pprof" - "sync" "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -var listenSignalOnce sync.Once - -func ensureNoGoroutinesLeak(t *testing.T, f func(t *testing.T)) { - t.Helper() - // Make sure test is not executed with "t.Parallel()" - t.Setenv("PARALLEL_CHECK", "1") - - // The signal package will start a goroutine the first time "signal.Notify" - // is called. Do so outside the function under test so the signal goroutine - // will not be shown as "leaking". - listenSignalOnce.Do(func() { - ch := make(chan os.Signal, 1) - signal.Notify(ch, os.Interrupt) - go func() { - for { - <-ch - } - }() - }) - - profile := pprof.Lookup("goroutine") - // Give time for things to settle before capturing the number of - // go routines - var before int - timeout := time.Now().Add(time.Second) - for time.Now().Before(timeout) { - before = profile.Count() - time.Sleep(10 * time.Millisecond) - if profile.Count() == before { - break - } - } - var prev bytes.Buffer - dumpGoroutines("Before:", &prev) - - t.Run("leakcheck", f) - - var after int - // Give time for things to settle before capturing the number of - // go routines - timeout = time.Now().Add(time.Second) - for time.Now().Before(timeout) { - after = profile.Count() - if after == before { - break - } - } - - if after != before { - io.Copy(os.Stderr, &prev) // nolint - dumpGoroutines("After:", os.Stderr) - require.Equal(t, before, after, "Number of Go routines has changed") - } -} - -func dumpGoroutines(prefix string, w io.Writer) { - if prefix != "" { - io.WriteString(w, prefix+"\n") // nolint - } - profile := pprof.Lookup("goroutine") - profile.WriteTo(w, 2) // nolint -} - func WaitForUsersJoined(ctx context.Context, t *testing.T, client1 *TestClient, hello1 *ServerMessage, client2 *TestClient, hello2 *ServerMessage) { t.Helper() // We will receive "joined" events for all clients. The ordering is not From 0006f74c2dec30dd20251361e86001f827b38a58 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 14:59:44 +0100 Subject: [PATCH 396/549] Move backoff code to async package. --- .codecov.yml | 4 ++++ backoff.go => async/backoff.go | 2 +- backoff_test.go => async/backoff_test.go | 2 +- backend_storage_etcd.go | 3 ++- etcd_client.go | 3 ++- grpc_client.go | 5 +++-- hub.go | 3 ++- natsclient.go | 3 ++- proxy/proxy_server.go | 3 ++- proxy_config_etcd.go | 3 ++- 10 files changed, 21 insertions(+), 10 deletions(-) rename backoff.go => async/backoff.go (99%) rename backoff_test.go => async/backoff_test.go (99%) diff --git a/.codecov.yml b/.codecov.yml index 71b2baf..3fb00fa 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -22,6 +22,10 @@ component_management: name: api paths: - api/** + - component_id: module_async + name: async + paths: + - async/** - component_id: module_client name: client paths: diff --git a/backoff.go b/async/backoff.go similarity index 99% rename from backoff.go rename to async/backoff.go index ae55550..4d6953d 100644 --- a/backoff.go +++ b/async/backoff.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 async import ( "context" diff --git a/backoff_test.go b/async/backoff_test.go similarity index 99% rename from backoff_test.go rename to async/backoff_test.go index 28d79e8..08af543 100644 --- a/backoff_test.go +++ b/async/backoff_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 async import ( "context" diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 718eecf..86a241e 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -32,6 +32,7 @@ import ( "github.com/dlintw/goconf" clientv3 "go.etcd.io/etcd/client/v3" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -113,7 +114,7 @@ func (s *backendStorageEtcd) EtcdClientCreated(client *EtcdClient) { panic(err) } - backoff, err := NewExponentialBackoff(initialWaitDelay, maxWaitDelay) + backoff, err := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) if err != nil { panic(err) } diff --git a/etcd_client.go b/etcd_client.go index 2dbacdc..793ffc8 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -38,6 +38,7 @@ import ( "go.uber.org/zap/zapcore" "google.golang.org/grpc/connectivity" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -247,7 +248,7 @@ func (c *EtcdClient) RemoveListener(listener EtcdClientListener) { } func (c *EtcdClient) WaitForConnection(ctx context.Context) error { - backoff, err := NewExponentialBackoff(initialWaitDelay, maxWaitDelay) + backoff, err := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) if err != nil { return err } diff --git a/grpc_client.go b/grpc_client.go index dc0b94d..6341622 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -43,6 +43,7 @@ import ( "google.golang.org/grpc/resolver" status "google.golang.org/grpc/status" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -588,7 +589,7 @@ func (c *GrpcClients) getServerIdWithTimeout(ctx context.Context, client *GrpcCl } func (c *GrpcClients) checkIsSelf(ctx context.Context, target string, client *GrpcClient) { - backoff, _ := NewExponentialBackoff(initialWaitDelay, maxWaitDelay) + backoff, _ := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) defer c.selfCheckWaitGroup.Done() loop: @@ -830,7 +831,7 @@ func (c *GrpcClients) EtcdClientCreated(client *EtcdClient) { panic(err) } - backoff, _ := NewExponentialBackoff(initialWaitDelay, maxWaitDelay) + backoff, _ := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) var nextRevision int64 for c.closeCtx.Err() == nil { response, err := c.getGrpcTargets(c.closeCtx, client, c.targetPrefix) diff --git a/hub.go b/hub.go index 5a33bde..a715e2a 100644 --- a/hub.go +++ b/hub.go @@ -52,6 +52,7 @@ import ( "github.com/gorilla/websocket" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -510,7 +511,7 @@ func (h *Hub) updateGeoDatabase() { } defer h.geoipUpdating.Store(false) - backoff, err := NewExponentialBackoff(time.Second, 5*time.Minute) + backoff, err := async.NewExponentialBackoff(time.Second, 5*time.Minute) if err != nil { h.logger.Printf("Could not create exponential backoff: %s", err) return diff --git a/natsclient.go b/natsclient.go index 0c92b03..070d5e0 100644 --- a/natsclient.go +++ b/natsclient.go @@ -34,6 +34,7 @@ import ( "github.com/nats-io/nats.go" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -79,7 +80,7 @@ func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (Nat return NewLoopbackNatsClient(logger) } - backoff, err := NewExponentialBackoff(initialConnectInterval, maxConnectInterval) + backoff, err := async.NewExponentialBackoff(initialConnectInterval, maxConnectInterval) if err != nil { return nil, err } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 43882c9..4efeaf8 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -51,6 +51,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -433,7 +434,7 @@ func (s *ProxyServer) Start(config *goconf.ConfigFile) error { mcuType = signaling.McuTypeDefault } - backoff, err := signaling.NewExponentialBackoff(initialMcuRetry, maxMcuRetry) + backoff, err := async.NewExponentialBackoff(initialMcuRetry, maxMcuRetry) if err != nil { return err } diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index 35d6c33..354e771 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -31,6 +31,7 @@ import ( "github.com/dlintw/goconf" clientv3 "go.etcd.io/etcd/client/v3" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -109,7 +110,7 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client *EtcdClient) { panic(err) } - backoff, err := NewExponentialBackoff(initialWaitDelay, maxWaitDelay) + backoff, err := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) if err != nil { panic(err) } From ff4e736cf76b5da3125ee9836221798fb67dfe7d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 15:01:34 +0100 Subject: [PATCH 397/549] Move deferred executor code to async package. --- deferred_executor.go => async/deferred_executor.go | 2 +- deferred_executor_test.go => async/deferred_executor_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename deferred_executor.go => async/deferred_executor.go (99%) rename deferred_executor_test.go => async/deferred_executor_test.go (99%) diff --git a/deferred_executor.go b/async/deferred_executor.go similarity index 99% rename from deferred_executor.go rename to async/deferred_executor.go index a6504a7..a251433 100644 --- a/deferred_executor.go +++ b/async/deferred_executor.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 async import ( "reflect" diff --git a/deferred_executor_test.go b/async/deferred_executor_test.go similarity index 99% rename from deferred_executor_test.go rename to async/deferred_executor_test.go index a9a02fb..6dfc312 100644 --- a/deferred_executor_test.go +++ b/async/deferred_executor_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 async import ( "testing" From 674b09d38d50d71b1076ba41e98f0fb9262d829e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 15:03:23 +0100 Subject: [PATCH 398/549] Move channel waiter code to async package. --- channel_waiter.go => async/channel_waiter.go | 2 +- channel_waiter_test.go => async/channel_waiter_test.go | 2 +- clientsession.go | 3 ++- mcu_proxy.go | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) rename channel_waiter.go => async/channel_waiter.go (98%) rename channel_waiter_test.go => async/channel_waiter_test.go (98%) diff --git a/channel_waiter.go b/async/channel_waiter.go similarity index 98% rename from channel_waiter.go rename to async/channel_waiter.go index dde6cfd..928c7d5 100644 --- a/channel_waiter.go +++ b/async/channel_waiter.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 async import ( "sync" diff --git a/channel_waiter_test.go b/async/channel_waiter_test.go similarity index 98% rename from channel_waiter_test.go rename to async/channel_waiter_test.go index cb6c961..3528e64 100644 --- a/channel_waiter_test.go +++ b/async/channel_waiter_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 async import ( "testing" diff --git a/clientsession.go b/clientsession.go index 16480c3..47c5165 100644 --- a/clientsession.go +++ b/clientsession.go @@ -37,6 +37,7 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -96,7 +97,7 @@ type ClientSession struct { // +checklocks:roomSessionIdLock roomSessionId RoomSessionId - publisherWaiters ChannelWaiters // +checklocksignore + publisherWaiters async.ChannelWaiters // +checklocksignore // +checklocks:mu publishers map[StreamType]McuPublisher diff --git a/mcu_proxy.go b/mcu_proxy.go index 15027fb..b7569cc 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -46,6 +46,7 @@ import ( "github.com/gorilla/websocket" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -1503,7 +1504,7 @@ type mcuProxy struct { // +checklocks:mu publishers map[StreamId]*mcuProxyConnection - publisherWaiters ChannelWaiters + publisherWaiters async.ChannelWaiters continentsMap atomic.Value From af4a7e7ab99db5f3bdd9e12f398d5547f337a835 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 15:05:52 +0100 Subject: [PATCH 399/549] Move notifier code to async package. --- notifier.go => async/notifier.go | 2 +- notifier_test.go => async/notifier_test.go | 2 +- single_notifier.go => async/single_notifier.go | 2 +- single_notifier_test.go => async/single_notifier_test.go | 2 +- mcu_janus.go | 5 +++-- mcu_proxy.go | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) rename notifier.go => async/notifier.go (99%) rename notifier_test.go => async/notifier_test.go (99%) rename single_notifier.go => async/single_notifier.go (99%) rename single_notifier_test.go => async/single_notifier_test.go (99%) diff --git a/notifier.go b/async/notifier.go similarity index 99% rename from notifier.go rename to async/notifier.go index 800711d..06305aa 100644 --- a/notifier.go +++ b/async/notifier.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 async import ( "context" diff --git a/notifier_test.go b/async/notifier_test.go similarity index 99% rename from notifier_test.go rename to async/notifier_test.go index aae7c58..9bd61eb 100644 --- a/notifier_test.go +++ b/async/notifier_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 async import ( "context" diff --git a/single_notifier.go b/async/single_notifier.go similarity index 99% rename from single_notifier.go rename to async/single_notifier.go index 2a41f7d..28c3a45 100644 --- a/single_notifier.go +++ b/async/single_notifier.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 async import ( "context" diff --git a/single_notifier_test.go b/async/single_notifier_test.go similarity index 99% rename from single_notifier_test.go rename to async/single_notifier_test.go index 5a90931..1f2c7dd 100644 --- a/single_notifier_test.go +++ b/async/single_notifier_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 async import ( "context" diff --git a/mcu_janus.go b/mcu_janus.go index ba440f0..9775ea4 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -35,6 +35,7 @@ import ( "github.com/notedit/janus-go" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -256,8 +257,8 @@ type mcuJanus struct { // +checklocks:mu publishers map[StreamId]*mcuJanusPublisher - publisherCreated Notifier - publisherConnected Notifier + publisherCreated async.Notifier + publisherConnected async.Notifier // +checklocks:mu remotePublishers map[StreamId]*mcuJanusRemotePublisher diff --git a/mcu_proxy.go b/mcu_proxy.go index b7569cc..fcf89fc 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -371,7 +371,7 @@ type mcuProxyConnection struct { trackClose atomic.Bool temporary atomic.Bool - connectedNotifier SingleNotifier + connectedNotifier async.SingleNotifier msgId atomic.Int64 helloMsgId string From 22f45ac48249cb86c49fc8aa390a160258a9b872 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 15:21:15 +0100 Subject: [PATCH 400/549] Move closer helper to internal package. --- client.go | 5 +++-- federation.go | 4 ++-- hub.go | 9 +++++---- closer.go => internal/closer.go | 2 +- closer_test.go => internal/closer_test.go | 2 +- janus_client.go | 9 +++++---- mcu_janus.go | 4 ++-- mcu_janus_publisher.go | 3 ++- mcu_proxy.go | 8 ++++---- room.go | 4 ++-- room_ping.go | 5 +++-- throttle.go | 5 +++-- 12 files changed, 33 insertions(+), 27 deletions(-) rename closer.go => internal/closer.go (98%) rename closer_test.go => internal/closer_test.go (98%) diff --git a/client.go b/client.go index 296319c..2f8bf94 100644 --- a/client.go +++ b/client.go @@ -37,6 +37,7 @@ import ( "github.com/gorilla/websocket" "github.com/mailru/easyjson" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -141,7 +142,7 @@ type Client struct { mu sync.Mutex - closer *Closer + closer *internal.Closer closeOnce sync.Once messagesDone chan struct{} messageChan chan *bytes.Buffer @@ -171,7 +172,7 @@ func (c *Client) SetConn(ctx context.Context, conn *websocket.Conn, remoteAddres c.conn = conn c.addr = remoteAddress c.SetHandler(handler) - c.closer = NewCloser() + c.closer = internal.NewCloser() c.messageChan = make(chan *bytes.Buffer, 16) c.messagesDone = make(chan struct{}) } diff --git a/federation.go b/federation.go index dfbfe8b..fd4cd8d 100644 --- a/federation.go +++ b/federation.go @@ -93,7 +93,7 @@ type FederationClient struct { url string // +checklocks:mu conn *websocket.Conn - closer *Closer + closer *internal.Closer // +checklocks:mu reconnectDelay time.Duration reconnecting atomic.Bool @@ -153,7 +153,7 @@ func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, dialer: dialer, url: url, - closer: NewCloser(), + closer: internal.NewCloser(), } result.roomId.Store(room.RoomId) result.remoteRoomId.Store(remoteRoomId) diff --git a/hub.go b/hub.go index a715e2a..f31169d 100644 --- a/hub.go +++ b/hub.go @@ -53,6 +53,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -149,11 +150,11 @@ type Hub struct { infoInternal *WelcomeServerMessage welcome atomic.Value // *ServerMessage - closer *Closer + closer *internal.Closer readPumpActive atomic.Int32 writePumpActive atomic.Int32 - shutdown *Closer + shutdown *internal.Closer shutdownScheduled atomic.Bool roomUpdated chan *BackendServerRoomRequest @@ -372,8 +373,8 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, info: NewWelcomeServerMessage(version, DefaultFeatures...), infoInternal: NewWelcomeServerMessage(version, DefaultFeaturesInternal...), - closer: NewCloser(), - shutdown: NewCloser(), + closer: internal.NewCloser(), + shutdown: internal.NewCloser(), roomUpdated: make(chan *BackendServerRoomRequest), roomDeleted: make(chan *BackendServerRoomRequest), diff --git a/closer.go b/internal/closer.go similarity index 98% rename from closer.go rename to internal/closer.go index ea00769..62ed06f 100644 --- a/closer.go +++ b/internal/closer.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 internal import ( "sync/atomic" diff --git a/closer_test.go b/internal/closer_test.go similarity index 98% rename from closer_test.go rename to internal/closer_test.go index 472c210..f74a166 100644 --- a/closer_test.go +++ b/internal/closer_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 internal import ( "sync" diff --git a/janus_client.go b/janus_client.go index b1db2f1..a3a7500 100644 --- a/janus_client.go +++ b/janus_client.go @@ -45,6 +45,7 @@ import ( "github.com/notedit/janus-go" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -188,7 +189,7 @@ func unexpected(request string) error { type transaction struct { ch chan any incoming chan any - closer *Closer + closer *internal.Closer } func (t *transaction) run() { @@ -214,7 +215,7 @@ func newTransaction() *transaction { t := &transaction{ ch: make(chan any, 1), incoming: make(chan any, 8), - closer: NewCloser(), + closer: internal.NewCloser(), } return t } @@ -264,7 +265,7 @@ type JanusGateway struct { // +checklocks:Mutex transactions map[uint64]*transaction - closer *Closer + closer *internal.Closer writeMu sync.Mutex } @@ -302,7 +303,7 @@ func NewJanusGateway(ctx context.Context, wsURL string, listener GatewayListener listener: listener, transactions: make(map[uint64]*transaction), Sessions: make(map[uint64]*JanusSession), - closer: NewCloser(), + closer: internal.NewCloser(), } go gateway.ping() diff --git a/mcu_janus.go b/mcu_janus.go index 9775ea4..210c139 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -816,7 +816,7 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu closeChan: make(chan struct{}, 1), deferred: make(chan func(), 64), }, - sdpReady: NewCloser(), + sdpReady: internal.NewCloser(), id: id, settings: settings, } @@ -1000,7 +1000,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re deferred: make(chan func(), 64), }, - sdpReady: NewCloser(), + sdpReady: internal.NewCloser(), id: controller.PublisherId(), settings: settings, }, diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 7d907fd..500a6b2 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -33,6 +33,7 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -52,7 +53,7 @@ type mcuJanusPublisher struct { settings NewPublisherSettings stats publisherStatsCounter sdpFlags Flags - sdpReady *Closer + sdpReady *internal.Closer offerSdp atomic.Pointer[sdp.SessionDescription] answerSdp atomic.Pointer[sdp.SessionDescription] } diff --git a/mcu_proxy.go b/mcu_proxy.go index fcf89fc..9b8824e 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -356,8 +356,8 @@ type mcuProxyConnection struct { load atomic.Uint64 bandwidth atomic.Pointer[EventProxyServerBandwidth] mu sync.Mutex - closer *Closer - closedDone *Closer + closer *internal.Closer + closedDone *internal.Closer closed atomic.Bool // +checklocks:mu conn *websocket.Conn @@ -409,8 +409,8 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str url: parsed, ip: ip, connectToken: token, - closer: NewCloser(), - closedDone: NewCloser(), + closer: internal.NewCloser(), + closedDone: internal.NewCloser(), callbacks: make(map[string]mcuProxyCallback), publishers: make(map[string]*mcuProxyPublisher), publisherIds: make(map[StreamId]PublicSessionId), diff --git a/room.go b/room.go index d2e089d..5688316 100644 --- a/room.go +++ b/room.go @@ -75,7 +75,7 @@ type Room struct { // +checklocks:mu properties json.RawMessage - closer *Closer + closer *internal.Closer mu *sync.RWMutex asyncCh AsyncChannel // +checklocks:mu @@ -119,7 +119,7 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEv properties: properties, - closer: NewCloser(), + closer: internal.NewCloser(), mu: &sync.RWMutex{}, asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), sessions: make(map[PublicSessionId]Session), diff --git a/room_ping.go b/room_ping.go index cb4bc14..38b5930 100644 --- a/room_ping.go +++ b/room_ping.go @@ -28,6 +28,7 @@ import ( "sync" "time" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -66,7 +67,7 @@ func (e *pingEntries) RemoveRoom(roomId string) { // and sent out batched every "updateActiveSessionsInterval" seconds. type RoomPing struct { mu sync.Mutex - closer *Closer + closer *internal.Closer backend *BackendClient capabilities *Capabilities @@ -77,7 +78,7 @@ type RoomPing struct { func NewRoomPing(backend *BackendClient, capabilities *Capabilities) (*RoomPing, error) { result := &RoomPing{ - closer: NewCloser(), + closer: internal.NewCloser(), backend: backend, capabilities: capabilities, } diff --git a/throttle.go b/throttle.go index 27bce25..9bf5e14 100644 --- a/throttle.go +++ b/throttle.go @@ -29,6 +29,7 @@ import ( "sync" "time" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -99,7 +100,7 @@ type memoryThrottler struct { // +checklocks:mu clients map[string]map[string][]throttleEntry - closer *Closer + closer *internal.Closer } func NewMemoryThrottler() (Throttler, error) { @@ -108,7 +109,7 @@ func NewMemoryThrottler() (Throttler, error) { clients: make(map[string]map[string][]throttleEntry), - closer: NewCloser(), + closer: internal.NewCloser(), } result.doDelay = result.delay go result.housekeeping() From 446936f7ff37c827b153807a63592865cd1847d3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 15:31:23 +0100 Subject: [PATCH 401/549] Move common stats code to metrics package. --- .codecov.yml | 4 +++ backend_client_stats_prometheus.go | 4 ++- backend_configuration_stats_prometheus.go | 4 ++- client_stats_prometheus.go | 4 ++- grpc_stats_prometheus.go | 6 ++-- http_client_pool_stats_prometheus.go | 4 ++- hub_stats_prometheus.go | 4 ++- mcu_stats_prometheus.go | 18 +++++----- metrics/prometheus.go | 42 +++++++++++++++++++++++ room_stats_prometheus.go | 4 ++- stats_prometheus.go | 20 ++--------- throttle_stats_prometheus.go | 4 ++- 12 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 metrics/prometheus.go diff --git a/.codecov.yml b/.codecov.yml index 3fb00fa..74160db 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -38,6 +38,10 @@ component_management: name: log paths: - log/** + - component_id: module_metrics + name: metrics + paths: + - metrics/** - component_id: module_proxy name: proxy paths: diff --git a/backend_client_stats_prometheus.go b/backend_client_stats_prometheus.go index b406df0..26a6188 100644 --- a/backend_client_stats_prometheus.go +++ b/backend_client_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -54,5 +56,5 @@ var ( ) func RegisterBackendClientStats() { - registerAll(backendClientStats...) + metrics.RegisterAll(backendClientStats...) } diff --git a/backend_configuration_stats_prometheus.go b/backend_configuration_stats_prometheus.go index 13664ad..1fbef3c 100644 --- a/backend_configuration_stats_prometheus.go +++ b/backend_configuration_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -53,7 +55,7 @@ var ( ) func RegisterBackendConfigurationStats() { - registerAll(backendConfigurationStats...) + metrics.RegisterAll(backendConfigurationStats...) } func updateBackendStats(backend *Backend) { diff --git a/client_stats_prometheus.go b/client_stats_prometheus.go index cb53299..f27248f 100644 --- a/client_stats_prometheus.go +++ b/client_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -61,5 +63,5 @@ var ( ) func RegisterClientStats() { - registerAll(clientStats...) + metrics.RegisterAll(clientStats...) } diff --git a/grpc_stats_prometheus.go b/grpc_stats_prometheus.go index 4697fc3..36f8995 100644 --- a/grpc_stats_prometheus.go +++ b/grpc_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -57,9 +59,9 @@ var ( ) func RegisterGrpcClientStats() { - registerAll(grpcClientStats...) + metrics.RegisterAll(grpcClientStats...) } func RegisterGrpcServerStats() { - registerAll(grpcServerStats...) + metrics.RegisterAll(grpcServerStats...) } diff --git a/http_client_pool_stats_prometheus.go b/http_client_pool_stats_prometheus.go index 72d7b2c..13ba363 100644 --- a/http_client_pool_stats_prometheus.go +++ b/http_client_pool_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -39,5 +41,5 @@ var ( ) func RegisterHttpClientPoolStats() { - registerAll(httpClientPoolStats...) + metrics.RegisterAll(httpClientPoolStats...) } diff --git a/hub_stats_prometheus.go b/hub_stats_prometheus.go index 2e847c9..571ac2f 100644 --- a/hub_stats_prometheus.go +++ b/hub_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -67,5 +69,5 @@ var ( ) func RegisterHubStats() { - registerAll(hubStats...) + metrics.RegisterAll(hubStats...) } diff --git a/mcu_stats_prometheus.go b/mcu_stats_prometheus.go index cb5ecf3..4e735db 100644 --- a/mcu_stats_prometheus.go +++ b/mcu_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -224,21 +226,21 @@ var ( ) func RegisterJanusMcuStats() { - registerAll(commonMcuStats...) - registerAll(janusMcuStats...) + metrics.RegisterAll(commonMcuStats...) + metrics.RegisterAll(janusMcuStats...) } func UnregisterJanusMcuStats() { - unregisterAll(commonMcuStats...) - unregisterAll(janusMcuStats...) + metrics.UnregisterAll(commonMcuStats...) + metrics.UnregisterAll(janusMcuStats...) } func RegisterProxyMcuStats() { - registerAll(commonMcuStats...) - registerAll(proxyMcuStats...) + metrics.RegisterAll(commonMcuStats...) + metrics.RegisterAll(proxyMcuStats...) } func UnregisterProxyMcuStats() { - unregisterAll(commonMcuStats...) - unregisterAll(proxyMcuStats...) + metrics.UnregisterAll(commonMcuStats...) + metrics.UnregisterAll(proxyMcuStats...) } diff --git a/metrics/prometheus.go b/metrics/prometheus.go new file mode 100644 index 0000000..93ad0ba --- /dev/null +++ b/metrics/prometheus.go @@ -0,0 +1,42 @@ +/** + * 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 metrics + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +func RegisterAll(cs ...prometheus.Collector) { + for _, c := range cs { + if err := prometheus.DefaultRegisterer.Register(c); err != nil { + if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { + panic(err) + } + } + } +} + +func UnregisterAll(cs ...prometheus.Collector) { + for _, c := range cs { + prometheus.Unregister(c) + } +} diff --git a/room_stats_prometheus.go b/room_stats_prometheus.go index eec8a96..0e312d5 100644 --- a/room_stats_prometheus.go +++ b/room_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -39,5 +41,5 @@ var ( ) func RegisterRoomStats() { - registerAll(roomStats...) + metrics.RegisterAll(roomStats...) } diff --git a/stats_prometheus.go b/stats_prometheus.go index 93af6af..7a8885a 100644 --- a/stats_prometheus.go +++ b/stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -38,22 +40,6 @@ var ( } ) -func registerAll(cs ...prometheus.Collector) { - for _, c := range cs { - if err := prometheus.DefaultRegisterer.Register(c); err != nil { - if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { - panic(err) - } - } - } -} - -func unregisterAll(cs ...prometheus.Collector) { - for _, c := range cs { - prometheus.Unregister(c) - } -} - func RegisterStats() { - registerAll(signalingStats...) + metrics.RegisterAll(signalingStats...) } diff --git a/throttle_stats_prometheus.go b/throttle_stats_prometheus.go index 8279fe0..681eee4 100644 --- a/throttle_stats_prometheus.go +++ b/throttle_stats_prometheus.go @@ -23,6 +23,8 @@ package signaling import ( "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) var ( @@ -47,5 +49,5 @@ var ( ) func RegisterThrottleStats() { - registerAll(throttleStats...) + metrics.RegisterAll(throttleStats...) } From 1c3a03e97274de3ac66b442f17ad6e68037afe8f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 16:05:14 +0100 Subject: [PATCH 402/549] Move throttler code to async package. --- throttle.go => async/throttle.go | 19 ++++++++---- .../throttle_stats_prometheus.go | 2 +- throttle_test.go => async/throttle_test.go | 19 +----------- backend_server.go | 3 +- hub.go | 8 ++--- hub_test.go | 29 +++++++++++++++---- 6 files changed, 44 insertions(+), 36 deletions(-) rename throttle.go => async/throttle.go (94%) rename throttle_stats_prometheus.go => async/throttle_stats_prometheus.go (98%) rename throttle_test.go => async/throttle_test.go (95%) diff --git a/throttle.go b/async/throttle.go similarity index 94% rename from throttle.go rename to async/throttle.go index 9bf5e14..40dc693 100644 --- a/throttle.go +++ b/async/throttle.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 async import ( "context" @@ -92,9 +92,12 @@ type throttleEntry struct { ts time.Time } +type GetTimeFunc func() time.Time +type ThrottleDelayFunc func(context.Context, time.Duration) + type memoryThrottler struct { - getNow func() time.Time - doDelay func(context.Context, time.Duration) + getNow GetTimeFunc + doDelay ThrottleDelayFunc mu sync.RWMutex // +checklocks:mu @@ -104,14 +107,18 @@ type memoryThrottler struct { } func NewMemoryThrottler() (Throttler, error) { + return NewCustomMemoryThrottler(time.Now, defaultDelay) +} + +func NewCustomMemoryThrottler(getNow GetTimeFunc, delay ThrottleDelayFunc) (Throttler, error) { result := &memoryThrottler{ - getNow: time.Now, + getNow: getNow, + doDelay: delay, clients: make(map[string]map[string][]throttleEntry), closer: internal.NewCloser(), } - result.doDelay = result.delay go result.housekeeping() return result, nil } @@ -310,7 +317,7 @@ func (t *memoryThrottler) throttle(ctx context.Context, client string, action st t.doDelay(ctx, delay) } -func (t *memoryThrottler) delay(ctx context.Context, duration time.Duration) { +func defaultDelay(ctx context.Context, duration time.Duration) { c, cancel := context.WithTimeout(ctx, duration) defer cancel() diff --git a/throttle_stats_prometheus.go b/async/throttle_stats_prometheus.go similarity index 98% rename from throttle_stats_prometheus.go rename to async/throttle_stats_prometheus.go index 681eee4..2e04984 100644 --- a/throttle_stats_prometheus.go +++ b/async/throttle_stats_prometheus.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 async import ( "github.com/prometheus/client_golang/prometheus" diff --git a/throttle_test.go b/async/throttle_test.go similarity index 95% rename from throttle_test.go rename to async/throttle_test.go index 5bd64d5..9f7474e 100644 --- a/throttle_test.go +++ b/async/throttle_test.go @@ -19,10 +19,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package async import ( - "context" "testing" "time" @@ -45,22 +44,6 @@ func newMemoryThrottlerForTest(t *testing.T) Throttler { return result } -type throttlerTiming struct { - t *testing.T - - now time.Time - expectedSleep time.Duration -} - -func (t *throttlerTiming) getNow() time.Time { - return t.now -} - -func (t *throttlerTiming) doDelay(ctx context.Context, duration time.Duration) { - t.t.Helper() - assert.Equal(t.t, t.expectedSleep, duration) -} - func expectDelay(t *testing.T, f func(), delay time.Duration) { t.Helper() a := time.Now() diff --git a/backend_server.go b/backend_server.go index a9b7351..18b8d40 100644 --- a/backend_server.go +++ b/backend_server.go @@ -49,6 +49,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -840,7 +841,7 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, body []byte) { throttle, err := b.hub.throttler.CheckBruteforce(ctx, b.hub.getRealUserIP(r), "BackendRoomAuth") - if err == ErrBruteforceDetected { + if err == async.ErrBruteforceDetected { http.Error(w, "Too many requests", http.StatusTooManyRequests) return } else if err != nil { diff --git a/hub.go b/hub.go index f31169d..f1d5252 100644 --- a/hub.go +++ b/hub.go @@ -213,7 +213,7 @@ type Hub struct { rpcServer *GrpcServer rpcClients *GrpcClients - throttler Throttler + throttler async.Throttler skipFederationVerify bool federationTimeout time.Duration @@ -352,7 +352,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, return nil, err } - throttler, err := NewMemoryThrottler() + throttler, err := async.NewMemoryThrottler() if err != nil { return nil, err } @@ -1277,7 +1277,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { resumeId := message.Hello.ResumeId if resumeId != "" { throttle, err := h.throttler.CheckBruteforce(ctx, client.RemoteAddr(), "HelloResume") - if err == ErrBruteforceDetected { + if err == async.ErrBruteforceDetected { client.SendMessage(message.NewErrorServerMessage(TooManyRequests)) return } else if err != nil { @@ -1581,7 +1581,7 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) ctx := log.NewLoggerContext(client.Context(), h.logger) throttle, err := h.throttler.CheckBruteforce(ctx, client.RemoteAddr(), "HelloInternal") - if err == ErrBruteforceDetected { + if err == async.ErrBruteforceDetected { client.SendMessage(message.NewErrorServerMessage(TooManyRequests)) return } else if err != nil { diff --git a/hub_test.go b/hub_test.go index 1e0df42..f3ea9d5 100644 --- a/hub_test.go +++ b/hub_test.go @@ -53,6 +53,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -1440,6 +1441,22 @@ func TestClientHelloResume(t *testing.T) { } } +type throttlerTiming struct { + t *testing.T + + now time.Time + expectedSleep time.Duration +} + +func (t *throttlerTiming) getNow() time.Time { + return t.now +} + +func (t *throttlerTiming) doDelay(ctx context.Context, duration time.Duration) { + t.t.Helper() + assert.Equal(t.t, t.expectedSleep, duration) +} + func TestClientHelloResumeThrottle(t *testing.T) { t.Parallel() require := require.New(t) @@ -1450,12 +1467,12 @@ func TestClientHelloResumeThrottle(t *testing.T) { t: t, now: time.Now(), } - throttler := newMemoryThrottlerForTest(t) - th, ok := throttler.(*memoryThrottler) - require.True(ok, "expected memoryThrottler, got %T", throttler) - th.getNow = timing.getNow - th.doDelay = timing.doDelay - hub.throttler = th + throttler, err := async.NewCustomMemoryThrottler(timing.getNow, timing.doDelay) + require.NoError(err) + t.Cleanup(func() { + throttler.Close() + }) + hub.throttler = throttler ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() From a1ec06d802b09c53713adefe97e898f45ab6d0d7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 16:17:11 +0100 Subject: [PATCH 403/549] Move SplitEntries helper to internal package. --- allowed_ips.go | 4 +- backend_server.go | 3 +- backend_storage_static.go | 6 +-- client/main.go | 3 +- config.go | 16 -------- etcd_client.go | 2 +- grpc_client.go | 3 +- hub_test.go | 2 +- internal/split_entries.go | 41 ++++++++++++++++++++ internal/split_entries_test.go | 68 ++++++++++++++++++++++++++++++++++ mcu_janus_publisher.go | 2 +- mcu_proxy.go | 2 +- proxy/main.go | 3 +- proxy/proxy_server_test.go | 3 +- proxy_config_static.go | 3 +- server/main.go | 5 ++- 16 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 internal/split_entries.go create mode 100644 internal/split_entries_test.go diff --git a/allowed_ips.go b/allowed_ips.go index 9325211..5162061 100644 --- a/allowed_ips.go +++ b/allowed_ips.go @@ -26,6 +26,8 @@ import ( "fmt" "net" "strings" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) type AllowedIps struct { @@ -83,7 +85,7 @@ func parseIPNet(s string) (*net.IPNet, error) { func ParseAllowedIps(allowed string) (*AllowedIps, error) { var allowedIps []*net.IPNet - for ip := range SplitEntries(allowed, ",") { + for ip := range internal.SplitEntries(allowed, ",") { i, err := parseIPNet(ip) if err != nil { return nil, err diff --git a/backend_server.go b/backend_server.go index 18b8d40..7912580 100644 --- a/backend_server.go +++ b/backend_server.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -92,7 +93,7 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, // TODO(jojo): Make the validity for TURN credentials configurable. turnvalid := 24 * time.Hour - turnserverslist := slices.Collect(SplitEntries(turnservers, ",")) + turnserverslist := slices.Collect(internal.SplitEntries(turnservers, ",")) if len(turnserverslist) != 0 { if turnapikey == "" { return nil, errors.New("need a TURN API key if TURN servers are configured") diff --git a/backend_storage_static.go b/backend_storage_static.go index 9c8967f..2624a56 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -103,7 +103,7 @@ func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { // Old-style configuration, only hosts are configured and are using a common secret. allowMap := make(map[string]bool) - for u := range SplitEntries(allowedUrls, ",") { + for u := range internal.SplitEntries(allowedUrls, ",") { if idx := strings.IndexByte(u, '/'); idx != -1 { logger.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u) if u = u[:idx]; u == "" { @@ -285,7 +285,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen func getConfiguredBackendIDs(backendIds string) (ids []string) { seen := make(map[string]bool) - for id := range SplitEntries(backendIds, ",") { + for id := range internal.SplitEntries(backendIds, ",") { if seen[id] { continue } @@ -330,7 +330,7 @@ func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.Con var urls []string if u, _ := GetStringOptionWithEnv(config, id, "urls"); u != "" { - urls = slices.Sorted(SplitEntries(u, ",")) + urls = slices.Sorted(internal.SplitEntries(u, ",")) urls = slices.Compact(urls) } else if u, _ := GetStringOptionWithEnv(config, id, "url"); u != "" { if u = strings.TrimSpace(u); u != "" { diff --git a/client/main.go b/client/main.go index fdae23b..bbfdd17 100644 --- a/client/main.go +++ b/client/main.go @@ -48,6 +48,7 @@ import ( "github.com/mailru/easyjson/jwriter" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) var ( @@ -566,7 +567,7 @@ func main() { urls := make([]url.URL, 0) urlstrings := make([]string, 0) - for host := range signaling.SplitEntries(*addr, ",") { + for host := range internal.SplitEntries(*addr, ",") { u := url.URL{ Scheme: "ws", Host: host, diff --git a/config.go b/config.go index 0ed4deb..c3a006e 100644 --- a/config.go +++ b/config.go @@ -23,10 +23,8 @@ package signaling import ( "errors" - "iter" "os" "regexp" - "strings" "github.com/dlintw/goconf" ) @@ -87,17 +85,3 @@ func GetStringOptions(config *goconf.ConfigFile, section string, ignoreErrors bo return result, nil } - -// SplitEntries returns an iterator over all non-empty substrings of s separated -// by sep. -func SplitEntries(s string, sep string) iter.Seq[string] { - return func(yield func(entry string) bool) { - for entry := range strings.SplitSeq(s, sep) { - if entry = strings.TrimSpace(entry); entry != "" { - if !yield(entry) { - return - } - } - } - } -} diff --git a/etcd_client.go b/etcd_client.go index 793ffc8..ef498ab 100644 --- a/etcd_client.go +++ b/etcd_client.go @@ -109,7 +109,7 @@ func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, opti func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { var endpoints []string if endpointsString := c.getConfigStringWithFallback(config, "endpoints"); endpointsString != "" { - endpoints = slices.Collect(SplitEntries(endpointsString, ",")) + endpoints = slices.Collect(internal.SplitEntries(endpointsString, ",")) } else if discoverySrv := c.getConfigStringWithFallback(config, "discoverysrv"); discoverySrv != "" { discoveryService := c.getConfigStringWithFallback(config, "discoveryservice") clients, err := srv.GetClient("etcd-client", discoverySrv, discoveryService) diff --git a/grpc_client.go b/grpc_client.go index 6341622..088b550 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -44,6 +44,7 @@ import ( status "google.golang.org/grpc/status" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -657,7 +658,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo } targets, _ := config.GetString("grpc", "targets") - for target := range SplitEntries(targets, ",") { + for target := range internal.SplitEntries(targets, ",") { if entries, found := clientsMap[target]; found { clients = append(clients, entries.clients...) if dnsDiscovery && entries.entry == nil { diff --git a/hub_test.go b/hub_test.go index f3ea9d5..afcccbe 100644 --- a/hub_test.go +++ b/hub_test.go @@ -887,7 +887,7 @@ func TestWebsocketFeatures(t *testing.T) { assert.True(strings.HasPrefix(serverHeader, "nextcloud-spreed-signaling/"), "expected valid server header, got \"%s\"", serverHeader) features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) - for f := range SplitEntries(features, ",") { + for f := range internal.SplitEntries(features, ",") { _, found := featuresList[f] assert.False(found, "duplicate feature id \"%s\" in \"%s\"", f, features) featuresList[f] = true diff --git a/internal/split_entries.go b/internal/split_entries.go new file mode 100644 index 0000000..3a6b767 --- /dev/null +++ b/internal/split_entries.go @@ -0,0 +1,41 @@ +/** + * 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 internal + +import ( + "iter" + "strings" +) + +// SplitEntries returns an iterator over all non-empty substrings of s separated +// by sep. +func SplitEntries(s string, sep string) iter.Seq[string] { + return func(yield func(entry string) bool) { + for entry := range strings.SplitSeq(s, sep) { + if entry = strings.TrimSpace(entry); entry != "" { + if !yield(entry) { + return + } + } + } + } +} diff --git a/internal/split_entries_test.go b/internal/split_entries_test.go new file mode 100644 index 0000000..0e77147 --- /dev/null +++ b/internal/split_entries_test.go @@ -0,0 +1,68 @@ +/** + * 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 internal + +import ( + "slices" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSplitEntries(t *testing.T) { + t.Parallel() + assert := assert.New(t) + testcases := []struct { + s string + sep string + expected []string + }{ + { + "a b", + " ", + []string{"a", "b"}, + }, + { + "a b", + ",", + []string{"a b"}, + }, + { + "a b", + " ", + []string{"a", "b"}, + }, + { + "a,b,", + ",", + []string{"a", "b"}, + }, + { + "a,,b", + ",", + []string{"a", "b"}, + }, + } + for idx, tc := range testcases { + assert.Equal(tc.expected, slices.Collect(SplitEntries(tc.s, tc.sep)), "failed for testcase %d: %s", idx, tc.s) + } +} diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index 500a6b2..b7ce847 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -256,7 +256,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli } func getFmtpValue(fmtp string, key string) (string, bool) { - for part := range SplitEntries(fmtp, ";") { + for part := range internal.SplitEntries(fmtp, ";") { kv := strings.SplitN(part, "=", 2) if len(kv) != 2 { continue diff --git a/mcu_proxy.go b/mcu_proxy.go index 9b8824e..983cb52 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -1609,7 +1609,7 @@ func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { } var values []string - for v := range SplitEntries(value, ",") { + for v := range internal.SplitEntries(value, ",") { v = strings.ToUpper(v) if !IsValidContinent(v) { m.logger.Printf("Ignore unknown continent %s for override %s", v, option) diff --git a/proxy/main.go b/proxy/main.go index 7b05cb7..76755ad 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -38,6 +38,7 @@ import ( "github.com/gorilla/mux" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -106,7 +107,7 @@ func main() { writeTimeout = defaultWriteTimeout } - for address := range signaling.SplitEntries(addr, " ") { + for address := range internal.SplitEntries(addr, " ") { go func(address string) { logger.Println("Listening on", address) listener, err := net.Listen("tcp", address) diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 148c2ec..3db2b55 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -46,6 +46,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -322,7 +323,7 @@ func TestWebsocketFeatures(t *testing.T) { } features := response.Header.Get("X-Spreed-Signaling-Features") featuresList := make(map[string]bool) - for f := range signaling.SplitEntries(features, ",") { + for f := range internal.SplitEntries(features, ",") { if _, found := featuresList[f]; found { assert.Fail("duplicate feature", "id \"%s\" in \"%s\"", f, features) } diff --git a/proxy_config_static.go b/proxy_config_static.go index 577fc39..2c81fa5 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -30,6 +30,7 @@ import ( "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -89,7 +90,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool remove := maps.Clone(p.connectionsMap) mcuUrl, _ := GetStringOptionWithEnv(config, "mcu", "url") - for u := range SplitEntries(mcuUrl, " ") { + for u := range internal.SplitEntries(mcuUrl, " ") { if existing, found := remove[u]; found { // Proxy connection still exists in new configuration delete(remove, u) diff --git a/server/main.go b/server/main.go index 5e11579..02c8835 100644 --- a/server/main.go +++ b/server/main.go @@ -43,6 +43,7 @@ import ( "github.com/nats-io/nats.go" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -342,7 +343,7 @@ func main() { if writeTimeout <= 0 { writeTimeout = defaultWriteTimeout } - for address := range signaling.SplitEntries(saddr, " ") { + for address := range internal.SplitEntries(saddr, " ") { go func(address string) { logger.Println("Listening on", address) listener, err := createTLSListener(address, cert, key) @@ -375,7 +376,7 @@ func main() { writeTimeout = defaultWriteTimeout } - for address := range signaling.SplitEntries(addr, " ") { + for address := range internal.SplitEntries(addr, " ") { go func(address string) { logger.Println("Listening on", address) listener, err := createListener(address) From bc9b353975420d1e73f0e9a85a0ea7eb5903cad1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 16:26:33 +0100 Subject: [PATCH 404/549] Move IPList (from AllowedIps) to container package. --- .codecov.yml | 4 ++ api_signaling.go | 9 ++-- api_signaling_test.go | 14 +++--- backend_server.go | 13 +++--- allowed_ips.go => container/ip_list.go | 43 +++++++++---------- .../ip_list_test.go | 43 +++++++++++++++---- hub.go | 27 ++++++------ hub_test.go | 3 +- mcu_janus.go | 9 ++-- proxy/proxy_server.go | 19 ++++---- 10 files changed, 109 insertions(+), 75 deletions(-) rename allowed_ips.go => container/ip_list.go (78%) rename allowed_ips_test.go => container/ip_list_test.go (57%) diff --git a/.codecov.yml b/.codecov.yml index 74160db..ef152ef 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -30,6 +30,10 @@ component_management: name: client paths: - client/** + - component_id: module_container + name: container + paths: + - container/** - component_id: module_internal name: internal paths: diff --git a/api_signaling.go b/api_signaling.go index 6efc3ed..2993a6b 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -37,6 +37,7 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) @@ -838,7 +839,7 @@ func (m *MessageClientMessageData) CheckValid() error { return nil } -func FilterCandidate(c ice.Candidate, allowed *AllowedIps, blocked *AllowedIps) bool { +func FilterCandidate(c ice.Candidate, allowed *container.IPList, blocked *container.IPList) bool { switch c { case nil: return true @@ -852,19 +853,19 @@ func FilterCandidate(c ice.Candidate, allowed *AllowedIps, blocked *AllowedIps) } // Whitelist has preference. - if allowed != nil && allowed.Allowed(ip) { + if allowed != nil && allowed.Contains(ip) { return false } // Check if address is blocked manually. - if blocked != nil && blocked.Allowed(ip) { + if blocked != nil && blocked.Contains(ip) { return true } return false } -func FilterSDPCandidates(s *sdp.SessionDescription, allowed *AllowedIps, blocked *AllowedIps) bool { +func FilterSDPCandidates(s *sdp.SessionDescription, allowed *container.IPList, blocked *container.IPList) bool { modified := false for _, m := range s.MediaDescriptions { m.Attributes = slices.DeleteFunc(m.Attributes, func(a sdp.Attribute) bool { diff --git a/api_signaling_test.go b/api_signaling_test.go index 67bb4f8..f1e0556 100644 --- a/api_signaling_test.go +++ b/api_signaling_test.go @@ -31,6 +31,8 @@ import ( "github.com/pion/ice/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/container" ) type testCheckValid interface { @@ -481,17 +483,17 @@ func TestFilterCandidates(t *testing.T) { continue } - var allowed *AllowedIps + var allowed *container.IPList if tc.allowed != "" { - allowed, err = ParseAllowedIps(tc.allowed) + allowed, err = container.ParseIPList(tc.allowed) if !assert.NoError(err, "parsing allowed list %s failed in testcase %d", tc.allowed, idx) { continue } } - var blocked *AllowedIps + var blocked *container.IPList if tc.blocked != "" { - blocked, err = ParseAllowedIps(tc.blocked) + blocked, err = container.ParseIPList(tc.blocked) if !assert.NoError(err, "parsing blocked list %s failed in testcase %d", tc.blocked, idx) { continue } @@ -527,7 +529,7 @@ func TestFilterSDPCandidates(t *testing.T) { assert.Equal(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) } - blocked, err := ParseAllowedIps("192.0.0.0/24, 192.168.0.0/16") + blocked, err := container.ParseIPList("192.0.0.0/24, 192.168.0.0/16") require.NoError(err) expectedAfter := map[string]int{ @@ -577,7 +579,7 @@ func TestNoFilterSDPCandidates(t *testing.T) { assert.Equal(expectedBefore[m.MediaName.Media], count, "invalid number of candidates for media description %s", m.MediaName.Media) } - blocked, err := ParseAllowedIps("192.0.0.0/24, 192.168.0.0/16") + blocked, err := container.ParseIPList("192.0.0.0/24, 192.168.0.0/16") require.NoError(err) expectedAfter := map[string]int{ diff --git a/backend_server.go b/backend_server.go index 7912580..08393f9 100644 --- a/backend_server.go +++ b/backend_server.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -79,7 +80,7 @@ type BackendServer struct { turnvalid time.Duration turnservers []string - statsAllowedIps atomic.Pointer[AllowedIps] + statsAllowedIps atomic.Pointer[container.IPList] invalidSecret []byte buffers BufferPool @@ -110,7 +111,7 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, } statsAllowed, _ := config.GetString("stats", "allowed_ips") - statsAllowedIps, err := ParseAllowedIps(statsAllowed) + statsAllowedIps, err := container.ParseIPList(statsAllowed) if err != nil { return nil, err } @@ -118,7 +119,7 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, if !statsAllowedIps.Empty() { logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - statsAllowedIps = DefaultAllowedIps() + statsAllowedIps = container.DefaultAllowedIPs() logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } @@ -152,11 +153,11 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, func (b *BackendServer) Reload(config *goconf.ConfigFile) { statsAllowed, _ := config.GetString("stats", "allowed_ips") - if statsAllowedIps, err := ParseAllowedIps(statsAllowed); err == nil { + if statsAllowedIps, err := container.ParseIPList(statsAllowed); err == nil { if !statsAllowedIps.Empty() { b.logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - statsAllowedIps = DefaultAllowedIps() + statsAllowedIps = container.DefaultAllowedIPs() b.logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } b.statsAllowedIps.Store(statsAllowedIps) @@ -980,7 +981,7 @@ func (b *BackendServer) allowStatsAccess(r *http.Request) bool { } allowed := b.statsAllowedIps.Load() - return allowed != nil && allowed.Allowed(ip) + return allowed != nil && allowed.Contains(ip) } func (b *BackendServer) validateStatsRequest(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { diff --git a/allowed_ips.go b/container/ip_list.go similarity index 78% rename from allowed_ips.go rename to container/ip_list.go index 5162061..6a592bf 100644 --- a/allowed_ips.go +++ b/container/ip_list.go @@ -19,25 +19,26 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package container import ( "bytes" "fmt" "net" + "slices" "strings" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) -type AllowedIps struct { - allowed []*net.IPNet +type IPList struct { + ips []*net.IPNet } -func (a *AllowedIps) String() string { +func (a *IPList) String() string { var b bytes.Buffer b.WriteString("[") - for idx, n := range a.allowed { + for idx, n := range a.ips { if idx > 0 { b.WriteString(", ") } @@ -47,18 +48,14 @@ func (a *AllowedIps) String() string { return b.String() } -func (a *AllowedIps) Empty() bool { - return len(a.allowed) == 0 +func (a *IPList) Empty() bool { + return len(a.ips) == 0 } -func (a *AllowedIps) Allowed(ip net.IP) bool { - for _, i := range a.allowed { - if i.Contains(ip) { - return true - } - } - - return false +func (a *IPList) Contains(ip net.IP) bool { + return slices.ContainsFunc(a.ips, func(n *net.IPNet) bool { + return n.Contains(ip) + }) } func parseIPNet(s string) (*net.IPNet, error) { @@ -83,7 +80,7 @@ func parseIPNet(s string) (*net.IPNet, error) { return ipnet, nil } -func ParseAllowedIps(allowed string) (*AllowedIps, error) { +func ParseIPList(allowed string) (*IPList, error) { var allowedIps []*net.IPNet for ip := range internal.SplitEntries(allowed, ",") { i, err := parseIPNet(ip) @@ -93,13 +90,13 @@ func ParseAllowedIps(allowed string) (*AllowedIps, error) { allowedIps = append(allowedIps, i) } - result := &AllowedIps{ - allowed: allowedIps, + result := &IPList{ + ips: allowedIps, } return result, nil } -func DefaultAllowedIps() *AllowedIps { +func DefaultAllowedIPs() *IPList { allowedIps := []*net.IPNet{ { IP: net.ParseIP("127.0.0.1"), @@ -111,8 +108,8 @@ func DefaultAllowedIps() *AllowedIps { }, } - result := &AllowedIps{ - allowed: allowedIps, + result := &IPList{ + ips: allowedIps, } return result } @@ -129,8 +126,8 @@ var ( } ) -func DefaultPrivateIps() *AllowedIps { - allowed, err := ParseAllowedIps(strings.Join(privateIpNets, ",")) +func DefaultPrivateIPs() *IPList { + allowed, err := ParseIPList(strings.Join(privateIpNets, ",")) if err != nil { panic(fmt.Errorf("could not parse private ips %+v: %w", privateIpNets, err)) } diff --git a/allowed_ips_test.go b/container/ip_list_test.go similarity index 57% rename from allowed_ips_test.go rename to container/ip_list_test.go index 2eb78de..73e6a2e 100644 --- a/allowed_ips_test.go +++ b/container/ip_list_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 container import ( "net" @@ -29,42 +29,67 @@ import ( "github.com/stretchr/testify/require" ) -func TestAllowedIps(t *testing.T) { +func TestIPList(t *testing.T) { t.Parallel() require := require.New(t) - a, err := ParseAllowedIps("127.0.0.1, 192.168.0.1, 192.168.1.1/24") + a, err := ParseIPList("127.0.0.1, 192.168.0.1, 192.168.1.1/24") require.NoError(err) require.False(a.Empty()) require.Equal(`[127.0.0.1/32, 192.168.0.1/32, 192.168.1.0/24]`, a.String()) - allowed := []string{ + contained := []string{ "127.0.0.1", "192.168.0.1", "192.168.1.1", "192.168.1.100", } - notAllowed := []string{ + notContained := []string{ "192.168.0.2", "10.1.2.3", } - for _, addr := range allowed { + for _, addr := range contained { t.Run(addr, func(t *testing.T) { t.Parallel() assert := assert.New(t) if ip := net.ParseIP(addr); assert.NotNil(ip, "error parsing %s", addr) { - assert.True(a.Allowed(ip), "should allow %s", addr) + assert.True(a.Contains(ip), "should contain %s", addr) } }) } - for _, addr := range notAllowed { + for _, addr := range notContained { t.Run(addr, func(t *testing.T) { t.Parallel() assert := assert.New(t) if ip := net.ParseIP(addr); assert.NotNil(ip, "error parsing %s", addr) { - assert.False(a.Allowed(ip), "should not allow %s", addr) + assert.False(a.Contains(ip), "should not contain %s", addr) } }) } } + +func TestDefaultAllowedIPs(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + ips := DefaultAllowedIPs() + assert.True(ips.Contains(net.ParseIP("127.0.0.1"))) + assert.False(ips.Contains(net.ParseIP("127.1.0.1"))) + assert.True(ips.Contains(net.ParseIP("::1"))) + assert.False(ips.Contains(net.ParseIP("1.1.1.1"))) +} + +func TestDefaultPrivateIPs(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + ips := DefaultPrivateIPs() + assert.True(ips.Contains(net.ParseIP("127.0.0.1"))) + assert.True(ips.Contains(net.ParseIP("127.1.0.1"))) + assert.True(ips.Contains(net.ParseIP("::1"))) + assert.True(ips.Contains(net.ParseIP("10.1.2.3"))) + assert.True(ips.Contains(net.ParseIP("172.16.17.18"))) + assert.True(ips.Contains(net.ParseIP("192.168.10.20"))) + assert.False(ips.Contains(net.ParseIP("1.1.1.1"))) +} diff --git a/hub.go b/hub.go index f1d5252..1f00466 100644 --- a/hub.go +++ b/hub.go @@ -53,6 +53,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -133,7 +134,7 @@ var ( // Allow time differences of up to one minute between server and proxy. tokenLeeway = time.Minute - DefaultTrustedProxies = DefaultPrivateIps() + DefaultTrustedProxies = container.DefaultPrivateIPs() ) func init() { @@ -204,7 +205,7 @@ type Hub struct { backendTimeout time.Duration backend *BackendClient - trustedProxies atomic.Pointer[AllowedIps] + trustedProxies atomic.Pointer[container.IPList] geoip *GeoLookup geoipOverrides atomic.Pointer[map[*net.IPNet]string] geoipUpdating atomic.Bool @@ -218,8 +219,8 @@ type Hub struct { skipFederationVerify bool federationTimeout time.Duration - allowedCandidates atomic.Pointer[AllowedIps] - blockedCandidates atomic.Pointer[AllowedIps] + allowedCandidates atomic.Pointer[container.IPList] + blockedCandidates atomic.Pointer[container.IPList] } func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { @@ -284,7 +285,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, } trustedProxies, _ := config.GetString("app", "trustedproxies") - trustedProxiesIps, err := ParseAllowedIps(trustedProxies) + trustedProxiesIps, err := container.ParseIPList(trustedProxies) if err != nil { return nil, err } @@ -419,7 +420,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, federationTimeout: federationTimeout, } if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { - allowed, err := ParseAllowedIps(value) + allowed, err := container.ParseIPList(value) if err != nil { return nil, fmt.Errorf("invalid allowedcandidates: %w", err) } @@ -430,7 +431,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, logger.Printf("No candidates allowlist") } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { - blocked, err := ParseAllowedIps(value) + blocked, err := container.ParseIPList(value) if err != nil { return nil, fmt.Errorf("invalid blockedcandidates: %w", err) } @@ -574,7 +575,7 @@ func (h *Hub) Stop() { func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { trustedProxies, _ := config.GetString("app", "trustedproxies") - if trustedProxiesIps, err := ParseAllowedIps(trustedProxies); err == nil { + if trustedProxiesIps, err := container.ParseIPList(trustedProxies); err == nil { if !trustedProxiesIps.Empty() { h.logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { @@ -594,7 +595,7 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { } if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { - if allowed, err := ParseAllowedIps(value); err != nil { + if allowed, err := container.ParseIPList(value); err != nil { h.logger.Printf("invalid allowedcandidates: %s", err) } else { h.logger.Printf("Candidates allowlist: %s", allowed) @@ -605,7 +606,7 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { h.allowedCandidates.Store(nil) } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { - if blocked, err := ParseAllowedIps(value); err != nil { + if blocked, err := container.ParseIPList(value); err != nil { h.logger.Printf("invalid blockedcandidates: %s", err) } else { h.logger.Printf("Candidates blocklist: %s", blocked) @@ -3055,7 +3056,7 @@ func (h *Hub) GetServerInfoDialout() (result []BackendServerInfoDialout) { return } -func GetRealUserIP(r *http.Request, trusted *AllowedIps) string { +func GetRealUserIP(r *http.Request, trusted *container.IPList) string { addr := r.RemoteAddr if host, _, err := net.SplitHostPort(addr); err == nil { addr = host @@ -3067,7 +3068,7 @@ func GetRealUserIP(r *http.Request, trusted *AllowedIps) string { } // Don't check any headers if the server can be reached by untrusted clients directly. - if trusted == nil || !trusted.Allowed(ip) { + if trusted == nil || !trusted.Contains(ip) { return addr } @@ -3094,7 +3095,7 @@ func GetRealUserIP(r *http.Request, trusted *AllowedIps) string { continue } - if trusted.Allowed(ip) { + if trusted.Contains(ip) { lastTrusted = hop continue } diff --git a/hub_test.go b/hub_test.go index afcccbe..d8f5bb4 100644 --- a/hub_test.go +++ b/hub_test.go @@ -54,6 +54,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "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/test" @@ -3294,7 +3295,7 @@ func TestGetRealUserIP(t *testing.T) { } for _, tc := range testcases { - trustedProxies, err := ParseAllowedIps(tc.trusted) + trustedProxies, err := container.ParseIPList(tc.trusted) if !assert.NoError(t, err, "invalid trusted proxies in %+v", tc) { continue } diff --git a/mcu_janus.go b/mcu_janus.go index 210c139..c40621a 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -36,6 +36,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -151,8 +152,8 @@ type clientInterface interface { type mcuJanusSettings struct { mcuCommonSettings - allowedCandidates atomic.Pointer[AllowedIps] - blockedCandidates atomic.Pointer[AllowedIps] + allowedCandidates atomic.Pointer[container.IPList] + blockedCandidates atomic.Pointer[container.IPList] } func newMcuJanusSettings(ctx context.Context, config *goconf.ConfigFile) (*mcuJanusSettings, error) { @@ -182,7 +183,7 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { s.setTimeout(mcuTimeout) if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { - allowed, err := ParseAllowedIps(value) + allowed, err := container.ParseIPList(value) if err != nil { return fmt.Errorf("invalid allowedcandidates: %w", err) } @@ -194,7 +195,7 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { s.allowedCandidates.Store(nil) } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { - blocked, err := ParseAllowedIps(value) + blocked, err := container.ParseIPList(value) if err != nil { return fmt.Errorf("invalid blockedcandidates: %w", err) } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 4efeaf8..87cf4d1 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -52,6 +52,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -129,8 +130,8 @@ type ProxyServer struct { upgrader websocket.Upgrader tokens ProxyTokens - statsAllowedIps atomic.Pointer[signaling.AllowedIps] - trustedProxies atomic.Pointer[signaling.AllowedIps] + statsAllowedIps atomic.Pointer[container.IPList] + trustedProxies atomic.Pointer[container.IPList] sid atomic.Uint64 cookie *signaling.SessionIdCodec @@ -252,7 +253,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * } statsAllowed, _ := config.GetString("stats", "allowed_ips") - statsAllowedIps, err := signaling.ParseAllowedIps(statsAllowed) + statsAllowedIps, err := container.ParseIPList(statsAllowed) if err != nil { return nil, err } @@ -260,12 +261,12 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * if !statsAllowedIps.Empty() { logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - statsAllowedIps = signaling.DefaultAllowedIps() + statsAllowedIps = container.DefaultAllowedIPs() logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } trustedProxies, _ := config.GetString("app", "trustedproxies") - trustedProxiesIps, err := signaling.ParseAllowedIps(trustedProxies) + trustedProxiesIps, err := container.ParseIPList(trustedProxies) if err != nil { return nil, err } @@ -617,11 +618,11 @@ func (s *ProxyServer) ScheduleShutdown() { func (s *ProxyServer) Reload(config *goconf.ConfigFile) { statsAllowed, _ := config.GetString("stats", "allowed_ips") - if statsAllowedIps, err := signaling.ParseAllowedIps(statsAllowed); err == nil { + if statsAllowedIps, err := container.ParseIPList(statsAllowed); err == nil { if !statsAllowedIps.Empty() { s.logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) } else { - statsAllowedIps = signaling.DefaultAllowedIps() + statsAllowedIps = container.DefaultAllowedIPs() s.logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } s.statsAllowedIps.Store(statsAllowedIps) @@ -630,7 +631,7 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { } trustedProxies, _ := config.GetString("app", "trustedproxies") - if trustedProxiesIps, err := signaling.ParseAllowedIps(trustedProxies); err == nil { + if trustedProxiesIps, err := container.ParseIPList(trustedProxies); err == nil { if !trustedProxiesIps.Empty() { s.logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { @@ -1655,7 +1656,7 @@ func (s *ProxyServer) allowStatsAccess(r *http.Request) bool { } allowed := s.statsAllowedIps.Load() - return allowed != nil && allowed.Allowed(ip) + return allowed != nil && allowed.Contains(ip) } func (s *ProxyServer) validateStatsRequest(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { From e478b93ba0b13e7317f69e520cc21c6980b1917c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 16:29:24 +0100 Subject: [PATCH 405/549] Move LruCache to container package. --- lru.go => container/lru.go | 2 +- lru_test.go => container/lru_test.go | 2 +- hub.go | 8 ++++---- hub_test.go | 8 ++++---- proxy/proxy_tokens_etcd.go | 5 +++-- 5 files changed, 13 insertions(+), 12 deletions(-) rename lru.go => container/lru.go (99%) rename lru_test.go => container/lru_test.go (99%) diff --git a/lru.go b/container/lru.go similarity index 99% rename from lru.go rename to container/lru.go index 781e8be..2c7992e 100644 --- a/lru.go +++ b/container/lru.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 container import ( "container/list" diff --git a/lru_test.go b/container/lru_test.go similarity index 99% rename from lru_test.go rename to container/lru_test.go index 35113dc..9446e86 100644 --- a/lru_test.go +++ b/container/lru_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 container import ( "strconv" diff --git a/hub.go b/hub.go index 1f00466..917546a 100644 --- a/hub.go +++ b/hub.go @@ -179,7 +179,7 @@ type Hub struct { // +checklocks:mu virtualSessions map[PublicSessionId]uint64 - decodeCaches []*LruCache[*SessionIdData] + decodeCaches []*container.LruCache[*SessionIdData] mcu Mcu mcuTimeout time.Duration @@ -307,9 +307,9 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } - decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) + decodeCaches := make([]*container.LruCache[*SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) + decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) } roomSessions, err := NewBuiltinRoomSessions(rpcClients) @@ -624,7 +624,7 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { h.rpcClients.Reload(config) } -func (h *Hub) getDecodeCache(cache_key string) *LruCache[*SessionIdData] { +func (h *Hub) getDecodeCache(cache_key string) *container.LruCache[*SessionIdData] { hash := fnv.New32a() // Make sure we don't have a temporary allocation for the string -> []byte conversion. hash.Write(unsafe.Slice(unsafe.StringData(cache_key), len(cache_key))) // nolint diff --git a/hub_test.go b/hub_test.go index d8f5bb4..ef1bd84 100644 --- a/hub_test.go +++ b/hub_test.go @@ -815,9 +815,9 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { require := require.New(b) - decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) + decodeCaches := make([]*container.LruCache[*SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) + decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) } backend := &Backend{ id: "compat", @@ -844,9 +844,9 @@ func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { func Benchmark_DecodePublicSessionIdCached(b *testing.B) { require := require.New(b) - decodeCaches := make([]*LruCache[*SessionIdData], 0, numDecodeCaches) + decodeCaches := make([]*container.LruCache[*SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, NewLruCache[*SessionIdData](decodeCacheSize)) + decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) } backend := &Backend{ id: "compat", diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index 77ffc18..00a94c2 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -34,6 +34,7 @@ import ( "github.com/golang-jwt/jwt/v5" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -51,7 +52,7 @@ type tokensEtcd struct { client *signaling.EtcdClient tokenFormats atomic.Value - tokenCache *signaling.LruCache[*tokenCacheEntry] + tokenCache *container.LruCache[*tokenCacheEntry] } func NewProxyTokensEtcd(logger log.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { @@ -67,7 +68,7 @@ func NewProxyTokensEtcd(logger log.Logger, config *goconf.ConfigFile) (ProxyToke result := &tokensEtcd{ logger: logger, client: client, - tokenCache: signaling.NewLruCache[*tokenCacheEntry](tokenCacheSize), + tokenCache: container.NewLruCache[*tokenCacheEntry](tokenCacheSize), } if err := result.load(config, false); err != nil { return nil, err From f1a719cb234f79423313d6391ba099a9b2bcdd3e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 16:31:29 +0100 Subject: [PATCH 406/549] Move ConcurrentMap to container package. --- backend_server.go | 8 ++++---- concurrentmap.go => container/concurrentmap.go | 2 +- concurrentmap_test.go => container/concurrentmap_test.go | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) rename concurrentmap.go => container/concurrentmap.go (98%) rename concurrentmap_test.go => container/concurrentmap_test.go (97%) diff --git a/backend_server.go b/backend_server.go index 08393f9..a6905c2 100644 --- a/backend_server.go +++ b/backend_server.go @@ -427,7 +427,7 @@ func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified } } -func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, cache *ConcurrentMap[RoomSessionId, PublicSessionId]) (PublicSessionId, error) { +func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, cache *container.ConcurrentMap[RoomSessionId, PublicSessionId]) (PublicSessionId, error) { if roomSessionId == sessionIdNotInMeeting { b.logger.Printf("Trying to lookup empty room session id: %s", roomSessionId) return "", nil @@ -452,7 +452,7 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId return sid, nil } -func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *ConcurrentMap[RoomSessionId, PublicSessionId], users []api.StringMap) []api.StringMap { +func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container.ConcurrentMap[RoomSessionId, PublicSessionId], users []api.StringMap) []api.StringMap { if len(users) == 0 { return users } @@ -504,7 +504,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request ctx := log.NewLoggerContext(context.Background(), b.logger) ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - var cache ConcurrentMap[RoomSessionId, PublicSessionId] + var cache container.ConcurrentMap[RoomSessionId, PublicSessionId] // Convert (Nextcloud) session ids to signaling session ids. request.InCall.Users = b.fixupUserSessions(ctx, &cache, request.InCall.Users) // Entries in "Changed" are most likely already fetched through the "Users" list. @@ -528,7 +528,7 @@ func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid s // Convert (Nextcloud) session ids to signaling session ids. ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - var cache ConcurrentMap[RoomSessionId, PublicSessionId] + var cache container.ConcurrentMap[RoomSessionId, PublicSessionId] request.Participants.Users = b.fixupUserSessions(ctx, &cache, request.Participants.Users) request.Participants.Changed = b.fixupUserSessions(ctx, &cache, request.Participants.Changed) diff --git a/concurrentmap.go b/container/concurrentmap.go similarity index 98% rename from concurrentmap.go rename to container/concurrentmap.go index 255d2a1..b9aa3db 100644 --- a/concurrentmap.go +++ b/container/concurrentmap.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 container import ( "sync" diff --git a/concurrentmap_test.go b/container/concurrentmap_test.go similarity index 97% rename from concurrentmap_test.go rename to container/concurrentmap_test.go index ad20098..41679b6 100644 --- a/concurrentmap_test.go +++ b/container/concurrentmap_test.go @@ -19,9 +19,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package container import ( + "crypto/rand" "strconv" "sync" "testing" @@ -83,7 +84,7 @@ func TestConcurrentStringStringMap(t *testing.T) { defer wg.Done() key := "key-" + strconv.Itoa(x) - rnd := newRandomString(32) + rnd := rand.Text() for y := range count { value := rnd + "-" + strconv.Itoa(y) m.Set(key, value) From 04decde5aac323ad77fb51e3106284c79621c4cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:54:10 +0000 Subject: [PATCH 407/549] Bump github.com/pion/ice/v4 from 4.1.0 to 4.2.0 Bumps [github.com/pion/ice/v4](https://github.com/pion/ice) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/pion/ice/releases) - [Commits](https://github.com/pion/ice/compare/v4.1.0...v4.2.0) --- updated-dependencies: - dependency-name: github.com/pion/ice/v4 dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index dce9629..3e75147 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/nats-io/nats.go v1.48.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/ice/v4 v4.1.0 + github.com/pion/ice/v4 v4.2.0 github.com/pion/sdp/v3 v3.0.17 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 @@ -57,13 +57,13 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.0.9 // indirect + github.com/pion/dtls/v3 v3.0.10 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/stun/v3 v3.0.2 // indirect - github.com/pion/transport/v3 v3.1.1 // indirect - github.com/pion/turn/v4 v4.1.3 // indirect + github.com/pion/stun/v3 v3.1.1 // indirect + github.com/pion/transport/v4 v4.0.1 // indirect + github.com/pion/turn/v4 v4.1.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect diff --git a/go.sum b/go.sum index e7ded63..38a7e88 100644 --- a/go.sum +++ b/go.sum @@ -86,10 +86,10 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.0.9 h1:4AijfFRm8mAjd1gfdlB1wzJF3fjjR/VPIpJgkEtvYmM= -github.com/pion/dtls/v3 v3.0.9/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os= -github.com/pion/ice/v4 v4.1.0 h1:YlxIii2bTPWyC08/4hdmtYq4srbrY0T9xcTsTjldGqU= -github.com/pion/ice/v4 v4.1.0/go.mod h1:5gPbzYxqenvn05k7zKPIZFuSAufolygiy6P1U9HzvZ4= +github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg= +github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= +github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw= +github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= @@ -98,12 +98,14 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo= github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= -github.com/pion/stun/v3 v3.0.2 h1:BJuGEN2oLrJisiNEJtUTJC4BGbzbfp37LizfqswblFU= -github.com/pion/stun/v3 v3.0.2/go.mod h1:JFJKfIWvt178MCF5H/YIgZ4VX3LYE77vca4b9HP60SA= +github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= +github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= -github.com/pion/turn/v4 v4.1.3 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA= -github.com/pion/turn/v4 v4.1.3/go.mod h1:TD/eiBUf5f5LwXbCJa35T7dPtTpCHRJ9oJWmyPLVT3A= +github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= +github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= +github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= +github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= From 554304630562700b0ebfa577e75065f9cd37c055 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 20:44:51 +0100 Subject: [PATCH 408/549] Move NATS client to nats package. --- .codecov.yml | 4 + async_events.go | 5 +- async_events_nats.go | 54 +++--- async_events_test.go | 15 +- backend_server_test.go | 5 +- clientsession.go | 4 +- hub_test.go | 5 +- natsclient.go => nats/client.go | 90 +++------- nats/client_test.go | 159 ++++++++++++++++++ natsclient_loopback.go => nats/loopback.go | 44 ++--- .../loopback_test.go | 45 ++--- nats/native.go | 114 +++++++++++++ natsclient_test.go => nats/native_test.go | 75 +++------ nats/test_helpers.go | 74 ++++++++ room.go | 4 +- server/main.go | 2 +- virtualsession.go | 4 +- 17 files changed, 484 insertions(+), 219 deletions(-) rename natsclient.go => nats/client.go (65%) create mode 100644 nats/client_test.go rename natsclient_loopback.go => nats/loopback.go (73%) rename natsclient_loopback_test.go => nats/loopback_test.go (61%) create mode 100644 nats/native.go rename natsclient_test.go => nats/native_test.go (67%) create mode 100644 nats/test_helpers.go diff --git a/.codecov.yml b/.codecov.yml index ef152ef..b3f0da5 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -46,6 +46,10 @@ component_management: name: metrics paths: - metrics/** + - component_id: module_nats + name: nats + paths: + - nats/** - component_id: module_proxy name: proxy paths: diff --git a/async_events.go b/async_events.go index c8e2284..e86a07c 100644 --- a/async_events.go +++ b/async_events.go @@ -25,9 +25,8 @@ import ( "context" "errors" - "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) var ( @@ -66,7 +65,7 @@ type AsyncEvents interface { } func NewAsyncEvents(ctx context.Context, url string) (AsyncEvents, error) { - client, err := NewNatsClient(ctx, url) + client, err := nats.NewClient(ctx, url) if err != nil { return nil, err } diff --git a/async_events_nats.go b/async_events_nats.go index b89a4ab..81edba0 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -27,44 +27,43 @@ import ( "sync" "time" - "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) func GetSubjectForBackendRoomId(roomId string, backend *Backend) string { if backend == nil || backend.IsCompat() { - return GetEncodedSubject("backend.room", roomId) + return nats.GetEncodedSubject("backend.room", roomId) } - return GetEncodedSubject("backend.room", roomId+"|"+backend.Id()) + return nats.GetEncodedSubject("backend.room", roomId+"|"+backend.Id()) } func GetSubjectForRoomId(roomId string, backend *Backend) string { if backend == nil || backend.IsCompat() { - return GetEncodedSubject("room", roomId) + return nats.GetEncodedSubject("room", roomId) } - return GetEncodedSubject("room", roomId+"|"+backend.Id()) + return nats.GetEncodedSubject("room", roomId+"|"+backend.Id()) } func GetSubjectForUserId(userId string, backend *Backend) string { if backend == nil || backend.IsCompat() { - return GetEncodedSubject("user", userId) + return nats.GetEncodedSubject("user", userId) } - return GetEncodedSubject("user", userId+"|"+backend.Id()) + return nats.GetEncodedSubject("user", userId+"|"+backend.Id()) } func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string { return string("session." + sessionId) } -type asyncEventsNatsSubscriptions map[string]map[AsyncEventListener]NatsSubscription +type asyncEventsNatsSubscriptions map[string]map[AsyncEventListener]nats.Subscription type asyncEventsNats struct { mu sync.Mutex - client NatsClient + client nats.Client logger log.Logger // +checklocksignore // +checklocks:mu @@ -77,7 +76,7 @@ type asyncEventsNats struct { sessionSubscriptions asyncEventsNatsSubscriptions } -func NewAsyncEventsNats(logger log.Logger, client NatsClient) (AsyncEvents, error) { +func NewAsyncEventsNats(logger log.Logger, client nats.Client) (AsyncEvents, error) { events := &asyncEventsNats{ client: client, logger: logger, @@ -91,28 +90,29 @@ func NewAsyncEventsNats(logger log.Logger, client NatsClient) (AsyncEvents, erro } func (e *asyncEventsNats) GetServerInfoNats() *BackendServerInfoNats { - var nats *BackendServerInfoNats + // TODO: This should call a method on "e.client" directly instead of having a type switch. + var result *BackendServerInfoNats switch n := e.client.(type) { - case *natsClient: - nats = &BackendServerInfoNats{ - Urls: n.conn.Servers(), + case *nats.NativeClient: + result = &BackendServerInfoNats{ + Urls: n.URLs(), } - if c := n.conn; c.IsConnected() { - nats.Connected = true - nats.ServerUrl = c.ConnectedUrl() - nats.ServerID = c.ConnectedServerId() - nats.ServerVersion = c.ConnectedServerVersion() - nats.ClusterName = c.ConnectedClusterName() + if n.IsConnected() { + result.Connected = true + result.ServerUrl = n.ConnectedUrl() + result.ServerID = n.ConnectedServerId() + result.ServerVersion = n.ConnectedServerVersion() + result.ClusterName = n.ConnectedClusterName() } - case *LoopbackNatsClient: - nats = &BackendServerInfoNats{ - Urls: []string{NatsLoopbackUrl}, + case *nats.LoopbackClient: + result = &BackendServerInfoNats{ + Urls: []string{nats.LoopbackUrl}, Connected: true, - ServerUrl: NatsLoopbackUrl, + ServerUrl: nats.LoopbackUrl, } } - return nats + return result } func closeSubscriptions(logger log.Logger, wg *sync.WaitGroup, subscriptions asyncEventsNatsSubscriptions) { @@ -153,7 +153,7 @@ func (e *asyncEventsNats) Close(ctx context.Context) error { func (e *asyncEventsNats) registerListener(key string, subscriptions asyncEventsNatsSubscriptions, listener AsyncEventListener) error { subs, found := subscriptions[key] if !found { - subs = make(map[AsyncEventListener]NatsSubscription) + subs = make(map[AsyncEventListener]nats.Subscription) subscriptions[key] = subs } else if _, found := subs[listener]; found { return ErrAlreadyRegistered diff --git a/async_events_test.go b/async_events_test.go index 107a41d..dead7c0 100644 --- a/async_events_test.go +++ b/async_events_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) var ( @@ -58,7 +59,7 @@ func getAsyncEventsForTest(t *testing.T) AsyncEvents { func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - server, _ := startLocalNatsServer(t) + server, _ := nats.StartLocalServer(t) events, err := NewAsyncEvents(ctx, server.ClientURL()) if err != nil { require.NoError(t, err) @@ -69,7 +70,7 @@ func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - events, err := NewAsyncEvents(ctx, NatsLoopbackUrl) + events, err := NewAsyncEvents(ctx, nats.LoopbackUrl) if err != nil { require.NoError(t, err) } @@ -78,8 +79,8 @@ func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - nats := (events.(*asyncEventsNats)).client - (nats).(*LoopbackNatsClient).waitForSubscriptionsEmpty(ctx, t) + client := (events.(*asyncEventsNats)).client + nats.WaitForSubscriptionsEmpty(ctx, t, client) }) return events } @@ -87,17 +88,17 @@ func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { func waitForAsyncEventsFlushed(ctx context.Context, t *testing.T, events AsyncEvents) { t.Helper() - nats, ok := (events.(*asyncEventsNats)) + e, ok := (events.(*asyncEventsNats)) if !ok { // Only can wait for NATS events. return } - client, ok := nats.client.(*natsClient) + client, ok := e.client.(*nats.NativeClient) if !ok { // The loopback NATS clients is executing all events synchronously. return } - assert.NoError(t, client.conn.FlushWithContext(ctx)) + assert.NoError(t, client.FlushWithContext(ctx)) } diff --git a/backend_server_test.go b/backend_server_test.go index aaddcf5..502f6ab 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -48,6 +48,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) var ( @@ -143,7 +144,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g server2.Close() }) - nats, _ := startLocalNatsServer(t) + nats, _ := nats.StartLocalServer(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -248,7 +249,7 @@ func expectRoomlistEvent(t *testing.T, ch AsyncChannel, msgType string) (*EventS select { case natsMsg := <-ch: var message AsyncMessage - if !assert.NoError(NatsDecode(natsMsg, &message)) || + if !assert.NoError(nats.Decode(natsMsg, &message)) || !assert.Equal("message", message.Type, "invalid message type, got %+v", message) || !assert.NotNil(message.Message, "message missing, got %+v", message) { return nil, false diff --git a/clientsession.go b/clientsession.go index 47c5165..fad2331 100644 --- a/clientsession.go +++ b/clientsession.go @@ -33,12 +33,12 @@ import ( "sync/atomic" "time" - "github.com/nats-io/nats.go" "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) var ( @@ -1070,7 +1070,7 @@ func (s *ClientSession) GetSubscriber(id PublicSessionId, streamType StreamType) func (s *ClientSession) processAsyncNatsMessage(msg *nats.Msg) { var message AsyncMessage - if err := NatsDecode(msg, &message); err != nil { + if err := nats.Decode(msg, &message); err != nil { s.logger.Printf("Could not decode NATS message %+v: %s", msg, err) return } diff --git a/hub_test.go b/hub_test.go index ef1bd84..2fc6f3a 100644 --- a/hub_test.go +++ b/hub_test.go @@ -57,6 +57,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/nats" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -226,10 +227,10 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http server2.Close() }) - nats1, _ := startLocalNatsServer(t) + nats1, _ := nats.StartLocalServer(t) var nats2 *server.Server if strings.Contains(t.Name(), "Federation") { - nats2, _ = startLocalNatsServer(t) + nats2, _ = nats.StartLocalServer(t) } else { nats2 = nats1 } diff --git a/natsclient.go b/nats/client.go similarity index 65% rename from natsclient.go rename to nats/client.go index 070d5e0..12dd75f 100644 --- a/natsclient.go +++ b/nats/client.go @@ -1,6 +1,6 @@ /** * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2017 struktur AG + * Copyright (C) 2025 struktur AG * * @author Joachim Bauch * @@ -19,21 +19,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package nats import ( "context" "encoding/base64" "encoding/json" "errors" - "net/url" "os" "os/signal" "strings" "time" "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -42,17 +40,25 @@ const ( initialConnectInterval = time.Second maxConnectInterval = 8 * time.Second - NatsLoopbackUrl = "nats://loopback" + LoopbackUrl = "nats://loopback" + + DefaultURL = nats.DefaultURL ) -type NatsSubscription interface { +var ( + ErrConnectionClosed = nats.ErrConnectionClosed +) + +type Msg = nats.Msg + +type Subscription interface { Unsubscribe() error } -type NatsClient interface { +type Client interface { Close(ctx context.Context) error - Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) + Subscribe(subject string, ch chan *Msg) (Subscription, error) Publish(subject string, message any) error } @@ -63,21 +69,15 @@ func GetEncodedSubject(prefix string, suffix string) string { return prefix + "." + base64.StdEncoding.EncodeToString([]byte(suffix)) } -type natsClient struct { - logger log.Logger - conn *nats.Conn - closed chan struct{} -} - -func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (NatsClient, error) { +func NewClient(ctx context.Context, url string, options ...nats.Option) (Client, error) { logger := log.LoggerFromContext(ctx) if url == ":loopback:" { - logger.Printf("WARNING: events url %s is deprecated, please use %s instead", url, NatsLoopbackUrl) - url = NatsLoopbackUrl + logger.Printf("WARNING: events url %s is deprecated, please use %s instead", url, LoopbackUrl) + url = LoopbackUrl } - if url == NatsLoopbackUrl { + if url == LoopbackUrl { logger.Println("Using internal NATS loopback client") - return NewLoopbackNatsClient(logger) + return NewLoopbackClient(logger) } backoff, err := async.NewExponentialBackoff(initialConnectInterval, maxConnectInterval) @@ -85,7 +85,7 @@ func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (Nat return nil, err } - client := &natsClient{ + client := &NativeClient{ logger: logger, closed: make(chan struct{}), } @@ -115,47 +115,7 @@ func NewNatsClient(ctx context.Context, url string, options ...nats.Option) (Nat return client, nil } -func (c *natsClient) Close(ctx context.Context) error { - c.conn.Close() - select { - case <-c.closed: - return nil - case <-ctx.Done(): - return ctx.Err() - } -} - -func (c *natsClient) onClosed(conn *nats.Conn) { - if err := conn.LastError(); err != nil { - c.logger.Printf("NATS client closed, last error %s", conn.LastError()) - } else { - c.logger.Println("NATS client closed") - } - close(c.closed) -} - -func (c *natsClient) onDisconnected(conn *nats.Conn) { - c.logger.Println("NATS client disconnected") -} - -func (c *natsClient) onReconnected(conn *nats.Conn) { - c.logger.Printf("NATS client reconnected to %s (%s)", conn.ConnectedUrl(), conn.ConnectedServerId()) -} - -func (c *natsClient) Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) { - return c.conn.ChanSubscribe(subject, ch) -} - -func (c *natsClient) Publish(subject string, message any) error { - data, err := json.Marshal(message) - if err != nil { - return err - } - - return c.conn.Publish(subject, data) -} - -func NatsDecode(msg *nats.Msg, vPtr any) (err error) { +func Decode(msg *nats.Msg, vPtr any) (err error) { switch arg := vPtr.(type) { case *string: // If they want a string and it is a JSON string, strip quotes @@ -174,11 +134,3 @@ func NatsDecode(msg *nats.Msg, vPtr any) (err error) { } return } - -func removeURLCredentials(u string) string { - if u, err := url.Parse(u); err == nil && u.User != nil { - u.User = url.User("***") - return u.String() - } - return u -} diff --git a/nats/client_test.go b/nats/client_test.go new file mode 100644 index 0000000..06894c5 --- /dev/null +++ b/nats/client_test.go @@ -0,0 +1,159 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 nats + +import ( + "testing" + + "github.com/nats-io/nats.go" + "github.com/stretchr/testify/assert" +) + +func TestGetEncodedSubject(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + encoded := GetEncodedSubject("foo", "this is the subject") + assert.NotContains(encoded, " ") + + encoded = GetEncodedSubject("foo", "this-is-the-subject") + assert.NotContains(encoded, "this-is") +} + +func TestDecodeToString(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + testcases := []struct { + data []byte + expected string + }{ + { + []byte(`""`), + "", + }, + { + []byte(`"foo"`), + "foo", + }, + { + []byte(`{"type":"foo"}`), + `{"type":"foo"}`, + }, + { + []byte(`1234`), + "1234", + }, + } + + for idx, tc := range testcases { + var dest string + if assert.NoError(Decode(&nats.Msg{ + Data: tc.data, + }, &dest), "decoding failed for test %d (%s)", idx, string(tc.data)) { + assert.Equal(tc.expected, dest, "failed for test %s (%s)", idx, string(tc.data)) + } + } +} + +func TestDecodeToByteSlice(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + testcases := []struct { + data []byte + expected []byte + }{ + { + []byte(``), + []byte{}, + }, + { + []byte(`""`), + []byte(`""`), + }, + { + []byte(`"foo"`), + []byte(`"foo"`), + }, + { + []byte(`{"type":"foo"}`), + []byte(`{"type":"foo"}`), + }, + { + []byte(`1234`), + []byte(`1234`), + }, + } + + for idx, tc := range testcases { + var dest []byte + if assert.NoError(Decode(&nats.Msg{ + Data: tc.data, + }, &dest), "decoding failed for test %d (%s)", idx, string(tc.data)) { + assert.Equal(tc.expected, dest, "failed for test %s (%s)", idx, string(tc.data)) + } + } +} + +func TestDecodeRegular(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + type testdata struct { + Type string `json:"type"` + Value any `json:"value"` + } + + testcases := []struct { + data []byte + expected *testdata + }{ + { + []byte(`null`), + nil, + }, + { + []byte(`{"value":"bar","type":"foo"}`), + &testdata{ + Type: "foo", + Value: "bar", + }, + }, + { + []byte(`{"value":123,"type":"foo"}`), + &testdata{ + Type: "foo", + Value: float64(123), + }, + }, + } + + for idx, tc := range testcases { + var dest *testdata + if assert.NoError(Decode(&nats.Msg{ + Data: tc.data, + }, &dest), "decoding failed for test %d (%s)", idx, string(tc.data)) { + assert.Equal(tc.expected, dest, "failed for test %s (%s)", idx, string(tc.data)) + } + } +} diff --git a/natsclient_loopback.go b/nats/loopback.go similarity index 73% rename from natsclient_loopback.go rename to nats/loopback.go index bd2deef..dba2955 100644 --- a/natsclient_loopback.go +++ b/nats/loopback.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 nats import ( "container/list" @@ -33,14 +33,14 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" ) -type LoopbackNatsClient struct { +type LoopbackClient struct { logger log.Logger mu sync.Mutex closed chan struct{} // +checklocks:mu - subscriptions map[string]map[*loopbackNatsSubscription]bool + subscriptions map[string]map[*loopbackSubscription]bool // +checklocks:mu wakeup sync.Cond @@ -48,19 +48,19 @@ type LoopbackNatsClient struct { incoming list.List } -func NewLoopbackNatsClient(logger log.Logger) (NatsClient, error) { - client := &LoopbackNatsClient{ +func NewLoopbackClient(logger log.Logger) (Client, error) { + client := &LoopbackClient{ logger: logger, closed: make(chan struct{}), - subscriptions: make(map[string]map[*loopbackNatsSubscription]bool), + subscriptions: make(map[string]map[*loopbackSubscription]bool), } client.wakeup.L = &client.mu go client.processMessages() return client, nil } -func (c *LoopbackNatsClient) processMessages() { +func (c *LoopbackClient) processMessages() { defer close(c.closed) c.mu.Lock() @@ -74,19 +74,19 @@ func (c *LoopbackNatsClient) processMessages() { break } - msg := c.incoming.Remove(c.incoming.Front()).(*nats.Msg) + msg := c.incoming.Remove(c.incoming.Front()).(*Msg) c.processMessage(msg) } } // +checklocks:c.mu -func (c *LoopbackNatsClient) processMessage(msg *nats.Msg) { +func (c *LoopbackClient) processMessage(msg *Msg) { subs, found := c.subscriptions[msg.Subject] if !found { return } - channels := make([]chan *nats.Msg, 0, len(subs)) + channels := make([]chan *Msg, 0, len(subs)) for sub := range subs { channels = append(channels, sub.ch) } @@ -101,7 +101,7 @@ func (c *LoopbackNatsClient) processMessage(msg *nats.Msg) { } } -func (c *LoopbackNatsClient) doClose() { +func (c *LoopbackClient) doClose() { c.mu.Lock() defer c.mu.Unlock() @@ -110,7 +110,7 @@ func (c *LoopbackNatsClient) doClose() { c.wakeup.Signal() } -func (c *LoopbackNatsClient) Close(ctx context.Context) error { +func (c *LoopbackClient) Close(ctx context.Context) error { c.doClose() select { case <-c.closed: @@ -120,19 +120,19 @@ func (c *LoopbackNatsClient) Close(ctx context.Context) error { } } -type loopbackNatsSubscription struct { +type loopbackSubscription struct { subject string - client *LoopbackNatsClient + client *LoopbackClient - ch chan *nats.Msg + ch chan *Msg } -func (s *loopbackNatsSubscription) Unsubscribe() error { +func (s *loopbackSubscription) Unsubscribe() error { s.client.unsubscribe(s) return nil } -func (c *LoopbackNatsClient) Subscribe(subject string, ch chan *nats.Msg) (NatsSubscription, error) { +func (c *LoopbackClient) Subscribe(subject string, ch chan *Msg) (Subscription, error) { if strings.HasSuffix(subject, ".") || strings.Contains(subject, " ") { return nil, nats.ErrBadSubject } @@ -143,14 +143,14 @@ func (c *LoopbackNatsClient) Subscribe(subject string, ch chan *nats.Msg) (NatsS return nil, nats.ErrConnectionClosed } - s := &loopbackNatsSubscription{ + s := &loopbackSubscription{ subject: subject, client: c, ch: ch, } subs, found := c.subscriptions[subject] if !found { - subs = make(map[*loopbackNatsSubscription]bool) + subs = make(map[*loopbackSubscription]bool) c.subscriptions[subject] = subs } subs[s] = true @@ -158,7 +158,7 @@ func (c *LoopbackNatsClient) Subscribe(subject string, ch chan *nats.Msg) (NatsS return s, nil } -func (c *LoopbackNatsClient) unsubscribe(s *loopbackNatsSubscription) { +func (c *LoopbackClient) unsubscribe(s *loopbackSubscription) { c.mu.Lock() defer c.mu.Unlock() @@ -170,7 +170,7 @@ func (c *LoopbackNatsClient) unsubscribe(s *loopbackNatsSubscription) { } } -func (c *LoopbackNatsClient) Publish(subject string, message any) error { +func (c *LoopbackClient) Publish(subject string, message any) error { if strings.HasSuffix(subject, ".") || strings.Contains(subject, " ") { return nats.ErrBadSubject } @@ -181,7 +181,7 @@ func (c *LoopbackNatsClient) Publish(subject string, message any) error { return nats.ErrConnectionClosed } - msg := &nats.Msg{ + msg := &Msg{ Subject: subject, } var err error diff --git a/natsclient_loopback_test.go b/nats/loopback_test.go similarity index 61% rename from natsclient_loopback_test.go rename to nats/loopback_test.go index bfb3bf0..01357f0 100644 --- a/natsclient_loopback_test.go +++ b/nats/loopback_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 nats import ( "context" @@ -32,30 +32,9 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" ) -func (c *LoopbackNatsClient) waitForSubscriptionsEmpty(ctx context.Context, t *testing.T) { - for { - c.mu.Lock() - count := len(c.subscriptions) - c.mu.Unlock() - if count == 0 { - break - } - - select { - case <-ctx.Done(): - c.mu.Lock() - assert.NoError(t, ctx.Err(), "Error waiting for subscriptions %+v to terminate", c.subscriptions) - c.mu.Unlock() - return - default: - time.Sleep(time.Millisecond) - } - } -} - -func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { +func CreateLoopbackClientForTest(t *testing.T) Client { logger := log.NewLoggerForTest(t) - result, err := NewLoopbackNatsClient(logger) + result, err := NewLoopbackClient(logger) require.NoError(t, err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -65,30 +44,30 @@ func CreateLoopbackNatsClientForTest(t *testing.T) NatsClient { return result } -func TestLoopbackNatsClient_Subscribe(t *testing.T) { +func TestLoopbackClient_Subscribe(t *testing.T) { t.Parallel() - client := CreateLoopbackNatsClientForTest(t) - testNatsClient_Subscribe(t, client) + client := CreateLoopbackClientForTest(t) + testClient_Subscribe(t, client) } func TestLoopbackClient_PublishAfterClose(t *testing.T) { t.Parallel() - client := CreateLoopbackNatsClientForTest(t) - testNatsClient_PublishAfterClose(t, client) + client := CreateLoopbackClientForTest(t) + test_PublishAfterClose(t, client) } func TestLoopbackClient_SubscribeAfterClose(t *testing.T) { t.Parallel() - client := CreateLoopbackNatsClientForTest(t) - testNatsClient_SubscribeAfterClose(t, client) + client := CreateLoopbackClientForTest(t) + testClient_SubscribeAfterClose(t, client) } func TestLoopbackClient_BadSubjects(t *testing.T) { t.Parallel() - client := CreateLoopbackNatsClientForTest(t) - testNatsClient_BadSubjects(t, client) + client := CreateLoopbackClientForTest(t) + testClient_BadSubjects(t, client) } diff --git a/nats/native.go b/nats/native.go new file mode 100644 index 0000000..fdf36a7 --- /dev/null +++ b/nats/native.go @@ -0,0 +1,114 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2017 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 nats + +import ( + "context" + "encoding/json" + "net/url" + + "github.com/nats-io/nats.go" + + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +type NativeClient struct { + logger log.Logger + conn *nats.Conn + closed chan struct{} +} + +func (c *NativeClient) URLs() []string { + return c.conn.Servers() +} + +func (c *NativeClient) IsConnected() bool { + return c.conn.IsConnected() +} + +func (c *NativeClient) ConnectedUrl() string { + return c.conn.ConnectedUrl() +} + +func (c *NativeClient) ConnectedServerId() string { + return c.conn.ConnectedServerId() +} + +func (c *NativeClient) ConnectedServerVersion() string { + return c.conn.ConnectedServerVersion() +} + +func (c *NativeClient) ConnectedClusterName() string { + return c.conn.ConnectedClusterName() +} + +func (c *NativeClient) Close(ctx context.Context) error { + c.conn.Close() + select { + case <-c.closed: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (c *NativeClient) FlushWithContext(ctx context.Context) error { + return c.conn.FlushWithContext(ctx) +} + +func (c *NativeClient) onClosed(conn *nats.Conn) { + if err := conn.LastError(); err != nil { + c.logger.Printf("NATS client closed, last error %s", conn.LastError()) + } else { + c.logger.Println("NATS client closed") + } + close(c.closed) +} + +func (c *NativeClient) onDisconnected(conn *nats.Conn) { + c.logger.Println("NATS client disconnected") +} + +func (c *NativeClient) onReconnected(conn *nats.Conn) { + c.logger.Printf("NATS client reconnected to %s (%s)", conn.ConnectedUrl(), conn.ConnectedServerId()) +} + +func (c *NativeClient) Subscribe(subject string, ch chan *Msg) (Subscription, error) { + return c.conn.ChanSubscribe(subject, ch) +} + +func (c *NativeClient) Publish(subject string, message any) error { + data, err := json.Marshal(message) + if err != nil { + return err + } + + return c.conn.Publish(subject, data) +} + +func removeURLCredentials(u string) string { + if u, err := url.Parse(u); err == nil && u.User != nil { + u.User = url.User("***") + return u.String() + } + return u +} diff --git a/natsclient_test.go b/nats/native_test.go similarity index 67% rename from natsclient_test.go rename to nats/native_test.go index e4ae1ae..fe7a56c 100644 --- a/natsclient_test.go +++ b/nats/native_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 nats import ( "context" @@ -27,41 +27,22 @@ import ( "testing" "time" - "github.com/nats-io/nats.go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/nats-io/nats-server/v2/server" - natsserver "github.com/nats-io/nats-server/v2/test" + "github.com/nats-io/nats.go" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) -func startLocalNatsServer(t *testing.T) (*server.Server, int) { +func CreateLocalClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, Client) { t.Helper() - return startLocalNatsServerPort(t, server.RANDOM_PORT) -} - -func startLocalNatsServerPort(t *testing.T, port int) (*server.Server, int) { - t.Helper() - opts := natsserver.DefaultTestOptions - opts.Port = port - opts.Cluster.Name = "testing" - srv := natsserver.RunServer(&opts) - t.Cleanup(func() { - srv.Shutdown() - srv.WaitForShutdown() - }) - return srv, opts.Port -} - -func CreateLocalNatsClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, NatsClient) { - t.Helper() - server, port := startLocalNatsServer(t) + server, port := StartLocalServer(t) logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - result, err := NewNatsClient(ctx, server.ClientURL(), options...) + result, err := NewClient(ctx, server.ClientURL(), options...) require.NoError(t, err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -71,10 +52,10 @@ func CreateLocalNatsClientForTest(t *testing.T, options ...nats.Option) (*server return server, port, result } -func testNatsClient_Subscribe(t *testing.T, client NatsClient) { +func testClient_Subscribe(t *testing.T, client Client) { require := require.New(t) assert := assert.New(t) - dest := make(chan *nats.Msg) + dest := make(chan *Msg) sub, err := client.Subscribe("foo", dest) require.NoError(err) ch := make(chan struct{}) @@ -113,76 +94,76 @@ func testNatsClient_Subscribe(t *testing.T, client NatsClient) { require.Equal(maxPublish, received.Load(), "Received wrong # of messages") } -func TestNatsClient_Subscribe(t *testing.T) { // nolint:paralleltest +func TestClient_Subscribe(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - _, _, client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalClientForTest(t) - testNatsClient_Subscribe(t, client) + testClient_Subscribe(t, client) }) } -func testNatsClient_PublishAfterClose(t *testing.T, client NatsClient) { +func test_PublishAfterClose(t *testing.T, client Client) { assert.NoError(t, client.Close(t.Context())) assert.ErrorIs(t, client.Publish("foo", "bar"), nats.ErrConnectionClosed) } -func TestNatsClient_PublishAfterClose(t *testing.T) { // nolint:paralleltest +func TestClient_PublishAfterClose(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - _, _, client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalClientForTest(t) - testNatsClient_PublishAfterClose(t, client) + test_PublishAfterClose(t, client) }) } -func testNatsClient_SubscribeAfterClose(t *testing.T, client NatsClient) { +func testClient_SubscribeAfterClose(t *testing.T, client Client) { assert.NoError(t, client.Close(t.Context())) - ch := make(chan *nats.Msg) + ch := make(chan *Msg) _, err := client.Subscribe("foo", ch) assert.ErrorIs(t, err, nats.ErrConnectionClosed) } -func TestNatsClient_SubscribeAfterClose(t *testing.T) { // nolint:paralleltest +func TestClient_SubscribeAfterClose(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - _, _, client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalClientForTest(t) - testNatsClient_SubscribeAfterClose(t, client) + testClient_SubscribeAfterClose(t, client) }) } -func testNatsClient_BadSubjects(t *testing.T, client NatsClient) { +func testClient_BadSubjects(t *testing.T, client Client) { assert := assert.New(t) subjects := []string{ "foo bar", "foo.", } - ch := make(chan *nats.Msg) + ch := make(chan *Msg) for _, s := range subjects { _, err := client.Subscribe(s, ch) assert.ErrorIs(err, nats.ErrBadSubject, "Expected error for subject %s", s) } } -func TestNatsClient_BadSubjects(t *testing.T) { // nolint:paralleltest +func TestClient_BadSubjects(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - _, _, client := CreateLocalNatsClientForTest(t) + _, _, client := CreateLocalClientForTest(t) - testNatsClient_BadSubjects(t, client) + testClient_BadSubjects(t, client) }) } -func TestNatsClient_MaxReconnects(t *testing.T) { // nolint:paralleltest +func TestClient_MaxReconnects(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) reconnectWait := time.Millisecond - server, port, client := CreateLocalNatsClientForTest(t, + server, port, client := CreateLocalClientForTest(t, nats.ReconnectWait(reconnectWait), nats.ReconnectJitter(0, 0), ) - c, ok := client.(*natsClient) + c, ok := client.(*NativeClient) require.True(ok, "wrong class: %T", client) require.True(c.conn.IsConnected(), "not connected initially") assert.Equal(server.ID(), c.conn.ConnectedServerId()) @@ -197,7 +178,7 @@ func TestNatsClient_MaxReconnects(t *testing.T) { // nolint:paralleltest } require.False(c.conn.IsConnected(), "should be disconnected after server shutdown") - server, _ = startLocalNatsServerPort(t, port) + server, _ = StartLocalServerPort(t, port) // Wait for automatic reconnection for i := 0; i < 1000 && !c.conn.IsConnected(); i++ { diff --git a/nats/test_helpers.go b/nats/test_helpers.go new file mode 100644 index 0000000..3dc7d9f --- /dev/null +++ b/nats/test_helpers.go @@ -0,0 +1,74 @@ +/** + * 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 nats + +import ( + "context" + "testing" + "time" + + "github.com/nats-io/nats-server/v2/server" + "github.com/nats-io/nats-server/v2/test" + "github.com/stretchr/testify/assert" +) + +func StartLocalServer(t *testing.T) (*server.Server, int) { + t.Helper() + return StartLocalServerPort(t, server.RANDOM_PORT) +} + +func StartLocalServerPort(t *testing.T, port int) (*server.Server, int) { + t.Helper() + opts := test.DefaultTestOptions + opts.Port = port + opts.Cluster.Name = "testing" + srv := test.RunServer(&opts) + t.Cleanup(func() { + srv.Shutdown() + srv.WaitForShutdown() + }) + return srv, opts.Port +} + +func WaitForSubscriptionsEmpty(ctx context.Context, t *testing.T, client Client) { + t.Helper() + if c, ok := client.(*LoopbackClient); assert.True(t, ok, "expected LoopbackNatsClient, got %T", client) { + for { + c.mu.Lock() + count := len(c.subscriptions) + c.mu.Unlock() + if count == 0 { + break + } + + select { + case <-ctx.Done(): + c.mu.Lock() + assert.NoError(t, ctx.Err(), "Error waiting for subscriptions %+v to terminate", c.subscriptions) + c.mu.Unlock() + return + default: + time.Sleep(time.Millisecond) + } + } + } +} diff --git a/room.go b/room.go index 5688316..218d42e 100644 --- a/room.go +++ b/room.go @@ -33,12 +33,12 @@ import ( "sync" "time" - "github.com/nats-io/nats.go" "github.com/prometheus/client_golang/prometheus" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) const ( @@ -235,7 +235,7 @@ func (r *Room) Close() []Session { func (r *Room) processAsyncNatsMessage(msg *nats.Msg) { var message AsyncMessage - if err := NatsDecode(msg, &message); err != nil { + if err := nats.Decode(msg, &message); err != nil { r.logger.Printf("Could not decode NATS message %+v: %s", msg, err) return } diff --git a/server/main.go b/server/main.go index 02c8835..4f2b7a4 100644 --- a/server/main.go +++ b/server/main.go @@ -40,11 +40,11 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" - "github.com/nats-io/nats.go" signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) var ( diff --git a/virtualsession.go b/virtualsession.go index 536655a..112c441 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -28,9 +28,9 @@ import ( "net/url" "sync/atomic" - "github.com/nats-io/nats.go" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) const ( @@ -310,7 +310,7 @@ func (s *VirtualSession) Options() *AddSessionOptions { func (s *VirtualSession) processAsyncNatsMessage(msg *nats.Msg) { var message AsyncMessage - if err := NatsDecode(msg, &message); err != nil { + if err := nats.Decode(msg, &message); err != nil { s.logger.Printf("Could not decode NATS message %+v: %s", msg, err) return } From 25e040ffb9154d095edc4d14ef2c83daa8947010 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 20:52:49 +0100 Subject: [PATCH 409/549] Move buffer/httpclient pools to pool package. --- .codecov.yml | 4 ++++ backend_client.go | 8 ++++---- backend_client_test.go | 3 ++- backend_server.go | 3 ++- capabilities.go | 7 ++++--- capabilities_test.go | 3 ++- client.go | 3 ++- buffer_pool.go => pool/buffer_pool.go | 2 +- http_client_pool.go => pool/http_client_pool.go | 6 +++++- .../http_client_pool_stats_prometheus.go | 2 +- http_client_pool_test.go => pool/http_client_pool_test.go | 2 +- 11 files changed, 28 insertions(+), 15 deletions(-) rename buffer_pool.go => pool/buffer_pool.go (98%) rename http_client_pool.go => pool/http_client_pool.go (97%) rename http_client_pool_stats_prometheus.go => pool/http_client_pool_stats_prometheus.go (98%) rename http_client_pool_test.go => pool/http_client_pool_test.go (99%) diff --git a/.codecov.yml b/.codecov.yml index b3f0da5..f59167a 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -50,6 +50,10 @@ component_management: name: nats paths: - nats/** + - component_id: module_pool + name: pool + paths: + - pool/** - component_id: module_proxy name: proxy paths: diff --git a/backend_client.go b/backend_client.go index b521ca6..826e816 100644 --- a/backend_client.go +++ b/backend_client.go @@ -34,10 +34,10 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/pool" ) var ( - ErrNotRedirecting = errors.New("not redirecting to different host") ErrUnsupportedContentType = errors.New("unsupported_content_type") ErrIncompleteResponse = errors.New("incomplete OCS response") @@ -53,9 +53,9 @@ type BackendClient struct { version string backends *BackendConfiguration - pool *HttpClientPool + pool *pool.HttpClientPool capabilities *Capabilities - buffers BufferPool + buffers pool.BufferPool } func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) { @@ -70,7 +70,7 @@ func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurr logger.Println("WARNING: Backend verification is disabled!") } - pool, err := NewHttpClientPool(maxConcurrentRequestsPerHost, skipverify) + pool, err := pool.NewHttpClientPool(maxConcurrentRequestsPerHost, skipverify) if err != nil { return nil, err } diff --git a/backend_client_test.go b/backend_client_test.go index 3f35d5d..3b60930 100644 --- a/backend_client_test.go +++ b/backend_client_test.go @@ -36,6 +36,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/pool" ) func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { @@ -145,7 +146,7 @@ func TestPostOnRedirectDifferentHost(t *testing.T) { err = client.PerformJSONRequest(ctx, u, request, &response) if err != nil { // The redirect to a different host should have failed. - require.ErrorIs(err, ErrNotRedirecting) + require.ErrorIs(err, pool.ErrNotRedirecting) } else { require.Fail("The redirect should have failed") } diff --git a/backend_server.go b/backend_server.go index a6905c2..a149f8d 100644 --- a/backend_server.go +++ b/backend_server.go @@ -53,6 +53,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/pool" ) const ( @@ -83,7 +84,7 @@ type BackendServer struct { statsAllowedIps atomic.Pointer[container.IPList] invalidSecret []byte - buffers BufferPool + buffers pool.BufferPool } func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { diff --git a/capabilities.go b/capabilities.go index c609eb9..784c18e 100644 --- a/capabilities.go +++ b/capabilities.go @@ -35,6 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/pool" ) const ( @@ -249,16 +250,16 @@ type Capabilities struct { getNow func() time.Time version string - pool *HttpClientPool + pool *pool.HttpClientPool // +checklocks:mu entries map[string]*capabilitiesEntry // +checklocks:mu nextInvalidate map[string]time.Time - buffers BufferPool + buffers pool.BufferPool } -func NewCapabilities(version string, pool *HttpClientPool) (*Capabilities, error) { +func NewCapabilities(version string, pool *pool.HttpClientPool) (*Capabilities, error) { result := &Capabilities{ getNow: time.Now, diff --git a/capabilities_test.go b/capabilities_test.go index a53d00f..b3980c9 100644 --- a/capabilities_test.go +++ b/capabilities_test.go @@ -43,12 +43,13 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/pool" ) func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*CapabilitiesResponse, http.ResponseWriter) error) (*url.URL, *Capabilities) { require := require.New(t) assert := assert.New(t) - pool, err := NewHttpClientPool(1, false) + pool, err := pool.NewHttpClientPool(1, false) require.NoError(err) capabilities, err := NewCapabilities("0.0", pool) require.NoError(err) diff --git a/client.go b/client.go index 2f8bf94..ebb5888 100644 --- a/client.go +++ b/client.go @@ -39,6 +39,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/pool" ) const ( @@ -85,7 +86,7 @@ func IsValidCountry(country string) bool { var ( InvalidFormat = NewError("invalid_format", "Invalid data format.") - bufferPool BufferPool + bufferPool pool.BufferPool ) type WritableClientMessage interface { diff --git a/buffer_pool.go b/pool/buffer_pool.go similarity index 98% rename from buffer_pool.go rename to pool/buffer_pool.go index 5e13c0b..8164bc5 100644 --- a/buffer_pool.go +++ b/pool/buffer_pool.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 pool import ( "bytes" diff --git a/http_client_pool.go b/pool/http_client_pool.go similarity index 97% rename from http_client_pool.go rename to pool/http_client_pool.go index 9b2b2f9..55004be 100644 --- a/http_client_pool.go +++ b/pool/http_client_pool.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 pool import ( "context" @@ -32,6 +32,10 @@ import ( "github.com/prometheus/client_golang/prometheus" ) +var ( + ErrNotRedirecting = errors.New("not redirecting to different host") +) + func init() { RegisterHttpClientPoolStats() } diff --git a/http_client_pool_stats_prometheus.go b/pool/http_client_pool_stats_prometheus.go similarity index 98% rename from http_client_pool_stats_prometheus.go rename to pool/http_client_pool_stats_prometheus.go index 13ba363..8d2c3b4 100644 --- a/http_client_pool_stats_prometheus.go +++ b/pool/http_client_pool_stats_prometheus.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 pool import ( "github.com/prometheus/client_golang/prometheus" diff --git a/http_client_pool_test.go b/pool/http_client_pool_test.go similarity index 99% rename from http_client_pool_test.go rename to pool/http_client_pool_test.go index dff3866..1433b5f 100644 --- a/http_client_pool_test.go +++ b/pool/http_client_pool_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 pool import ( "context" From b7f8f839447a4602fcfaea7635f6e5a275bb370d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 21:07:32 +0100 Subject: [PATCH 410/549] 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") +} From cfd508005d5ff35109cc60371e3f79b7ef8a7598 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 21:07:43 +0100 Subject: [PATCH 411/549] Update generated files. --- api_backend_easyjson.go | 662 +++++++++++++--------------------------- talk/ocs_easyjson.go | 261 ++++++++++++++++ 2 files changed, 471 insertions(+), 452 deletions(-) create mode 100644 talk/ocs_easyjson.go diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 5952647..cb244db 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -215,249 +215,7 @@ func (v *RoomSessionData) UnmarshalJSON(data []byte) error { func (v *RoomSessionData) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling1(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *OcsResponse) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "ocs": - if in.IsNull() { - in.Skip() - out.Ocs = nil - } else { - if out.Ocs == nil { - out.Ocs = new(OcsBody) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Ocs).UnmarshalEasyJSON(in) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in OcsResponse) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"ocs\":" - out.RawString(prefix[1:]) - if in.Ocs == nil { - out.RawString("null") - } else { - (*in.Ocs).MarshalEasyJSON(out) - } - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v OcsResponse) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v OcsResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *OcsResponse) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *OcsResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v) -} -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlexer.Lexer, out *OcsMeta) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "status": - if in.IsNull() { - in.Skip() - } else { - out.Status = string(in.String()) - } - case "statuscode": - if in.IsNull() { - in.Skip() - } else { - out.StatusCode = int(in.Int()) - } - case "message": - if in.IsNull() { - in.Skip() - } else { - out.Message = string(in.String()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwriter.Writer, in OcsMeta) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"status\":" - out.RawString(prefix[1:]) - out.String(string(in.Status)) - } - { - const prefix string = ",\"statuscode\":" - out.RawString(prefix) - out.Int(int(in.StatusCode)) - } - { - const prefix string = ",\"message\":" - out.RawString(prefix) - out.String(string(in.Message)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v OcsMeta) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v OcsMeta) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *OcsMeta) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *OcsMeta) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(l, v) -} -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlexer.Lexer, out *OcsBody) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "meta": - if in.IsNull() { - in.Skip() - } else { - (out.Meta).UnmarshalEasyJSON(in) - } - case "data": - if in.IsNull() { - in.Skip() - } else { - if data := in.Raw(); in.Ok() { - in.AddError((out.Data).UnmarshalJSON(data)) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwriter.Writer, in OcsBody) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"meta\":" - out.RawString(prefix[1:]) - (in.Meta).MarshalEasyJSON(out) - } - { - const prefix string = ",\"data\":" - out.RawString(prefix) - out.Raw((in.Data).MarshalJSON()) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v OcsBody) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v OcsBody) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *OcsBody) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *OcsBody) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(l, v) -} -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlexer.Lexer, out *BackendServerRoomResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *BackendServerRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -501,7 +259,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwriter.Writer, in BackendServerRoomResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in BackendServerRoomResponse) { out.RawByte('{') first := true _ = first @@ -521,27 +279,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlexer.Lexer, out *BackendServerRoomRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlexer.Lexer, out *BackendServerRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -717,7 +475,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwriter.Writer, in BackendServerRoomRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwriter.Writer, in BackendServerRoomRequest) { out.RawByte('{') first := true _ = first @@ -787,27 +545,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -849,7 +607,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in BackendServerInfoVideoRoom) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwriter.Writer, in BackendServerInfoVideoRoom) { out.RawByte('{') first := true _ = first @@ -885,27 +643,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoVideoRoom) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoVideoRoom) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoVideoRoom) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoVideoRoom) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1050,7 +808,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in BackendServerInfoSfuProxy) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwriter.Writer, in BackendServerInfoSfuProxy) { out.RawByte('{') first := true _ = first @@ -1124,27 +882,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuProxy) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuProxy) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuProxy) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuProxy) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1260,7 +1018,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in BackendServerInfoSfuJanus) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwriter.Writer, in BackendServerInfoSfuJanus) { out.RawByte('{') first := true _ = first @@ -1320,27 +1078,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuJanus) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuJanus) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuJanus) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuJanus) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendServerInfoSfu) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *BackendServerInfoSfu) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1411,7 +1169,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendServerInfoSfu) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in BackendServerInfoSfu) { out.RawByte('{') first := true _ = first @@ -1445,27 +1203,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfu) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfu) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfu) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfu) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendServerInfoNats) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *BackendServerInfoNats) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1546,7 +1304,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendServerInfoNats) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in BackendServerInfoNats) { out.RawByte('{') first := true _ = first @@ -1597,27 +1355,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoNats) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoNats) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoNats) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoNats) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendServerInfoGrpc) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *BackendServerInfoGrpc) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1665,7 +1423,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendServerInfoGrpc) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in BackendServerInfoGrpc) { out.RawByte('{') first := true _ = first @@ -1695,27 +1453,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoGrpc) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoGrpc) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoGrpc) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoGrpc) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendServerInfoEtcd) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendServerInfoEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1786,7 +1544,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendServerInfoEtcd) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendServerInfoEtcd) { out.RawByte('{') first := true _ = first @@ -1822,27 +1580,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendServerInfoDialout) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendServerInfoDialout) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1923,7 +1681,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendServerInfoDialout) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendServerInfoDialout) { out.RawByte('{') first := true _ = first @@ -1972,27 +1730,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoDialout) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoDialout) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendServerInfo) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendServerInfo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2145,7 +1903,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendServerInfo) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendServerInfo) { out.RawByte('{') first := true _ = first @@ -2219,27 +1977,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2298,7 +2056,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomUpdateRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendRoomUpdateRequest) { out.RawByte('{') first := true _ = first @@ -2333,27 +2091,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomUpdateRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomUpdateRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomTransientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendRoomTransientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2403,7 +2161,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomTransientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendRoomTransientRequest) { out.RawByte('{') first := true _ = first @@ -2439,27 +2197,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomTransientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomTransientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2550,7 +2308,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { out.RawByte('{') first := true _ = first @@ -2603,27 +2361,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2741,7 +2499,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendRoomParticipantsRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomParticipantsRequest) { out.RawByte('{') first := true _ = first @@ -2829,27 +2587,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendRoomMessageRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2881,7 +2639,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendRoomMessageRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomMessageRequest) { out.RawByte('{') first := true _ = first @@ -2897,27 +2655,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendRoomInviteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomInviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3003,7 +2761,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendRoomInviteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomInviteRequest) { out.RawByte('{') first := true _ = first @@ -3057,27 +2815,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendRoomInCallRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendRoomInCallRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3209,7 +2967,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendRoomInCallRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendRoomInCallRequest) { out.RawByte('{') first := true _ = first @@ -3317,27 +3075,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInCallRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInCallRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3450,7 +3208,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendRoomDisinviteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendRoomDisinviteRequest) { out.RawByte('{') first := true _ = first @@ -3523,27 +3281,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3587,7 +3345,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendRoomDialoutResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendRoomDialoutResponse) { out.RawByte('{') first := true _ = first @@ -3613,27 +3371,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3671,7 +3429,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendRoomDialoutRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendRoomDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3691,27 +3449,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendRoomDialoutError) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendRoomDialoutError) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3747,7 +3505,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendRoomDialoutError) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendRoomDialoutError) { out.RawByte('{') first := true _ = first @@ -3767,27 +3525,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutError) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutError) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3838,7 +3596,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendRoomDeleteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendRoomDeleteRequest) { out.RawByte('{') first := true _ = first @@ -3863,27 +3621,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDeleteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDeleteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendPingEntry) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendPingEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3919,7 +3677,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendPingEntry) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendPingEntry) { out.RawByte('{') first := true _ = first @@ -3945,27 +3703,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendPingEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendPingEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendPingEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendPingEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendInformationEtcd) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendInformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4046,7 +3804,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendInformationEtcd) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendInformationEtcd) { out.RawByte('{') first := true _ = first @@ -4106,27 +3864,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendInformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendInformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientSessionResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientSessionResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4162,7 +3920,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientSessionResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientSessionResponse) { out.RawByte('{') first := true _ = first @@ -4182,27 +3940,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientSessionRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientSessionRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4264,7 +4022,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientSessionRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientSessionRequest) { out.RawByte('{') first := true _ = first @@ -4304,27 +4062,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientRoomResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4411,7 +4169,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientRoomResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientRoomResponse) { out.RawByte('{') first := true _ = first @@ -4457,27 +4215,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientRoomRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4549,7 +4307,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientRoomRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientRoomRequest) { out.RawByte('{') first := true _ = first @@ -4599,27 +4357,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientRingResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientRingResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4655,7 +4413,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientRingResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientRingResponse) { out.RawByte('{') first := true _ = first @@ -4675,27 +4433,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRingResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRingResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *BackendClientResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4795,7 +4553,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in BackendClientResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientResponse) { out.RawByte('{') first := true _ = first @@ -4835,27 +4593,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *BackendClientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4941,7 +4699,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in BackendClientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientRequest) { out.RawByte('{') first := true _ = first @@ -4976,27 +4734,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *BackendClientPingRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientPingRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5059,7 +4817,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in BackendClientPingRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientPingRequest) { out.RawByte('{') first := true _ = first @@ -5095,27 +4853,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientPingRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientPingRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *BackendClientAuthResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *BackendClientAuthResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5159,7 +4917,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in BackendClientAuthResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in BackendClientAuthResponse) { out.RawByte('{') first := true _ = first @@ -5184,27 +4942,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *BackendClientAuthRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *BackendClientAuthRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5242,7 +5000,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in BackendClientAuthRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in BackendClientAuthRequest) { out.RawByte('{') first := true _ = first @@ -5262,23 +5020,23 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) } diff --git a/talk/ocs_easyjson.go b/talk/ocs_easyjson.go new file mode 100644 index 0000000..60aa069 --- /dev/null +++ b/talk/ocs_easyjson.go @@ -0,0 +1,261 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package talk + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(in *jlexer.Lexer, out *OcsResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "ocs": + if in.IsNull() { + in.Skip() + out.Ocs = nil + } else { + if out.Ocs == nil { + out.Ocs = new(OcsBody) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Ocs).UnmarshalEasyJSON(in) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(out *jwriter.Writer, in OcsResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ocs\":" + out.RawString(prefix[1:]) + if in.Ocs == nil { + out.RawString("null") + } else { + (*in.Ocs).MarshalEasyJSON(out) + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v OcsResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v OcsResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *OcsResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *OcsResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(l, v) +} +func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(in *jlexer.Lexer, out *OcsMeta) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "status": + if in.IsNull() { + in.Skip() + } else { + out.Status = string(in.String()) + } + case "statuscode": + if in.IsNull() { + in.Skip() + } else { + out.StatusCode = int(in.Int()) + } + case "message": + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(out *jwriter.Writer, in OcsMeta) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + { + const prefix string = ",\"statuscode\":" + out.RawString(prefix) + out.Int(int(in.StatusCode)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + out.String(string(in.Message)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v OcsMeta) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v OcsMeta) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *OcsMeta) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *OcsMeta) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(l, v) +} +func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(in *jlexer.Lexer, out *OcsBody) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "meta": + if in.IsNull() { + in.Skip() + } else { + (out.Meta).UnmarshalEasyJSON(in) + } + case "data": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(out *jwriter.Writer, in OcsBody) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"meta\":" + out.RawString(prefix[1:]) + (in.Meta).MarshalEasyJSON(out) + } + { + const prefix string = ",\"data\":" + out.RawString(prefix) + out.Raw((in.Data).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v OcsBody) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v OcsBody) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *OcsBody) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *OcsBody) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(l, v) +} From 00796dd8add4115235af900e1d6aec46879071ca Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 21:30:48 +0100 Subject: [PATCH 412/549] Move crypto helper functions to internal package. --- etcd_client_test.go | 7 +- grpc_client_test.go | 18 ++--- grpc_common_test.go | 86 ---------------------- grpc_server_test.go | 52 +++++++------- internal/crypto_helpers.go | 124 ++++++++++++++++++++++++++++++++ internal/crypto_helpers_test.go | 119 ++++++++++++++++++++++++++++++ mcu_proxy_test.go | 5 +- 7 files changed, 285 insertions(+), 126 deletions(-) create mode 100644 internal/crypto_helpers.go create mode 100644 internal/crypto_helpers_test.go diff --git a/etcd_client_test.go b/etcd_client_test.go index 251a8d0..dcda596 100644 --- a/etcd_client_test.go +++ b/etcd_client_test.go @@ -46,6 +46,7 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -95,13 +96,13 @@ func NewEtcdForTestWithTls(t *testing.T, withTLS bool) (*embed.Etcd, string, str key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) keyfile = path.Join(tmpdir, "etcd.key") - require.NoError(WritePrivateKey(key, keyfile)) + require.NoError(internal.WritePrivateKey(key, keyfile)) cfg.ClientTLSInfo.KeyFile = keyfile cfg.PeerTLSInfo.KeyFile = keyfile - cert := GenerateSelfSignedCertificateForTesting(t, 1024, "etcd", key) + cert := internal.GenerateSelfSignedCertificateForTesting(t, "etcd", key) certfile = path.Join(tmpdir, "etcd.pem") - require.NoError(os.WriteFile(certfile, cert, 0755)) + require.NoError(internal.WriteCertificate(cert, certfile)) cfg.ClientTLSInfo.CertFile = certfile cfg.ClientTLSInfo.TrustedCAFile = certfile cfg.PeerTLSInfo.CertFile = certfile diff --git a/grpc_client_test.go b/grpc_client_test.go index 61c0b15..3885aaf 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -27,7 +27,6 @@ import ( "crypto/rsa" "fmt" "net" - "os" "path" "testing" "time" @@ -37,6 +36,7 @@ import ( "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -315,22 +315,22 @@ func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest clientKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) - serverCert := GenerateSelfSignedCertificateForTesting(t, 1024, "Server cert", serverKey) - clientCert := GenerateSelfSignedCertificateForTesting(t, 1024, "Testing client", clientKey) + serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) + clientCert := internal.GenerateSelfSignedCertificateForTesting(t, "Testing client", clientKey) dir := t.TempDir() serverPrivkeyFile := path.Join(dir, "server-privkey.pem") serverPubkeyFile := path.Join(dir, "server-pubkey.pem") serverCertFile := path.Join(dir, "server-cert.pem") - WritePrivateKey(serverKey, serverPrivkeyFile) // nolint - WritePublicKey(&serverKey.PublicKey, serverPubkeyFile) // nolint - os.WriteFile(serverCertFile, serverCert, 0755) // nolint + require.NoError(internal.WritePrivateKey(serverKey, serverPrivkeyFile)) + require.NoError(internal.WritePublicKey(&serverKey.PublicKey, serverPubkeyFile)) + require.NoError(internal.WriteCertificate(serverCert, serverCertFile)) clientPrivkeyFile := path.Join(dir, "client-privkey.pem") clientPubkeyFile := path.Join(dir, "client-pubkey.pem") clientCertFile := path.Join(dir, "client-cert.pem") - WritePrivateKey(clientKey, clientPrivkeyFile) // nolint - WritePublicKey(&clientKey.PublicKey, clientPubkeyFile) // nolint - os.WriteFile(clientCertFile, clientCert, 0755) // nolint + require.NoError(internal.WritePrivateKey(clientKey, clientPrivkeyFile)) + require.NoError(internal.WritePublicKey(&clientKey.PublicKey, clientPubkeyFile)) + require.NoError(internal.WriteCertificate(clientCert, clientCertFile)) serverConfig := goconf.NewConfigFile() serverConfig.AddOption("grpc", "servercertificate", serverCertFile) diff --git a/grpc_common_test.go b/grpc_common_test.go index fbe1f0e..f2ed944 100644 --- a/grpc_common_test.go +++ b/grpc_common_test.go @@ -23,20 +23,7 @@ package signaling import ( "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "errors" - "io/fs" - "math/big" - "net" - "os" - "testing" - "time" - - "github.com/stretchr/testify/require" ) func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context, counter uint64) error { @@ -54,76 +41,3 @@ func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context, count return c.pool.WaitForReload(ctx, counter) } - -func GenerateSelfSignedCertificateForTesting(t *testing.T, bits int, organization string, key *rsa.PrivateKey) []byte { - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{organization}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 180), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - x509.ExtKeyUsageServerAuth, - }, - BasicConstraintsValid: true, - DNSNames: []string{"localhost"}, - IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, - } - - data, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) - require.NoError(t, err) - - data = pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: data, - }) - return data -} - -func WritePrivateKey(key *rsa.PrivateKey, filename string) error { - data := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(key), - }) - - return os.WriteFile(filename, data, 0600) -} - -func WritePublicKey(key *rsa.PublicKey, filename string) error { - data, err := x509.MarshalPKIXPublicKey(key) - if err != nil { - return err - } - - data = pem.EncodeToMemory(&pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: data, - }) - - return os.WriteFile(filename, data, 0755) -} - -func replaceFile(t *testing.T, filename string, data []byte, perm fs.FileMode) { - t.Helper() - require := require.New(t) - oldStat, err := os.Stat(filename) - require.NoError(err, "can't stat old file %s", filename) - - for { - require.NoError(os.WriteFile(filename, data, perm), "can't write file %s", filename) - - newStat, err := os.Stat(filename) - require.NoError(err, "can't stat new file %s", filename) - - // We need different modification times. - if !newStat.ModTime().Equal(oldStat.ModTime()) { - break - } - - time.Sleep(time.Millisecond) - } -} diff --git a/grpc_server_test.go b/grpc_server_test.go index 125107d..a7051d6 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -30,7 +30,6 @@ import ( "encoding/pem" "errors" "net" - "os" "path" "strconv" "testing" @@ -42,6 +41,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -107,15 +107,15 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { require.NoError(err) org1 := "Testing certificate" - cert1 := GenerateSelfSignedCertificateForTesting(t, 1024, org1, key) + cert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, key) dir := t.TempDir() privkeyFile := path.Join(dir, "privkey.pem") pubkeyFile := path.Join(dir, "pubkey.pem") certFile := path.Join(dir, "cert.pem") - WritePrivateKey(key, privkeyFile) // nolint - WritePublicKey(&key.PublicKey, pubkeyFile) // nolint - os.WriteFile(certFile, cert1, 0755) // nolint + require.NoError(internal.WritePrivateKey(key, privkeyFile)) + require.NoError(internal.WritePublicKey(&key.PublicKey, pubkeyFile)) + require.NoError(internal.WriteCertificate(cert1, certFile)) config := goconf.NewConfigFile() config.AddOption("grpc", "servercertificate", certFile) @@ -124,9 +124,7 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { server, addr := NewGrpcServerForTestWithConfig(t, config) cp1 := x509.NewCertPool() - if !cp1.AppendCertsFromPEM(cert1) { - require.Fail("could not add certificate") - } + cp1.AddCert(cert1) cfg1 := &tls.Config{ RootCAs: cp1, @@ -142,8 +140,8 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { } org2 := "Updated certificate" - cert2 := GenerateSelfSignedCertificateForTesting(t, 1024, org2, key) - replaceFile(t, certFile, cert2, 0755) + cert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, key) + internal.ReplaceCertificate(t, certFile, cert2) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -151,9 +149,7 @@ func Test_GrpcServer_ReloadCerts(t *testing.T) { require.NoError(server.WaitForCertificateReload(ctx, 0)) cp2 := x509.NewCertPool() - if !cp2.AppendCertsFromPEM(cert2) { - require.Fail("could not add certificate") - } + cp2.AddCert(cert2) cfg2 := &tls.Config{ RootCAs: cp2, @@ -178,19 +174,19 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { clientKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) - serverCert := GenerateSelfSignedCertificateForTesting(t, 1024, "Server cert", serverKey) + serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) org1 := "Testing client" - clientCert1 := GenerateSelfSignedCertificateForTesting(t, 1024, org1, clientKey) + clientCert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, clientKey) dir := t.TempDir() privkeyFile := path.Join(dir, "privkey.pem") pubkeyFile := path.Join(dir, "pubkey.pem") certFile := path.Join(dir, "cert.pem") caFile := path.Join(dir, "ca.pem") - WritePrivateKey(serverKey, privkeyFile) // nolint - WritePublicKey(&serverKey.PublicKey, pubkeyFile) // nolint - os.WriteFile(certFile, serverCert, 0755) // nolint - os.WriteFile(caFile, clientCert1, 0755) // nolint + require.NoError(internal.WritePrivateKey(serverKey, privkeyFile)) + require.NoError(internal.WritePublicKey(&serverKey.PublicKey, pubkeyFile)) + require.NoError(internal.WriteCertificate(serverCert, certFile)) + require.NoError(internal.WriteCertificate(clientCert1, caFile)) config := goconf.NewConfigFile() config.AddOption("grpc", "servercertificate", certFile) @@ -200,11 +196,12 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { server, addr := NewGrpcServerForTestWithConfig(t, config) pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(serverCert) { - require.Fail("could not add certificate") - } + pool.AddCert(serverCert) - pair1, err := tls.X509KeyPair(clientCert1, pem.EncodeToMemory(&pem.Block{ + pair1, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCert1.Raw, + }), pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey), })) @@ -225,12 +222,15 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { require.NoError(err) org2 := "Updated client" - clientCert2 := GenerateSelfSignedCertificateForTesting(t, 1024, org2, clientKey) - replaceFile(t, caFile, clientCert2, 0755) + clientCert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, clientKey) + internal.ReplaceCertificate(t, caFile, clientCert2) require.NoError(server.WaitForCertPoolReload(ctx1, 0)) - pair2, err := tls.X509KeyPair(clientCert2, pem.EncodeToMemory(&pem.Block{ + pair2, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCert2.Raw, + }), pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey), })) diff --git a/internal/crypto_helpers.go b/internal/crypto_helpers.go new file mode 100644 index 0000000..fb4b18c --- /dev/null +++ b/internal/crypto_helpers.go @@ -0,0 +1,124 @@ +/** + * 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 internal + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func GenerateSelfSignedCertificateForTesting(t *testing.T, organization string, key *rsa.PrivateKey) *x509.Certificate { + t.Helper() + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{organization}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 180), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + x509.ExtKeyUsageServerAuth, + }, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + + data, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + require.NoError(t, err) + + cert, err := x509.ParseCertificate(data) + require.NoError(t, err) + + return cert +} + +func WritePrivateKey(key *rsa.PrivateKey, filename string) error { + data := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + }) + + return os.WriteFile(filename, data, 0600) +} + +func WritePublicKey(key *rsa.PublicKey, filename string) error { + data, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return err + } + + data = pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: data, + }) + + return os.WriteFile(filename, data, 0755) +} + +func WriteCertificate(cert *x509.Certificate, filename string) error { + data := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }) + + return os.WriteFile(filename, data, 0755) +} + +func ReplaceCertificate(t *testing.T, filename string, cert *x509.Certificate) { + t.Helper() + require := require.New(t) + oldStat, err := os.Stat(filename) + require.NoError(err, "can't stat old file %s", filename) + + data := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }) + + for { + require.NoError(os.WriteFile(filename, data, 0755), "can't write file %s", filename) + + newStat, err := os.Stat(filename) + require.NoError(err, "can't stat new file %s", filename) + + // We need different modification times. + if !newStat.ModTime().Equal(oldStat.ModTime()) { + break + } + + time.Sleep(time.Millisecond) + } +} diff --git a/internal/crypto_helpers_test.go b/internal/crypto_helpers_test.go new file mode 100644 index 0000000..e52b168 --- /dev/null +++ b/internal/crypto_helpers_test.go @@ -0,0 +1,119 @@ +/** + * 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 internal + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateSelfSignedCertificateForTesting(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + bits := 1024 + key, err := rsa.GenerateKey(rand.Reader, bits) + require.NoError(err) + cert := GenerateSelfSignedCertificateForTesting(t, "Testing", key) + require.NotNil(cert) + + if assert.Len(cert.Subject.Organization, 1) { + assert.Equal("Testing", cert.Subject.Organization[0]) + } + if assert.Len(cert.DNSNames, 1) { + assert.Equal("localhost", cert.DNSNames[0]) + } + if assert.Len(cert.IPAddresses, 1) { + assert.Equal("127.0.0.1", cert.IPAddresses[0].String()) + } + if assert.IsType(&rsa.PublicKey{}, cert.PublicKey) { + pkey := cert.PublicKey.(*rsa.PublicKey) + assert.Equal(bits/8, pkey.Size()) + } +} + +func TestWriteKeys(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + key, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + dir := t.TempDir() + privateFilename := path.Join(dir, "testing.key") + if assert.NoError(WritePrivateKey(key, privateFilename)) { + if data, err := os.ReadFile(privateFilename); assert.NoError(err) { + if block, rest := pem.Decode(data); assert.Equal("RSA PRIVATE KEY", block.Type) && assert.Empty(rest) { + if parsed, err := x509.ParsePKCS1PrivateKey(block.Bytes); assert.NoError(err) { + assert.True(key.Equal(parsed), "keys should be equal") + } + } + } + } + + publicFilename := path.Join(dir, "testing.pem") + if assert.NoError(WritePublicKey(&key.PublicKey, publicFilename)) { + if data, err := os.ReadFile(publicFilename); assert.NoError(err) { + if block, rest := pem.Decode(data); assert.Equal("RSA PUBLIC KEY", block.Type) && assert.Empty(rest) { + if parsed, err := x509.ParsePKIXPublicKey(block.Bytes); assert.NoError(err) { + assert.True(key.PublicKey.Equal(parsed), "keys should be equal") + } + } + } + } +} + +func TestReplaceCertificate(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + bits := 1024 + key, err := rsa.GenerateKey(rand.Reader, bits) + require.NoError(err) + cert1 := GenerateSelfSignedCertificateForTesting(t, "Testing", key) + require.NotNil(cert1) + + dir := t.TempDir() + filename := path.Join(dir, "testing.crt") + require.NoError(WriteCertificate(cert1, filename)) + stat1, err := os.Stat(filename) + require.NoError(err) + + cert2 := GenerateSelfSignedCertificateForTesting(t, "Testing", key) + require.NotNil(cert2) + ReplaceCertificate(t, filename, cert2) + stat2, err := os.Stat(filename) + require.NoError(err) + + assert.NotEqual(stat1.ModTime(), stat2.ModTime()) +} diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 8d738ed..73174df 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -49,6 +49,7 @@ import ( "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -860,8 +861,8 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i dir := t.TempDir() privkeyFile := path.Join(dir, "privkey.pem") pubkeyFile := path.Join(dir, "pubkey.pem") - WritePrivateKey(tokenKey, privkeyFile) // nolint - WritePublicKey(&tokenKey.PublicKey, pubkeyFile) // nolint + require.NoError(internal.WritePrivateKey(tokenKey, privkeyFile)) + require.NoError(internal.WritePublicKey(&tokenKey.PublicKey, pubkeyFile)) cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "urltype", "static") From 179498f28b20fd09a138b5440c5e341644a15514 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 16:34:03 +0100 Subject: [PATCH 413/549] Move signaling api types to api package. --- .codecov.yml | 4 + Makefile | 2 +- api_signaling.go => api/signaling.go | 157 ++++---- .../signaling_test.go | 17 +- api_async.go | 14 +- api_backend.go | 66 ++-- api_proxy.go | 24 +- async_events.go | 7 +- async_events_nats.go | 9 +- backend_configuration.go | 8 +- backend_server.go | 87 +++-- backend_server_test.go | 48 +-- client.go | 33 +- client/main.go | 39 +- clientsession.go | 157 ++++---- clientsession_test.go | 37 +- federation.go | 120 +++--- federation_test.go | 153 ++++---- grpc_client.go | 27 +- grpc_remote_client.go | 13 +- grpc_server.go | 19 +- hub.go | 362 +++++++++--------- hub_test.go | 273 ++++++------- mcu_common.go | 18 +- mcu_common_test.go | 4 +- mcu_janus.go | 18 +- mcu_janus_client.go | 2 +- mcu_janus_publisher.go | 26 +- mcu_janus_publisher_test.go | 4 +- mcu_janus_subscriber.go | 14 +- mcu_janus_test.go | 207 +++++----- mcu_proxy.go | 54 +-- mcu_proxy_test.go | 95 ++--- mcu_test.go | 35 +- mock_data_test.go => mock/data.go | 2 +- proxy/proxy_remote.go | 7 +- proxy/proxy_server.go | 50 +-- proxy/proxy_server_test.go | 54 +-- proxy/proxy_session.go | 10 +- proxy/proxy_testclient_test.go | 4 +- remotesession.go | 9 +- room.go | 156 ++++---- roomsessions.go | 8 +- roomsessions_builtin.go | 17 +- roomsessions_test.go | 18 +- session.go | 11 +- sessionid_codec.go | 14 +- sessionid_codec_test.go | 6 +- testclient_test.go | 200 +++++----- testutils_test.go | 4 +- transient_data.go | 20 +- transient_data_test.go | 8 +- virtualsession.go | 61 +-- virtualsession_test.go | 110 +++--- 54 files changed, 1498 insertions(+), 1424 deletions(-) rename api_signaling.go => api/signaling.go (89%) rename api_signaling_test.go => api/signaling_test.go (95%) rename mock_data_test.go => mock/data.go (99%) diff --git a/.codecov.yml b/.codecov.yml index 241b309..48adc51 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -46,6 +46,10 @@ component_management: name: metrics paths: - metrics/** + - component_id: module_mock + name: mock + paths: + - mock/** - component_id: module_nats name: nats paths: diff --git a/Makefile b/Makefile index a203c28..28f78fe 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 talk/ocs.go)) +EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go api/signaling.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_signaling.go b/api/signaling.go similarity index 89% rename from api_signaling.go rename to api/signaling.go index 2993a6b..04a7988 100644 --- a/api_signaling.go +++ b/api/signaling.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 api import ( "encoding/json" @@ -36,7 +36,6 @@ import ( "github.com/pion/ice/v4" "github.com/pion/sdp/v3" - "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) @@ -53,6 +52,9 @@ const ( ) var ( + // InvalidHelloVersion is returned if the version in the "hello" message is not supported. + InvalidHelloVersion = NewError("invalid_hello_version", "The hello version is not supported.") + ErrNoSdp = NewError("no_sdp", "Payload does not contain a SDP.") // +checklocksignore: Global readonly variable. ErrInvalidSdp = NewError("invalid_sdp", "Payload does not contain a valid SDP.") @@ -68,6 +70,10 @@ type PublicSessionId string type RoomSessionId string +const ( + FederatedRoomSessionIdPrefix = "federated|" +) + func (s RoomSessionId) IsFederated() bool { return strings.HasPrefix(string(s), FederatedRoomSessionIdPrefix) } @@ -205,7 +211,11 @@ type ServerMessage struct { Dialout *DialoutInternalClientMessage `json:"dialout,omitempty"` } -func (r *ServerMessage) CloseAfterSend(session Session) bool { +type RoomAware interface { + IsInRoom(id string) bool +} + +func (r *ServerMessage) CloseAfterSend(session RoomAware) bool { if r.Type == "bye" { return true } @@ -214,10 +224,8 @@ func (r *ServerMessage) CloseAfterSend(session Session) bool { if evt := r.Event; evt != nil && evt.Target == "roomlist" && evt.Type == "disinvite" { // Only close session / connection if the disinvite was for the room // the session is currently in. - if session != nil && evt.Disinvite != nil { - if room := session.GetRoom(); room != nil && evt.Disinvite.RoomId == room.Id() { - return true - } + if session != nil && evt.Disinvite != nil && session.IsInRoom(evt.Disinvite.RoomId) { + return true } } } @@ -357,8 +365,8 @@ type ClientTypeInternalAuthParams struct { Random string `json:"random"` Token string `json:"token"` - Backend string `json:"backend"` - parsedBackend *url.URL + Backend string `json:"backend"` + ParsedBackend *url.URL `json:"-"` } func (p *ClientTypeInternalAuthParams) CheckValid() error { @@ -377,7 +385,7 @@ func (p *ClientTypeInternalAuthParams) CheckValid() error { p.Backend = u.String() } - p.parsedBackend = u + p.ParsedBackend = u } return nil } @@ -437,12 +445,12 @@ type HelloClientMessageAuth struct { Params json.RawMessage `json:"params"` - Url string `json:"url"` - parsedUrl *url.URL + Url string `json:"url"` + ParsedUrl *url.URL `json:"-"` - internalParams ClientTypeInternalAuthParams - helloV2Params HelloV2AuthParams - federationParams FederationAuthParams + InternalParams ClientTypeInternalAuthParams `json:"-"` + HelloV2Params HelloV2AuthParams `json:"-"` + FederationParams FederationAuthParams `json:"-"` } // Type "hello" @@ -492,7 +500,7 @@ func (m *HelloClientMessage) CheckValid() error { m.Auth.Url = u.String() } - m.Auth.parsedUrl = u + m.Auth.ParsedUrl = u } switch m.Version { @@ -501,23 +509,23 @@ func (m *HelloClientMessage) CheckValid() error { case HelloVersionV2: switch m.Auth.Type { case HelloClientTypeClient: - if err := json.Unmarshal(m.Auth.Params, &m.Auth.helloV2Params); err != nil { + if err := json.Unmarshal(m.Auth.Params, &m.Auth.HelloV2Params); err != nil { return err - } else if err := m.Auth.helloV2Params.CheckValid(); err != nil { + } 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 { + if err := json.Unmarshal(m.Auth.Params, &m.Auth.FederationParams); err != nil { return err - } else if err := m.Auth.federationParams.CheckValid(); err != nil { + } else if err := m.Auth.FederationParams.CheckValid(); err != nil { return err } } } case HelloClientTypeInternal: - if err := json.Unmarshal(m.Auth.Params, &m.Auth.internalParams); err != nil { + if err := json.Unmarshal(m.Auth.Params, &m.Auth.InternalParams); err != nil { return err - } else if err := m.Auth.internalParams.CheckValid(); err != nil { + } else if err := m.Auth.InternalParams.CheckValid(); err != nil { return err } default: @@ -654,11 +662,11 @@ func (m *RoomClientMessage) CheckValid() error { } type RoomFederationMessage struct { - SignalingUrl string `json:"signaling"` - parsedSignalingUrl *url.URL + SignalingUrl string `json:"signaling"` + ParsedSignalingUrl *url.URL `json:"-"` - NextcloudUrl string `json:"url"` - parsedNextcloudUrl *url.URL + NextcloudUrl string `json:"url"` + ParsedNextcloudUrl *url.URL `json:"-"` RoomId string `json:"roomid,omitempty"` Token string `json:"token"` @@ -675,14 +683,14 @@ func (m *RoomFederationMessage) CheckValid() error { if u, err := url.Parse(m.SignalingUrl); err != nil { return fmt.Errorf("invalid signaling url: %w", err) } else { - m.parsedSignalingUrl = u + 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 + m.ParsedNextcloudUrl = u } if m.Token == "" { return errors.New("token missing") @@ -698,8 +706,8 @@ type RoomServerMessage struct { } type RoomBandwidth struct { - MaxStreamBitrate api.Bandwidth `json:"maxstreambitrate"` - MaxScreenBitrate api.Bandwidth `json:"maxscreenbitrate"` + MaxStreamBitrate Bandwidth `json:"maxstreambitrate"` + MaxScreenBitrate Bandwidth `json:"maxscreenbitrate"` } type RoomErrorDetails struct { @@ -732,21 +740,21 @@ type MessageClientMessageData struct { json.Marshaler json.Unmarshaler - Type string `json:"type"` - Sid string `json:"sid"` - RoomType string `json:"roomType"` - Payload api.StringMap `json:"payload"` + Type string `json:"type"` + Sid string `json:"sid"` + RoomType string `json:"roomType"` + Payload StringMap `json:"payload"` // Only supported if Type == "offer" - Bitrate api.Bandwidth `json:"bitrate,omitempty"` - AudioCodec string `json:"audiocodec,omitempty"` - VideoCodec string `json:"videocodec,omitempty"` - VP9Profile string `json:"vp9profile,omitempty"` - H264Profile string `json:"h264profile,omitempty"` + Bitrate Bandwidth `json:"bitrate,omitempty"` + AudioCodec string `json:"audiocodec,omitempty"` + VideoCodec string `json:"videocodec,omitempty"` + VP9Profile string `json:"vp9profile,omitempty"` + H264Profile string `json:"h264profile,omitempty"` - offerSdp *sdp.SessionDescription // Only set if Type == "offer" - answerSdp *sdp.SessionDescription // Only set if Type == "answer" - candidate ice.Candidate // Only set if Type == "candidate" + OfferSdp *sdp.SessionDescription `json:"-"` // Only set if Type == "offer" + AnswerSdp *sdp.SessionDescription `json:"-"` // Only set if Type == "answer" + Candidate ice.Candidate `json:"-"` // Only set if Type == "candidate" } func (m *MessageClientMessageData) String() string { @@ -757,10 +765,10 @@ func (m *MessageClientMessageData) String() string { return string(data) } -func parseSDP(s string) (*sdp.SessionDescription, error) { +func ParseSDP(s string) (*sdp.SessionDescription, error) { var sdp sdp.SessionDescription if err := sdp.UnmarshalString(s); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", api.StringMap{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing SDP from payload.", StringMap{ "error": err.Error(), }) } @@ -772,7 +780,7 @@ func parseSDP(s string) (*sdp.SessionDescription, error) { } if _, err := ice.UnmarshalCandidate(a.Value); err != nil { - return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", api.StringMap{ + return nil, NewErrorDetail("invalid_sdp", "Error parsing candidate from media description.", StringMap{ "media": m.MediaName.Media, "idx": idx, "error": err.Error(), @@ -788,52 +796,66 @@ var ( emptyCandidate = &ice.CandidateHost{} ) +// TODO: Use shared method from "mcu_common.go". +func isValidStreamType(s string) bool { + switch s { + case "audio": + fallthrough + case "video": + fallthrough + case "screen": + return true + default: + return false + } +} + func (m *MessageClientMessageData) CheckValid() error { - if m.RoomType != "" && !IsValidStreamType(m.RoomType) { + if m.RoomType != "" && !isValidStreamType(m.RoomType) { return fmt.Errorf("invalid room type: %s", m.RoomType) } switch m.Type { case "offer", "answer": - sdpText, ok := api.GetStringMapEntry[string](m.Payload, "sdp") + sdpText, ok := GetStringMapEntry[string](m.Payload, "sdp") if !ok { return ErrInvalidSdp } - sdp, err := parseSDP(sdpText) + sdp, err := ParseSDP(sdpText) if err != nil { return err } switch m.Type { case "offer": - m.offerSdp = sdp + m.OfferSdp = sdp case "answer": - m.answerSdp = sdp + m.AnswerSdp = sdp } case "candidate": candValue, found := m.Payload["candidate"] if !found { return ErrNoCandidate } - candItem, ok := api.ConvertStringMap(candValue) + candItem, ok := ConvertStringMap(candValue) if !ok { return ErrInvalidCandidate } - candText, ok := api.GetStringMapEntry[string](candItem, "candidate") + candText, ok := GetStringMapEntry[string](candItem, "candidate") if !ok { return ErrInvalidCandidate } if candText == "" { - m.candidate = emptyCandidate + m.Candidate = emptyCandidate } else { cand, err := ice.UnmarshalCandidate(candText) if err != nil { - return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", api.StringMap{ + return NewErrorDetail("invalid_candidate", "Error parsing candidate from payload.", StringMap{ "error": err.Error(), }) } - m.candidate = cand + m.Candidate = cand } } return nil @@ -1113,11 +1135,18 @@ func (m *InternalClientMessage) CheckValid() error { return nil } +type InternalServerDialoutRequestContents struct { + // E.164 number to dial (e.g. "+1234567890") + Number string `json:"number"` + + Options json.RawMessage `json:"options,omitempty"` +} + type InternalServerDialoutRequest struct { RoomId string `json:"roomid"` Backend string `json:"backend"` - Request *BackendRoomDialoutRequest `json:"request"` + Request *InternalServerDialoutRequestContents `json:"request"` } type InternalServerMessage struct { @@ -1133,8 +1162,8 @@ type RoomEventServerMessage struct { Properties json.RawMessage `json:"properties,omitempty"` // TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk. InCall json.RawMessage `json:"incall,omitempty"` - Changed []api.StringMap `json:"changed,omitempty"` - Users []api.StringMap `json:"users,omitempty"` + Changed []StringMap `json:"changed,omitempty"` + Users []StringMap `json:"users,omitempty"` All bool `json:"all,omitempty"` } @@ -1158,7 +1187,7 @@ type RoomDisinviteEventServerMessage struct { Reason string `json:"reason"` } -type ChatComment api.StringMap +type ChatComment StringMap type RoomEventMessageDataChat struct { // Refresh will be included if the client does not support the "chat-relay" feature. @@ -1260,7 +1289,7 @@ type AnswerOfferMessage struct { From PublicSessionId `json:"from"` Type string `json:"type"` RoomType string `json:"roomType"` - Payload api.StringMap `json:"payload"` + Payload StringMap `json:"payload"` Sid string `json:"sid,omitempty"` } @@ -1292,8 +1321,8 @@ func (m *TransientDataClientMessage) CheckValid() error { type TransientDataServerMessage struct { Type string `json:"type"` - Key string `json:"key,omitempty"` - OldValue any `json:"oldvalue,omitempty"` - Value any `json:"value,omitempty"` - Data api.StringMap `json:"data,omitempty"` + Key string `json:"key,omitempty"` + OldValue any `json:"oldvalue,omitempty"` + Value any `json:"value,omitempty"` + Data StringMap `json:"data,omitempty"` } diff --git a/api_signaling_test.go b/api/signaling_test.go similarity index 95% rename from api_signaling_test.go rename to api/signaling_test.go index f1e0556..e7103dc 100644 --- a/api_signaling_test.go +++ b/api/signaling_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 api import ( "encoding/json" @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/mock" ) type testCheckValid interface { @@ -509,10 +510,10 @@ func TestFilterSDPCandidates(t *testing.T) { require := require.New(t) assert := assert.New(t) - s, err := parseSDP(MockSdpOfferAudioOnly) + s, err := ParseSDP(mock.MockSdpOfferAudioOnly) require.NoError(err) if encoded, err := s.Marshal(); assert.NoError(err) { - assert.Equal(MockSdpOfferAudioOnly, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + assert.Equal(mock.MockSdpOfferAudioOnly, strings.ReplaceAll(string(encoded), "\r\n", "\n")) } expectedBefore := map[string]int{ @@ -549,8 +550,8 @@ func TestFilterSDPCandidates(t *testing.T) { } if encoded, err := s.Marshal(); assert.NoError(err) { - assert.NotEqual(MockSdpOfferAudioOnly, strings.ReplaceAll(string(encoded), "\r\n", "\n")) - assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + assert.NotEqual(mock.MockSdpOfferAudioOnly, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) } } @@ -559,10 +560,10 @@ func TestNoFilterSDPCandidates(t *testing.T) { require := require.New(t) assert := assert.New(t) - s, err := parseSDP(MockSdpOfferAudioOnlyNoFilter) + s, err := ParseSDP(mock.MockSdpOfferAudioOnlyNoFilter) require.NoError(err) if encoded, err := s.Marshal(); assert.NoError(err) { - assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) } expectedBefore := map[string]int{ @@ -599,6 +600,6 @@ func TestNoFilterSDPCandidates(t *testing.T) { } if encoded, err := s.Marshal(); assert.NoError(err) { - assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) + assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n")) } } diff --git a/api_async.go b/api_async.go index 9bcde9c..97a92ab 100644 --- a/api_async.go +++ b/api_async.go @@ -25,6 +25,8 @@ import ( "encoding/json" "fmt" "time" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) type AsyncMessage struct { @@ -32,7 +34,7 @@ type AsyncMessage struct { Type string `json:"type"` - Message *ServerMessage `json:"message,omitempty"` + Message *api.ServerMessage `json:"message,omitempty"` Room *BackendServerRoomRequest `json:"room,omitempty"` @@ -56,12 +58,12 @@ func (m *AsyncMessage) String() string { type AsyncRoomMessage struct { Type string `json:"type"` - SessionId PublicSessionId `json:"sessionid,omitempty"` - ClientType ClientType `json:"clienttype,omitempty"` + SessionId api.PublicSessionId `json:"sessionid,omitempty"` + ClientType api.ClientType `json:"clienttype,omitempty"` } type SendOfferMessage struct { - MessageId string `json:"messageid,omitempty"` - SessionId PublicSessionId `json:"sessionid"` - Data *MessageClientMessageData `json:"data"` + MessageId string `json:"messageid,omitempty"` + SessionId api.PublicSessionId `json:"sessionid"` + Data *api.MessageClientMessageData `json:"data"` } diff --git a/api_backend.go b/api_backend.go index b295d5e..6b061de 100644 --- a/api_backend.go +++ b/api_backend.go @@ -127,8 +127,8 @@ type BackendRoomInviteRequest struct { } type BackendRoomDisinviteRequest struct { - UserIds []string `json:"userids,omitempty"` - SessionIds []RoomSessionId `json:"sessionids,omitempty"` + UserIds []string `json:"userids,omitempty"` + SessionIds []api.RoomSessionId `json:"sessionids,omitempty"` // TODO(jojo): We should get rid of "AllUserIds" and find a better way to // notify existing users the room has changed and they need to update it. AllUserIds []string `json:"alluserids,omitempty"` @@ -161,11 +161,11 @@ type BackendRoomMessageRequest struct { Data json.RawMessage `json:"data,omitempty"` } -type BackendRoomSwitchToSessionsList []RoomSessionId -type BackendRoomSwitchToSessionsMap map[RoomSessionId]json.RawMessage +type BackendRoomSwitchToSessionsList []api.RoomSessionId +type BackendRoomSwitchToSessionsMap map[api.RoomSessionId]json.RawMessage -type BackendRoomSwitchToPublicSessionsList []PublicSessionId -type BackendRoomSwitchToPublicSessionsMap map[PublicSessionId]json.RawMessage +type BackendRoomSwitchToPublicSessionsList []api.PublicSessionId +type BackendRoomSwitchToPublicSessionsMap map[api.PublicSessionId]json.RawMessage type BackendRoomSwitchToMessageRequest struct { // Target room id @@ -198,13 +198,13 @@ func isValidNumber(s string) bool { return checkE164Number.MatchString(s) } -func (r *BackendRoomDialoutRequest) ValidateNumber() *Error { +func (r *BackendRoomDialoutRequest) ValidateNumber() *api.Error { if r.Number == "" { - return NewError("number_missing", "No number provided") + return api.NewError("number_missing", "No number provided") } if !isValidNumber(r.Number) { - return NewError("invalid_number", "Expected E.164 number.") + return api.NewError("invalid_number", "Expected E.164 number.") } return nil @@ -246,7 +246,7 @@ type BackendRoomDialoutError struct { type BackendRoomDialoutResponse struct { CallId string `json:"callid,omitempty"` - Error *Error `json:"error,omitempty"` + Error *api.Error `json:"error,omitempty"` } // Requests from the signaling server to the Nextcloud backend. @@ -287,7 +287,7 @@ type BackendClientResponse struct { Type string `json:"type"` - Error *Error `json:"error,omitempty"` + Error *api.Error `json:"error,omitempty"` Auth *BackendClientAuthResponse `json:"auth,omitempty"` @@ -305,11 +305,11 @@ type BackendClientAuthResponse struct { } type BackendClientRoomRequest struct { - Version string `json:"version"` - RoomId string `json:"roomid"` - Action string `json:"action,omitempty"` - UserId string `json:"userid"` - SessionId RoomSessionId `json:"sessionid"` + Version string `json:"version"` + RoomId string `json:"roomid"` + Action string `json:"action,omitempty"` + UserId string `json:"userid"` + SessionId api.RoomSessionId `json:"sessionid"` // For Nextcloud Talk with SIP support and for federated sessions. ActorId string `json:"actorid,omitempty"` @@ -318,7 +318,7 @@ type BackendClientRoomRequest struct { } func (r *BackendClientRoomRequest) UpdateFromSession(s Session) { - if s.ClientType() == HelloClientTypeFederation { + if s.ClientType() == api.HelloClientTypeFederation { // Need to send additional data for requests of federated users. if u, err := s.ParsedUserData(); err == nil && len(u) > 0 { if actorType, found := api.GetStringMapEntry[string](u, "actorType"); found { @@ -331,7 +331,7 @@ func (r *BackendClientRoomRequest) UpdateFromSession(s Session) { } } -func NewBackendClientRoomRequest(roomid string, userid string, sessionid RoomSessionId) *BackendClientRequest { +func NewBackendClientRoomRequest(roomid string, userid string, sessionid api.RoomSessionId) *BackendClientRequest { return &BackendClientRequest{ Type: "room", Room: &BackendClientRoomRequest{ @@ -361,8 +361,8 @@ type RoomSessionData struct { } type BackendPingEntry struct { - UserId string `json:"userid,omitempty"` - SessionId RoomSessionId `json:"sessionid"` + UserId string `json:"userid,omitempty"` + SessionId api.RoomSessionId `json:"sessionid"` } type BackendClientPingRequest struct { @@ -388,12 +388,12 @@ type BackendClientRingResponse struct { } type BackendClientSessionRequest struct { - Version string `json:"version"` - RoomId string `json:"roomid"` - Action string `json:"action"` - SessionId PublicSessionId `json:"sessionid"` - UserId string `json:"userid,omitempty"` - User json.RawMessage `json:"user,omitempty"` + Version string `json:"version"` + RoomId string `json:"roomid"` + Action string `json:"action"` + SessionId api.PublicSessionId `json:"sessionid"` + UserId string `json:"userid,omitempty"` + User json.RawMessage `json:"user,omitempty"` } type BackendClientSessionResponse struct { @@ -401,7 +401,7 @@ type BackendClientSessionResponse struct { RoomId string `json:"roomid"` } -func NewBackendClientSessionRequest(roomid string, action string, sessionid PublicSessionId, msg *AddSessionInternalClientMessage) *BackendClientRequest { +func NewBackendClientSessionRequest(roomid string, action string, sessionid api.PublicSessionId, msg *api.AddSessionInternalClientMessage) *BackendClientRequest { request := &BackendClientRequest{ Type: "session", Session: &BackendClientSessionRequest{ @@ -548,12 +548,12 @@ type BackendServerInfoSfu struct { } type BackendServerInfoDialout struct { - SessionId PublicSessionId `json:"sessionid"` - Connected bool `json:"connected"` - Address string `json:"address,omitempty"` - UserAgent string `json:"useragent,omitempty"` - Version string `json:"version,omitempty"` - Features []string `json:"features,omitempty"` + SessionId api.PublicSessionId `json:"sessionid"` + Connected bool `json:"connected"` + Address string `json:"address,omitempty"` + UserAgent string `json:"useragent,omitempty"` + Version string `json:"version,omitempty"` + Features []string `json:"features,omitempty"` } type BackendServerInfoNats struct { diff --git a/api_proxy.go b/api_proxy.go index 68efa0e..b551cee 100644 --- a/api_proxy.go +++ b/api_proxy.go @@ -93,7 +93,7 @@ func (m *ProxyClientMessage) CheckValid() error { return nil } -func (m *ProxyClientMessage) NewErrorServerMessage(e *Error) *ProxyServerMessage { +func (m *ProxyClientMessage) NewErrorServerMessage(e *api.Error) *ProxyServerMessage { return &ProxyServerMessage{ Id: m.Id, Type: "error", @@ -102,7 +102,7 @@ func (m *ProxyClientMessage) NewErrorServerMessage(e *Error) *ProxyServerMessage } func (m *ProxyClientMessage) NewWrappedErrorServerMessage(e error) *ProxyServerMessage { - return m.NewErrorServerMessage(NewError("internal_error", e.Error())) + return m.NewErrorServerMessage(api.NewError("internal_error", e.Error())) } // ProxyServerMessage is a message that is sent from the server to a client. @@ -114,7 +114,7 @@ type ProxyServerMessage struct { Type string `json:"type"` - Error *Error `json:"error,omitempty"` + Error *api.Error `json:"error,omitempty"` Hello *HelloProxyServerMessage `json:"hello,omitempty"` @@ -135,7 +135,7 @@ func (r *ProxyServerMessage) String() string { return string(data) } -func (r *ProxyServerMessage) CloseAfterSend(session Session) bool { +func (r *ProxyServerMessage) CloseAfterSend(session api.RoomAware) bool { switch r.Type { case "bye": return true @@ -153,7 +153,7 @@ type TokenClaims struct { type HelloProxyClientMessage struct { Version string `json:"version"` - ResumeId PublicSessionId `json:"resumeid"` + ResumeId api.PublicSessionId `json:"resumeid"` Features []string `json:"features,omitempty"` @@ -162,7 +162,7 @@ type HelloProxyClientMessage struct { } func (m *HelloProxyClientMessage) CheckValid() error { - if m.Version != HelloVersionV1 { + if m.Version != api.HelloVersionV1 { return fmt.Errorf("unsupported hello version: %s", m.Version) } if m.ResumeId == "" { @@ -176,8 +176,8 @@ func (m *HelloProxyClientMessage) CheckValid() error { type HelloProxyServerMessage struct { Version string `json:"version"` - SessionId PublicSessionId `json:"sessionid"` - Server *WelcomeServerMessage `json:"server,omitempty"` + SessionId api.PublicSessionId `json:"sessionid"` + Server *api.WelcomeServerMessage `json:"server,omitempty"` } // Type "bye" @@ -209,10 +209,10 @@ type NewPublisherSettings struct { type CommandProxyClientMessage struct { Type string `json:"type"` - Sid string `json:"sid,omitempty"` - StreamType StreamType `json:"streamType,omitempty"` - PublisherId PublicSessionId `json:"publisherId,omitempty"` - ClientId string `json:"clientId,omitempty"` + Sid string `json:"sid,omitempty"` + StreamType StreamType `json:"streamType,omitempty"` + PublisherId api.PublicSessionId `json:"publisherId,omitempty"` + ClientId string `json:"clientId,omitempty"` // Deprecated: use PublisherSettings instead. Bitrate api.Bandwidth `json:"bitrate,omitempty"` diff --git a/async_events.go b/async_events.go index e86a07c..9699853 100644 --- a/async_events.go +++ b/async_events.go @@ -25,6 +25,7 @@ import ( "context" "errors" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" ) @@ -55,13 +56,13 @@ type AsyncEvents interface { RegisterUserListener(userId string, backend *Backend, listener AsyncEventListener) error UnregisterUserListener(userId string, backend *Backend, listener AsyncEventListener) error - RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error - UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error + RegisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error + UnregisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error PublishBackendRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error PublishRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error PublishUserMessage(userId string, backend *Backend, message *AsyncMessage) error - PublishSessionMessage(sessionId PublicSessionId, backend *Backend, message *AsyncMessage) error + PublishSessionMessage(sessionId api.PublicSessionId, backend *Backend, message *AsyncMessage) error } func NewAsyncEvents(ctx context.Context, url string) (AsyncEvents, error) { diff --git a/async_events_nats.go b/async_events_nats.go index 81edba0..1f363c6 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -27,6 +27,7 @@ import ( "sync" "time" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" ) @@ -55,7 +56,7 @@ func GetSubjectForUserId(userId string, backend *Backend) string { return nats.GetEncodedSubject("user", userId+"|"+backend.Id()) } -func GetSubjectForSessionId(sessionId PublicSessionId, backend *Backend) string { +func GetSubjectForSessionId(sessionId api.PublicSessionId, backend *Backend) string { return string("session." + sessionId) } @@ -242,7 +243,7 @@ func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend return e.unregisterListener(key, e.userSubscriptions, listener) } -func (e *asyncEventsNats) RegisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) RegisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() @@ -251,7 +252,7 @@ func (e *asyncEventsNats) RegisterSessionListener(sessionId PublicSessionId, bac return e.registerListener(key, e.sessionSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterSessionListener(sessionId PublicSessionId, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) UnregisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() @@ -280,7 +281,7 @@ func (e *asyncEventsNats) PublishUserMessage(userId string, backend *Backend, me return e.publish(subject, message) } -func (e *asyncEventsNats) PublishSessionMessage(sessionId PublicSessionId, backend *Backend, message *AsyncMessage) error { +func (e *asyncEventsNats) PublishSessionMessage(sessionId api.PublicSessionId, backend *Backend, message *AsyncMessage) error { subject := GetSubjectForSessionId(sessionId, backend) return e.publish(subject, message) } diff --git a/backend_configuration.go b/backend_configuration.go index a955eac..41a7495 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -44,7 +44,7 @@ const ( ) var ( - SessionLimitExceeded = NewError("session_limit_exceeded", "Too many sessions connected for this backend.") + SessionLimitExceeded = api.NewError("session_limit_exceeded", "Too many sessions connected for this backend.") ) type Backend struct { @@ -60,7 +60,7 @@ type Backend struct { sessionLimit uint64 sessionsLock sync.Mutex // +checklocks:sessionsLock - sessions map[PublicSessionId]bool + sessions map[api.PublicSessionId]bool counted bool } @@ -134,7 +134,7 @@ func (b *Backend) Len() int { } func (b *Backend) AddSession(session Session) error { - if session.ClientType() == HelloClientTypeInternal || session.ClientType() == HelloClientTypeVirtual { + if session.ClientType() == api.HelloClientTypeInternal || session.ClientType() == api.HelloClientTypeVirtual { // Internal and virtual sessions are not counting to the limit. return nil } @@ -147,7 +147,7 @@ func (b *Backend) AddSession(session Session) error { b.sessionsLock.Lock() defer b.sessionsLock.Unlock() if b.sessions == nil { - b.sessions = make(map[PublicSessionId]bool) + b.sessions = make(map[api.PublicSessionId]bool) } else if uint64(len(b.sessions)) >= b.sessionLimit { statsBackendLimitExceededTotal.WithLabelValues(b.id).Inc() return SessionLimitExceeded diff --git a/backend_server.go b/backend_server.go index a149f8d..94530ff 100644 --- a/backend_server.go +++ b/backend_server.go @@ -61,7 +61,7 @@ const ( randomUsernameLength = 32 - sessionIdNotInMeeting = RoomSessionId("0") + sessionIdNotInMeeting = api.RoomSessionId("0") startDialoutTimeout = 45 * time.Second ) @@ -329,12 +329,12 @@ func (b *BackendServer) parseRequestBody(f func(context.Context, http.ResponseWr func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids []string, properties json.RawMessage) { msg := &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "roomlist", Type: "invite", - Invite: &RoomEventServerMessage{ + Invite: &api.RoomEventServerMessage{ RoomId: roomid, Properties: properties, }, @@ -348,16 +348,16 @@ func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids } } -func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reason string, userids []string, sessionids []RoomSessionId) { +func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reason string, userids []string, sessionids []api.RoomSessionId) { msg := &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "roomlist", Type: "disinvite", - Disinvite: &RoomDisinviteEventServerMessage{ - RoomEventServerMessage: RoomEventServerMessage{ + Disinvite: &api.RoomDisinviteEventServerMessage{ + RoomEventServerMessage: api.RoomEventServerMessage{ RoomId: roomid, }, Reason: reason, @@ -383,7 +383,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso } wg.Add(1) - go func(sessionid RoomSessionId) { + go func(sessionid api.RoomSessionId) { defer wg.Done() if sid, err := b.lookupByRoomSessionId(ctx, sessionid, nil); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", sessionid, err) @@ -400,12 +400,12 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified_userids []string, all_userids []string, properties json.RawMessage) { msg := &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "roomlist", Type: "update", - Update: &RoomEventServerMessage{ + Update: &api.RoomEventServerMessage{ RoomId: roomid, Properties: properties, }, @@ -428,7 +428,7 @@ func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified } } -func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, cache *container.ConcurrentMap[RoomSessionId, PublicSessionId]) (PublicSessionId, error) { +func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId, cache *container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId]) (api.PublicSessionId, error) { if roomSessionId == sessionIdNotInMeeting { b.logger.Printf("Trying to lookup empty room session id: %s", roomSessionId) return "", nil @@ -453,14 +453,14 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId return sid, nil } -func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container.ConcurrentMap[RoomSessionId, PublicSessionId], users []api.StringMap) []api.StringMap { +func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId], users []api.StringMap) []api.StringMap { if len(users) == 0 { return users } var wg sync.WaitGroup for _, user := range users { - roomSessionId, found := api.GetStringMapString[RoomSessionId](user, "sessionId") + roomSessionId, found := api.GetStringMapString[api.RoomSessionId](user, "sessionId") if !found { b.logger.Printf("User %+v has invalid room session id, ignoring", user) delete(user, "sessionId") @@ -474,7 +474,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container. } wg.Add(1) - go func(roomSessionId RoomSessionId, u api.StringMap) { + go func(roomSessionId api.RoomSessionId, u api.StringMap) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -505,7 +505,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request ctx := log.NewLoggerContext(context.Background(), b.logger) ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - var cache container.ConcurrentMap[RoomSessionId, PublicSessionId] + var cache container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId] // Convert (Nextcloud) session ids to signaling session ids. request.InCall.Users = b.fixupUserSessions(ctx, &cache, request.InCall.Users) // Entries in "Changed" are most likely already fetched through the "Users" list. @@ -529,7 +529,7 @@ func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid s // Convert (Nextcloud) session ids to signaling session ids. ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - var cache container.ConcurrentMap[RoomSessionId, PublicSessionId] + var cache container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId] request.Participants.Users = b.fixupUserSessions(ctx, &cache, request.Participants.Users) request.Participants.Changed = b.fixupUserSessions(ctx, &cache, request.Participants.Changed) @@ -545,7 +545,7 @@ loop: continue } - sessionId, found := api.GetStringMapString[PublicSessionId](user, "sessionId") + sessionId, found := api.GetStringMapString[api.PublicSessionId](user, "sessionId") if !found { b.logger.Printf("User entry has no session id: %+v", user) continue @@ -567,7 +567,7 @@ loop: } wg.Add(1) - go func(sessionId PublicSessionId, permissions []Permission) { + go func(sessionId api.PublicSessionId, permissions []Permission) { defer wg.Done() message := &AsyncMessage{ Type: "permissions", @@ -623,7 +623,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac } wg.Add(1) - go func(roomSessionId RoomSessionId) { + go func(roomSessionId api.RoomSessionId) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -661,7 +661,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac } wg.Add(1) - go func(roomSessionId RoomSessionId, details json.RawMessage) { + go func(roomSessionId api.RoomSessionId, details json.RawMessage) { defer wg.Done() if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) @@ -707,7 +707,7 @@ func (r *DialoutErrorResponse) Status() int { return r.status } -func returnDialoutError(status int, err *Error) (any, error) { +func returnDialoutError(status int, err *api.Error) (any, error) { response := &DialoutErrorResponse{ BackendServerRoomResponse: BackendServerRoomResponse{ Type: "dialout", @@ -741,15 +741,18 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie } } id := newRandomString(32) - msg := &ServerMessage{ + msg := &api.ServerMessage{ Id: id, Type: "internal", - Internal: &InternalServerMessage{ + Internal: &api.InternalServerMessage{ Type: "dialout", - Dialout: &InternalServerDialoutRequest{ + Dialout: &api.InternalServerDialoutRequest{ RoomId: roomid, Backend: url, - Request: request.Dialout, + Request: &api.InternalServerDialoutRequestContents{ + Number: request.Dialout.Number, + Options: request.Dialout.Options, + }, }, }, } @@ -757,9 +760,9 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie subCtx, cancel := context.WithTimeout(ctx, startDialoutTimeout) defer cancel() - var response atomic.Pointer[DialoutInternalClientMessage] + var response atomic.Pointer[api.DialoutInternalClientMessage] - session.HandleResponse(id, func(message *ClientMessage) bool { + session.HandleResponse(id, func(message *api.ClientMessage) bool { response.Store(message.Internal.Dialout) cancel() // Don't send error to other sessions in the room. @@ -768,13 +771,13 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie defer session.ClearResponseHandler(id) if !session.SendMessage(msg) { - return nil, NewError("error_notify", "Could not notify about new dialout.") + return nil, api.NewError("error_notify", "Could not notify about new dialout.") } <-subCtx.Done() if err := subCtx.Err(); err != nil { if errors.Is(err, context.DeadlineExceeded) { - return nil, NewError("timeout", "Timeout while waiting for dialout to start.") + return nil, api.NewError("timeout", "Timeout while waiting for dialout to start.") } else if errors.Is(err, context.Canceled) && errors.Is(ctx.Err(), context.Canceled) { // Upstream request was cancelled. return nil, err @@ -783,15 +786,15 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie dialout := response.Load() if dialout == nil { - return nil, NewError("error_notify", "No dialout response received.") + return nil, api.NewError("error_notify", "No dialout response received.") } switch dialout.Type { case "error": return nil, dialout.Error case "status": - if dialout.Status.Status != DialoutStatusAccepted { - return nil, NewError("unsupported_status", fmt.Sprintf("Unsupported dialout status received: %+v", dialout)) + if dialout.Status.Status != api.DialoutStatusAccepted { + return nil, api.NewError("unsupported_status", fmt.Sprintf("Unsupported dialout status received: %+v", dialout)) } return &BackendServerRoomResponse{ @@ -801,7 +804,7 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie }, }, nil default: - return nil, NewError("unsupported_type", fmt.Sprintf("Unsupported dialout type received: %+v", dialout)) + return nil, api.NewError("unsupported_type", fmt.Sprintf("Unsupported dialout type received: %+v", dialout)) } } @@ -811,10 +814,10 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend } if !isNumeric(roomid) { - return returnDialoutError(http.StatusBadRequest, NewError("invalid_roomid", "The room id must be numeric.")) + return returnDialoutError(http.StatusBadRequest, api.NewError("invalid_roomid", "The room id must be numeric.")) } - var sessionError *Error + var sessionError *api.Error sessions := b.hub.GetDialoutSessions(roomid, backend) for _, session := range sessions { if ctx.Err() != nil { @@ -825,7 +828,7 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend response, err := b.startDialoutInSession(ctx, session, roomid, backend, backendUrl, request) if err != nil { b.logger.Printf("Error starting dialout request %+v in session %s: %+v", request.Dialout, session.PublicId(), err) - var e *Error + var e *api.Error if sessionError == nil && errors.As(err, &e) { sessionError = e } @@ -839,7 +842,7 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend return returnDialoutError(http.StatusBadGateway, sessionError) } - return returnDialoutError(http.StatusNotFound, NewError("no_client_available", "No available client found to trigger dialout.")) + return returnDialoutError(http.StatusNotFound, api.NewError("no_client_available", "No available client found to trigger dialout.")) } func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, body []byte) { @@ -914,7 +917,7 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, b.sendRoomInvite(roomid, backend, request.Invite.UserIds, request.Invite.Properties) b.sendRoomUpdate(roomid, backend, request.Invite.UserIds, request.Invite.AllUserIds, request.Invite.Properties) case "disinvite": - b.sendRoomDisinvite(roomid, backend, DisinviteReasonDisinvited, request.Disinvite.UserIds, request.Disinvite.SessionIds) + b.sendRoomDisinvite(roomid, backend, api.DisinviteReasonDisinvited, request.Disinvite.UserIds, request.Disinvite.SessionIds) b.sendRoomUpdate(roomid, backend, request.Disinvite.UserIds, request.Disinvite.AllUserIds, request.Disinvite.Properties) case "update": message := &AsyncMessage{ @@ -929,7 +932,7 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, Room: &request, } err = b.events.PublishBackendRoomMessage(roomid, backend, message) - b.sendRoomDisinvite(roomid, backend, DisinviteReasonDeleted, request.Delete.UserIds, nil) + b.sendRoomDisinvite(roomid, backend, api.DisinviteReasonDeleted, request.Delete.UserIds, nil) case "incall": err = b.sendRoomIncall(roomid, backend, &request) case "participants": diff --git a/backend_server_test.go b/backend_server_test.go index 502f6ab..01e1144 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -242,7 +242,7 @@ func performBackendRequest(requestUrl string, body []byte) (*http.Response, erro return client.Do(request) } -func expectRoomlistEvent(t *testing.T, ch AsyncChannel, msgType string) (*EventServerMessage, bool) { +func expectRoomlistEvent(t *testing.T, ch AsyncChannel, msgType string) (*api.EventServerMessage, bool) { assert := assert.New(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -515,8 +515,8 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { UserIds: []string{ testDefaultUserId, }, - SessionIds: []RoomSessionId{ - RoomSessionId(fmt.Sprintf("%s-%s"+roomId, hello.Hello.SessionId)), + SessionIds: []api.RoomSessionId{ + api.RoomSessionId(fmt.Sprintf("%s-%s"+roomId, hello.Hello.SessionId)), }, AllUserIds: []string{}, Properties: roomProperties, @@ -575,8 +575,8 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { UserIds: []string{ testDefaultUserId, }, - SessionIds: []RoomSessionId{ - RoomSessionId(fmt.Sprintf("%s-%s"+roomId1, hello1.Hello.SessionId)), + SessionIds: []api.RoomSessionId{ + api.RoomSessionId(fmt.Sprintf("%s-%s"+roomId1, hello1.Hello.SessionId)), }, AllUserIds: []string{}, }, @@ -1517,15 +1517,15 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { assert.Equal(roomId, msg.Internal.Dialout.RoomId) assert.Equal(server.URL+"/", msg.Internal.Dialout.Backend) - response := &ClientMessage{ + response := &api.ClientMessage{ Id: msg.Id, Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "dialout", - Dialout: &DialoutInternalClientMessage{ + Dialout: &api.DialoutInternalClientMessage{ Type: "status", RoomId: msg.Internal.Dialout.RoomId, - Status: &DialoutStatusInternalClientMessage{ + Status: &api.DialoutStatusInternalClientMessage{ Status: "accepted", CallId: callId, }, @@ -1604,15 +1604,15 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { assert.Equal(roomId, msg.Internal.Dialout.RoomId) assert.Equal(server.URL+"/", msg.Internal.Dialout.Backend) - response := &ClientMessage{ + response := &api.ClientMessage{ Id: msg.Id, Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "dialout", - Dialout: &DialoutInternalClientMessage{ + Dialout: &api.DialoutInternalClientMessage{ Type: "status", RoomId: msg.Internal.Dialout.RoomId, - Status: &DialoutStatusInternalClientMessage{ + Status: &api.DialoutStatusInternalClientMessage{ Status: "accepted", CallId: callId, }, @@ -1692,14 +1692,14 @@ func TestBackendServer_DialoutRejected(t *testing.T) { assert.Equal(roomId, msg.Internal.Dialout.RoomId) assert.Equal(server.URL+"/", msg.Internal.Dialout.Backend) - response := &ClientMessage{ + response := &api.ClientMessage{ Id: msg.Id, Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "dialout", - Dialout: &DialoutInternalClientMessage{ + Dialout: &api.DialoutInternalClientMessage{ Type: "error", - Error: NewError(errorCode, errorMessage), + Error: api.NewError(errorCode, errorMessage), }, }, } @@ -1783,31 +1783,31 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { assert.Equal(roomId, msg.Internal.Dialout.RoomId) assert.Equal(server.URL+"/", msg.Internal.Dialout.Backend) - var dialout *DialoutInternalClientMessage + var dialout *api.DialoutInternalClientMessage // The first session should return an error to make sure the second is retried afterwards. if returnedError.CompareAndSwap(false, true) { errorCode := "error-code" errorMessage := "rejected call" - dialout = &DialoutInternalClientMessage{ + dialout = &api.DialoutInternalClientMessage{ Type: "error", - Error: NewError(errorCode, errorMessage), + Error: api.NewError(errorCode, errorMessage), } } else { - dialout = &DialoutInternalClientMessage{ + dialout = &api.DialoutInternalClientMessage{ Type: "status", RoomId: msg.Internal.Dialout.RoomId, - Status: &DialoutStatusInternalClientMessage{ + Status: &api.DialoutStatusInternalClientMessage{ Status: "accepted", CallId: callId, }, } } - response := &ClientMessage{ + response := &api.ClientMessage{ Id: msg.Id, Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "dialout", Dialout: dialout, }, diff --git a/client.go b/client.go index ebb5888..fcb86a3 100644 --- a/client.go +++ b/client.go @@ -37,6 +37,7 @@ import ( "github.com/gorilla/websocket" "github.com/mailru/easyjson" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" @@ -84,7 +85,7 @@ func IsValidCountry(country string) bool { } var ( - InvalidFormat = NewError("invalid_format", "Invalid data format.") + InvalidFormat = api.NewError("invalid_format", "Invalid data format.") bufferPool pool.BufferPool ) @@ -92,7 +93,7 @@ var ( type WritableClientMessage interface { json.Marshaler - CloseAfterSend(session Session) bool + CloseAfterSend(session api.RoomAware) bool } type HandlerClient interface { @@ -106,9 +107,9 @@ type HandlerClient interface { GetSession() Session SetSession(session Session) - SendError(e *Error) bool - SendByeResponse(message *ClientMessage) bool - SendByeResponseWithReason(message *ClientMessage, reason string) bool + SendError(e *api.Error) bool + SendByeResponse(message *api.ClientMessage) bool + SendByeResponseWithReason(message *api.ClientMessage, reason string) bool SendMessage(message WritableClientMessage) bool Close() @@ -139,7 +140,7 @@ type Client struct { handler ClientHandler session atomic.Pointer[Session] - sessionId atomic.Pointer[PublicSessionId] + sessionId atomic.Pointer[api.PublicSessionId] mu sync.Mutex @@ -219,11 +220,11 @@ func (c *Client) SetSession(session Session) { } } -func (c *Client) SetSessionId(sessionId PublicSessionId) { +func (c *Client) SetSessionId(sessionId api.PublicSessionId) { c.sessionId.Store(&sessionId) } -func (c *Client) GetSessionId() PublicSessionId { +func (c *Client) GetSessionId() api.PublicSessionId { sessionId := c.sessionId.Load() if sessionId == nil { session := c.GetSession() @@ -293,20 +294,20 @@ func (c *Client) doClose() { } } -func (c *Client) SendError(e *Error) bool { - message := &ServerMessage{ +func (c *Client) SendError(e *api.Error) bool { + message := &api.ServerMessage{ Type: "error", Error: e, } return c.SendMessage(message) } -func (c *Client) SendByeResponse(message *ClientMessage) bool { +func (c *Client) SendByeResponse(message *api.ClientMessage) bool { return c.SendByeResponseWithReason(message, "") } -func (c *Client) SendByeResponseWithReason(message *ClientMessage, reason string) bool { - response := &ServerMessage{ +func (c *Client) SendByeResponseWithReason(message *api.ClientMessage, reason string) bool { + response := &api.ServerMessage{ Type: "bye", } if message != nil { @@ -314,7 +315,7 @@ func (c *Client) SendByeResponseWithReason(message *ClientMessage, reason string } if reason != "" { if response.Bye == nil { - response.Bye = &ByeServerMessage{} + response.Bye = &api.ByeServerMessage{} } response.Bye.Reason = reason } @@ -497,9 +498,9 @@ close: } func (c *Client) writeError(e error) bool { // nolint - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "error", - Error: NewError("internal_error", e.Error()), + Error: api.NewError("internal_error", e.Error()), } c.mu.Lock() defer c.mu.Unlock() diff --git a/client/main.go b/client/main.go index 5d87f3f..9156623 100644 --- a/client/main.go +++ b/client/main.go @@ -48,6 +48,7 @@ import ( "github.com/mailru/easyjson/jwriter" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -138,9 +139,9 @@ type SignalingClient struct { lock sync.Mutex // +checklocks:lock - privateSessionId signaling.PrivateSessionId + privateSessionId api.PrivateSessionId // +checklocks:lock - publicSessionId signaling.PublicSessionId + publicSessionId api.PublicSessionId // +checklocks:lock userId string } @@ -183,9 +184,9 @@ func (c *SignalingClient) Close() { c.lock.Lock() c.publicSessionId = "" c.privateSessionId = "" - c.writeInternal(&signaling.ClientMessage{ + c.writeInternal(&api.ClientMessage{ Type: "bye", - Bye: &signaling.ByeClientMessage{}, + Bye: &api.ByeClientMessage{}, }) c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) // nolint @@ -194,7 +195,7 @@ func (c *SignalingClient) Close() { c.lock.Unlock() } -func (c *SignalingClient) Send(message *signaling.ClientMessage) { +func (c *SignalingClient) Send(message *api.ClientMessage) { c.lock.Lock() if c.conn == nil { c.lock.Unlock() @@ -209,7 +210,7 @@ func (c *SignalingClient) Send(message *signaling.ClientMessage) { c.lock.Unlock() } -func (c *SignalingClient) processMessage(message *signaling.ServerMessage) { +func (c *SignalingClient) processMessage(message *api.ServerMessage) { c.stats.numRecvMessages.Add(1) switch message.Type { case "welcome": @@ -229,7 +230,7 @@ func (c *SignalingClient) processMessage(message *signaling.ServerMessage) { } } -func (c *SignalingClient) processHelloMessage(message *signaling.ServerMessage) { +func (c *SignalingClient) processHelloMessage(message *api.ServerMessage) { c.lock.Lock() defer c.lock.Unlock() c.privateSessionId = message.Hello.ResumeId @@ -239,13 +240,13 @@ func (c *SignalingClient) processHelloMessage(message *signaling.ServerMessage) c.readyWg.Done() } -func (c *SignalingClient) PublicSessionId() signaling.PublicSessionId { +func (c *SignalingClient) PublicSessionId() api.PublicSessionId { c.lock.Lock() defer c.lock.Unlock() return c.publicSessionId } -func (c *SignalingClient) processMessageMessage(message *signaling.ServerMessage) { +func (c *SignalingClient) processMessageMessage(message *api.ServerMessage) { var msg MessagePayload if err := msg.UnmarshalJSON(message.Message.Data); err != nil { log.Println("Error in unmarshal", err) @@ -304,7 +305,7 @@ func (c *SignalingClient) readPump() { c.stats.numRecvBytes.Add(uint64(decodeBuffer.Len())) - var message signaling.ServerMessage + var message api.ServerMessage if err := message.UnmarshalJSON(decodeBuffer.Bytes()); err != nil { log.Printf("Error: %v", err) break @@ -314,7 +315,7 @@ func (c *SignalingClient) readPump() { } } -func (c *SignalingClient) writeInternal(message *signaling.ClientMessage) bool { +func (c *SignalingClient) writeInternal(message *api.ClientMessage) bool { var closeData []byte c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint @@ -383,7 +384,7 @@ func (c *SignalingClient) writePump() { } func (c *SignalingClient) SendMessages(clients []*SignalingClient) { - sessionIds := make(map[*SignalingClient]signaling.PublicSessionId) + sessionIds := make(map[*SignalingClient]api.PublicSessionId) for _, c := range clients { sessionIds[c] = c.PublicSessionId() } @@ -402,10 +403,10 @@ func (c *SignalingClient) SendMessages(clients []*SignalingClient) { Now: now, } data, _ := msgdata.MarshalJSON() - msg := &signaling.ClientMessage{ + msg := &api.ClientMessage{ Type: "message", - Message: &signaling.MessageClientMessage{ - Recipient: signaling.MessageClientMessageRecipient{ + Message: &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ Type: "session", SessionId: sessionIds[recipient], }, @@ -599,11 +600,11 @@ func main() { defer client.Close() readyWg.Add(1) - request := &signaling.ClientMessage{ + request := &api.ClientMessage{ Type: "hello", - Hello: &signaling.HelloClientMessage{ - Version: signaling.HelloVersionV1, - Auth: &signaling.HelloClientMessageAuth{ + Hello: &api.HelloClientMessage{ + Version: api.HelloVersionV1, + Auth: &api.HelloClientMessageAuth{ Url: backendUrl, Params: json.RawMessage("{}"), }, diff --git a/clientsession.go b/clientsession.go index fad2331..059f173 100644 --- a/clientsession.go +++ b/clientsession.go @@ -50,24 +50,20 @@ var ( PathToOcsSignalingBackend = "ocs/v2.php/apps/spreed/api/v1/signaling/backend" ) -const ( - FederatedRoomSessionIdPrefix = "federated|" -) - // ResponseHandlerFunc will return "true" has been fully processed. -type ResponseHandlerFunc func(message *ClientMessage) bool +type ResponseHandlerFunc func(message *api.ClientMessage) bool type ClientSession struct { logger log.Logger hub *Hub events AsyncEvents - privateId PrivateSessionId - publicId PublicSessionId + privateId api.PrivateSessionId + publicId api.PublicSessionId data *SessionIdData ctx context.Context closeFunc context.CancelFunc - clientType ClientType + clientType api.ClientType features []string userId string userData json.RawMessage @@ -95,7 +91,7 @@ type ClientSession struct { roomSessionIdLock sync.RWMutex // +checklocks:roomSessionIdLock - roomSessionId RoomSessionId + roomSessionId api.RoomSessionId publisherWaiters async.ChannelWaiters // +checklocksignore @@ -105,7 +101,7 @@ type ClientSession struct { subscribers map[StreamId]McuSubscriber // +checklocks:mu - pendingClientMessages []*ServerMessage + pendingClientMessages []*api.ServerMessage // +checklocks:mu hasPendingChat bool // +checklocks:mu @@ -116,14 +112,14 @@ type ClientSession struct { seenJoinedLock sync.Mutex // +checklocks:seenJoinedLock - seenJoinedEvents map[PublicSessionId]bool + seenJoinedEvents map[api.PublicSessionId]bool responseHandlersLock sync.Mutex // +checklocks:responseHandlersLock responseHandlers map[string]ResponseHandlerFunc } -func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { +func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, backend *Backend, hello *api.HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { ctx := log.NewLoggerContext(context.Background(), hub.logger) ctx, closeFunc := context.WithCancel(ctx) s := &ClientSession{ @@ -145,15 +141,15 @@ func NewClientSession(hub *Hub, privateId PrivateSessionId, publicId PublicSessi backend: backend, asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), } - if s.clientType == HelloClientTypeInternal { - s.backendUrl = hello.Auth.internalParams.Backend - s.parsedBackendUrl = hello.Auth.internalParams.parsedBackend - if !s.HasFeature(ClientFeatureInternalInCall) { + if s.clientType == api.HelloClientTypeInternal { + s.backendUrl = hello.Auth.InternalParams.Backend + s.parsedBackendUrl = hello.Auth.InternalParams.ParsedBackend + if !s.HasFeature(api.ClientFeatureInternalInCall) { s.SetInCall(FlagInCall | FlagWithAudio) } } else { s.backendUrl = hello.Auth.Url - s.parsedBackendUrl = hello.Auth.parsedUrl + s.parsedBackendUrl = hello.Auth.ParsedUrl } if err := s.SubscribeEvents(); err != nil { @@ -167,15 +163,15 @@ func (s *ClientSession) Context() context.Context { return s.ctx } -func (s *ClientSession) PrivateId() PrivateSessionId { +func (s *ClientSession) PrivateId() api.PrivateSessionId { return s.privateId } -func (s *ClientSession) PublicId() PublicSessionId { +func (s *ClientSession) PublicId() api.PublicSessionId { return s.publicId } -func (s *ClientSession) RoomSessionId() RoomSessionId { +func (s *ClientSession) RoomSessionId() api.RoomSessionId { s.roomSessionIdLock.RLock() defer s.roomSessionIdLock.RUnlock() return s.roomSessionId @@ -185,7 +181,7 @@ func (s *ClientSession) Data() *SessionIdData { return s.data } -func (s *ClientSession) ClientType() ClientType { +func (s *ClientSession) ClientType() api.ClientType { return s.clientType } @@ -345,6 +341,11 @@ func (s *ClientSession) onRoomSet(hasRoom bool) { s.seenJoinedEvents = nil } +func (s *ClientSession) IsInRoom(id string) bool { + room := s.GetRoom() + return room != nil && room.Id() == id +} + func (s *ClientSession) GetFederationClient() *FederationClient { return s.federation.Load() } @@ -460,7 +461,7 @@ func (s *ClientSession) SubscribeEvents() error { return s.events.RegisterSessionListener(s.publicId, s.backend, s) } -func (s *ClientSession) UpdateRoomSessionId(roomSessionId RoomSessionId) error { +func (s *ClientSession) UpdateRoomSessionId(roomSessionId api.RoomSessionId) error { s.roomSessionIdLock.Lock() defer s.roomSessionIdLock.Unlock() @@ -494,7 +495,7 @@ func (s *ClientSession) UpdateRoomSessionId(roomSessionId RoomSessionId) error { return nil } -func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId RoomSessionId) error { +func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId api.RoomSessionId) error { s.roomSessionIdLock.Lock() defer s.roomSessionIdLock.Unlock() @@ -530,7 +531,7 @@ func (s *ClientSession) LeaveRoom(notify bool) *Room { return s.LeaveRoomWithMessage(notify, nil) } -func (s *ClientSession) LeaveRoomWithMessage(notify bool, message *ClientMessage) *Room { +func (s *ClientSession) LeaveRoomWithMessage(notify bool, message *api.ClientMessage) *Room { if prev := s.federation.Swap(nil); prev != nil { // Session was connected to a federation room. if err := prev.Leave(message); err != nil { @@ -580,7 +581,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { defer s.roomSessionIdLock.Unlock() if notify && room != nil && s.roomSessionId != "" && !s.roomSessionId.IsFederated() { // Notify - go func(sid RoomSessionId) { + go func(sid api.RoomSessionId) { ctx := log.NewLoggerContext(context.Background(), s.logger) request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) @@ -652,8 +653,8 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { } // +checklocks:s.mu -func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, streamType StreamType, offer api.StringMap) { - offer_message := &AnswerOfferMessage{ +func (s *ClientSession) sendOffer(client McuClient, sender api.PublicSessionId, streamType StreamType, offer api.StringMap) { + offer_message := &api.AnswerOfferMessage{ To: s.PublicId(), From: sender, Type: "offer", @@ -666,10 +667,10 @@ func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, stre s.logger.Println("Could not serialize offer", offer_message, err) return } - response_message := &ServerMessage{ + response_message := &api.ServerMessage{ Type: "message", - Message: &MessageServerMessage{ - Sender: &MessageServerMessageSender{ + Message: &api.MessageServerMessage{ + Sender: &api.MessageServerMessageSender{ Type: "session", SessionId: sender, }, @@ -681,8 +682,8 @@ func (s *ClientSession) sendOffer(client McuClient, sender PublicSessionId, stre } // +checklocks:s.mu -func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, streamType StreamType, candidate any) { - candidate_message := &AnswerOfferMessage{ +func (s *ClientSession) sendCandidate(client McuClient, sender api.PublicSessionId, streamType StreamType, candidate any) { + candidate_message := &api.AnswerOfferMessage{ To: s.PublicId(), From: sender, Type: "candidate", @@ -697,10 +698,10 @@ func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, s.logger.Println("Could not serialize candidate", candidate_message, err) return } - response_message := &ServerMessage{ + response_message := &api.ServerMessage{ Type: "message", - Message: &MessageServerMessage{ - Sender: &MessageServerMessageSender{ + Message: &api.MessageServerMessage{ + Sender: &api.MessageServerMessageSender{ Type: "session", SessionId: sender, }, @@ -712,7 +713,7 @@ func (s *ClientSession) sendCandidate(client McuClient, sender PublicSessionId, } // +checklocks:s.mu -func (s *ClientSession) sendMessageUnlocked(message *ServerMessage) bool { +func (s *ClientSession) sendMessageUnlocked(message *api.ServerMessage) bool { if c := s.getClientUnlocked(); c != nil { if c.SendMessage(message) { return true @@ -723,15 +724,15 @@ func (s *ClientSession) sendMessageUnlocked(message *ServerMessage) bool { return true } -func (s *ClientSession) SendError(e *Error) bool { - message := &ServerMessage{ +func (s *ClientSession) SendError(e *api.Error) bool { + message := &api.ServerMessage{ Type: "error", Error: e, } return s.SendMessage(message) } -func (s *ClientSession) SendMessage(message *ServerMessage) bool { +func (s *ClientSession) SendMessage(message *api.ServerMessage) bool { message = s.filterMessage(message) if message == nil { return true @@ -743,7 +744,7 @@ func (s *ClientSession) SendMessage(message *ServerMessage) bool { return s.sendMessageUnlocked(message) } -func (s *ClientSession) SendMessages(messages []*ServerMessage) bool { +func (s *ClientSession) SendMessages(messages []*api.ServerMessage) bool { s.mu.Lock() defer s.mu.Unlock() @@ -837,7 +838,7 @@ func (e *PermissionError) Error() string { func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (MediaType, error) { if sdp == nil { // Should have already been checked when data was validated. - return 0, ErrNoSdp + return 0, api.ErrNoSdp } var mediaTypes MediaType @@ -862,7 +863,7 @@ func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (M return mediaTypes, nil } -func (s *ClientSession) IsAllowedToSend(data *MessageClientMessageData) error { +func (s *ClientSession) IsAllowedToSend(data *api.MessageClientMessageData) error { s.mu.Lock() defer s.mu.Unlock() @@ -877,7 +878,7 @@ func (s *ClientSession) IsAllowedToSend(data *MessageClientMessageData) error { return nil case data != nil && data.Type == "offer": // Check what user is trying to publish and check permissions accordingly. - if _, err := s.isSdpAllowedToSendLocked(data.offerSdp); err != nil { + if _, err := s.isSdpAllowedToSendLocked(data.OfferSdp); err != nil { return err } @@ -892,7 +893,7 @@ func (s *ClientSession) IsAllowedToSend(data *MessageClientMessageData) error { } } -func (s *ClientSession) CheckOfferType(streamType StreamType, data *MessageClientMessageData) (MediaType, error) { +func (s *ClientSession) CheckOfferType(streamType StreamType, data *api.MessageClientMessageData) (MediaType, error) { s.mu.Lock() defer s.mu.Unlock() @@ -900,7 +901,7 @@ func (s *ClientSession) CheckOfferType(streamType StreamType, data *MessageClien } // +checklocks:s.mu -func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *MessageClientMessageData) (MediaType, error) { +func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *api.MessageClientMessageData) (MediaType, error) { if streamType == StreamTypeScreen { if !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { return 0, &PermissionError{PERMISSION_MAY_PUBLISH_SCREEN} @@ -908,7 +909,7 @@ func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *Messag return MediaTypeScreen, nil } else if data != nil && data.Type == "offer" { - mediaTypes, err := s.isSdpAllowedToSendLocked(data.offerSdp) + mediaTypes, err := s.isSdpAllowedToSendLocked(data.OfferSdp) if err != nil { return 0, err } @@ -919,7 +920,7 @@ func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *Messag return 0, nil } -func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, streamType StreamType, data *MessageClientMessageData) (McuPublisher, error) { +func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, streamType StreamType, data *api.MessageClientMessageData) (McuPublisher, error) { s.mu.Lock() defer s.mu.Unlock() @@ -1026,7 +1027,7 @@ func (s *ClientSession) GetOrWaitForPublisher(ctx context.Context, streamType St } } -func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id PublicSessionId, streamType StreamType) (McuSubscriber, error) { +func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id api.PublicSessionId, streamType StreamType) (McuSubscriber, error) { s.mu.Lock() defer s.mu.Unlock() @@ -1061,7 +1062,7 @@ func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id P return subscriber, nil } -func (s *ClientSession) GetSubscriber(id PublicSessionId, streamType StreamType) McuSubscriber { +func (s *ClientSession) GetSubscriber(id api.PublicSessionId, streamType StreamType) McuSubscriber { s.mu.Lock() defer s.mu.Unlock() @@ -1128,10 +1129,10 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { s.logger.Printf("Could not create MCU subscriber for session %s to process sendoffer in %s: %s", message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Id: message.SendOffer.MessageId, Type: "error", - Error: NewError("client_not_found", "No MCU client found to send message to."), + Error: api.NewError("client_not_found", "No MCU client found to send message to."), }, }); err != nil { s.logger.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) @@ -1141,10 +1142,10 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { s.logger.Printf("No MCU subscriber found for session %s to process sendoffer in %s", message.SendOffer.SessionId, s.PublicId()) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Id: message.SendOffer.MessageId, Type: "error", - Error: NewError("client_not_found", "No MCU client found to send message to."), + Error: api.NewError("client_not_found", "No MCU client found to send message to."), }, }); err != nil { s.logger.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) @@ -1157,10 +1158,10 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { s.logger.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Id: message.SendOffer.MessageId, Type: "error", - Error: NewError("processing_failed", "Processing of the message failed, please check server logs."), + Error: api.NewError("processing_failed", "Processing of the message failed, please check server logs."), }, }); err != nil { s.logger.Printf("Error sending sendoffer error response to %s: %s", message.SendOffer.SessionId, err) @@ -1171,8 +1172,8 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { return } - s.hub.sendMcuMessageResponse(s, mc, &MessageClientMessage{ - Recipient: MessageClientMessageRecipient{ + s.hub.sendMcuMessageResponse(s, mc, &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ SessionId: message.SendOffer.SessionId, }, }, message.SendOffer.Data, response) @@ -1190,7 +1191,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { } // +checklocks:s.mu -func (s *ClientSession) storePendingMessage(message *ServerMessage) { +func (s *ClientSession) storePendingMessage(message *api.ServerMessage) { if message.IsChatRefresh() { if s.hasPendingChat { // Only send a single "chat-refresh" message on resume. @@ -1208,8 +1209,8 @@ func (s *ClientSession) storePendingMessage(message *ServerMessage) { } } -func filterDisplayNames(events []EventServerMessageSessionEntry) []EventServerMessageSessionEntry { - result := make([]EventServerMessageSessionEntry, 0, len(events)) +func filterDisplayNames(events []api.EventServerMessageSessionEntry) []api.EventServerMessageSessionEntry { + result := make([]api.EventServerMessageSessionEntry, 0, len(events)) for _, event := range events { if len(event.User) == 0 { result = append(result, event) @@ -1249,14 +1250,14 @@ func filterDisplayNames(events []EventServerMessageSessionEntry) []EventServerMe return result } -func (s *ClientSession) filterDuplicateJoin(entries []EventServerMessageSessionEntry) []EventServerMessageSessionEntry { +func (s *ClientSession) filterDuplicateJoin(entries []api.EventServerMessageSessionEntry) []api.EventServerMessageSessionEntry { s.seenJoinedLock.Lock() defer s.seenJoinedLock.Unlock() // Due to the asynchronous events, a session might received a "Joined" event // for the same (other) session twice, so filter these out on a per-session // level. - result := make([]EventServerMessageSessionEntry, 0, len(entries)) + result := make([]api.EventServerMessageSessionEntry, 0, len(entries)) for _, e := range entries { if s.seenJoinedEvents[e.SessionId] { s.logger.Printf("Session %s got duplicate joined event for %s, ignoring", s.publicId, e.SessionId) @@ -1264,7 +1265,7 @@ func (s *ClientSession) filterDuplicateJoin(entries []EventServerMessageSessionE } if s.seenJoinedEvents == nil { - s.seenJoinedEvents = make(map[PublicSessionId]bool) + s.seenJoinedEvents = make(map[api.PublicSessionId]bool) } s.seenJoinedEvents[e.SessionId] = true result = append(result, e) @@ -1272,7 +1273,7 @@ func (s *ClientSession) filterDuplicateJoin(entries []EventServerMessageSessionE return result } -func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { +func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMessage { switch message.Type { case "event": switch message.Event.Target { @@ -1304,10 +1305,10 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { if len(join) != len(message.Event.Join) { // Create unique copy of message for only this client. copied = true - message = &ServerMessage{ + message = &api.ServerMessage{ Id: message.Id, Type: message.Type, - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Type: message.Event.Type, Target: message.Event.Target, Join: join, @@ -1319,10 +1320,10 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { if copied { message.Event.Join = filterDisplayNames(message.Event.Join) } else { - message = &ServerMessage{ + message = &api.ServerMessage{ Id: message.Id, Type: message.Type, - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Type: message.Event.Type, Target: message.Event.Target, Join: filterDisplayNames(message.Event.Join), @@ -1351,7 +1352,7 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { update := false if data.Chat.Refresh && len(data.Chat.Comment) > 0 { // New-style chat event, check what the client supports. - if s.HasFeature(ClientFeatureChatRelay) { + if s.HasFeature(api.ClientFeatureChatRelay) { data.Chat.Refresh = false } else { data.Chat.Comment = nil @@ -1360,7 +1361,7 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { } if len(data.Chat.Comment) > 0 && s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { - var comment ChatComment + var comment api.ChatComment if err := json.Unmarshal(data.Chat.Comment, &comment); err != nil { return message } @@ -1378,13 +1379,13 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { if update { if encoded, err := json.Marshal(data); err == nil { // Create unique copy of message for only this client. - message = &ServerMessage{ + message = &api.ServerMessage{ Id: message.Id, Type: message.Type, - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Type: message.Event.Type, Target: message.Event.Target, - Message: &RoomEventMessage{ + Message: &api.RoomEventMessage{ RoomId: message.Event.Message.RoomId, Data: encoded, }, @@ -1397,7 +1398,7 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { } case "message": if message.Message != nil && len(message.Message.Data) > 0 && s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { - var data MessageServerMessageData + var data api.MessageServerMessageData if err := json.Unmarshal(message.Message.Data, &data); err != nil { return message } @@ -1411,7 +1412,7 @@ func (s *ClientSession) filterMessage(message *ServerMessage) *ServerMessage { return message } -func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { +func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *api.ServerMessage { switch msg.Type { case "message": if msg.Message == nil { @@ -1427,7 +1428,7 @@ func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { // Don't send message back to sender (can happen if sent to user or room) return nil } - if sender.Type == RecipientTypeCall { + if sender.Type == api.RecipientTypeCall { if room := s.GetRoom(); room == nil || !room.IsSessionInCall(s) { // Session is not in call, so discard. return nil @@ -1442,7 +1443,7 @@ func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *ServerMessage { // Don't send message back to sender (can happen if sent to user or room) return nil } - if sender.Type == RecipientTypeCall { + if sender.Type == api.RecipientTypeCall { if room := s.GetRoom(); room == nil || !room.IsSessionInCall(s) { // Session is not in call, so discard. return nil @@ -1541,7 +1542,7 @@ func (s *ClientSession) ClearResponseHandler(id string) { delete(s.responseHandlers, id) } -func (s *ClientSession) ProcessResponse(message *ClientMessage) bool { +func (s *ClientSession) ProcessResponse(message *api.ClientMessage) bool { id := message.Id if id == "" { return false diff --git a/clientsession_test.go b/clientsession_test.go index d236458..c37007a 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/mock" ) func TestBandwidth_Client(t *testing.T) { @@ -63,20 +64,20 @@ func TestBandwidth_Client(t *testing.T) { // Client may not send an offer with audio and video. bitrate := api.BandwidthFromBits(10000) - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "video", Bitrate: bitrate, Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) pub := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(pub) @@ -122,7 +123,7 @@ func TestBandwidth_Backend(t *testing.T) { params := TestBackendClientAuthParams{ UserId: testDefaultUserId, } - require.NoError(client.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params)) + require.NoError(client.SendHelloParams(server.URL+"/one", api.HelloVersionV1, "client", nil, params)) hello := MustSucceed1(t, client.RunUntilHello, ctx) @@ -136,20 +137,20 @@ func TestBandwidth_Backend(t *testing.T) { // Client may not send an offer with audio and video. bitrate := api.BandwidthFromBits(10000) - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: string(streamType), Bitrate: bitrate, Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) pub := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(pub, "Could not find publisher") @@ -179,7 +180,7 @@ func TestFeatureChatRelay(t *testing.T) { defer client.CloseWithBye() var features []string if feature { - features = append(features, ClientFeatureChatRelay) + features = append(features, api.ClientFeatureChatRelay) } require.NoError(client.SendHelloClientWithFeatures(testDefaultUserId, features)) @@ -263,11 +264,11 @@ func TestFeatureChatRelayFederation(t *testing.T) { hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) localFeatures := []string{ - ClientFeatureChatRelay, + api.ClientFeatureChatRelay, } var federatedFeatures []string if feature { - federatedFeatures = append(federatedFeatures, ClientFeatureChatRelay) + federatedFeatures = append(federatedFeatures, api.ClientFeatureChatRelay) } client1 := NewTestClient(t, server1, hub1) @@ -300,13 +301,13 @@ func TestFeatureChatRelayFederation(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -323,7 +324,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -551,7 +552,7 @@ func TestPermissionHideDisplayNames(t *testing.T) { client.RunUntilJoined(ctx, hello2.Hello) client2.RunUntilJoined(ctx, hello.Hello, hello2.Hello) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, } diff --git a/federation.go b/federation.go index fd4cd8d..f936eeb 100644 --- a/federation.go +++ b/federation.go @@ -48,7 +48,7 @@ const ( ) var ( - ErrFederationNotSupported = NewError("federation_unsupported", "The target server does not support federation.") + ErrFederationNotSupported = api.NewError("federation_unsupported", "The target server does not support federation.") federationWriteBufferPool = &sync.Pool{} ) @@ -81,12 +81,12 @@ type FederationClient struct { logger log.Logger hub *Hub session *ClientSession - message atomic.Pointer[ClientMessage] + message atomic.Pointer[api.ClientMessage] roomId atomic.Value remoteRoomId atomic.Value changeRoomId atomic.Bool - federation atomic.Pointer[RoomFederationMessage] + federation atomic.Pointer[api.RoomFederationMessage] mu sync.Mutex dialer *websocket.Dialer @@ -104,18 +104,18 @@ type FederationClient struct { // +checklocks:helloMu helloMsgId string // +checklocks:helloMu - helloAuth *FederationAuthParams + helloAuth *api.FederationAuthParams // +checklocks:helloMu - resumeId PrivateSessionId - hello atomic.Pointer[HelloServerMessage] + resumeId api.PrivateSessionId + hello atomic.Pointer[api.HelloServerMessage] // +checklocks:helloMu - pendingMessages []*ClientMessage + pendingMessages []*api.ClientMessage closeOnLeave atomic.Bool } -func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, message *ClientMessage) (*FederationClient, error) { +func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, message *api.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) } @@ -130,7 +130,7 @@ func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, } room := message.Room - u := *room.Federation.parsedSignalingUrl + u := *room.Federation.ParsedSignalingUrl switch u.Scheme { case "http": u.Scheme = "ws" @@ -187,7 +187,7 @@ func (c *FederationClient) LocalAddr() net.Addr { } func (c *FederationClient) URL() string { - return c.federation.Load().parsedSignalingUrl.String() + return c.federation.Load().ParsedSignalingUrl.String() } func (c *FederationClient) RoomId() string { @@ -198,7 +198,7 @@ func (c *FederationClient) RemoteRoomId() string { return c.remoteRoomId.Load().(string) } -func (c *FederationClient) CanReuse(federation *RoomFederationMessage) bool { +func (c *FederationClient) CanReuse(federation *api.RoomFederationMessage) bool { fed := c.federation.Load() return fed.NextcloudUrl == federation.NextcloudUrl && fed.SignalingUrl == federation.SignalingUrl @@ -215,7 +215,7 @@ func (c *FederationClient) connect(ctx context.Context) error { supportsFederation := false for _, f := range features { f = strings.TrimSpace(f) - if f == ServerFeatureFederation { + if f == api.ServerFeatureFederation { supportsFederation = true break } @@ -250,7 +250,7 @@ func (c *FederationClient) connect(ctx context.Context) error { return nil } -func (c *FederationClient) ChangeRoom(message *ClientMessage) error { +func (c *FederationClient) ChangeRoom(message *api.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) { @@ -261,14 +261,14 @@ func (c *FederationClient) ChangeRoom(message *ClientMessage) error { return c.joinRoom() } -func (c *FederationClient) Leave(message *ClientMessage) error { +func (c *FederationClient) Leave(message *api.ClientMessage) error { c.mu.Lock() defer c.mu.Unlock() if message == nil { - message = &ClientMessage{ + message = &api.ClientMessage{ Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: "", }, } @@ -302,7 +302,7 @@ func (c *FederationClient) closeConnection(withBye bool) { } if withBye { - if err := c.sendMessageLocked(&ClientMessage{ + if err := c.sendMessageLocked(&api.ClientMessage{ Type: "bye", }); err != nil && !isClosedError(err) { c.logger.Printf("Error sending bye on federation connection to %s: %s", c.URL(), err) @@ -339,9 +339,9 @@ func (c *FederationClient) scheduleReconnect() { func (c *FederationClient) scheduleReconnectLocked() { c.reconnecting.Store(true) if c.hello.Swap(nil) != nil { - c.session.SendMessage(&ServerMessage{ + c.session.SendMessage(&api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "federation_interrupted", }, @@ -404,7 +404,7 @@ func (c *FederationClient) readPump(conn *websocket.Conn) { continue } - var msg ServerMessage + var msg api.ServerMessage if err := json.Unmarshal(data, &msg); err != nil { c.logger.Printf("Error unmarshalling %s from %s: %s", string(data), c.URL(), err) continue @@ -456,9 +456,9 @@ func (c *FederationClient) writePump() { func (c *FederationClient) closeWithError(err error) { c.Close() - var e *Error + var e *api.Error if !errors.As(err, &e) { - e = NewError("federation_error", err.Error()) + e = api.NewError("federation_error", err.Error()) } var id string @@ -466,14 +466,14 @@ func (c *FederationClient) closeWithError(err error) { id = message.Id } - c.session.SendMessage(&ServerMessage{ + c.session.SendMessage(&api.ServerMessage{ Id: id, Type: "error", Error: e, }) } -func (c *FederationClient) sendHello(auth *FederationAuthParams) error { +func (c *FederationClient) sendHello(auth *api.FederationAuthParams) error { c.helloMu.Lock() defer c.helloMu.Unlock() @@ -481,7 +481,7 @@ func (c *FederationClient) sendHello(auth *FederationAuthParams) error { } // +checklocks:c.helloMu -func (c *FederationClient) sendHelloLocked(auth *FederationAuthParams) error { +func (c *FederationClient) sendHelloLocked(auth *api.FederationAuthParams) error { c.helloMsgId = newRandomString(8) authData, err := json.Marshal(auth) @@ -490,19 +490,19 @@ func (c *FederationClient) sendHelloLocked(auth *FederationAuthParams) error { } c.helloAuth = auth - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: c.helloMsgId, Type: "hello", - Hello: &HelloClientMessage{ - Version: HelloVersionV2, + Hello: &api.HelloClientMessage{ + Version: api.HelloVersionV2, Features: c.session.GetFeatures(), }, } if resumeId := c.resumeId; resumeId != "" { msg.Hello.ResumeId = resumeId } else { - msg.Hello.Auth = &HelloClientMessageAuth{ - Type: HelloClientTypeFederation, + msg.Hello.Auth = &api.HelloClientMessageAuth{ + Type: api.HelloClientTypeFederation, Url: c.federation.Load().NextcloudUrl, Params: authData, } @@ -510,13 +510,13 @@ func (c *FederationClient) sendHelloLocked(auth *FederationAuthParams) error { return c.SendMessage(msg) } -func (c *FederationClient) processWelcome(msg *ServerMessage) { - if !msg.Welcome.HasFeature(ServerFeatureFederation) { +func (c *FederationClient) processWelcome(msg *api.ServerMessage) { + if !msg.Welcome.HasFeature(api.ServerFeatureFederation) { c.closeWithError(ErrFederationNotSupported) return } - federationParams := &FederationAuthParams{ + federationParams := &api.FederationAuthParams{ Token: c.federation.Load().Token, } if err := c.sendHello(federationParams); err != nil { @@ -525,7 +525,7 @@ func (c *FederationClient) processWelcome(msg *ServerMessage) { } } -func (c *FederationClient) processHello(msg *ServerMessage) { +func (c *FederationClient) processHello(msg *api.ServerMessage) { c.resetReconnect() c.helloMu.Lock() @@ -567,9 +567,9 @@ func (c *FederationClient) processHello(msg *ServerMessage) { if c.resumeId == "" { c.resumeId = msg.Hello.ResumeId if c.reconnecting.Load() { - c.session.SendMessage(&ServerMessage{ + c.session.SendMessage(&api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "federation_resumed", Resumed: internal.MakePtr(false), @@ -584,9 +584,9 @@ func (c *FederationClient) processHello(msg *ServerMessage) { c.closeWithError(err) } } else { - c.session.SendMessage(&ServerMessage{ + c.session.SendMessage(&api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "federation_resumed", Resumed: internal.MakePtr(true), @@ -627,10 +627,10 @@ func (c *FederationClient) joinRoom() error { remoteRoomId = room.RoomId } - return c.SendMessage(&ClientMessage{ + return c.SendMessage(&api.ClientMessage{ Id: message.Id, Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: remoteRoomId, SessionId: room.SessionId, }, @@ -641,15 +641,15 @@ func (c *FederationClient) updateActor(u api.StringMap, actorIdKey, actorTypeKey if actorType, found := api.GetStringMapEntry[string](u, actorTypeKey); found { if actorId, found := api.GetStringMapEntry[string](u, actorIdKey); found { switch actorType { - case ActorTypeFederatedUsers: + case api.ActorTypeFederatedUsers: if strings.HasSuffix(actorId, localCloudUrl) { u[actorIdKey] = actorId[:len(actorId)-len(localCloudUrl)] - u[actorTypeKey] = ActorTypeUsers + u[actorTypeKey] = api.ActorTypeUsers changed = true } - case ActorTypeUsers: + case api.ActorTypeUsers: u[actorIdKey] = actorId + remoteCloudUrl - u[actorTypeKey] = ActorTypeFederatedUsers + u[actorTypeKey] = api.ActorTypeFederatedUsers changed = true } } @@ -713,7 +713,7 @@ func (c *FederationClient) updateComment(comment api.StringMap, localCloudUrl st return changed } -func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { +func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionId api.PublicSessionId, remoteSessionId api.PublicSessionId) { localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) checkSessionId := true @@ -722,10 +722,10 @@ func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionI if checkSessionId { key := "sessionId" - sid, found := api.GetStringMapString[PublicSessionId](u, key) + sid, found := api.GetStringMapString[api.PublicSessionId](u, key) if !found { key := "sessionid" - sid, found = api.GetStringMapString[PublicSessionId](u, key) + sid, found = api.GetStringMapString[api.PublicSessionId](u, key) } if found && sid == remoteSessionId { u[key] = localSessionId @@ -735,21 +735,21 @@ func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionI } } -func (c *FederationClient) updateSessionRecipient(recipient *MessageClientMessageRecipient, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { - if recipient != nil && recipient.Type == RecipientTypeSession && remoteSessionId != "" && recipient.SessionId == remoteSessionId { +func (c *FederationClient) updateSessionRecipient(recipient *api.MessageClientMessageRecipient, localSessionId api.PublicSessionId, remoteSessionId api.PublicSessionId) { + if recipient != nil && recipient.Type == api.RecipientTypeSession && remoteSessionId != "" && recipient.SessionId == remoteSessionId { recipient.SessionId = localSessionId } } -func (c *FederationClient) updateSessionSender(sender *MessageServerMessageSender, localSessionId PublicSessionId, remoteSessionId PublicSessionId) { - if sender != nil && sender.Type == RecipientTypeSession && remoteSessionId != "" && sender.SessionId == remoteSessionId { +func (c *FederationClient) updateSessionSender(sender *api.MessageServerMessageSender, localSessionId api.PublicSessionId, remoteSessionId api.PublicSessionId) { + if sender != nil && sender.Type == api.RecipientTypeSession && remoteSessionId != "" && sender.SessionId == remoteSessionId { sender.SessionId = localSessionId } } -func (c *FederationClient) processMessage(msg *ServerMessage) { +func (c *FederationClient) processMessage(msg *api.ServerMessage) { localSessionId := c.session.PublicId() - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if hello := c.hello.Load(); hello != nil { remoteSessionId = hello.SessionId } @@ -767,7 +767,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { var data api.StringMap if err := json.Unmarshal(msg.Control.Data, &data); err == nil { if action, found := data["action"]; found && action == "forceMute" { - if peerId, found := api.GetStringMapString[PublicSessionId](data, "peerId"); found && peerId == remoteSessionId { + if peerId, found := api.GetStringMapString[api.PublicSessionId](data, "peerId"); found && peerId == remoteSessionId { data["peerId"] = localSessionId if d, err := json.Marshal(data); err == nil { msg.Control.Data = d @@ -874,7 +874,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { case "error": if c.changeRoomId.Load() && msg.Error.Code == "already_joined" { if len(msg.Error.Details) > 0 { - var details RoomErrorDetails + var details api.RoomErrorDetails if err := json.Unmarshal(msg.Error.Details, &details); err == nil && details.Room != nil { if details.Room.RoomId == remoteRoomId { details.Room.RoomId = roomId @@ -916,7 +916,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { c.updateSessionRecipient(msg.Message.Recipient, localSessionId, remoteSessionId) c.updateSessionSender(msg.Message.Sender, localSessionId, remoteSessionId) if remoteSessionId != "" && len(msg.Message.Data) > 0 { - var ao AnswerOfferMessage + var ao api.AnswerOfferMessage if json.Unmarshal(msg.Message.Data, &ao) == nil && (ao.Type == "offer" || ao.Type == "answer") { changed := false if ao.From == remoteSessionId { @@ -947,7 +947,7 @@ func (c *FederationClient) processMessage(msg *ServerMessage) { } } -func (c *FederationClient) ProxyMessage(message *ClientMessage) error { +func (c *FederationClient) ProxyMessage(message *api.ClientMessage) error { switch message.Type { case "message": if hello := c.hello.Load(); hello != nil { @@ -964,14 +964,14 @@ func (c *FederationClient) ProxyMessage(message *ClientMessage) error { return c.SendMessage(message) } -func (c *FederationClient) SendMessage(message *ClientMessage) error { +func (c *FederationClient) SendMessage(message *api.ClientMessage) error { c.mu.Lock() defer c.mu.Unlock() return c.sendMessageLocked(message) } -func (c *FederationClient) deferMessage(message *ClientMessage) { +func (c *FederationClient) deferMessage(message *api.ClientMessage) { c.helloMu.Lock() defer c.helloMu.Unlock() if c.resumeId == "" { @@ -985,7 +985,7 @@ func (c *FederationClient) deferMessage(message *ClientMessage) { } // +checklocks:c.mu -func (c *FederationClient) sendMessageLocked(message *ClientMessage) error { +func (c *FederationClient) sendMessageLocked(message *api.ClientMessage) error { if c.conn == nil { if message.Type != "room" { // Join requests will be automatically sent after the hello response has diff --git a/federation_test.go b/federation_test.go index d92bbf7..2965bf7 100644 --- a/federation_test.go +++ b/federation_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/mock" ) func Test_FederationInvalidToken(t *testing.T) { @@ -51,13 +52,13 @@ func Test_FederationInvalidToken(t *testing.T) { MustSucceed1(t, client.RunUntilHello, ctx) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: "test-room", SessionId: "room-session-id", - Federation: &RoomFederationMessage{ + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, Token: "invalid-token", @@ -115,13 +116,13 @@ func Test_Federation(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -138,7 +139,7 @@ func Test_Federation(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -174,7 +175,7 @@ func Test_Federation(t *testing.T) { assert.Equal("1.0", ping.Version) assert.Len(ping.Entries, 2) // The order of entries is not defined - if ping.Entries[0].SessionId == RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)) { + if ping.Entries[0].SessionId == api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)) { assert.Equal(hello2.Hello.UserId, ping.Entries[0].UserId) assert.EqualValues(fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), ping.Entries[1].SessionId) @@ -217,7 +218,7 @@ func Test_Federation(t *testing.T) { assert.Equal(roomId, room.Room.RoomId) } - client1.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + client1.RunUntilJoined(ctx, hello1.Hello, &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, }) @@ -228,7 +229,7 @@ func Test_Federation(t *testing.T) { assert.Empty(room.Room.RoomId) } - client1.RunUntilLeft(ctx, &HelloServerMessage{ + client1.RunUntilLeft(ctx, &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, }) @@ -262,7 +263,7 @@ func Test_Federation(t *testing.T) { // Test sending messages between sessions. data1 := "from-1-to-2" data2 := "from-2-to-1" - if assert.NoError(client1.SendMessage(MessageClientMessageRecipient{ + if assert.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: remoteSessionId, }, data1)) { @@ -272,7 +273,7 @@ func Test_Federation(t *testing.T) { } } - if assert.NoError(client1.SendControl(MessageClientMessageRecipient{ + if assert.NoError(client1.SendControl(api.MessageClientMessageRecipient{ Type: "session", SessionId: remoteSessionId, }, data1)) { @@ -282,12 +283,12 @@ func Test_Federation(t *testing.T) { } } - if assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + if assert.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, }, data2)) { var payload string - if checkReceiveClientMessage(ctx, t, client1, "session", &HelloServerMessage{ + if checkReceiveClientMessage(ctx, t, client1, "session", &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: testDefaultUserId + "2", }, &payload) { @@ -295,12 +296,12 @@ func Test_Federation(t *testing.T) { } } - if assert.NoError(client2.SendControl(MessageClientMessageRecipient{ + if assert.NoError(client2.SendControl(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, }, data2)) { var payload string - if checkReceiveClientControl(ctx, t, client1, "session", &HelloServerMessage{ + if checkReceiveClientControl(ctx, t, client1, "session", &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: testDefaultUserId + "2", }, &payload) { @@ -313,7 +314,7 @@ func Test_Federation(t *testing.T) { "action": "forceMute", "peerId": remoteSessionId, } - if assert.NoError(client1.SendControl(MessageClientMessageRecipient{ + if assert.NoError(client1.SendControl(api.MessageClientMessageRecipient{ Type: "session", SessionId: remoteSessionId, }, forceMute)) { @@ -327,7 +328,7 @@ func Test_Federation(t *testing.T) { data3 := "from-2-to-2" // Clients can't send to their own (local) session id. - if assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + if assert.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, }, data3)) { @@ -338,7 +339,7 @@ func Test_Federation(t *testing.T) { } // Clients can't send to their own (remote) session id. - if assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + if assert.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: remoteSessionId, }, data3)) { @@ -358,7 +359,7 @@ func Test_Federation(t *testing.T) { }, } room.PublishUsersInCallChanged(users, users) - var event *EventServerMessage + var event *api.EventServerMessage // For the local user, it's a federated user on server 2 that joined. if checkReceiveClientEvent(ctx, t, client1, "update", &event) { assert.EqualValues(remoteSessionId, event.Update.Users[0]["sessionId"]) @@ -414,7 +415,7 @@ func Test_Federation(t *testing.T) { client1.RunUntilJoined(ctx, hello3.Hello) client2.RunUntilJoined(ctx, hello3.Hello) - client3.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + client3.RunUntilJoined(ctx, hello1.Hello, &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, }, hello3.Hello) @@ -435,13 +436,13 @@ func Test_Federation(t *testing.T) { token, err = client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"4", now, now.Add(time.Minute), userdata) require.NoError(err) - msg = &ClientMessage{ + msg = &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello4.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello4.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -458,7 +459,7 @@ func Test_Federation(t *testing.T) { } // The client1 will see the remote session id for client4. - var remoteSessionId4 PublicSessionId + var remoteSessionId4 api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -469,17 +470,17 @@ func Test_Federation(t *testing.T) { assert.Equal(features2, evt.Features) } - client2.RunUntilJoined(ctx, &HelloServerMessage{ + client2.RunUntilJoined(ctx, &api.HelloServerMessage{ SessionId: remoteSessionId4, UserId: hello4.Hello.UserId, }) - client3.RunUntilJoined(ctx, &HelloServerMessage{ + client3.RunUntilJoined(ctx, &api.HelloServerMessage{ SessionId: remoteSessionId4, UserId: hello4.Hello.UserId, }) - client4.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + client4.RunUntilJoined(ctx, hello1.Hello, &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, }, hello3.Hello, hello4.Hello) @@ -526,13 +527,13 @@ func Test_FederationJoinRoomTwice(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -549,7 +550,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -562,13 +563,13 @@ func Test_FederationJoinRoomTwice(t *testing.T) { // The client2 will see its own session id, not the one from the remote server. client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) - msg2 := &ClientMessage{ + msg2 := &api.ClientMessage{ Id: "join-room-fed-2", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -584,7 +585,7 @@ func Test_FederationJoinRoomTwice(t *testing.T) { assert.Equal("already_joined", message.Error.Code) } if assert.NotNil(message.Error.Details) { - var roomMsg RoomErrorDetails + var roomMsg api.RoomErrorDetails if assert.NoError(json.Unmarshal(message.Error.Details, &roomMsg)) { if assert.NotNil(roomMsg.Room) { assert.Equal(federatedRoomId, roomMsg.Room.RoomId) @@ -632,13 +633,13 @@ func Test_FederationChangeRoom(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -660,7 +661,7 @@ func Test_FederationChangeRoom(t *testing.T) { localAddr := fed.LocalAddr() // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -675,13 +676,13 @@ func Test_FederationChangeRoom(t *testing.T) { roomId2 := roomId + "-2" federatedRoomId2 := roomId2 + "@federated" - msg2 := &ClientMessage{ + msg2 := &api.ClientMessage{ Id: "join-room-fed-2", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId2, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId2, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId2, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId2, @@ -752,13 +753,13 @@ func Test_FederationMedia(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -775,7 +776,7 @@ func Test_FederationMedia(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -788,19 +789,19 @@ func Test_FederationMedia(t *testing.T) { // The client2 will see its own session id, not the one from the remote server. client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "12345", RoomType: "screen", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client2.RunUntilAnswerFromSender(ctx, MockSdpAnswerAudioAndVideo, &MessageServerMessageSender{ + client2.RunUntilAnswerFromSender(ctx, mock.MockSdpAnswerAudioAndVideo, &api.MessageServerMessageSender{ Type: "session", SessionId: hello2.Hello.SessionId, UserId: hello2.Hello.UserId, @@ -844,13 +845,13 @@ func Test_FederationResume(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -867,7 +868,7 @@ func Test_FederationResume(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -887,7 +888,7 @@ func Test_FederationResume(t *testing.T) { err = fed2.conn.Close() data2 := "from-2-to-1" - assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + assert.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, }, data2)) @@ -912,7 +913,7 @@ func Test_FederationResume(t *testing.T) { defer cancel1() var payload string - if checkReceiveClientMessage(ctx, t, client1, "session", &HelloServerMessage{ + if checkReceiveClientMessage(ctx, t, client1, "session", &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: testDefaultUserId + "2", }, &payload) { @@ -964,13 +965,13 @@ func Test_FederationResumeNewSession(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -987,7 +988,7 @@ func Test_FederationResumeNewSession(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] @@ -1024,7 +1025,7 @@ func Test_FederationResumeNewSession(t *testing.T) { // Client1 will get a "leave" for the expired session and a "join" with the // new remote session id. - client1.RunUntilLeft(ctx, &HelloServerMessage{ + client1.RunUntilLeft(ctx, &api.HelloServerMessage{ SessionId: remoteSessionId, UserId: hello2.Hello.UserId, }) @@ -1087,13 +1088,13 @@ func Test_FederationTransientData(t *testing.T) { token, err := client1.CreateHelloV2TokenWithUserdata(testDefaultUserId+"2", now, now.Add(time.Minute), userdata) require.NoError(err) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "join-room-fed", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: federatedRoomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), - Federation: &RoomFederationMessage{ + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", federatedRoomId, hello2.Hello.SessionId)), + Federation: &api.RoomFederationMessage{ SignalingUrl: server1.URL, NextcloudUrl: server1.URL, RoomId: roomId, @@ -1110,7 +1111,7 @@ func Test_FederationTransientData(t *testing.T) { } // The client1 will see the remote session id for client2. - var remoteSessionId PublicSessionId + var remoteSessionId api.PublicSessionId if message, ok := client1.RunUntilMessage(ctx); ok { client1.checkSingleMessageJoined(message) evt := message.Event.Join[0] diff --git a/grpc_client.go b/grpc_client.go index 088b550..82f79c5 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -43,6 +43,7 @@ import ( "google.golang.org/grpc/resolver" status "google.golang.org/grpc/status" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -202,7 +203,7 @@ func (c *GrpcClient) GetServerId(ctx context.Context) (string, string, error) { return response.GetServerId(), response.GetVersion(), nil } -func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId PrivateSessionId) (*LookupResumeIdReply, error) { +func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId api.PrivateSessionId) (*LookupResumeIdReply, error) { statsGrpcClientCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging c.logger.Printf("Lookup resume id %s on %s", resumeId, c.Target()) @@ -222,7 +223,7 @@ func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId PrivateSession return response, nil } -func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) { +func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId api.RoomSessionId, disconnectReason string) (api.PublicSessionId, error) { statsGrpcClientCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging c.logger.Printf("Lookup room session %s on %s", roomSessionId, c.Target()) @@ -241,10 +242,10 @@ func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId RoomSess return "", ErrNoSuchRoomSession } - return PublicSessionId(sessionId), nil + return api.PublicSessionId(sessionId), nil } -func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId PublicSessionId, room *Room, backendUrl string) (bool, error) { +func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId api.PublicSessionId, room *Room, backendUrl string) (bool, error) { statsGrpcClientCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging c.logger.Printf("Check if session %s is in call %s on %s", sessionId, room.Id(), c.Target()) @@ -262,7 +263,7 @@ func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId PublicSessio return response.GetInCall(), nil } -func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[PublicSessionId]*InternalSessionData, virtual map[PublicSessionId]*VirtualSessionData, err error) { +func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[api.PublicSessionId]*InternalSessionData, virtual map[api.PublicSessionId]*VirtualSessionData, err error) { statsGrpcClientCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging c.logger.Printf("Get internal sessions for %s on %s", roomId, c.Target()) @@ -282,22 +283,22 @@ func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, bac } if len(response.InternalSessions) > 0 { - internal = make(map[PublicSessionId]*InternalSessionData, len(response.InternalSessions)) + internal = make(map[api.PublicSessionId]*InternalSessionData, len(response.InternalSessions)) for _, s := range response.InternalSessions { - internal[PublicSessionId(s.SessionId)] = s + internal[api.PublicSessionId(s.SessionId)] = s } } if len(response.VirtualSessions) > 0 { - virtual = make(map[PublicSessionId]*VirtualSessionData, len(response.VirtualSessions)) + virtual = make(map[api.PublicSessionId]*VirtualSessionData, len(response.VirtualSessions)) for _, s := range response.VirtualSessions { - virtual[PublicSessionId(s.SessionId)] = s + virtual[api.PublicSessionId(s.SessionId)] = s } } return } -func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId PublicSessionId, streamType StreamType) (PublicSessionId, string, net.IP, string, string, error) { +func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId api.PublicSessionId, streamType StreamType) (api.PublicSessionId, string, net.IP, string, string, error) { statsGrpcClientCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging c.logger.Printf("Get %s publisher id %s on %s", streamType, sessionId, c.Target()) @@ -311,7 +312,7 @@ func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId PublicSession return "", "", nil, "", "", err } - return PublicSessionId(response.GetPublisherId()), response.GetProxyUrl(), net.ParseIP(response.GetIp()), response.GetConnectToken(), response.GetPublisherToken(), nil + return api.PublicSessionId(response.GetPublisherId()), response.GetProxyUrl(), net.ParseIP(response.GetIp()), response.GetConnectToken(), response.GetPublisherToken(), nil } func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, error) { @@ -375,7 +376,7 @@ type ProxySessionReceiver interface { type SessionProxy struct { logger log.Logger - sessionId PublicSessionId + sessionId api.PublicSessionId receiver ProxySessionReceiver sendMu sync.Mutex @@ -421,7 +422,7 @@ func (p *SessionProxy) Close() error { return p.client.CloseSend() } -func (c *GrpcClient) ProxySession(ctx context.Context, sessionId PublicSessionId, receiver ProxySessionReceiver) (*SessionProxy, error) { +func (c *GrpcClient) ProxySession(ctx context.Context, sessionId api.PublicSessionId, receiver ProxySessionReceiver) (*SessionProxy, error) { statsGrpcClientCalls.WithLabelValues("ProxySession").Inc() md := metadata.Pairs( "sessionId", string(sessionId), diff --git a/grpc_remote_client.go b/grpc_remote_client.go index 9ba69e5..b04f52b 100644 --- a/grpc_remote_client.go +++ b/grpc_remote_client.go @@ -33,6 +33,7 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -159,20 +160,20 @@ func (c *remoteGrpcClient) SetSession(session Session) { } } -func (c *remoteGrpcClient) SendError(e *Error) bool { - message := &ServerMessage{ +func (c *remoteGrpcClient) SendError(e *api.Error) bool { + message := &api.ServerMessage{ Type: "error", Error: e, } return c.SendMessage(message) } -func (c *remoteGrpcClient) SendByeResponse(message *ClientMessage) bool { +func (c *remoteGrpcClient) SendByeResponse(message *api.ClientMessage) bool { return c.SendByeResponseWithReason(message, "") } -func (c *remoteGrpcClient) SendByeResponseWithReason(message *ClientMessage, reason string) bool { - response := &ServerMessage{ +func (c *remoteGrpcClient) SendByeResponseWithReason(message *api.ClientMessage, reason string) bool { + response := &api.ServerMessage{ Type: "bye", } if message != nil { @@ -180,7 +181,7 @@ func (c *remoteGrpcClient) SendByeResponseWithReason(message *ClientMessage, rea } if reason != "" { if response.Bye == nil { - response.Bye = &ByeServerMessage{} + response.Bye = &api.ByeServerMessage{} } response.Bye.Reason = reason } diff --git a/grpc_server.go b/grpc_server.go index e8addcb..307c091 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -38,6 +38,7 @@ import ( "google.golang.org/grpc/credentials" status "google.golang.org/grpc/status" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -60,9 +61,9 @@ func init() { } type GrpcServerHub interface { - GetSessionByResumeId(resumeId PrivateSessionId) Session - GetSessionByPublicId(sessionId PublicSessionId) Session - GetSessionIdByRoomSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) + GetSessionByResumeId(resumeId api.PrivateSessionId) Session + GetSessionByPublicId(sessionId api.PublicSessionId) Session + GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) GetRoomForBackend(roomId string, backend *Backend) *Room GetBackend(u *url.URL) *Backend @@ -136,7 +137,7 @@ func (s *GrpcServer) LookupResumeId(ctx context.Context, request *LookupResumeId statsGrpcServerCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging s.logger.Printf("Lookup session for resume id %s", request.ResumeId) - session := s.hub.GetSessionByResumeId(PrivateSessionId(request.ResumeId)) + session := s.hub.GetSessionByResumeId(api.PrivateSessionId(request.ResumeId)) if session == nil { return nil, status.Error(codes.NotFound, "no such room session id") } @@ -150,7 +151,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession statsGrpcServerCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging s.logger.Printf("Lookup session id for room session id %s", request.RoomSessionId) - sid, err := s.hub.GetSessionIdByRoomSessionId(RoomSessionId(request.RoomSessionId)) + sid, err := s.hub.GetSessionIdByRoomSessionId(api.RoomSessionId(request.RoomSessionId)) if errors.Is(err, ErrNoSuchRoomSession) { return nil, status.Error(codes.NotFound, "no such room session id") } else if err != nil { @@ -158,7 +159,7 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession } if sid != "" && request.DisconnectReason != "" { - if session := s.hub.GetSessionByPublicId(PublicSessionId(sid)); session != nil { + if session := s.hub.GetSessionByPublicId(api.PublicSessionId(sid)); session != nil { s.logger.Printf("Closing session %s because same room session %s connected", session.PublicId(), request.RoomSessionId) session.LeaveRoom(false) switch sess := session.(type) { @@ -179,7 +180,7 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa statsGrpcServerCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging s.logger.Printf("Check if session %s is in call %s on %s", request.SessionId, request.RoomId, request.BackendUrl) - session := s.hub.GetSessionByPublicId(PublicSessionId(request.SessionId)) + session := s.hub.GetSessionByPublicId(api.PublicSessionId(request.SessionId)) if session == nil { return nil, status.Error(codes.NotFound, "no such session id") } @@ -187,7 +188,7 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa result := &IsSessionInCallReply{} room := session.GetRoom() if room == nil || room.Id() != request.GetRoomId() || !room.Backend().HasUrl(request.GetBackendUrl()) || - (session.ClientType() != HelloClientTypeInternal && !room.IsSessionInCall(session)) { + (session.ClientType() != api.HelloClientTypeInternal && !room.IsSessionInCall(session)) { // Recipient is not in a room, a different room or not in the call. result.InCall = false } else { @@ -265,7 +266,7 @@ func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherId statsGrpcServerCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging s.logger.Printf("Get %s publisher id for session %s", request.StreamType, request.SessionId) - session := s.hub.GetSessionByPublicId(PublicSessionId(request.SessionId)) + session := s.hub.GetSessionByPublicId(api.PublicSessionId(request.SessionId)) if session == nil { return nil, status.Error(codes.NotFound, "no such session") } diff --git a/hub.go b/hub.go index 9a3783c..8f41a36 100644 --- a/hub.go +++ b/hub.go @@ -61,30 +61,28 @@ import ( var ( // HelloExpected is returned if a client sends a message before the "hello" request. - HelloExpected = NewError("hello_expected", "Expected Hello request.") - // InvalidHelloVersion is returned if the version in the "hello" message is not supported. - InvalidHelloVersion = NewError("invalid_hello_version", "The hello version is not supported.") + HelloExpected = api.NewError("hello_expected", "Expected Hello request.") // UserAuthFailed is returned if the Talk response to a v1 hello is not an error but also not a valid auth response. - UserAuthFailed = NewError("auth_failed", "The user could not be authenticated.") + UserAuthFailed = api.NewError("auth_failed", "The user could not be authenticated.") // RoomJoinFailed is returned if the Talk response to a room join request is not an error and not a valid join response. - RoomJoinFailed = NewError("room_join_failed", "Could not join the room.") + RoomJoinFailed = api.NewError("room_join_failed", "Could not join the room.") // InvalidClientType is returned if the client type in the "hello" request is not supported. - InvalidClientType = NewError("invalid_client_type", "The client type is not supported.") + InvalidClientType = api.NewError("invalid_client_type", "The client type is not supported.") // InvalidBackendUrl is returned if no backend is configured for URL in the "hello" request. - InvalidBackendUrl = NewError("invalid_backend", "The backend URL is not supported.") + InvalidBackendUrl = api.NewError("invalid_backend", "The backend URL is not supported.") // InvalidToken is returned if the token in a "hello" request could not be validated. - InvalidToken = NewError("invalid_token", "The passed token is invalid.") + InvalidToken = api.NewError("invalid_token", "The passed token is invalid.") // NoSuchSession is returned if the session to be resumed is unknown or expired. - NoSuchSession = NewError("no_such_session", "The session to resume does not exist.") + NoSuchSession = api.NewError("no_such_session", "The session to resume does not exist.") // TokenNotValidYet is returned if the token in a "hello" request could be authenticated but is not valid yet. // This hints to a mismatch in the time between the server running Talk and the server running the signaling server. - TokenNotValidYet = NewError("token_not_valid_yet", "The token is not valid yet.") + TokenNotValidYet = api.NewError("token_not_valid_yet", "The token is not valid yet.") // TokenExpired is returned if the token in a "hello" request could be authenticated but is expired. // This hints to a mismatch in the time between the server running Talk and the server running the signaling server, // but could also be a client trying to connect with an old token. - TokenExpired = NewError("token_expired", "The token is expired.") + TokenExpired = api.NewError("token_expired", "The token is expired.") // TooManyRequests is returned if brute force detection reports too many failed "hello" requests. - TooManyRequests = NewError("too_many_requests", "Too many requests.") + TooManyRequests = api.NewError("too_many_requests", "Too many requests.") // Maximum number of concurrent requests to a backend. defaultMaxConcurrentRequestsPerHost = 8 @@ -148,9 +146,9 @@ type Hub struct { events AsyncEvents upgrader websocket.Upgrader sessionIds *SessionIdCodec - info *WelcomeServerMessage - infoInternal *WelcomeServerMessage - welcome atomic.Value // *ServerMessage + info *api.WelcomeServerMessage + infoInternal *api.WelcomeServerMessage + welcome atomic.Value // *api.ServerMessage closer *internal.Closer readPumpActive atomic.Int32 @@ -178,7 +176,7 @@ type Hub struct { roomSessions RoomSessions roomPing *RoomPing // +checklocks:mu - virtualSessions map[PublicSessionId]uint64 + virtualSessions map[api.PublicSessionId]uint64 decodeCaches []*container.LruCache[*SessionIdData] @@ -372,8 +370,8 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, }, }, sessionIds: sessionIds, - info: NewWelcomeServerMessage(version, DefaultFeatures...), - infoInternal: NewWelcomeServerMessage(version, DefaultFeaturesInternal...), + info: api.NewWelcomeServerMessage(version, api.DefaultFeatures...), + infoInternal: api.NewWelcomeServerMessage(version, api.DefaultFeaturesInternal...), closer: internal.NewCloser(), shutdown: internal.NewCloser(), @@ -389,7 +387,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, roomSessions: roomSessions, roomPing: roomPing, - virtualSessions: make(map[PublicSessionId]uint64), + virtualSessions: make(map[api.PublicSessionId]uint64), decodeCaches: decodeCaches, @@ -447,9 +445,9 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, if len(geoipOverrides) > 0 { hub.geoipOverrides.Store(&geoipOverrides) } - hub.setWelcomeMessage(&ServerMessage{ + hub.setWelcomeMessage(&api.ServerMessage{ Type: "welcome", - Welcome: NewWelcomeServerMessage(version, DefaultWelcomeFeatures...), + Welcome: api.NewWelcomeServerMessage(version, api.DefaultWelcomeFeatures...), }) backend.hub = hub if rpcServer != nil { @@ -463,12 +461,12 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, return hub, nil } -func (h *Hub) setWelcomeMessage(msg *ServerMessage) { +func (h *Hub) setWelcomeMessage(msg *api.ServerMessage) { h.welcome.Store(msg) } -func (h *Hub) getWelcomeMessage() *ServerMessage { - return h.welcome.Load().(*ServerMessage) +func (h *Hub) getWelcomeMessage() *api.ServerMessage { + return h.welcome.Load().(*api.ServerMessage) } func (h *Hub) SetMcu(mcu Mcu) { @@ -476,16 +474,16 @@ func (h *Hub) SetMcu(mcu Mcu) { // Create copy of message so it can be updated concurrently. welcome := *h.getWelcomeMessage() if mcu == nil { - h.info.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) - h.infoInternal.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) + h.info.RemoveFeature(api.ServerFeatureMcu, api.ServerFeatureSimulcast, api.ServerFeatureUpdateSdp) + h.infoInternal.RemoveFeature(api.ServerFeatureMcu, api.ServerFeatureSimulcast, api.ServerFeatureUpdateSdp) - welcome.Welcome.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) + welcome.Welcome.RemoveFeature(api.ServerFeatureMcu, api.ServerFeatureSimulcast, api.ServerFeatureUpdateSdp) } else { h.logger.Printf("Using a timeout of %s for MCU requests", h.mcuTimeout) - h.info.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) - h.infoInternal.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) + h.info.AddFeature(api.ServerFeatureMcu, api.ServerFeatureSimulcast, api.ServerFeatureUpdateSdp) + h.infoInternal.AddFeature(api.ServerFeatureMcu, api.ServerFeatureSimulcast, api.ServerFeatureUpdateSdp) - welcome.Welcome.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp) + welcome.Welcome.AddFeature(api.ServerFeatureMcu, api.ServerFeatureSimulcast, api.ServerFeatureUpdateSdp) } h.setWelcomeMessage(&welcome) } @@ -495,8 +493,8 @@ func (h *Hub) checkOrigin(r *http.Request) bool { return true } -func (h *Hub) GetServerInfo(session Session) *WelcomeServerMessage { - if session.ClientType() == HelloClientTypeInternal { +func (h *Hub) GetServerInfo(session Session) *api.WelcomeServerMessage { + if session.ClientType() == api.HelloClientTypeInternal { return h.infoInternal } @@ -633,11 +631,11 @@ func (h *Hub) getDecodeCache(cache_key string) *container.LruCache[*SessionIdDat return h.decodeCaches[idx] } -func (h *Hub) invalidatePublicSessionId(id PublicSessionId) { +func (h *Hub) invalidatePublicSessionId(id api.PublicSessionId) { h.invalidateSessionId(string(id)) } -func (h *Hub) invalidatePrivateSessionId(id PrivateSessionId) { +func (h *Hub) invalidatePrivateSessionId(id api.PrivateSessionId) { h.invalidateSessionId(string(id)) } @@ -650,11 +648,11 @@ func (h *Hub) invalidateSessionId(id string) { cache.Remove(id) } -func (h *Hub) setDecodedPublicSessionId(id PublicSessionId, data *SessionIdData) { +func (h *Hub) setDecodedPublicSessionId(id api.PublicSessionId, data *SessionIdData) { h.setDecodedSessionId(string(id), data) } -func (h *Hub) setDecodedPrivateSessionId(id PrivateSessionId, data *SessionIdData) { +func (h *Hub) setDecodedPrivateSessionId(id api.PrivateSessionId, data *SessionIdData) { h.setDecodedSessionId(string(id), data) } @@ -667,7 +665,7 @@ func (h *Hub) setDecodedSessionId(id string, data *SessionIdData) { cache.Set(id, data) } -func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { +func (h *Hub) decodePrivateSessionId(id api.PrivateSessionId) *SessionIdData { if len(id) == 0 { return nil } @@ -687,7 +685,7 @@ func (h *Hub) decodePrivateSessionId(id PrivateSessionId) *SessionIdData { return data } -func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { +func (h *Hub) decodePublicSessionId(id api.PublicSessionId) *SessionIdData { if len(id) == 0 { return nil } @@ -707,7 +705,7 @@ func (h *Hub) decodePublicSessionId(id PublicSessionId) *SessionIdData { return data } -func (h *Hub) GetSessionByPublicId(sessionId PublicSessionId) Session { +func (h *Hub) GetSessionByPublicId(sessionId api.PublicSessionId) Session { data := h.decodePublicSessionId(sessionId) if data == nil { return nil @@ -723,7 +721,7 @@ func (h *Hub) GetSessionByPublicId(sessionId PublicSessionId) Session { return session } -func (h *Hub) GetSessionByResumeId(resumeId PrivateSessionId) Session { +func (h *Hub) GetSessionByResumeId(resumeId api.PrivateSessionId) Session { data := h.decodePrivateSessionId(resumeId) if data == nil { return nil @@ -739,7 +737,7 @@ func (h *Hub) GetSessionByResumeId(resumeId PrivateSessionId) Session { return session } -func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) { +func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { return h.roomSessions.GetSessionId(roomSessionId) } @@ -866,7 +864,7 @@ func (h *Hub) hasSessionsLocked(withInternal bool) bool { } for _, s := range h.sessions { - if s.ClientType() != HelloClientTypeInternal { + if s.ClientType() != api.HelloClientTypeInternal { return true } } @@ -883,7 +881,7 @@ func (h *Hub) startWaitAnonymousSessionRoom(session *ClientSession) { // +checklocks:h.mu func (h *Hub) startWaitAnonymousSessionRoomLocked(session *ClientSession) { - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == api.HelloClientTypeInternal { // Internal clients don't need to join a room. return } @@ -956,7 +954,7 @@ func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData { return sessionIdData } -func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend *Backend, auth *BackendClientResponse) { +func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backend *Backend, auth *BackendClientResponse) { if !c.IsConnected() { // Client disconnected while waiting for "hello" response. return @@ -992,7 +990,7 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * userId := auth.Auth.UserId if userId != "" { h.logger.Printf("Register user %s@%s from %s in %s (%s) %s (private=%s)", userId, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) - } else if message.Hello.Auth.Type != HelloClientTypeClient { + } else if message.Hello.Auth.Type != api.HelloClientTypeClient { h.logger.Printf("Register %s@%s from %s in %s (%s) %s (private=%s)", message.Hello.Auth.Type, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) } else { h.logger.Printf("Register anonymous@%s from %s in %s (%s) %s (private=%s)", backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId) @@ -1057,9 +1055,9 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend * h.sessions[sessionIdData.Sid] = session h.clients[sessionIdData.Sid] = client delete(h.expectHelloClients, client) - if userId == "" && session.ClientType() != HelloClientTypeInternal { + if userId == "" && session.ClientType() != api.HelloClientTypeInternal { h.startWaitAnonymousSessionRoomLocked(session) - } else if session.ClientType() == HelloClientTypeInternal && session.HasFeature(ClientFeatureStartDialout) { + } else if session.ClientType() == api.HelloClientTypeInternal && session.HasFeature(api.ClientFeatureStartDialout) { // TODO: There is a small race condition for sessions that take some time // between connecting and joining a room. h.dialoutSessions[session] = true @@ -1102,7 +1100,7 @@ func (h *Hub) processUnregister(client HandlerClient) Session { } func (h *Hub) processMessage(client HandlerClient, data []byte) { - var message ClientMessage + var message api.ClientMessage if err := message.UnmarshalJSON(data); err != nil { if session := client.GetSession(); session != nil { h.logger.Printf("Error decoding message from client %s: %v", session.PublicId(), err) @@ -1117,14 +1115,14 @@ func (h *Hub) processMessage(client HandlerClient, data []byte) { if err := message.CheckValid(); err != nil { if session := client.GetSession(); session != nil { h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) - if err, ok := err.(*Error); ok { + if err, ok := err.(*api.Error); ok { session.SendMessage(message.NewErrorServerMessage(err)) } else { session.SendMessage(message.NewErrorServerMessage(InvalidFormat)) } } else { h.logger.Printf("Invalid message %+v from %s: %v", message, client.RemoteAddr(), err) - if err, ok := err.(*Error); ok { + if err, ok := err.(*api.Error); ok { client.SendMessage(message.NewErrorServerMessage(err)) } else { client.SendMessage(message.NewErrorServerMessage(InvalidFormat)) @@ -1178,11 +1176,11 @@ func (h *Hub) processMessage(client HandlerClient, data []byte) { } } -func (h *Hub) sendHelloResponse(session *ClientSession, message *ClientMessage) bool { - response := &ServerMessage{ +func (h *Hub) sendHelloResponse(session *ClientSession, message *api.ClientMessage) bool { + response := &api.ServerMessage{ Id: message.Id, Type: "hello", - Hello: &HelloServerMessage{ + Hello: &api.HelloServerMessage{ Version: message.Hello.Version, SessionId: session.PublicId(), ResumeId: session.PrivateId(), @@ -1198,7 +1196,7 @@ type remoteClientInfo struct { response *LookupResumeIdReply } -func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message *ClientMessage) bool { +func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, message *api.ClientMessage) bool { client, ok := c.(*Client) if !ok { return false @@ -1254,7 +1252,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message return false } - rs, err := NewRemoteSession(h, client, info.client, PublicSessionId(info.response.SessionId)) + rs, err := NewRemoteSession(h, client, info.client, api.PublicSessionId(info.response.SessionId)) if err != nil { h.logger.Printf("Could not create remote session %s on %s: %s", info.response.SessionId, info.client.Target(), err) return false @@ -1274,7 +1272,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId PrivateSessionId, message return true } -func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { +func (h *Hub) processHello(client HandlerClient, message *api.ClientMessage) { ctx := log.NewLoggerContext(client.Context(), h.logger) resumeId := message.Hello.ResumeId if resumeId != "" { @@ -1354,11 +1352,11 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { h.mu.Unlock() switch message.Hello.Auth.Type { - case HelloClientTypeClient: + case api.HelloClientTypeClient: fallthrough - case HelloClientTypeFederation: + case api.HelloClientTypeFederation: h.processHelloClient(client, message) - case HelloClientTypeInternal: + case api.HelloClientTypeInternal: h.processHelloInternal(client, message) default: h.startExpectHello(client) @@ -1366,8 +1364,8 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { } } -func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *ClientMessage) (*Backend, *BackendClientResponse, error) { - url := message.Hello.Auth.parsedUrl +func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*Backend, *BackendClientResponse, error) { + url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { return nil, nil, InvalidBackendUrl @@ -1390,8 +1388,8 @@ func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message return backend, &auth, nil } -func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *ClientMessage) (*Backend, *BackendClientResponse, error) { - url := message.Hello.Auth.parsedUrl +func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*Backend, *BackendClientResponse, error) { + url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { return nil, nil, InvalidBackendUrl @@ -1400,16 +1398,16 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message var tokenString string var tokenClaims jwt.Claims switch message.Hello.Auth.Type { - case HelloClientTypeClient: - tokenString = message.Hello.Auth.helloV2Params.Token - tokenClaims = &HelloV2TokenClaims{} - case HelloClientTypeFederation: + case api.HelloClientTypeClient: + tokenString = message.Hello.Auth.HelloV2Params.Token + tokenClaims = &api.HelloV2TokenClaims{} + case api.HelloClientTypeFederation: if !h.backend.capabilities.HasCapabilityFeature(ctx, url, talk.FeatureFederationV2) { return nil, nil, ErrFederationNotSupported } - tokenString = message.Hello.Auth.federationParams.Token - tokenClaims = &FederationTokenClaims{} + tokenString = message.Hello.Auth.FederationParams.Token + tokenClaims = &api.FederationTokenClaims{} default: return nil, nil, InvalidClientType } @@ -1494,16 +1492,16 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return nil, nil, InvalidToken } - var authTokenClaims AuthTokenClaims + var authTokenClaims api.AuthTokenClaims switch message.Hello.Auth.Type { - case HelloClientTypeClient: - claims, ok := token.Claims.(*HelloV2TokenClaims) + case api.HelloClientTypeClient: + claims, ok := token.Claims.(*api.HelloV2TokenClaims) if !ok || !token.Valid { return nil, nil, InvalidToken } authTokenClaims = claims - case HelloClientTypeFederation: - claims, ok := token.Claims.(*FederationTokenClaims) + case api.HelloClientTypeFederation: + claims, ok := token.Claims.(*api.FederationTokenClaims) if !ok || !token.Valid { return nil, nil, InvalidToken } @@ -1543,27 +1541,27 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return backend, auth, nil } -func (h *Hub) processHelloClient(client HandlerClient, message *ClientMessage) { +func (h *Hub) processHelloClient(client HandlerClient, message *api.ClientMessage) { // Make sure the client must send another "hello" in case of errors. defer h.startExpectHello(client) - var authFunc func(context.Context, HandlerClient, *ClientMessage) (*Backend, *BackendClientResponse, error) + var authFunc func(context.Context, HandlerClient, *api.ClientMessage) (*Backend, *BackendClientResponse, error) switch message.Hello.Version { - case HelloVersionV1: + case api.HelloVersionV1: // Auth information contains a ticket that must be validated against the // Nextcloud instance. authFunc = h.processHelloV1 - case HelloVersionV2: + case api.HelloVersionV2: // Auth information contains a JWT that contains all information of the user. authFunc = h.processHelloV2 default: - client.SendMessage(message.NewErrorServerMessage(InvalidHelloVersion)) + client.SendMessage(message.NewErrorServerMessage(api.InvalidHelloVersion)) return } backend, auth, err := authFunc(client.Context(), client, message) if err != nil { - if e, ok := err.(*Error); ok { + if e, ok := err.(*api.Error); ok { client.SendMessage(message.NewErrorServerMessage(e)) } else { client.SendMessage(message.NewWrappedErrorServerMessage(err)) @@ -1574,7 +1572,7 @@ func (h *Hub) processHelloClient(client HandlerClient, message *ClientMessage) { h.processRegister(client, message, backend, auth) } -func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) { +func (h *Hub) processHelloInternal(client HandlerClient, message *api.ClientMessage) { defer h.startExpectHello(client) if len(h.internalClientsSecret) == 0 { client.SendMessage(message.NewErrorServerMessage(InvalidClientType)) @@ -1593,17 +1591,17 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) } // Validate internal connection. - rnd := message.Hello.Auth.internalParams.Random + rnd := message.Hello.Auth.InternalParams.Random mac := hmac.New(sha256.New, h.internalClientsSecret) mac.Write([]byte(rnd)) // nolint check := hex.EncodeToString(mac.Sum(nil)) - if len(rnd) < minTokenRandomLength || check != message.Hello.Auth.internalParams.Token { + if len(rnd) < minTokenRandomLength || check != message.Hello.Auth.InternalParams.Token { throttle(ctx) client.SendMessage(message.NewErrorServerMessage(InvalidToken)) return } - backend := h.backend.GetBackend(message.Hello.Auth.internalParams.parsedBackend) + backend := h.backend.GetBackend(message.Hello.Auth.InternalParams.ParsedBackend) if backend == nil { throttle(ctx) client.SendMessage(message.NewErrorServerMessage(InvalidBackendUrl)) @@ -1617,7 +1615,7 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *ClientMessage) h.processRegister(client, message, backend, auth) } -func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId RoomSessionId, backend *Backend) { +func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId, backend *Backend) { sessionId, err := h.roomSessions.LookupSessionId(ctx, roomSessionId, "room_session_reconnected") if err == ErrNoSuchRoomSession { return @@ -1632,9 +1630,9 @@ func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId RoomS // but send "bye" again as additional safeguard. msg := &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Type: "bye", - Bye: &ByeServerMessage{ + Bye: &api.ByeServerMessage{ Reason: "room_session_reconnected", }, }, @@ -1656,19 +1654,19 @@ func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId RoomS session.Close() } -func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Room) bool { - response := &ServerMessage{ +func (h *Hub) sendRoom(session *ClientSession, message *api.ClientMessage, room *Room) bool { + response := &api.ServerMessage{ Type: "room", } if message != nil { response.Id = message.Id } if room == nil { - response.Room = &RoomServerMessage{ + response.Room = &api.RoomServerMessage{ RoomId: "", } } else { - response.Room = &RoomServerMessage{ + response.Room = &api.RoomServerMessage{ RoomId: room.id, Properties: room.Properties(), } @@ -1704,7 +1702,7 @@ func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Roo } if maxStreamBitrate != 0 || maxScreenBitrate != 0 { - response.Room.Bandwidth = &RoomBandwidth{ + response.Room.Bandwidth = &api.RoomBandwidth{ MaxStreamBitrate: maxStreamBitrate, MaxScreenBitrate: maxScreenBitrate, } @@ -1713,7 +1711,7 @@ func (h *Hub) sendRoom(session *ClientSession, message *ClientMessage, room *Roo return session.SendMessage(response) } -func (h *Hub) processRoom(sess Session, message *ClientMessage) { +func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { session, ok := sess.(*ClientSession) if !ok { return @@ -1723,7 +1721,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if roomId == "" { // We can handle leaving a room directly. if session.LeaveRoomWithMessage(true, message) != nil { - if session.UserId() == "" && session.ClientType() != HelloClientTypeInternal { + if session.UserId() == "" && session.ClientType() != api.HelloClientTypeInternal { h.startWaitAnonymousSessionRoom(session) } // User was in a room before, so need to notify about leaving it. @@ -1762,7 +1760,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if session.UserId() == "" && client == nil { h.startWaitAnonymousSessionRoom(session) } - var ae *Error + var ae *api.Error if errors.As(err, &ae) { session.SendMessage(message.NewErrorServerMessage(ae)) return @@ -1800,7 +1798,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { h.logger.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), + api.NewErrorDetail("federation_error", "Failed to create federation client.", details), )) return } @@ -1811,12 +1809,12 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if roomSessionId == "" { // TODO(jojo): Better make the session id required in the request. h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) - roomSessionId = RoomSessionId(session.PublicId()) + roomSessionId = api.RoomSessionId(session.PublicId()) } // Prefix room session id to allow using the same signaling server for two Nextcloud instances during development. // Otherwise the same room session id will be detected and the other session will be kicked. - if err := session.UpdateRoomSessionId(FederatedRoomSessionIdPrefix + roomSessionId); err != nil { + if err := session.UpdateRoomSessionId(api.FederatedRoomSessionIdPrefix + roomSessionId); err != nil { h.logger.Printf("Error updating room session id for session %s: %s", session.PublicId(), err) } @@ -1833,15 +1831,15 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if roomSessionId == "" { // TODO(jojo): Better make the session id required in the request. h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) - roomSessionId = RoomSessionId(session.PublicId()) + roomSessionId = api.RoomSessionId(session.PublicId()) } if err := session.UpdateRoomSessionId(roomSessionId); err != nil { h.logger.Printf("Error updating room session id for session %s: %s", session.PublicId(), err) } session.SendMessage(message.NewErrorServerMessage( - NewErrorDetail("already_joined", "Already joined this room.", &RoomErrorDetails{ - Room: &RoomServerMessage{ + api.NewErrorDetail("already_joined", "Already joined this room.", &api.RoomErrorDetails{ + Room: &api.RoomServerMessage{ RoomId: room.id, Properties: room.Properties(), }, @@ -1851,7 +1849,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { } var room BackendClientResponse - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == api.HelloClientTypeInternal { // Internal clients can join any room. room = BackendClientResponse{ Type: "room", @@ -1868,7 +1866,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { if sessionId == "" { // TODO(jojo): Better make the session id required in the request. h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) - sessionId = RoomSessionId(session.PublicId()) + sessionId = api.RoomSessionId(session.PublicId()) } request := NewBackendClientRoomRequest(roomId, session.UserId(), sessionId) request.Room.UpdateFromSession(session) @@ -1914,7 +1912,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { continue } - var sid RoomSessionId + var sid api.RoomSessionId var uid string // Use Nextcloud session id and user id if sid = session.RoomSessionId().WithoutFederation(); sid == "" { @@ -2008,7 +2006,7 @@ func (h *Hub) createRoomLocked(id string, properties json.RawMessage, backend *B return room, nil } -func (h *Hub) processJoinRoom(session *ClientSession, message *ClientMessage, room *BackendClientResponse) { +func (h *Hub) processJoinRoom(session *ClientSession, message *api.ClientMessage, room *BackendClientResponse) { if room.Type == "error" { session.SendMessage(message.NewErrorServerMessage(room.Error)) return @@ -2049,7 +2047,7 @@ func (h *Hub) processJoinRoom(session *ClientSession, message *ClientMessage, ro h.mu.Lock() // The session now joined a room, don't expire if it is anonymous. delete(h.anonymousSessions, session) - if session.ClientType() == HelloClientTypeInternal && session.HasFeature(ClientFeatureStartDialout) { + if session.ClientType() == api.HelloClientTypeInternal && session.HasFeature(api.ClientFeatureStartDialout) { // An internal session in a room can not be used for dialout. delete(h.dialoutSessions, session) } @@ -2062,7 +2060,7 @@ func (h *Hub) processJoinRoom(session *ClientSession, message *ClientMessage, ro r.AddSession(session, room.Room.Session) } -func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { +func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { session, ok := sess.(*ClientSession) if !ok { // Client is not connected yet. @@ -2072,19 +2070,19 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { msg := message.Message var recipient *ClientSession var subject string - var clientData *MessageClientMessageData - var serverRecipient *MessageClientMessageRecipient - var recipientSessionId PublicSessionId + var clientData *api.MessageClientMessageData + var serverRecipient *api.MessageClientMessageRecipient + var recipientSessionId api.PublicSessionId var room *Room switch msg.Recipient.Type { - case RecipientTypeSession: + case api.RecipientTypeSession: if h.mcu != nil { // Maybe this is a message to be processed by the MCU. - var data MessageClientMessageData + var data api.MessageClientMessageData if err := data.UnmarshalJSON(msg.Data); err == nil { if err := data.CheckValid(); err != nil { h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) - if err, ok := err.(*Error); ok { + if err, ok := err.(*api.Error); ok { session.SendMessage(message.NewErrorServerMessage(err)) } else { session.SendMessage(message.NewErrorServerMessage(InvalidFormat)) @@ -2161,14 +2159,14 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } // Send to client connection for virtual sessions. - if sess.ClientType() == HelloClientTypeVirtual { + if sess.ClientType() == api.HelloClientTypeVirtual { virtualSession := sess.(*VirtualSession) clientSession := virtualSession.Session() subject = GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) recipientSessionId = clientSession.PublicId() recipient = clientSession // The client should see his session id as recipient. - serverRecipient = &MessageClientMessageRecipient{ + serverRecipient = &api.MessageClientMessageRecipient{ Type: "session", SessionId: virtualSession.SessionId(), } @@ -2178,7 +2176,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { recipientSessionId = msg.Recipient.SessionId serverRecipient = &msg.Recipient } - case RecipientTypeUser: + case api.RecipientTypeUser: if msg.Recipient.UserId != "" { if msg.Recipient.UserId == session.UserId() { // Don't loop messages to the sender. @@ -2189,19 +2187,19 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { subject = GetSubjectForUserId(msg.Recipient.UserId, session.Backend()) } - case RecipientTypeRoom: + case api.RecipientTypeRoom: fallthrough - case RecipientTypeCall: + case api.RecipientTypeCall: if session != nil { if room = session.GetRoom(); room != nil { subject = GetSubjectForRoomId(room.Id(), room.Backend()) if h.mcu != nil { - var data MessageClientMessageData + var data api.MessageClientMessageData if err := data.UnmarshalJSON(msg.Data); err == nil { if err := data.CheckValid(); err != nil { h.logger.Printf("Invalid message %+v from client %s: %v", message, session.PublicId(), err) - if err, ok := err.(*Error); ok { + if err, ok := err.(*api.Error); ok { session.SendMessage(message.NewErrorServerMessage(err)) } else { session.SendMessage(message.NewErrorServerMessage(InvalidFormat)) @@ -2220,10 +2218,10 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { return } - response := &ServerMessage{ + response := &api.ServerMessage{ Type: "message", - Message: &MessageServerMessage{ - Sender: &MessageServerMessageSender{ + Message: &api.MessageServerMessage{ + Sender: &api.MessageServerMessageSender{ Type: msg.Recipient.Type, SessionId: session.PublicId(), UserId: session.UserId(), @@ -2307,13 +2305,13 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } var err error switch msg.Recipient.Type { - case RecipientTypeSession: + case api.RecipientTypeSession: err = h.events.PublishSessionMessage(recipientSessionId, session.Backend(), async) - case RecipientTypeUser: + case api.RecipientTypeUser: err = h.events.PublishUserMessage(msg.Recipient.UserId, session.Backend(), async) - case RecipientTypeRoom: + case api.RecipientTypeRoom: fallthrough - case RecipientTypeCall: + case api.RecipientTypeCall: err = h.events.PublishRoomMessage(room.Id(), session.Backend(), async) default: err = fmt.Errorf("unsupported recipient type: %s", msg.Recipient.Type) @@ -2326,7 +2324,7 @@ func (h *Hub) processMessageMsg(sess Session, message *ClientMessage) { } func isAllowedToControl(session Session) bool { - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == api.HelloClientTypeInternal { // Internal clients are allowed to send any control message. return true } @@ -2339,7 +2337,7 @@ func isAllowedToControl(session Session) bool { return false } -func (h *Hub) processControlMsg(session Session, message *ClientMessage) { +func (h *Hub) processControlMsg(session Session, message *api.ClientMessage) { msg := message.Control if !isAllowedToControl(session) { h.logger.Printf("Ignore control message %+v from %s", msg, session.PublicId()) @@ -2348,11 +2346,11 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { var recipient *ClientSession var subject string - var serverRecipient *MessageClientMessageRecipient - var recipientSessionId PublicSessionId + var serverRecipient *api.MessageClientMessageRecipient + var recipientSessionId api.PublicSessionId var room *Room switch msg.Recipient.Type { - case RecipientTypeSession: + case api.RecipientTypeSession: data := h.decodePublicSessionId(msg.Recipient.SessionId) if data != nil { if msg.Recipient.SessionId == session.PublicId() { @@ -2370,14 +2368,14 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { } // Send to client connection for virtual sessions. - if sess.ClientType() == HelloClientTypeVirtual { + if sess.ClientType() == api.HelloClientTypeVirtual { virtualSession := sess.(*VirtualSession) clientSession := virtualSession.Session() subject = GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) recipientSessionId = clientSession.PublicId() recipient = clientSession // The client should see his session id as recipient. - serverRecipient = &MessageClientMessageRecipient{ + serverRecipient = &api.MessageClientMessageRecipient{ Type: "session", SessionId: virtualSession.SessionId(), } @@ -2389,7 +2387,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { } else { serverRecipient = &msg.Recipient } - case RecipientTypeUser: + case api.RecipientTypeUser: if msg.Recipient.UserId != "" { if msg.Recipient.UserId == session.UserId() { // Don't loop messages to the sender. @@ -2400,9 +2398,9 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { subject = GetSubjectForUserId(msg.Recipient.UserId, session.Backend()) } - case RecipientTypeRoom: + case api.RecipientTypeRoom: fallthrough - case RecipientTypeCall: + case api.RecipientTypeCall: if session != nil { if room = session.GetRoom(); room != nil { subject = GetSubjectForRoomId(room.Id(), room.Backend()) @@ -2414,10 +2412,10 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { return } - response := &ServerMessage{ + response := &api.ServerMessage{ Type: "control", - Control: &ControlServerMessage{ - Sender: &MessageServerMessageSender{ + Control: &api.ControlServerMessage{ + Sender: &api.MessageServerMessageSender{ Type: msg.Recipient.Type, SessionId: session.PublicId(), UserId: session.UserId(), @@ -2435,13 +2433,13 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { } var err error switch msg.Recipient.Type { - case RecipientTypeSession: + case api.RecipientTypeSession: err = h.events.PublishSessionMessage(recipientSessionId, session.Backend(), async) - case RecipientTypeUser: + case api.RecipientTypeUser: err = h.events.PublishUserMessage(msg.Recipient.UserId, session.Backend(), async) - case RecipientTypeRoom: + case api.RecipientTypeRoom: fallthrough - case RecipientTypeCall: + case api.RecipientTypeCall: err = h.events.PublishRoomMessage(room.Id(), room.Backend(), async) default: err = fmt.Errorf("unsupported recipient type: %s", msg.Recipient.Type) @@ -2452,13 +2450,13 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) { } } -func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { +func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { msg := message.Internal session, ok := sess.(*ClientSession) if !ok { // Client is not connected yet. return - } else if session.ClientType() != HelloClientTypeInternal { + } else if session.ClientType() != api.HelloClientTypeInternal { h.logger.Printf("Ignore internal message %+v from %s", msg, session.PublicId()) return } @@ -2496,13 +2494,13 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { sess, err := NewVirtualSession(session, privateSessionId, publicSessionId, sessionIdData, msg) if err != nil { h.logger.Printf("Could not create virtual session %s: %s", virtualSessionId, err) - reply := message.NewErrorServerMessage(NewError("add_failed", "Could not create virtual session.")) + reply := message.NewErrorServerMessage(api.NewError("add_failed", "Could not create virtual session.")) session.SendMessage(reply) return } if options := msg.Options; options != nil && options.ActorId != "" && options.ActorType != "" { - request := NewBackendClientRoomRequest(room.Id(), msg.UserId, RoomSessionId(publicSessionId)) + request := NewBackendClientRoomRequest(room.Id(), msg.UserId, api.RoomSessionId(publicSessionId)) request.Room.ActorId = options.ActorId request.Room.ActorType = options.ActorType request.Room.InCall = sess.GetInCall() @@ -2511,7 +2509,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() h.logger.Printf("Could not join virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) - reply := message.NewErrorServerMessage(NewError("add_failed", "Could not join virtual session.")) + reply := message.NewErrorServerMessage(api.NewError("add_failed", "Could not join virtual session.")) session.SendMessage(reply) return } @@ -2519,7 +2517,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { if response.Type == "error" { sess.Close() h.logger.Printf("Could not join virtual session %s at backend %s: %+v", virtualSessionId, session.BackendUrl(), response.Error) - reply := message.NewErrorServerMessage(NewError("add_failed", response.Error.Error())) + reply := message.NewErrorServerMessage(api.NewError("add_failed", response.Error.Error())) session.SendMessage(reply) return } @@ -2529,7 +2527,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() h.logger.Printf("Could not add virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) - reply := message.NewErrorServerMessage(NewError("add_failed", "Could not add virtual session.")) + reply := message.NewErrorServerMessage(api.NewError("add_failed", "Could not add virtual session.")) session.SendMessage(reply) return } @@ -2633,7 +2631,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { }, }, } - if msg.Dialout.Status.Status == DialoutStatusCleared || msg.Dialout.Status.Status == DialoutStatusRejected { + if msg.Dialout.Status.Status == api.DialoutStatusCleared || msg.Dialout.Status.Status == api.DialoutStatusRejected { asyncMessage.Room.Transient.TTL = removeCallStatusTTL } if err := h.events.PublishBackendRoomMessage(roomId, session.Backend(), asyncMessage); err != nil { @@ -2642,7 +2640,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { } else { if err := h.events.PublishRoomMessage(roomId, session.Backend(), &AsyncMessage{ Type: "message", - Message: &ServerMessage{ + Message: &api.ServerMessage{ Type: "dialout", Dialout: msg.Dialout, }, @@ -2657,7 +2655,7 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) { } func isAllowedToUpdateTransientData(session Session) bool { - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == api.HelloClientTypeInternal { // Internal clients are always allowed. return true } @@ -2670,7 +2668,7 @@ func isAllowedToUpdateTransientData(session Session) bool { } func isAllowedToUpdateTransientDataKey(session Session, key string) bool { - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == api.HelloClientTypeInternal { // Internal clients may update all transient keys. return true } @@ -2683,10 +2681,10 @@ func isAllowedToUpdateTransientDataKey(session Session, key string) bool { return true } -func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { +func (h *Hub) processTransientMsg(session Session, message *api.ClientMessage) { room := session.GetRoom() if room == nil { - response := message.NewErrorServerMessage(NewError("not_in_room", "No room joined yet.")) + response := message.NewErrorServerMessage(api.NewError("not_in_room", "No room joined yet.")) session.SendMessage(response) return } @@ -2722,27 +2720,27 @@ func (h *Hub) processTransientMsg(session Session, message *ClientMessage) { return } default: - response := message.NewErrorServerMessage(NewError("ignored", "Unsupported message type.")) + response := message.NewErrorServerMessage(api.NewError("ignored", "Unsupported message type.")) session.SendMessage(response) } } -func sendNotAllowed(session Session, message *ClientMessage, reason string) { - response := message.NewErrorServerMessage(NewError("not_allowed", reason)) +func sendNotAllowed(session Session, message *api.ClientMessage, reason string) { + response := message.NewErrorServerMessage(api.NewError("not_allowed", reason)) session.SendMessage(response) } -func sendMcuClientNotFound(session Session, message *ClientMessage) { - response := message.NewErrorServerMessage(NewError("client_not_found", "No MCU client found to send message to.")) +func sendMcuClientNotFound(session Session, message *api.ClientMessage) { + response := message.NewErrorServerMessage(api.NewError("client_not_found", "No MCU client found to send message to.")) session.SendMessage(response) } -func sendMcuProcessingFailed(session Session, message *ClientMessage) { - response := message.NewErrorServerMessage(NewError("processing_failed", "Processing of the message failed, please check server logs.")) +func sendMcuProcessingFailed(session Session, message *api.ClientMessage) { + response := message.NewErrorServerMessage(api.NewError("processing_failed", "Processing of the message failed, please check server logs.")) session.SendMessage(response) } -func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSession, senderRoom *Room, recipientSessionId PublicSessionId) bool { +func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSession, senderRoom *Room, recipientSessionId api.PublicSessionId) bool { clients := h.rpcClients.GetClients() if len(clients) == 0 { return false @@ -2776,8 +2774,8 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi return result.Load() } -func (h *Hub) isInSameCall(ctx context.Context, senderSession *ClientSession, recipientSessionId PublicSessionId) bool { - if senderSession.ClientType() == HelloClientTypeInternal { +func (h *Hub) isInSameCall(ctx context.Context, senderSession *ClientSession, recipientSessionId api.PublicSessionId) bool { + if senderSession.ClientType() == api.HelloClientTypeInternal { // Internal clients may subscribe all streams. return true } @@ -2796,7 +2794,7 @@ func (h *Hub) isInSameCall(ctx context.Context, senderSession *ClientSession, re recipientRoom := recipientSession.GetRoom() if recipientRoom == nil || !senderRoom.IsEqual(recipientRoom) || - (recipientSession.ClientType() != HelloClientTypeInternal && !recipientRoom.IsSessionInCall(recipientSession)) { + (recipientSession.ClientType() != api.HelloClientTypeInternal && !recipientRoom.IsSessionInCall(recipientSession)) { // Recipient is not in a room, a different room or not in the call. return false } @@ -2804,7 +2802,7 @@ func (h *Hub) isInSameCall(ctx context.Context, senderSession *ClientSession, re return true } -func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMessage, message *MessageClientMessage, data *MessageClientMessageData) { +func (h *Hub) processMcuMessage(session *ClientSession, client_message *api.ClientMessage, message *api.MessageClientMessage, data *api.MessageClientMessageData) { ctx, cancel := context.WithTimeout(session.Context(), h.mcuTimeout) defer cancel() @@ -2848,7 +2846,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe clientType = "subscriber" mc = session.GetSubscriber(message.Recipient.SessionId, StreamType(data.RoomType)) default: - if data.Type == "candidate" && FilterCandidate(data.candidate, h.allowedCandidates.Load(), h.blockedCandidates.Load()) { + if data.Type == "candidate" && api.FilterCandidate(data.Candidate, h.allowedCandidates.Load(), h.blockedCandidates.Load()) { // Silently ignore filtered candidates. return } @@ -2879,7 +2877,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe mc.SendMessage(session.Context(), message, data, func(err error, response api.StringMap) { if err != nil { - if !errors.Is(err, ErrCandidateFiltered) { + if !errors.Is(err, api.ErrCandidateFiltered) { h.logger.Printf("Could not send MCU message %+v for session %s to %s: %s", data, session.PublicId(), message.Recipient.SessionId, err) sendMcuProcessingFailed(session, client_message) } @@ -2893,11 +2891,11 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *ClientMe }) } -func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *MessageClientMessage, data *MessageClientMessageData, response api.StringMap) { - var response_message *ServerMessage +func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *api.MessageClientMessage, data *api.MessageClientMessageData, response api.StringMap) { + var response_message *api.ServerMessage switch response["type"] { case "answer": - answer_message := &AnswerOfferMessage{ + answer_message := &api.AnswerOfferMessage{ To: session.PublicId(), From: session.PublicId(), Type: "answer", @@ -2910,10 +2908,10 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient h.logger.Printf("Could not serialize answer %+v to %s: %s", answer_message, session.PublicId(), err) return } - response_message = &ServerMessage{ + response_message = &api.ServerMessage{ Type: "message", - Message: &MessageServerMessage{ - Sender: &MessageServerMessageSender{ + Message: &api.MessageServerMessage{ + Sender: &api.MessageServerMessageSender{ Type: "session", SessionId: session.PublicId(), UserId: session.UserId(), @@ -2922,7 +2920,7 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient }, } case "offer": - offer_message := &AnswerOfferMessage{ + offer_message := &api.AnswerOfferMessage{ To: session.PublicId(), From: message.Recipient.SessionId, Type: "offer", @@ -2935,10 +2933,10 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient h.logger.Printf("Could not serialize offer %+v to %s: %s", offer_message, session.PublicId(), err) return } - response_message = &ServerMessage{ + response_message = &api.ServerMessage{ Type: "message", - Message: &MessageServerMessage{ - Sender: &MessageServerMessageSender{ + Message: &api.MessageServerMessage{ + Sender: &api.MessageServerMessageSender{ Type: "session", SessionId: message.Recipient.SessionId, // TODO(jojo): Set "UserId" field if known user. @@ -2954,7 +2952,7 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient session.SendMessage(response_message) } -func (h *Hub) processByeMsg(client HandlerClient, message *ClientMessage) { +func (h *Hub) processByeMsg(client HandlerClient, message *api.ClientMessage) { client.SendByeResponse(message) if session := h.processUnregister(client); session != nil { session.Close() diff --git a/hub_test.go b/hub_test.go index 1ea1fd7..d68654b 100644 --- a/hub_test.go +++ b/hub_test.go @@ -57,6 +57,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/mock" "github.com/strukturag/nextcloud-spreed-signaling/nats" "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -444,7 +445,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re case "test-invalid-room": response := &BackendClientResponse{ Type: "error", - Error: &Error{ + Error: &api.Error{ Code: "no_such_room", Message: "The user is not invited to this room.", }, @@ -455,7 +456,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re if strings.Contains(t.Name(), "Federation") { // Check additional fields present for federated sessions. if strings.Contains(string(request.Room.SessionId), "@federated") { - assert.Equal(ActorTypeFederatedUsers, request.Room.ActorType) + assert.Equal(api.ActorTypeFederatedUsers, request.Room.ActorType) assert.NotEmpty(request.Room.ActorId) } else { assert.Empty(request.Room.ActorType) @@ -1301,7 +1302,7 @@ func TestClientHelloSessionLimit(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: testDefaultUserId, } - require.NoError(client.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params1)) + require.NoError(client.SendHelloParams(server1.URL+"/one", api.HelloVersionV1, "client", nil, params1)) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1318,12 +1319,12 @@ func TestClientHelloSessionLimit(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: testDefaultUserId + "2", } - require.NoError(client2.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params2)) + require.NoError(client2.SendHelloParams(server1.URL+"/one", api.HelloVersionV1, "client", nil, params2)) client2.RunUntilError(ctx, "session_limit_exceeded") //nolint // The client can connect to a different backend. - require.NoError(client2.SendHelloParams(server1.URL+"/two", HelloVersionV1, "client", nil, params2)) + require.NoError(client2.SendHelloParams(server1.URL+"/two", api.HelloVersionV1, "client", nil, params2)) if hello, ok := client2.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId+"2", hello.Hello.UserId, "%+v", hello.Hello) @@ -1340,7 +1341,7 @@ func TestClientHelloSessionLimit(t *testing.T) { params3 := TestBackendClientAuthParams{ UserId: testDefaultUserId + "3", } - require.NoError(client3.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params3)) + require.NoError(client3.SendHelloParams(server1.URL+"/one", api.HelloVersionV1, "client", nil, params3)) if hello, ok := client3.RunUntilHello(ctx); ok { assert.Equal(testDefaultUserId+"3", hello.Hello.UserId, "%+v", hello.Hello) @@ -1357,7 +1358,7 @@ func TestSessionIdsUnordered(t *testing.T) { hub, _, _, server := CreateHubForTest(t) var mu sync.Mutex - var publicSessionIds []PublicSessionId + var publicSessionIds []api.PublicSessionId var wg sync.WaitGroup for range 20 { wg.Add(1) @@ -1397,7 +1398,7 @@ func TestSessionIdsUnordered(t *testing.T) { larger := 0 smaller := 0 - var prevSid PublicSessionId + var prevSid api.PublicSessionId for i, sid := range publicSessionIds { if i > 0 { if sid > prevSid { @@ -1642,7 +1643,7 @@ func TestClientHelloResumePublicId(t *testing.T) { client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -1651,7 +1652,7 @@ func TestClientHelloResumePublicId(t *testing.T) { client1.SendMessage(recipient2, data) // nolint var payload string - var sender *MessageServerMessageSender + var sender *api.MessageServerMessageSender if checkReceiveClientMessageWithSender(ctx, t, client2, "session", hello1.Hello, &payload, &sender) { assert.Equal(data, payload) } @@ -1663,7 +1664,7 @@ func TestClientHelloResumePublicId(t *testing.T) { defer client1.CloseWithBye() // Can't resume a session with the id received from messages of a client. - require.NoError(client1.SendHelloResume(PrivateSessionId(sender.SessionId))) + require.NoError(client1.SendHelloResume(api.PrivateSessionId(sender.SessionId))) client1.RunUntilError(ctx, "no_such_session") // nolint // Expire old sessions @@ -1937,7 +1938,7 @@ func TestClientHelloClient_V3Api(t *testing.T) { } // The "/api/v1/signaling/" URL will be changed to use "v3" as the "signaling-v3" // feature is returned by the capabilities endpoint. - require.NoError(client.SendHelloParams(server.URL, HelloVersionV1, "client", nil, params)) + require.NoError(client.SendHelloParams(server.URL, api.HelloVersionV1, "client", nil, params)) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -2010,11 +2011,11 @@ func TestClientMessageToSessionId(t *testing.T) { waitForAsyncEventsFlushed(ctx, t, hub1.events) waitForAsyncEventsFlushed(ctx, t, hub2.events) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -2071,11 +2072,11 @@ func TestClientControlToSessionId(t *testing.T) { waitForAsyncEventsFlushed(ctx, t, hub1.events) waitForAsyncEventsFlushed(ctx, t, hub2.events) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -2126,11 +2127,11 @@ func TestClientControlMissingPermissions(t *testing.T) { PERMISSION_MAY_CONTROL, }) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -2165,11 +2166,11 @@ func TestClientMessageToUserId(t *testing.T) { require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "user", UserId: hello1.Hello.UserId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "user", UserId: hello2.Hello.UserId, } @@ -2203,11 +2204,11 @@ func TestClientControlToUserId(t *testing.T) { require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "user", UserId: hello1.Hello.UserId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "user", UserId: hello2.Hello.UserId, } @@ -2248,7 +2249,7 @@ func TestClientMessageToUserIdMultipleSessions(t *testing.T) { require.NotEqual(hello1.Hello.UserId, hello2b.Hello.UserId) require.Equal(hello2a.Hello.UserId, hello2b.Hello.UserId) - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "user", UserId: hello2a.Hello.UserId, } @@ -2308,7 +2309,7 @@ func TestClientMessageToRoom(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "room", } @@ -2371,7 +2372,7 @@ func TestClientControlToRoom(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "room", } @@ -2447,7 +2448,7 @@ func TestClientMessageToCall(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "call", } @@ -2552,7 +2553,7 @@ func TestClientControlToCall(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "call", } @@ -2743,12 +2744,12 @@ func TestJoinInvalidRoom(t *testing.T) { // Join room by id. roomId := "test-invalid-room" - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "ABCD", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: roomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)), + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)), }, } require.NoError(client.WriteJSON(msg)) @@ -2781,12 +2782,12 @@ func TestJoinRoomTwice(t *testing.T) { // We will receive a "joined" event. client.RunUntilJoined(ctx, hello.Hello) - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "ABCD", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: roomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s-2", roomId, client.publicId)), + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s-2", roomId, client.publicId)), }, } require.NoError(client.WriteJSON(msg)) @@ -2796,7 +2797,7 @@ func TestJoinRoomTwice(t *testing.T) { if checkMessageType(t, message, "error") { assert.Equal("already_joined", message.Error.Code) if assert.NotEmpty(message.Error.Details) { - var roomDetails RoomErrorDetails + var roomDetails api.RoomErrorDetails if err := json.Unmarshal(message.Error.Details, &roomDetails); assert.NoError(err) { if assert.NotNil(roomDetails.Room, "%+v", message) { assert.Equal(roomId, roomDetails.Room.RoomId) @@ -3043,12 +3044,12 @@ func TestJoinRoomSwitchClient(t *testing.T) { // Join room by id. roomId := "test-room-slow" - msg := &ClientMessage{ + msg := &api.ClientMessage{ Id: "ABCD", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: roomId, - SessionId: RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)), + SessionId: api.RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)), }, } require.NoError(client.WriteJSON(msg)) @@ -3325,7 +3326,7 @@ func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { client2.Close() assert.NoError(client2.WaitForClientRemoved(ctx)) - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -3473,7 +3474,7 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) { // Give asynchronous events some time to be processed. time.Sleep(100 * time.Millisecond) - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -3540,7 +3541,7 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { // Join room by id. roomId := "test-room-takeover-room-session" - roomSessionid := RoomSessionId("room-session-id") + roomSessionid := api.RoomSessionId("room-session-id") roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId, roomSessionid) require.Equal(roomId, roomMsg.Room.RoomId) @@ -3635,10 +3636,10 @@ func TestClientSendOfferPermissions(t *testing.T) { session2.SetPermissions([]Permission{}) // Client 2 may not send an offer (he doesn't have the necessary permissions). - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "sendoffer", Sid: "12345", RoomType: "screen", @@ -3647,25 +3648,25 @@ func TestClientSendOfferPermissions(t *testing.T) { msg := MustSucceed1(t, client2.RunUntilMessage, ctx) require.True(checkMessageError(t, msg, "not_allowed")) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "12345", RoomType: "screen", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) // Client 1 may send an offer. - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "sendoffer", Sid: "54321", RoomType: "screen", @@ -3678,7 +3679,7 @@ func TestClientSendOfferPermissions(t *testing.T) { client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // ...but the other peer will get an offer. - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) } func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { @@ -3711,15 +3712,15 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO}) // Client may not send an offer with audio and video. - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) @@ -3727,19 +3728,19 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { require.True(checkMessageError(t, msg, "not_allowed")) // Client may send an offer (audio only). - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioOnly, + "sdp": mock.MockSdpOfferAudioOnly, }, })) - client.RunUntilAnswer(ctx, MockSdpAnswerAudioOnly) + client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioOnly) } func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { @@ -3772,19 +3773,19 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { // Client is allowed to send audio and video. session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO}) - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) // Client is no longer allowed to send video, this will stop the publisher. msg := &BackendServerRoomRequest{ @@ -3869,19 +3870,19 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_MEDIA}) // Client may send an offer (audio and video). - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - require.True(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) // Client is no longer allowed to send video, this will stop the publisher. msg := &BackendServerRoomRequest{ @@ -3977,25 +3978,25 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { // We will receive a "joined" event. client1.RunUntilJoined(ctx, hello1.Hello) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "screen", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - require.True(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) // Client 2 may not request an offer (he is not in the room yet). - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", Sid: "12345", RoomType: "screen", @@ -4012,10 +4013,10 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { require.True(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) // Client 2 may not request an offer (he is not in the call yet). - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", Sid: "12345", RoomType: "screen", @@ -4038,10 +4039,10 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { checkReceiveClientEvent(ctx, t, client2, "update", nil) // Client 2 may not request an offer (recipient is not in the call yet). - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", Sid: "12345", RoomType: "screen", @@ -4064,26 +4065,26 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { checkReceiveClientEvent(ctx, t, client2, "update", nil) // Client 2 may request an offer now (both are in the same room and call). - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", Sid: "12345", RoomType: "screen", })) - require.True(client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo)) + require.True(client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo)) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "answer", Sid: "12345", RoomType: "screen", Payload: api.StringMap{ - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, })) @@ -4111,7 +4112,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: "user1", } - require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) + require.NoError(client1.SendHelloParams(server.URL+"/one", api.HelloVersionV1, "client", nil, params1)) hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) @@ -4120,14 +4121,14 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: "user2", } - require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) + require.NoError(client2.SendHelloParams(server.URL+"/two", api.HelloVersionV1, "client", nil, params2)) hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -4163,7 +4164,7 @@ func TestSendBetweenDifferentUrls(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: "user1", } - require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) + require.NoError(client1.SendHelloParams(server.URL+"/one", api.HelloVersionV1, "client", nil, params1)) hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) @@ -4172,14 +4173,14 @@ func TestSendBetweenDifferentUrls(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: "user2", } - require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) + require.NoError(client2.SendHelloParams(server.URL+"/two", api.HelloVersionV1, "client", nil, params2)) hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) - recipient1 := MessageClientMessageRecipient{ + recipient1 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, } - recipient2 := MessageClientMessageRecipient{ + recipient2 := api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, } @@ -4213,7 +4214,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: "user1", } - require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) + require.NoError(client1.SendHelloParams(server.URL+"/one", api.HelloVersionV1, "client", nil, params1)) hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) @@ -4222,7 +4223,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: "user2", } - require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) + require.NoError(client2.SendHelloParams(server.URL+"/two", api.HelloVersionV1, "client", nil, params2)) hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) // Join room by id. @@ -4251,7 +4252,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { assert.False(rooms[0].IsEqual(rooms[1]), "Rooms should be different: %+v", rooms) } - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "room", } @@ -4286,7 +4287,7 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: "user1", } - require.NoError(client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1)) + require.NoError(client1.SendHelloParams(server.URL+"/one", api.HelloVersionV1, "client", nil, params1)) hello1 := MustSucceed1(t, client1.RunUntilHello, ctx) client2 := NewTestClient(t, server, hub) @@ -4295,7 +4296,7 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: "user2", } - require.NoError(client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2)) + require.NoError(client2.SendHelloParams(server.URL+"/two", api.HelloVersionV1, "client", nil, params2)) hello2 := MustSucceed1(t, client2.RunUntilHello, ctx) // Join room by id. @@ -4318,7 +4319,7 @@ func TestSameRoomOnDifferentUrls(t *testing.T) { assert.Len(rooms, 1) - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "room", } @@ -4381,24 +4382,24 @@ func TestClientSendOffer(t *testing.T) { WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "12345", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - require.True(client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo)) + require.True(client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello2.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "sendoffer", RoomType: "video", })) @@ -4410,7 +4411,7 @@ func TestClientSendOffer(t *testing.T) { client1.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // ...but the other peer will get an offer. - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) }) } } @@ -4441,19 +4442,19 @@ func TestClientUnshareScreen(t *testing.T) { session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", Sid: "54321", RoomType: "screen", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioOnly, + "sdp": mock.MockSdpOfferAudioOnly, }, })) - client.RunUntilAnswer(ctx, MockSdpAnswerAudioOnly) + client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioOnly) publisher := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(publisher, "No publisher for %s found", hello.Hello.SessionId) @@ -4465,10 +4466,10 @@ func TestClientUnshareScreen(t *testing.T) { cleanupScreenPublisherDelay = old }() - require.NoError(client.SendMessage(MessageClientMessageRecipient{ + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "unshareScreen", Sid: "54321", RoomType: "screen", @@ -4550,7 +4551,7 @@ func TestVirtualClientSessions(t *testing.T) { calledCtx, calledCancel := context.WithTimeout(ctx, time.Second) - virtualSessionId := PublicSessionId("virtual-session-id") + virtualSessionId := api.PublicSessionId("virtual-session-id") virtualUserId := "virtual-user-id" generatedSessionId := GetVirtualSessionId(session2, virtualSessionId) @@ -4562,8 +4563,8 @@ func TestVirtualClientSessions(t *testing.T) { assert.Equal(virtualUserId, request.UserId, "%+v", request) }) - require.NoError(client2.SendInternalAddSession(&AddSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + require.NoError(client2.SendInternalAddSession(&api.AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: virtualSessionId, RoomId: roomId, }, @@ -4636,8 +4637,8 @@ func TestVirtualClientSessions(t *testing.T) { } updatedFlags := uint32(0) - require.NoError(client2.SendInternalUpdateSession(&UpdateSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + require.NoError(client2.SendInternalUpdateSession(&api.UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: virtualSessionId, RoomId: roomId, }, @@ -4672,7 +4673,7 @@ func TestVirtualClientSessions(t *testing.T) { }) // Messages to virtual sessions are sent to the associated client session. - virtualRecipient := MessageClientMessageRecipient{ + virtualRecipient := api.MessageClientMessageRecipient{ Type: "session", SessionId: virtualSession.PublicId(), } @@ -4681,8 +4682,8 @@ func TestVirtualClientSessions(t *testing.T) { client1.SendMessage(virtualRecipient, data) // nolint var payload string - var sender *MessageServerMessageSender - var recipient *MessageClientMessageRecipient + var sender *api.MessageServerMessageSender + var recipient *api.MessageClientMessageRecipient if checkReceiveClientMessageWithSenderAndRecipient(ctx, t, client2, "session", hello1.Hello, &payload, &sender, &recipient) { assert.Equal(virtualSessionId, recipient.SessionId, "%+v", recipient) assert.Equal(data, payload) @@ -4696,8 +4697,8 @@ func TestVirtualClientSessions(t *testing.T) { assert.Equal(data, payload) } - require.NoError(client2.SendInternalRemoveSession(&RemoveSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + require.NoError(client2.SendInternalRemoveSession(&api.RemoveSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: virtualSessionId, RoomId: roomId, }, @@ -4792,7 +4793,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { calledCtx, calledCancel := context.WithTimeout(ctx, time.Second) - virtualSessionId := PublicSessionId("virtual-session-id") + virtualSessionId := api.PublicSessionId("virtual-session-id") virtualUserId := "virtual-user-id" generatedSessionId := GetVirtualSessionId(session2, virtualSessionId) @@ -4804,8 +4805,8 @@ func TestDuplicateVirtualSessions(t *testing.T) { assert.Equal(virtualUserId, request.UserId, "%+v", request) }) - require.NoError(client2.SendInternalAddSession(&AddSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + require.NoError(client2.SendInternalAddSession(&api.AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: virtualSessionId, RoomId: roomId, }, @@ -5020,12 +5021,12 @@ func DoTestSwitchToOne(t *testing.T, details api.StringMap) { client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1") client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") - roomSessionId1 := RoomSessionId("roomsession1") + roomSessionId1 := api.RoomSessionId("roomsession1") roomId1 := "test-room" roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) - roomSessionId2 := RoomSessionId("roomsession2") + roomSessionId2 := api.RoomSessionId("roomsession2") roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) @@ -5035,12 +5036,12 @@ func DoTestSwitchToOne(t *testing.T, details api.StringMap) { var sessions json.RawMessage var err error if details != nil { - sessions, err = json.Marshal(map[RoomSessionId]any{ + sessions, err = json.Marshal(map[api.RoomSessionId]any{ roomSessionId1: details, }) require.NoError(err) } else { - sessions, err = json.Marshal([]RoomSessionId{ + sessions, err = json.Marshal([]api.RoomSessionId{ roomSessionId1, }) require.NoError(err) @@ -5120,12 +5121,12 @@ func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.S client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2") defer client2.CloseWithBye() - roomSessionId1 := RoomSessionId("roomsession1") + roomSessionId1 := api.RoomSessionId("roomsession1") roomId1 := "test-room" roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) - roomSessionId2 := RoomSessionId("roomsession2") + roomSessionId2 := api.RoomSessionId("roomsession2") roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) @@ -5135,13 +5136,13 @@ func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.S var sessions json.RawMessage var err error if details1 != nil || details2 != nil { - sessions, err = json.Marshal(map[RoomSessionId]any{ + sessions, err = json.Marshal(map[api.RoomSessionId]any{ roomSessionId1: details1, roomSessionId2: details2, }) require.NoError(err) } else { - sessions, err = json.Marshal([]RoomSessionId{ + sessions, err = json.Marshal([]api.RoomSessionId{ roomSessionId1, roomSessionId2, }) @@ -5271,15 +5272,15 @@ func TestDialoutStatus(t *testing.T) { assert.Equal(roomId, msg.Internal.Dialout.RoomId) - response := &ClientMessage{ + response := &api.ClientMessage{ Id: msg.Id, Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "dialout", - Dialout: &DialoutInternalClientMessage{ + Dialout: &api.DialoutInternalClientMessage{ Type: "status", RoomId: msg.Internal.Dialout.RoomId, - Status: &DialoutStatusInternalClientMessage{ + Status: &api.DialoutStatusInternalClientMessage{ Status: "accepted", CallId: callId, }, @@ -5326,10 +5327,10 @@ func TestDialoutStatus(t *testing.T) { }, nil) } - require.NoError(internalClient.SendInternalDialout(&DialoutInternalClientMessage{ + require.NoError(internalClient.SendInternalDialout(&api.DialoutInternalClientMessage{ RoomId: roomId, Type: "status", - Status: &DialoutStatusInternalClientMessage{ + Status: &api.DialoutStatusInternalClientMessage{ CallId: callId, Status: "ringing", }, @@ -5352,10 +5353,10 @@ func TestDialoutStatus(t *testing.T) { removeCallStatusTTL = 500 * time.Millisecond clearedCause := "cleared-call" - require.NoError(internalClient.SendInternalDialout(&DialoutInternalClientMessage{ + require.NoError(internalClient.SendInternalDialout(&api.DialoutInternalClientMessage{ RoomId: roomId, Type: "status", - Status: &DialoutStatusInternalClientMessage{ + Status: &api.DialoutStatusInternalClientMessage{ CallId: callId, Status: "cleared", Cause: clearedCause, diff --git a/mcu_common.go b/mcu_common.go index f744227..38d852c 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -56,7 +56,7 @@ const ( ) type McuListener interface { - PublicId() PublicSessionId + PublicId() api.PublicSessionId OnUpdateOffer(client McuClient, offer api.StringMap) @@ -137,8 +137,8 @@ type Mcu interface { GetServerInfoSfu() *BackendServerInfoSfu GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) - NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) - NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) + NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) + NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) } // PublisherStream contains the available properties when creating a @@ -171,7 +171,7 @@ type PublisherStream struct { } type RemotePublisherController interface { - PublisherId() PublicSessionId + PublisherId() api.PublicSessionId StartPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error StopPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error @@ -220,7 +220,7 @@ type McuClient interface { Close(ctx context.Context) - SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) + SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) } type McuClientWithBandwidth interface { @@ -232,7 +232,7 @@ type McuClientWithBandwidth interface { type McuPublisher interface { McuClient - PublisherId() PublicSessionId + PublisherId() api.PublicSessionId HasMedia(MediaType) bool SetMedia(MediaType) @@ -247,14 +247,14 @@ type McuPublisherWithStreams interface { type McuRemoteAwarePublisher interface { McuPublisher - PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error - UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error + PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error + UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error } type McuSubscriber interface { McuClient - Publisher() PublicSessionId + Publisher() api.PublicSessionId } type McuRemotePublisherProperties interface { diff --git a/mcu_common_test.go b/mcu_common_test.go index 0e4e839..736b447 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -33,10 +33,10 @@ func TestCommonMcuStats(t *testing.T) { } type MockMcuListener struct { - publicId PublicSessionId + publicId api.PublicSessionId } -func (m *MockMcuListener) PublicId() PublicSessionId { +func (m *MockMcuListener) PublicId() api.PublicSessionId { return m.publicId } diff --git a/mcu_janus.go b/mcu_janus.go index c40621a..a58403b 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -66,7 +66,7 @@ var ( type StreamId string -func getStreamId(publisherId PublicSessionId, streamType StreamType) StreamId { +func getStreamId(publisherId api.PublicSessionId, streamType StreamType) StreamId { return StreamId(fmt.Sprintf("%s|%s", publisherId, streamType)) } @@ -680,7 +680,7 @@ func (m *mcuJanus) sendKeepalive(ctx context.Context) { } } -func (m *mcuJanus) SubscriberConnected(id string, publisher PublicSessionId, streamType StreamType) { +func (m *mcuJanus) SubscriberConnected(id string, publisher api.PublicSessionId, streamType StreamType) { m.mu.Lock() defer m.mu.Unlock() @@ -689,7 +689,7 @@ func (m *mcuJanus) SubscriberConnected(id string, publisher PublicSessionId, str } } -func (m *mcuJanus) SubscriberDisconnected(id string, publisher PublicSessionId, streamType StreamType) { +func (m *mcuJanus) SubscriberDisconnected(id string, publisher api.PublicSessionId, streamType StreamType) { m.mu.Lock() defer m.mu.Unlock() @@ -698,7 +698,7 @@ func (m *mcuJanus) SubscriberDisconnected(id string, publisher PublicSessionId, } } -func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, api.Bandwidth, error) { +func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id api.PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, api.Bandwidth, error) { create_msg := api.StringMap{ "request": "create", "description": getStreamId(id, streamType), @@ -753,7 +753,7 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, return roomId, bitrate, nil } -func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSessionId, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, api.Bandwidth, error) { +func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id api.PublicSessionId, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, api.Bandwidth, error) { session := m.session if session == nil { return nil, 0, 0, 0, ErrNotConnected @@ -791,7 +791,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id PublicSess return handle, response.Session, roomId, bitrate, nil } -func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } @@ -842,7 +842,7 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id Pu return client, nil } -func (m *mcuJanus) getPublisher(ctx context.Context, publisher PublicSessionId, streamType StreamType) (*mcuJanusPublisher, error) { +func (m *mcuJanus) getPublisher(ctx context.Context, publisher api.PublicSessionId, streamType StreamType) (*mcuJanusPublisher, error) { // Do the direct check immediately as this should be the normal case. key := getStreamId(publisher, streamType) m.mu.Lock() @@ -869,7 +869,7 @@ func (m *mcuJanus) getPublisher(ctx context.Context, publisher PublicSessionId, } } -func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher PublicSessionId, streamType StreamType) (*JanusHandle, *mcuJanusPublisher, error) { +func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher api.PublicSessionId, streamType StreamType) (*JanusHandle, *mcuJanusPublisher, error) { var pub *mcuJanusPublisher var err error if pub, err = m.getPublisher(ctx, publisher, streamType); err != nil { @@ -890,7 +890,7 @@ func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher Pu return handle, pub, nil } -func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } diff --git a/mcu_janus_client.go b/mcu_janus_client.go index 27af7bb..1444bde 100644 --- a/mcu_janus_client.go +++ b/mcu_janus_client.go @@ -86,7 +86,7 @@ func (c *mcuJanusClient) MaxBitrate() api.Bandwidth { func (c *mcuJanusClient) Close(ctx context.Context) { } -func (c *mcuJanusClient) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (c *mcuJanusClient) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { } func (c *mcuJanusClient) UpdateBandwidth(media string, sent api.Bandwidth, received api.Bandwidth) { diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index b7ce847..fcf7801 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -49,7 +49,7 @@ const ( type mcuJanusPublisher struct { mcuJanusClient - id PublicSessionId + id api.PublicSessionId settings NewPublisherSettings stats publisherStatsCounter sdpFlags Flags @@ -58,7 +58,7 @@ type mcuJanusPublisher struct { answerSdp atomic.Pointer[sdp.SessionDescription] } -func (p *mcuJanusPublisher) PublisherId() PublicSessionId { +func (p *mcuJanusPublisher) PublisherId() api.PublicSessionId { return p.id } @@ -173,21 +173,21 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { p.mcuJanusClient.Close(ctx) } -func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { case "offer": p.deferred <- func() { - if data.offerSdp == nil { + if data.OfferSdp == nil { // Should have been checked before. go callback(errors.New("no sdp found in offer"), nil) return } - if FilterSDPCandidates(data.offerSdp, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + if api.FilterSDPCandidates(data.OfferSdp, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { // Update request with filtered SDP. - marshalled, err := data.offerSdp.Marshal() + marshalled, err := data.OfferSdp.Marshal() if err != nil { go callback(fmt.Errorf("could not marshal filtered offer: %w", err), nil) return @@ -196,7 +196,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli jsep_msg["sdp"] = string(marshalled) } - p.offerSdp.Store(data.offerSdp) + p.offerSdp.Store(data.OfferSdp) p.sdpFlags.Add(sdpHasOffer) if p.sdpFlags.Get() == sdpHasAnswer|sdpHasOffer { p.sdpReady.Close() @@ -216,7 +216,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli sdpString, found := api.GetStringMapEntry[string](jsep, "sdp") if !found { p.logger.Printf("No/invalid sdp found in answer %+v", jsep) - } else if answerSdp, err := parseSDP(sdpString); err != nil { + } else if answerSdp, err := api.ParseSDP(sdpString); err != nil { p.logger.Printf("Error parsing answer sdp %+v: %s", sdpString, err) p.answerSdp.Store(nil) p.sdpFlags.Remove(sdpHasAnswer) @@ -233,8 +233,8 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli }) } case "candidate": - if FilterCandidate(data.candidate, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { - go callback(ErrCandidateFiltered, nil) + if api.FilterCandidate(data.Candidate, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + go callback(api.ErrCandidateFiltered, nil) return } @@ -399,11 +399,11 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, return streams, nil } -func getPublisherRemoteId(id PublicSessionId, remoteId PublicSessionId, hostname string, port int, rtcpPort int) string { +func getPublisherRemoteId(id api.PublicSessionId, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) string { return fmt.Sprintf("%s-%s@%s:%d:%d", id, remoteId, hostname, port, rtcpPort) } -func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { handle := p.handle.Load() if handle == nil { return ErrNotConnected @@ -445,7 +445,7 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId PublicSe return nil } -func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { handle := p.handle.Load() if handle == nil { return ErrNotConnected diff --git a/mcu_janus_publisher_test.go b/mcu_janus_publisher_test.go index a4c7b43..aabc3f3 100644 --- a/mcu_janus_publisher_test.go +++ b/mcu_janus_publisher_test.go @@ -110,7 +110,7 @@ func TestJanusPublisherRemote(t *testing.T) { var remotePublishId atomic.Value - remoteId := PublicSessionId("the-remote-id") + remoteId := api.PublicSessionId("the-remote-id") hostname := "remote.server" port := 12345 rtcpPort := 23456 @@ -155,7 +155,7 @@ func TestJanusPublisherRemote(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } diff --git a/mcu_janus_subscriber.go b/mcu_janus_subscriber.go index 945dca8..8b2f4b2 100644 --- a/mcu_janus_subscriber.go +++ b/mcu_janus_subscriber.go @@ -34,10 +34,10 @@ import ( type mcuJanusSubscriber struct { mcuJanusClient - publisher PublicSessionId + publisher api.PublicSessionId } -func (p *mcuJanusSubscriber) Publisher() PublicSessionId { +func (p *mcuJanusSubscriber) Publisher() api.PublicSessionId { return p.publisher } @@ -290,7 +290,7 @@ func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection callback(nil, configure_response.Jsep) } -func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { @@ -315,9 +315,9 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl } case "answer": p.deferred <- func() { - if FilterSDPCandidates(data.answerSdp, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + if api.FilterSDPCandidates(data.AnswerSdp, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { // Update request with filtered SDP. - marshalled, err := data.answerSdp.Marshal() + marshalled, err := data.AnswerSdp.Marshal() if err != nil { go callback(fmt.Errorf("could not marshal filtered answer: %w", err), nil) return @@ -336,8 +336,8 @@ func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *MessageCl } } case "candidate": - if FilterCandidate(data.candidate, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { - go callback(ErrCandidateFiltered, nil) + if api.FilterCandidate(data.Candidate, p.mcu.settings.allowedCandidates.Load(), p.mcu.settings.blockedCandidates.Load()) { + go callback(api.ErrCandidateFiltered, nil) return } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index b76e63e..1b2eebb 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -38,6 +38,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/mock" ) func TestMcuJanusStats(t *testing.T) { @@ -329,7 +330,7 @@ func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJan }, Jsep: map[string]any{ "type": "offer", - "sdp": MockSdpOfferAudioOnly, + "sdp": mock.MockSdpOfferAudioOnly, }, }) } @@ -612,10 +613,10 @@ func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { } type TestMcuListener struct { - id PublicSessionId + id api.PublicSessionId } -func (t *TestMcuListener) PublicId() PublicSessionId { +func (t *TestMcuListener) PublicId() api.PublicSessionId { return t.id } @@ -644,10 +645,10 @@ func (t *TestMcuListener) SubscriberClosed(subscriber McuSubscriber) { } type TestMcuController struct { - id PublicSessionId + id api.PublicSessionId } -func (c *TestMcuController) PublisherId() PublicSessionId { +func (c *TestMcuController) PublisherId() api.PublicSessionId { return c.id } @@ -695,14 +696,14 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { if sdpValue, found := jsep["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } return &janus.EventMsg{ Jsep: api.StringMap{ - "sdp": MockSdpAnswerAudioOnly, + "sdp": mock.MockSdpAnswerAudioOnly, }, }, nil }, @@ -715,7 +716,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -730,31 +731,31 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { defer pub.Close(context.Background()) // Send offer containing candidates that will be blocked / filtered. - data := &MessageClientMessageData{ + data := &api.MessageClientMessageData{ Type: "offer", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioOnly, + "sdp": mock.MockSdpOfferAudioOnly, }, } require.NoError(data.CheckValid()) var wg sync.WaitGroup wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() if assert.NoError(err) { if sdpValue, found := m["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } }) wg.Wait() - data = &MessageClientMessageData{ + data = &api.MessageClientMessageData{ Type: "candidate", Payload: api.StringMap{ "candidate": api.StringMap{ @@ -764,7 +765,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -772,7 +773,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { }) wg.Wait() - data = &MessageClientMessageData{ + data = &api.MessageClientMessageData{ Type: "candidate", Payload: api.StringMap{ "candidate": api.StringMap{ @@ -782,7 +783,7 @@ func Test_JanusPublisherFilterOffer(t *testing.T) { } require.NoError(data.CheckValid()) wg.Add(1) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.NoError(err) @@ -805,7 +806,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { if sdpValue, found := jsep["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpAnswerAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpAnswerAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } @@ -830,7 +831,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -856,17 +857,17 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { defer sub.Close(context.Background()) // Send answer containing candidates that will be blocked / filtered. - data := &MessageClientMessageData{ + data := &api.MessageClientMessageData{ Type: "answer", Payload: api.StringMap{ - "sdp": MockSdpAnswerAudioOnly, + "sdp": mock.MockSdpAnswerAudioOnly, }, } require.NoError(data.CheckValid()) var wg sync.WaitGroup wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() if assert.NoError(err) { @@ -875,7 +876,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { }) wg.Wait() - data = &MessageClientMessageData{ + data = &api.MessageClientMessageData{ Type: "candidate", Payload: api.StringMap{ "candidate": api.StringMap{ @@ -885,7 +886,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.ErrorContains(err, "filtered") @@ -893,7 +894,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { }) wg.Wait() - data = &MessageClientMessageData{ + data = &api.MessageClientMessageData{ Type: "candidate", Payload: api.StringMap{ "candidate": api.StringMap{ @@ -903,7 +904,7 @@ func Test_JanusSubscriberFilterAnswer(t *testing.T) { } require.NoError(data.CheckValid()) wg.Add(1) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer wg.Done() assert.NoError(err) @@ -925,14 +926,14 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { if sdpValue, found := jsep["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpOfferAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpOfferAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } return &janus.EventMsg{ Jsep: api.StringMap{ - "sdp": MockSdpAnswerAudioOnly, + "sdp": mock.MockSdpAnswerAudioOnly, }, }, nil }, @@ -941,7 +942,7 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -955,23 +956,23 @@ func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { require.NoError(err) defer pub.Close(context.Background()) - data := &MessageClientMessageData{ + data := &api.MessageClientMessageData{ Type: "offer", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioOnly, + "sdp": mock.MockSdpOfferAudioOnly, }, } require.NoError(data.CheckValid()) done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { if sdpValue, found := m["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } @@ -1011,7 +1012,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1020,7 +1021,7 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1034,10 +1035,10 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { require.NoError(err) defer pub.Close(context.Background()) - data := &MessageClientMessageData{ + data := &api.MessageClientMessageData{ Type: "offer", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, } require.NoError(data.CheckValid()) @@ -1045,14 +1046,14 @@ func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { // Defer sending of offer / answer so "GetStreams" will wait. go func() { done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { if sdpValue, found := m["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } @@ -1114,7 +1115,7 @@ func Test_JanusPublisherSubscriber(t *testing.T) { assert.EqualValues(0, stats.incoming) assert.EqualValues(0, stats.outgoing) - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1188,7 +1189,7 @@ func Test_JanusSubscriberPublisher(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1248,7 +1249,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1257,7 +1258,7 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - pubId := PublicSessionId("publisher-id") + pubId := api.PublicSessionId("publisher-id") listener1 := &TestMcuListener{ id: pubId, } @@ -1283,10 +1284,10 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { defer sub.Close(context.Background()) go func() { - data := &MessageClientMessageData{ + data := &api.MessageClientMessageData{ Type: "offer", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, } if !assert.NoError(data.CheckValid()) { @@ -1294,14 +1295,14 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { } done := make(chan struct{}) - pub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { if sdpValue, found := m["sdp"]; assert.True(found) { sdpText, ok := sdpValue.(string) if assert.True(ok) { - assert.Equal(MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) } } } @@ -1309,13 +1310,13 @@ func Test_JanusSubscriberRequestOffer(t *testing.T) { <-done }() - data := &MessageClientMessageData{ + data := &api.MessageClientMessageData{ Type: "requestoffer", } require.NoError(data.CheckValid()) done := make(chan struct{}) - sub.SendMessage(ctx, &MessageClientMessage{}, data, func(err error, m api.StringMap) { + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { defer close(done) if assert.NoError(err) { @@ -1477,7 +1478,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1524,38 +1525,38 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) } func test_JanusSubscriberAlreadyJoined(t *testing.T) { @@ -1579,7 +1580,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1626,23 +1627,23 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) @@ -1650,16 +1651,16 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) { if strings.Contains(t.Name(), "AttachError") { MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) } - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) } func Test_JanusSubscriberAlreadyJoined(t *testing.T) { @@ -1694,7 +1695,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1741,25 +1742,25 @@ func Test_JanusSubscriberTimeout(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) oldTimeout := mcu.settings.timeout.Swap(100 * int64(time.Millisecond)) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) @@ -1768,15 +1769,15 @@ func Test_JanusSubscriberTimeout(t *testing.T) { mcu.settings.timeout.Store(oldTimeout) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) } func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { @@ -1801,7 +1802,7 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1848,28 +1849,28 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) require.NotNil(sess2) @@ -1915,7 +1916,7 @@ func Test_JanusSubscriberRoomDestroyed(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -1962,28 +1963,28 @@ func Test_JanusSubscriberRoomDestroyed(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) require.NotNil(sess2) @@ -2029,7 +2030,7 @@ func Test_JanusSubscriberUpdateOffer(t *testing.T) { return &janus.EventMsg{ Jsep: api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }, }, nil }, @@ -2076,29 +2077,29 @@ func Test_JanusSubscriberUpdateOffer(t *testing.T) { checkReceiveClientEvent(ctx, t, client1, "update", nil) checkReceiveClientEvent(ctx, t, client2, "update", nil) - require.NoError(client1.SendMessage(MessageClientMessageRecipient{ + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "offer", RoomType: "video", Payload: api.StringMap{ - "sdp": MockSdpOfferAudioAndVideo, + "sdp": mock.MockSdpOfferAudioAndVideo, }, })) - client1.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo) + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ Type: "session", SessionId: hello1.Hello.SessionId, - }, MessageClientMessageData{ + }, api.MessageClientMessageData{ Type: "requestoffer", RoomType: "video", })) - client2.RunUntilOffer(ctx, MockSdpOfferAudioAndVideo) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) // Test MCU will trigger an updated offer. - client2.RunUntilOffer(ctx, MockSdpOfferAudioOnly) + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioOnly) } diff --git a/mcu_proxy.go b/mcu_proxy.go index 983cb52..3e471bc 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -147,11 +147,11 @@ func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadPr type mcuProxyPublisher struct { mcuProxyPubSubCommon - id PublicSessionId + id api.PublicSessionId settings NewPublisherSettings } -func newMcuProxyPublisher(logger log.Logger, id PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { +func newMcuProxyPublisher(logger log.Logger, id api.PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { return &mcuProxyPublisher{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ logger: logger, @@ -168,7 +168,7 @@ func newMcuProxyPublisher(logger log.Logger, id PublicSessionId, sid string, str } } -func (p *mcuProxyPublisher) PublisherId() PublicSessionId { +func (p *mcuProxyPublisher) PublisherId() api.PublicSessionId { return p.id } @@ -209,7 +209,7 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { p.logger.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) } -func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ @@ -241,11 +241,11 @@ func (p *mcuProxyPublisher) ProcessEvent(msg *EventProxyServerMessage) { type mcuProxySubscriber struct { mcuProxyPubSubCommon - publisherId PublicSessionId + publisherId api.PublicSessionId publisherConn *mcuProxyConnection } -func newMcuProxySubscriber(logger log.Logger, publisherId PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { +func newMcuProxySubscriber(logger log.Logger, publisherId api.PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { return &mcuProxySubscriber{ mcuProxyPubSubCommon: mcuProxyPubSubCommon{ logger: logger, @@ -263,7 +263,7 @@ func newMcuProxySubscriber(logger log.Logger, publisherId PublicSessionId, sid s } } -func (s *mcuProxySubscriber) Publisher() PublicSessionId { +func (s *mcuProxySubscriber) Publisher() api.PublicSessionId { return s.publisherId } @@ -311,7 +311,7 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { } } -func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { msg := &ProxyClientMessage{ Type: "payload", Payload: &PayloadProxyClientMessage{ @@ -389,7 +389,7 @@ type mcuProxyConnection struct { // +checklocks:publishersLock publishers map[string]*mcuProxyPublisher // +checklocks:publishersLock - publisherIds map[StreamId]PublicSessionId + publisherIds map[StreamId]api.PublicSessionId subscribersLock sync.RWMutex // +checklocks:subscribersLock @@ -413,7 +413,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str closedDone: internal.NewCloser(), callbacks: make(map[string]mcuProxyCallback), publishers: make(map[string]*mcuProxyPublisher), - publisherIds: make(map[StreamId]PublicSessionId), + publisherIds: make(map[StreamId]api.PublicSessionId), subscribers: make(map[string]*mcuProxySubscriber), } conn.reconnectInterval.Store(int64(initialReconnectInterval)) @@ -548,13 +548,13 @@ func (c *mcuProxyConnection) Features() []string { return c.features.Load().([]string) } -func (c *mcuProxyConnection) SessionId() PublicSessionId { +func (c *mcuProxyConnection) SessionId() api.PublicSessionId { sid := c.sessionId.Load() if sid == nil { return "" } - return sid.(PublicSessionId) + return sid.(api.PublicSessionId) } func (c *mcuProxyConnection) IsConnected() bool { @@ -989,7 +989,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { c.clearPublishers() c.clearSubscribers() c.clearCallbacks() - c.sessionId.Store(PublicSessionId("")) + c.sessionId.Store(api.PublicSessionId("")) if err := c.sendHello(); err != nil { c.logger.Printf("Could not send hello request to %s: %s", c, err) c.scheduleReconnect() @@ -1157,7 +1157,7 @@ func (c *mcuProxyConnection) processBye(msg *ProxyServerMessage) { default: c.logger.Printf("Received bye with unsupported reason from %s %+v", c, bye) } - c.sessionId.Store(PublicSessionId("")) + c.sessionId.Store(api.PublicSessionId("")) } func (c *mcuProxyConnection) sendHello() error { @@ -1253,7 +1253,7 @@ func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyC } } -func (c *mcuProxyConnection) deferredDeletePublisher(id PublicSessionId, streamType StreamType, response *ProxyServerMessage) { +func (c *mcuProxyConnection) deferredDeletePublisher(id api.PublicSessionId, streamType StreamType, response *ProxyServerMessage) { if response.Type == "error" { c.logger.Printf("Publisher for %s was not created at %s: %s", id, c, response.Error) return @@ -1283,7 +1283,7 @@ func (c *mcuProxyConnection) deferredDeletePublisher(id PublicSessionId, streamT c.logger.Printf("Deleted publisher %s at %s", proxyId, c) } -func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { +func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { msg := &ProxyClientMessage{ Type: "command", Command: &CommandProxyClientMessage{ @@ -1314,14 +1314,14 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe publisher := newMcuProxyPublisher(c.logger, id, sid, streamType, response.Command.Bitrate, settings, proxyId, c, listener) c.publishersLock.Lock() c.publishers[proxyId] = publisher - c.publisherIds[getStreamId(id, streamType)] = PublicSessionId(proxyId) + c.publisherIds[getStreamId(id, streamType)] = api.PublicSessionId(proxyId) c.publishersLock.Unlock() statsPublishersCurrent.WithLabelValues(string(streamType)).Inc() statsPublishersTotal.WithLabelValues(string(streamType)).Inc() return publisher, nil } -func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { +func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId api.PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { if response.Type == "error" { c.logger.Printf("Subscriber for %s was not created at %s: %s", publisherSessionId, c, response.Error) return @@ -1364,7 +1364,7 @@ func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId PublicS } } -func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuListener, publisherId PublicSessionId, publisherSessionId PublicSessionId, streamType StreamType) (McuSubscriber, error) { +func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuListener, publisherId api.PublicSessionId, publisherSessionId api.PublicSessionId, streamType StreamType) (McuSubscriber, error) { msg := &ProxyClientMessage{ Type: "command", Command: &CommandProxyClientMessage{ @@ -1397,7 +1397,7 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList return subscriber, nil } -func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId PublicSessionId, publisherSessionId PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, remoteToken string) (McuSubscriber, error) { +func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId api.PublicSessionId, publisherSessionId api.PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, remoteToken string) (McuSubscriber, error) { if c == publisherConn { return c.newSubscriber(ctx, listener, publisherId, publisherSessionId, streamType) } @@ -2024,7 +2024,7 @@ func (m *mcuProxy) removePublisher(publisher *mcuProxyPublisher) { delete(m.publishers, getStreamId(publisher.id, publisher.StreamType())) } -func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuPublisher { +func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuPublisher { var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { maxBitrate = m.settings.MaxScreenBitrate() @@ -2063,7 +2063,7 @@ func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id return nil } -func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { connections := m.getSortedConnections(initiator) publisher := m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections, func(c *mcuProxyConnection) bool { bw := c.Bandwidth() @@ -2116,14 +2116,14 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id Pu return publisher, nil } -func (m *mcuProxy) getPublisherConnection(publisher PublicSessionId, streamType StreamType) *mcuProxyConnection { +func (m *mcuProxy) getPublisherConnection(publisher api.PublicSessionId, streamType StreamType) *mcuProxyConnection { m.mu.RLock() defer m.mu.RUnlock() return m.publishers[getStreamId(publisher, streamType)] } -func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher PublicSessionId, streamType StreamType) *mcuProxyConnection { +func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher api.PublicSessionId, streamType StreamType) *mcuProxyConnection { m.mu.Lock() defer m.mu.Unlock() @@ -2155,13 +2155,13 @@ func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher Pub } type proxyPublisherInfo struct { - id PublicSessionId + id api.PublicSessionId conn *mcuProxyConnection token string err error } -func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, info *proxyPublisherInfo, publisher PublicSessionId, streamType StreamType, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { +func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, info *proxyPublisherInfo, publisher api.PublicSessionId, streamType StreamType, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { for _, conn := range connections { if !isAllowed(conn) || conn.IsShutdownScheduled() || conn.IsTemporary() { continue @@ -2188,7 +2188,7 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i return nil } -func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { var publisherInfo *proxyPublisherInfo if conn := m.getPublisherConnection(publisher, streamType); conn != nil { // Fast common path: publisher is available locally. diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 73174df..6e7df14 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -49,6 +49,7 @@ import ( "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -199,7 +200,7 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { type proxyServerClientHandler func(msg *ProxyClientMessage) (*ProxyServerMessage, error) type testProxyServerPublisher struct { - id PublicSessionId + id api.PublicSessionId } type testProxyServerSubscriber struct { @@ -219,7 +220,7 @@ type testProxyServerClient struct { processMessage proxyServerClientHandler mu sync.Mutex - sessionId PublicSessionId + sessionId api.PublicSessionId } func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxyServerMessage, error) { @@ -233,7 +234,7 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer response := &ProxyServerMessage{ Id: msg.Id, Type: "error", - Error: &Error{ + Error: &api.Error{ Code: "no_such_session", }, } @@ -248,7 +249,7 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer Hello: &HelloProxyServerMessage{ Version: "1.0", SessionId: c.sessionId, - Server: &WelcomeServerMessage{ + Server: &api.WelcomeServerMessage{ Version: "1.0", Country: c.server.country, }, @@ -284,7 +285,7 @@ func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxySer Hello: &HelloProxyServerMessage{ Version: "1.0", SessionId: c.sessionId, - Server: &WelcomeServerMessage{ + Server: &api.WelcomeServerMessage{ Version: "1.0", Country: c.server.country, }, @@ -347,7 +348,7 @@ func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) ( defer c.server.Wakeup() } - if pub, found := c.server.deletePublisher(PublicSessionId(msg.Command.ClientId)); !found { + if pub, found := c.server.deletePublisher(api.PublicSessionId(msg.Command.ClientId)); !found { response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.ClientId)) } else { response = &ProxyServerMessage{ @@ -564,9 +565,9 @@ type TestProxyServerHandler struct { incoming atomic.Pointer[float64] outgoing atomic.Pointer[float64] // +checklocks:mu - clients map[PublicSessionId]*testProxyServerClient + clients map[api.PublicSessionId]*testProxyServerClient // +checklocks:mu - publishers map[PublicSessionId]*testProxyServerPublisher + publishers map[api.PublicSessionId]*testProxyServerPublisher // +checklocks:mu subscribers map[string]*testProxyServerSubscriber @@ -577,7 +578,7 @@ func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { h.mu.Lock() defer h.mu.Unlock() pub := &testProxyServerPublisher{ - id: PublicSessionId(newRandomString(32)), + id: api.PublicSessionId(newRandomString(32)), } for { @@ -585,20 +586,20 @@ func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { break } - pub.id = PublicSessionId(newRandomString(32)) + pub.id = api.PublicSessionId(newRandomString(32)) } h.publishers[pub.id] = pub return pub } -func (h *TestProxyServerHandler) getPublisher(id PublicSessionId) *testProxyServerPublisher { +func (h *TestProxyServerHandler) getPublisher(id api.PublicSessionId) *testProxyServerPublisher { h.mu.Lock() defer h.mu.Unlock() return h.publishers[id] } -func (h *TestProxyServerHandler) deletePublisher(id PublicSessionId) (*testProxyServerPublisher, bool) { +func (h *TestProxyServerHandler) deletePublisher(id api.PublicSessionId) (*testProxyServerPublisher, bool) { h.mu.Lock() defer h.mu.Unlock() @@ -748,7 +749,7 @@ func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques t: h.t, server: h, ws: ws, - sessionId: PublicSessionId(newRandomString(32)), + sessionId: api.PublicSessionId(newRandomString(32)), } h.setClient(client.sessionId, client) @@ -756,14 +757,14 @@ func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques go client.run() } -func (h *TestProxyServerHandler) getClient(sessionId PublicSessionId) *testProxyServerClient { +func (h *TestProxyServerHandler) getClient(sessionId api.PublicSessionId) *testProxyServerClient { h.mu.Lock() defer h.mu.Unlock() return h.clients[sessionId] } -func (h *TestProxyServerHandler) setClient(sessionId PublicSessionId, client *testProxyServerClient) { +func (h *TestProxyServerHandler) setClient(sessionId api.PublicSessionId, client *testProxyServerClient) { h.mu.Lock() defer h.mu.Unlock() @@ -820,8 +821,8 @@ func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler tokens: make(map[string]*rsa.PublicKey), upgrader: &upgrader, country: country, - clients: make(map[PublicSessionId]*testProxyServerClient), - publishers: make(map[PublicSessionId]*testProxyServerPublisher), + clients: make(map[api.PublicSessionId]*testProxyServerClient), + publishers: make(map[api.PublicSessionId]*testProxyServerPublisher), subscribers: make(map[string]*testProxyServerSubscriber), wakeupChan: make(chan struct{}), } @@ -1150,7 +1151,7 @@ func Test_ProxyPublisherSubscriber(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1185,7 +1186,7 @@ func Test_ProxyPublisherCodecs(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1211,7 +1212,7 @@ func Test_ProxyWaitForPublisher(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1265,7 +1266,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pub1Id := PublicSessionId("the-publisher-1") + pub1Id := api.PublicSessionId("the-publisher-1") pub1Sid := "1234567890" pub1Listener := &MockMcuListener{ publicId: pub1Id + "-public", @@ -1304,7 +1305,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { time.Sleep(time.Millisecond) } - pub2Id := PublicSessionId("the-publisher-2") + pub2Id := api.PublicSessionId("the-publisher-2") pub2id := "1234567890" pub2Listener := &MockMcuListener{ publicId: pub2Id + "-public", @@ -1334,7 +1335,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pub1Id := PublicSessionId("the-publisher-1") + pub1Id := api.PublicSessionId("the-publisher-1") pub1Sid := "1234567890" pub1Listener := &MockMcuListener{ publicId: pub1Id + "-public", @@ -1376,7 +1377,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { time.Sleep(time.Millisecond) } - pub2Id := PublicSessionId("the-publisher-2") + pub2Id := api.PublicSessionId("the-publisher-2") pub2id := "1234567890" pub2Listener := &MockMcuListener{ publicId: pub2Id + "-public", @@ -1406,7 +1407,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pub1Id := PublicSessionId("the-publisher-1") + pub1Id := api.PublicSessionId("the-publisher-1") pub1Sid := "1234567890" pub1Listener := &MockMcuListener{ publicId: pub1Id + "-public", @@ -1425,7 +1426,7 @@ func Test_ProxyPublisherLoad(t *testing.T) { mcu.nextSort.Store(0) time.Sleep(100 * time.Millisecond) - pub2Id := PublicSessionId("the-publisher-2") + pub2Id := api.PublicSessionId("the-publisher-2") pub2id := "1234567890" pub2Listener := &MockMcuListener{ publicId: pub2Id + "-public", @@ -1455,7 +1456,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubDEId := PublicSessionId("the-publisher-de") + pubDEId := api.PublicSessionId("the-publisher-de") pubDESid := "1234567890" pubDEListener := &MockMcuListener{ publicId: pubDEId + "-public", @@ -1472,7 +1473,7 @@ func Test_ProxyPublisherCountry(t *testing.T) { assert.Equal(t, serverDE.URL, pubDE.(*mcuProxyPublisher).conn.rawUrl) - pubUSId := PublicSessionId("the-publisher-us") + pubUSId := api.PublicSessionId("the-publisher-us") pubUSSid := "1234567890" pubUSListener := &MockMcuListener{ publicId: pubUSId + "-public", @@ -1502,7 +1503,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubDEId := PublicSessionId("the-publisher-de") + pubDEId := api.PublicSessionId("the-publisher-de") pubDESid := "1234567890" pubDEListener := &MockMcuListener{ publicId: pubDEId + "-public", @@ -1519,7 +1520,7 @@ func Test_ProxyPublisherContinent(t *testing.T) { assert.Equal(t, serverDE.URL, pubDE.(*mcuProxyPublisher).conn.rawUrl) - pubFRId := PublicSessionId("the-publisher-fr") + pubFRId := api.PublicSessionId("the-publisher-fr") pubFRSid := "1234567890" pubFRListener := &MockMcuListener{ publicId: pubFRId + "-public", @@ -1549,7 +1550,7 @@ func Test_ProxySubscriberCountry(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1592,7 +1593,7 @@ func Test_ProxySubscriberContinent(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1635,7 +1636,7 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1698,7 +1699,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1754,14 +1755,14 @@ type mockGrpcServerHub struct { proxy atomic.Pointer[mcuProxy] sessionsLock sync.Mutex // +checklocks:sessionsLock - sessionByPublicId map[PublicSessionId]Session + sessionByPublicId map[api.PublicSessionId]Session } func (h *mockGrpcServerHub) addSession(session *ClientSession) { h.sessionsLock.Lock() defer h.sessionsLock.Unlock() if h.sessionByPublicId == nil { - h.sessionByPublicId = make(map[PublicSessionId]Session) + h.sessionByPublicId = make(map[api.PublicSessionId]Session) } h.sessionByPublicId[session.PublicId()] = session } @@ -1772,17 +1773,17 @@ func (h *mockGrpcServerHub) removeSession(session *ClientSession) { delete(h.sessionByPublicId, session.PublicId()) } -func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId PrivateSessionId) Session { +func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId api.PrivateSessionId) Session { return nil } -func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId PublicSessionId) Session { +func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId api.PublicSessionId) Session { h.sessionsLock.Lock() defer h.sessionsLock.Unlock() return h.sessionByPublicId[sessionId] } -func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) { +func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { return "", nil } @@ -1842,7 +1843,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -1937,7 +1938,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2027,7 +2028,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2120,7 +2121,7 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2231,7 +2232,7 @@ func Test_ProxyConnectToken(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2316,7 +2317,7 @@ func Test_ProxyPublisherToken(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2368,7 +2369,7 @@ func Test_ProxyPublisherTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", @@ -2408,7 +2409,7 @@ func Test_ProxySubscriberTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() - pubId := PublicSessionId("the-publisher") + pubId := api.PublicSessionId("the-publisher") pubSid := "1234567890" pubListener := &MockMcuListener{ publicId: pubId + "-public", diff --git a/mcu_test.go b/mcu_test.go index 321ee33..8df744b 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -34,6 +34,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/mock" ) var ( @@ -45,7 +46,7 @@ type TestMCU struct { t *testing.T mu sync.Mutex // +checklocks:mu - publishers map[PublicSessionId]*TestMCUPublisher + publishers map[api.PublicSessionId]*TestMCUPublisher // +checklocks:mu subscribers map[string]*TestMCUSubscriber @@ -57,7 +58,7 @@ func NewTestMCU(t *testing.T) *TestMCU { return &TestMCU{ t: t, - publishers: make(map[PublicSessionId]*TestMCUPublisher), + publishers: make(map[api.PublicSessionId]*TestMCUPublisher), subscribers: make(map[string]*TestMCUSubscriber), } } @@ -95,7 +96,7 @@ func (m *TestMCU) GetServerInfoSfu() *BackendServerInfoSfu { return nil } -func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { maxBitrate = TestMaxBitrateScreen @@ -125,7 +126,7 @@ func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id Pub return pub, nil } -func (m *TestMCU) GetPublishers() map[PublicSessionId]*TestMCUPublisher { +func (m *TestMCU) GetPublishers() map[api.PublicSessionId]*TestMCUPublisher { m.mu.Lock() defer m.mu.Unlock() @@ -133,14 +134,14 @@ func (m *TestMCU) GetPublishers() map[PublicSessionId]*TestMCUPublisher { return result } -func (m *TestMCU) GetPublisher(id PublicSessionId) *TestMCUPublisher { +func (m *TestMCU) GetPublisher(id api.PublicSessionId) *TestMCUPublisher { m.mu.Lock() defer m.mu.Unlock() return m.publishers[id] } -func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publisher PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { m.mu.Lock() defer m.mu.Unlock() @@ -206,8 +207,8 @@ type TestMCUPublisher struct { sdp string } -func (p *TestMCUPublisher) PublisherId() PublicSessionId { - return PublicSessionId(p.id) +func (p *TestMCUPublisher) PublisherId() api.PublicSessionId { + return api.PublicSessionId(p.id) } func (p *TestMCUPublisher) HasMedia(mt MediaType) bool { @@ -218,7 +219,7 @@ func (p *TestMCUPublisher) SetMedia(mt MediaType) { p.settings.MediaTypes = mt } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { go func() { if p.isClosed() { callback(errors.New("Already closed"), nil) @@ -231,16 +232,16 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *MessageClie if sdp, ok := sdp.(string); ok { p.sdp = sdp switch sdp { - case MockSdpOfferAudioOnly: + case mock.MockSdpOfferAudioOnly: callback(nil, api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioOnly, + "sdp": mock.MockSdpAnswerAudioOnly, }) return - case MockSdpOfferAudioAndVideo: + case mock.MockSdpOfferAudioAndVideo: callback(nil, api.StringMap{ "type": "answer", - "sdp": MockSdpAnswerAudioAndVideo, + "sdp": mock.MockSdpAnswerAudioAndVideo, }) return } @@ -256,11 +257,11 @@ func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]PublisherStream, e return nil, errors.New("not implemented") } -func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("remote publishing not supported") } -func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("remote publishing not supported") } @@ -270,11 +271,11 @@ type TestMCUSubscriber struct { publisher *TestMCUPublisher } -func (s *TestMCUSubscriber) Publisher() PublicSessionId { +func (s *TestMCUSubscriber) Publisher() api.PublicSessionId { return s.publisher.PublisherId() } -func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *MessageClientMessage, data *MessageClientMessageData, callback func(error, api.StringMap)) { +func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { go func() { if s.isClosed() { callback(errors.New("Already closed"), nil) diff --git a/mock_data_test.go b/mock/data.go similarity index 99% rename from mock_data_test.go rename to mock/data.go index 495b2d0..fa4def2 100644 --- a/mock_data_test.go +++ b/mock/data.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 mock const ( // See https://tools.ietf.org/id/draft-ietf-rtcweb-sdp-08.html#rfc.section.5.2.1 diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 5d9df6e..05c1f4f 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -40,6 +40,7 @@ import ( "github.com/gorilla/websocket" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -84,7 +85,7 @@ type RemoteConnection struct { // +checklocks:mu helloMsgId string // +checklocks:mu - sessionId signaling.PublicSessionId + sessionId api.PublicSessionId // +checklocks:mu helloReceived bool @@ -128,7 +129,7 @@ func (c *RemoteConnection) String() string { return c.url.String() } -func (c *RemoteConnection) SessionId() signaling.PublicSessionId { +func (c *RemoteConnection) SessionId() api.PublicSessionId { c.mu.Lock() defer c.mu.Unlock() return c.sessionId @@ -546,7 +547,7 @@ func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { // Ignore case "publisher-closed": c.logger.Printf("Remote publisher %s was closed on %s", msg.Event.ClientId, c) - c.p.RemotePublisherDeleted(signaling.PublicSessionId(msg.Event.ClientId)) + c.p.RemotePublisherDeleted(api.PublicSessionId(msg.Event.ClientId)) default: c.logger.Printf("Received unsupported event %+v from %s", msg, c) } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 87cf4d1..7c12372 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -92,24 +92,24 @@ type ContextKey string var ( ContextKeySession = ContextKey("session") - TimeoutCreatingPublisher = signaling.NewError("timeout", "Timeout creating publisher.") - TimeoutCreatingSubscriber = signaling.NewError("timeout", "Timeout creating subscriber.") - TokenAuthFailed = signaling.NewError("auth_failed", "The token could not be authenticated.") - TokenExpired = signaling.NewError("token_expired", "The token is expired.") - TokenNotValidYet = signaling.NewError("token_not_valid_yet", "The token is not valid yet.") - UnknownClient = signaling.NewError("unknown_client", "Unknown client id given.") - UnsupportedCommand = signaling.NewError("bad_request", "Unsupported command received.") - UnsupportedMessage = signaling.NewError("bad_request", "Unsupported message received.") - UnsupportedPayload = signaling.NewError("unsupported_payload", "Unsupported payload type.") - ShutdownScheduled = signaling.NewError("shutdown_scheduled", "The server is scheduled to shutdown.") - RemoteSubscribersNotSupported = signaling.NewError("unsupported_subscriber", "Remote subscribers are not supported.") + TimeoutCreatingPublisher = api.NewError("timeout", "Timeout creating publisher.") + TimeoutCreatingSubscriber = api.NewError("timeout", "Timeout creating subscriber.") + TokenAuthFailed = api.NewError("auth_failed", "The token could not be authenticated.") + TokenExpired = api.NewError("token_expired", "The token is expired.") + TokenNotValidYet = api.NewError("token_not_valid_yet", "The token is not valid yet.") + UnknownClient = api.NewError("unknown_client", "Unknown client id given.") + UnsupportedCommand = api.NewError("bad_request", "Unsupported command received.") + UnsupportedMessage = api.NewError("bad_request", "Unsupported message received.") + UnsupportedPayload = api.NewError("unsupported_payload", "Unsupported payload type.") + ShutdownScheduled = api.NewError("shutdown_scheduled", "The server is scheduled to shutdown.") + RemoteSubscribersNotSupported = api.NewError("unsupported_subscriber", "Remote subscribers are not supported.") ) type ProxyServer struct { version string country string welcomeMessage string - welcomeMsg *signaling.WelcomeServerMessage + welcomeMsg *api.WelcomeServerMessage config *goconf.ConfigFile mcuTimeout time.Duration logger log.Logger @@ -349,7 +349,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * version: version, country: country, welcomeMessage: string(welcomeMessage) + "\n", - welcomeMsg: &signaling.WelcomeServerMessage{ + welcomeMsg: &api.WelcomeServerMessage{ Version: version, Country: country, Features: defaultProxyFeatures, @@ -783,7 +783,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { } var session *ProxySession - if resumeId := signaling.PublicSessionId(message.Hello.ResumeId); resumeId != "" { + if resumeId := api.PublicSessionId(message.Hello.ResumeId); resumeId != "" { if data, err := s.cookie.DecodePublic(resumeId); err == nil { session = s.GetSession(data.Sid) } @@ -804,7 +804,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { } else { var err error if session, err = s.NewSession(message.Hello); err != nil { - if e, ok := err.(*signaling.Error); ok { + if e, ok := err.(*api.Error); ok { client.SendMessage(message.NewErrorServerMessage(e)) } else { client.SendMessage(message.NewWrappedErrorServerMessage(err)) @@ -827,7 +827,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { Id: message.Id, Type: "hello", Hello: &signaling.HelloProxyServerMessage{ - Version: signaling.HelloVersionV1, + Version: api.HelloVersionV1, SessionId: session.PublicId(), Server: s.welcomeMsg, }, @@ -866,10 +866,10 @@ type proxyRemotePublisher struct { proxy *ProxyServer remoteUrl string - publisherId signaling.PublicSessionId + publisherId api.PublicSessionId } -func (p *proxyRemotePublisher) PublisherId() signaling.PublicSessionId { +func (p *proxyRemotePublisher) PublisherId() api.PublicSessionId { return p.publisherId } @@ -1009,7 +1009,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s MediaTypes: cmd.MediaTypes, // nolint } } - publisher, err := s.mcu.NewPublisher(ctx2, session, signaling.PublicSessionId(id), cmd.Sid, cmd.StreamType, *settings, &emptyInitiator{}) + publisher, err := s.mcu.NewPublisher(ctx2, session, api.PublicSessionId(id), cmd.Sid, cmd.StreamType, *settings, &emptyInitiator{}) if err == context.DeadlineExceeded { s.logger.Printf("Timeout while creating %s publisher %s for %s", cmd.StreamType, id, session.PublicId()) session.sendMessage(message.NewErrorServerMessage(TimeoutCreatingPublisher)) @@ -1069,7 +1069,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s claims, _, err := s.parseToken(cmd.RemoteToken) if err != nil { - if e, ok := err.(*signaling.Error); ok { + if e, ok := err.(*api.Error); ok { client.SendMessage(message.NewErrorServerMessage(e)) } else { client.SendMessage(message.NewWrappedErrorServerMessage(err)) @@ -1339,7 +1339,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s statsPayloadMessagesTotal.WithLabelValues(payload.Type).Inc() - var mcuData *signaling.MessageClientMessageData + var mcuData *api.MessageClientMessageData switch payload.Type { case "offer": fallthrough @@ -1348,7 +1348,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s case "selectStream": fallthrough case "candidate": - mcuData = &signaling.MessageClientMessageData{ + mcuData = &api.MessageClientMessageData{ RoomType: string(mcuClient.StreamType()), Type: payload.Type, Sid: payload.Sid, @@ -1368,7 +1368,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s case "requestoffer": fallthrough case "sendoffer": - mcuData = &signaling.MessageClientMessageData{ + mcuData = &api.MessageClientMessageData{ RoomType: string(mcuClient.StreamType()), Type: payload.Type, Sid: payload.Sid, @@ -1389,7 +1389,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response api.StringMap) { var responseMsg *signaling.ProxyServerMessage - if errors.Is(err, signaling.ErrCandidateFiltered) { + if errors.Is(err, api.ErrCandidateFiltered) { // Silently ignore filtered candidates. err = nil } @@ -1717,7 +1717,7 @@ func (s *ProxyServer) PublisherDeleted(publisher signaling.McuPublisher) { } } -func (s *ProxyServer) RemotePublisherDeleted(publisherId signaling.PublicSessionId) { +func (s *ProxyServer) RemotePublisherDeleted(publisherId api.PublicSessionId) { s.sessionsLock.RLock() defer s.sessionsLock.RUnlock() diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 3db2b55..09a1f23 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -391,16 +391,16 @@ func (m *TestMCU) GetServerInfoSfu() *signaling.BackendServerInfoSfu { return nil } -func (m *TestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *TestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { return nil, errors.New("not implemented") } -func (m *TestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { +func (m *TestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher api.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { return nil, errors.New("not implemented") } type TestMCUPublisher struct { - id signaling.PublicSessionId + id api.PublicSessionId sid string streamType signaling.StreamType } @@ -409,7 +409,7 @@ func (p *TestMCUPublisher) Id() string { return string(p.id) } -func (p *TestMCUPublisher) PublisherId() signaling.PublicSessionId { +func (p *TestMCUPublisher) PublisherId() api.PublicSessionId { return p.id } @@ -428,7 +428,7 @@ func (p *TestMCUPublisher) MaxBitrate() api.Bandwidth { func (p *TestMCUPublisher) Close(ctx context.Context) { } -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, api.StringMap)) { +func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { callback(errors.New("not implemented"), nil) } @@ -443,11 +443,11 @@ func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]signaling.Publishe return nil, errors.New("not implemented") } -func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("not implemented") } -func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { return errors.New("not implemented") } @@ -465,7 +465,7 @@ func (p *TestPublisherWithBandwidth) Bandwidth() *signaling.McuClientBandwidthIn return p.bandwidth } -func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { publisher := &TestPublisherWithBandwidth{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -573,7 +573,7 @@ func NewHangingTestMCU(t *testing.T) *HangingTestMCU { } } -func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { ctx2, cancel := context.WithTimeout(m.ctx, testTimeout*2) defer cancel() @@ -591,7 +591,7 @@ func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.Mc } } -func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { +func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher api.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { ctx2, cancel := context.WithTimeout(m.ctx, testTimeout*2) defer cancel() @@ -678,7 +678,7 @@ func NewCodecsTestMCU(t *testing.T) *CodecsTestMCU { } } -func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { assert.Equal(m.t, "opus,g722", settings.AudioCodec) assert.Equal(m.t, "vp9,vp8,av1", settings.VideoCodec) return &TestMCUPublisher{ @@ -745,7 +745,7 @@ type StreamsTestPublisher struct { streams []signaling.PublisherStream } -func (m *StreamTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *StreamTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { return &StreamsTestPublisher{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -878,7 +878,7 @@ func (p *TestRemotePublisher) Id() string { return "id" } -func (p *TestRemotePublisher) PublisherId() signaling.PublicSessionId { +func (p *TestRemotePublisher) PublisherId() api.PublicSessionId { return "id" } @@ -907,7 +907,7 @@ func (p *TestRemotePublisher) Close(ctx context.Context) { } } -func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, api.StringMap)) { +func (p *TestRemotePublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { callback(errors.New("not implemented"), nil) } @@ -972,12 +972,12 @@ func (s *TestRemoteSubscriber) Close(ctx context.Context) { s.closeFunc() } -func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *signaling.MessageClientMessage, data *signaling.MessageClientMessageData, callback func(error, api.StringMap)) { +func (s *TestRemoteSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { callback(errors.New("not implemented"), nil) } -func (s *TestRemoteSubscriber) Publisher() signaling.PublicSessionId { - return signaling.PublicSessionId(s.publisher.Id()) +func (s *TestRemoteSubscriber) Publisher() api.PublicSessionId { + return api.PublicSessionId(s.publisher.Id()) } func (m *RemoteSubscriberTestMCU) NewRemoteSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.McuRemotePublisher) (signaling.McuRemoteSubscriber, error) { @@ -1024,7 +1024,7 @@ func TestProxyRemoteSubscriber(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := signaling.PublicSessionId("the-publisher-id") + publisherId := api.PublicSessionId("the-publisher-id") claims := &signaling.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), @@ -1119,7 +1119,7 @@ func TestProxyCloseRemoteOnSessionClose(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := signaling.PublicSessionId("the-publisher-id") + publisherId := api.PublicSessionId("the-publisher-id") claims := &signaling.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), @@ -1188,12 +1188,12 @@ type UnpublishRemoteTestPublisher struct { mu sync.RWMutex // +checklocks:mu - remoteId signaling.PublicSessionId + remoteId api.PublicSessionId // +checklocks:mu remoteData *remotePublisherData } -func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id signaling.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { publisher := &UnpublishRemoteTestPublisher{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -1207,7 +1207,7 @@ func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener sign return publisher, nil } -func (p *UnpublishRemoteTestPublisher) getRemoteId() signaling.PublicSessionId { +func (p *UnpublishRemoteTestPublisher) getRemoteId() api.PublicSessionId { p.mu.RLock() defer p.mu.RUnlock() return p.remoteId @@ -1226,7 +1226,7 @@ func (p *UnpublishRemoteTestPublisher) clearRemote() { p.remoteData = nil } -func (p *UnpublishRemoteTestPublisher) PublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *UnpublishRemoteTestPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { p.mu.Lock() defer p.mu.Unlock() if assert.Empty(p.t, p.remoteId) { @@ -1240,7 +1240,7 @@ func (p *UnpublishRemoteTestPublisher) PublishRemote(ctx context.Context, remote return nil } -func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remoteId signaling.PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *UnpublishRemoteTestPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { p.mu.Lock() defer p.mu.Unlock() assert.Equal(p.t, remoteId, p.remoteId) @@ -1278,7 +1278,7 @@ func TestProxyUnpublishRemote(t *testing.T) { _, err := client1.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := signaling.PublicSessionId("the-publisher-id") + publisherId := api.PublicSessionId("the-publisher-id") require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ Id: "2345", Type: "command", @@ -1395,7 +1395,7 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { _, err := client1.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := signaling.PublicSessionId("the-publisher-id") + publisherId := api.PublicSessionId("the-publisher-id") require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ Id: "2345", Type: "command", @@ -1527,7 +1527,7 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { _, err := client1.RunUntilLoad(ctx, 0) assert.NoError(err) - publisherId := signaling.PublicSessionId("the-publisher-id") + publisherId := api.PublicSessionId("the-publisher-id") require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ Id: "2345", Type: "command", diff --git a/proxy/proxy_session.go b/proxy/proxy_session.go index 82ed8f5..482e75f 100644 --- a/proxy/proxy_session.go +++ b/proxy/proxy_session.go @@ -39,7 +39,7 @@ const ( ) type remotePublisherData struct { - id signaling.PublicSessionId + id api.PublicSessionId hostname string port int rtcpPort int @@ -48,7 +48,7 @@ type remotePublisherData struct { type ProxySession struct { logger log.Logger proxy *ProxyServer - id signaling.PublicSessionId + id api.PublicSessionId sid uint64 lastUsed atomic.Int64 ctx context.Context @@ -77,7 +77,7 @@ type ProxySession struct { remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData } -func NewProxySession(proxy *ProxyServer, sid uint64, id signaling.PublicSessionId) *ProxySession { +func NewProxySession(proxy *ProxyServer, sid uint64, id api.PublicSessionId) *ProxySession { ctx, closeFunc := context.WithCancel(context.Background()) result := &ProxySession{ logger: proxy.logger, @@ -101,7 +101,7 @@ func (s *ProxySession) Context() context.Context { return s.ctx } -func (s *ProxySession) PublicId() signaling.PublicSessionId { +func (s *ProxySession) PublicId() api.PublicSessionId { return s.id } @@ -469,7 +469,7 @@ func (s *ProxySession) OnRemoteAwarePublisherDeleted(publisher signaling.McuRemo } } -func (s *ProxySession) OnRemotePublisherDeleted(publisherId signaling.PublicSessionId) { +func (s *ProxySession) OnRemotePublisherDeleted(publisherId api.PublicSessionId) { s.subscribersLock.Lock() defer s.subscribersLock.Unlock() diff --git a/proxy/proxy_testclient_test.go b/proxy/proxy_testclient_test.go index 0c91bdc..7cd42a8 100644 --- a/proxy/proxy_testclient_test.go +++ b/proxy/proxy_testclient_test.go @@ -34,7 +34,9 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/api" ) var ( @@ -52,7 +54,7 @@ type ProxyTestClient struct { messageChan chan []byte readErrorChan chan error - sessionId signaling.PublicSessionId + sessionId api.PublicSessionId } func NewProxyTestClient(ctx context.Context, t *testing.T, url string) *ProxyTestClient { diff --git a/remotesession.go b/remotesession.go index a5a8ed5..94bd4b7 100644 --- a/remotesession.go +++ b/remotesession.go @@ -28,6 +28,7 @@ import ( "sync/atomic" "time" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -36,12 +37,12 @@ type RemoteSession struct { hub *Hub client *Client remoteClient *GrpcClient - sessionId PublicSessionId + sessionId api.PublicSessionId proxy atomic.Pointer[SessionProxy] } -func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessionId PublicSessionId) (*RemoteSession, error) { +func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessionId api.PublicSessionId) (*RemoteSession, error) { remoteSession := &RemoteSession{ logger: hub.logger, hub: hub, @@ -80,12 +81,12 @@ func (s *RemoteSession) IsConnected() bool { return true } -func (s *RemoteSession) Start(message *ClientMessage) error { +func (s *RemoteSession) Start(message *api.ClientMessage) error { return s.sendMessage(message) } func (s *RemoteSession) OnProxyMessage(msg *ServerSessionMessage) error { - var message *ServerMessage + var message *api.ServerMessage if err := json.Unmarshal(msg.Message, &message); err != nil { return err } diff --git a/room.go b/room.go index 218d42e..b4e82a2 100644 --- a/room.go +++ b/room.go @@ -79,7 +79,7 @@ type Room struct { mu *sync.RWMutex asyncCh AsyncChannel // +checklocks:mu - sessions map[PublicSessionId]Session + sessions map[api.PublicSessionId]Session // +checklocks:mu internalSessions map[*ClientSession]bool // +checklocks:mu @@ -87,7 +87,7 @@ type Room struct { // +checklocks:mu inCallSessions map[Session]bool // +checklocks:mu - roomSessionData map[PublicSessionId]*RoomSessionData + roomSessionData map[api.PublicSessionId]*RoomSessionData // +checklocks:mu statsRoomSessionsCurrent *prometheus.GaugeVec @@ -122,12 +122,12 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEv closer: internal.NewCloser(), mu: &sync.RWMutex{}, asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), - sessions: make(map[PublicSessionId]Session), + sessions: make(map[api.PublicSessionId]Session), internalSessions: make(map[*ClientSession]bool), virtualSessions: make(map[*VirtualSession]bool), inCallSessions: make(map[Session]bool), - roomSessionData: make(map[PublicSessionId]*RoomSessionData), + roomSessionData: make(map[api.PublicSessionId]*RoomSessionData), statsRoomSessionsCurrent: statsRoomSessionsCurrent.MustCurryWith(prometheus.Labels{ "backend": backend.Id(), @@ -226,9 +226,9 @@ func (r *Room) Close() []Session { result = append(result, s) } r.sessions = nil - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeClient)}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeInternal)}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeVirtual)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeClient)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeInternal)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeVirtual)}) r.mu.Unlock() return result } @@ -303,7 +303,7 @@ func (r *Room) processBackendRoomRequestAsyncRoom(message *AsyncRoomMessage) { switch message.Type { case "sessionjoined": r.notifySessionJoined(message.SessionId) - if message.ClientType == HelloClientTypeInternal { + if message.ClientType == api.HelloClientTypeInternal { r.publishUsersChangedWithInternal() } default: @@ -331,7 +331,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { } var publishUsersChanged bool switch session.ClientType() { - case HelloClientTypeInternal: + case api.HelloClientTypeInternal: clientSession, ok := session.(*ClientSession) if !ok { delete(r.sessions, sid) @@ -339,7 +339,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { panic(fmt.Sprintf("Expected a client session, got %v (%T)", session, session)) } r.internalSessions[clientSession] = true - case HelloClientTypeVirtual: + case api.HelloClientTypeVirtual: virtualSession, ok := session.(*VirtualSession) if !ok { delete(r.sessions, sid) @@ -383,7 +383,7 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { } } -func (r *Room) getOtherSessions(ignoreSessionId PublicSessionId) (Session, []Session) { +func (r *Room) getOtherSessions(ignoreSessionId api.PublicSessionId) (Session, []Session) { r.mu.Lock() defer r.mu.Unlock() @@ -399,19 +399,19 @@ func (r *Room) getOtherSessions(ignoreSessionId PublicSessionId) (Session, []Ses return r.sessions[ignoreSessionId], sessions } -func (r *Room) notifySessionJoined(sessionId PublicSessionId) { +func (r *Room) notifySessionJoined(sessionId api.PublicSessionId) { session, sessions := r.getOtherSessions(sessionId) if len(sessions) == 0 { return } - if session != nil && session.ClientType() != HelloClientTypeClient { + if session != nil && session.ClientType() != api.HelloClientTypeClient { session = nil } - events := make([]EventServerMessageSessionEntry, 0, len(sessions)) + events := make([]api.EventServerMessageSessionEntry, 0, len(sessions)) for _, s := range sessions { - entry := EventServerMessageSessionEntry{ + entry := api.EventServerMessageSessionEntry{ SessionId: s.PublicId(), UserId: s.UserId(), User: s.UserData(), @@ -419,14 +419,14 @@ func (r *Room) notifySessionJoined(sessionId PublicSessionId) { if s, ok := s.(*ClientSession); ok { entry.Features = s.GetFeatures() entry.RoomSessionId = s.RoomSessionId() - entry.Federated = s.ClientType() == HelloClientTypeFederation + entry.Federated = s.ClientType() == api.HelloClientTypeFederation } events = append(events, entry) } - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "join", Join: events, @@ -452,12 +452,12 @@ func (r *Room) notifySessionJoined(sessionId PublicSessionId) { continue } - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "participants", Type: "flags", - Flags: &RoomFlagsServerMessage{ + Flags: &api.RoomFlagsServerMessage{ RoomId: r.id, SessionId: vsess.PublicId(), Flags: vsess.Flags(), @@ -504,7 +504,7 @@ func (r *Room) RemoveSession(session Session) bool { // Handle case where virtual session was also sent by Nextcloud. users := make([]api.StringMap, 0, len(r.users)) for _, u := range r.users { - if value, found := api.GetStringMapString[PublicSessionId](u, "sessionId"); !found || value != sid { + if value, found := api.GetStringMapString[api.PublicSessionId](u, "sessionId"); !found || value != sid { users = append(users, u) } } @@ -528,9 +528,9 @@ func (r *Room) RemoveSession(session Session) bool { } r.hub.removeRoom(r) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeClient)}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeInternal)}) - r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(HelloClientTypeVirtual)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeClient)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeInternal)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeVirtual)}) r.unsubscribeBackend() r.doClose() r.mu.Unlock() @@ -542,7 +542,7 @@ func (r *Room) RemoveSession(session Session) bool { return false } -func (r *Room) publish(message *ServerMessage) error { +func (r *Room) publish(message *api.ServerMessage) error { return r.events.PublishRoomMessage(r.id, r.backend, &AsyncMessage{ Type: "message", Message: message, @@ -559,9 +559,9 @@ func (r *Room) UpdateProperties(properties json.RawMessage) { } r.properties = properties - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "room", - Room: &RoomServerMessage{ + Room: &api.RoomServerMessage{ RoomId: r.id, Properties: r.properties, }, @@ -588,12 +588,12 @@ func (r *Room) PublishSessionJoined(session Session, sessionData *RoomSessionDat userid = sessionData.UserId } - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "join", - Join: []EventServerMessageSessionEntry{ + Join: []api.EventServerMessageSessionEntry{ { SessionId: sessionId, UserId: userid, @@ -605,7 +605,7 @@ func (r *Room) PublishSessionJoined(session Session, sessionData *RoomSessionDat if session, ok := session.(*ClientSession); ok { message.Event.Join[0].Features = session.GetFeatures() message.Event.Join[0].RoomSessionId = session.RoomSessionId() - message.Event.Join[0].Federated = session.ClientType() == HelloClientTypeFederation + message.Event.Join[0].Federated = session.ClientType() == api.HelloClientTypeFederation } if err := r.publish(message); err != nil { r.logger.Printf("Could not publish session joined message in room %s: %s", r.Id(), err) @@ -618,12 +618,12 @@ func (r *Room) PublishSessionLeft(session Session) { return } - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "leave", - Leave: []PublicSessionId{ + Leave: []api.PublicSessionId{ sessionId, }, }, @@ -632,13 +632,13 @@ func (r *Room) PublishSessionLeft(session Session) { r.logger.Printf("Could not publish session left message in room %s: %s", r.Id(), err) } - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == api.HelloClientTypeInternal { r.publishUsersChangedWithInternal() } } // +checklocksread:r.mu -func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSessionId]*InternalSessionData, virtual map[PublicSessionId]*VirtualSessionData) { +func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSessionId]*InternalSessionData, virtual map[api.PublicSessionId]*VirtualSessionData) { if r.hub.rpcClients == nil { return nil, nil } @@ -665,11 +665,11 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[PublicSession mu.Lock() defer mu.Unlock() if internal == nil { - internal = make(map[PublicSessionId]*InternalSessionData, len(clientInternal)) + internal = make(map[api.PublicSessionId]*InternalSessionData, len(clientInternal)) } maps.Copy(internal, clientInternal) if virtual == nil { - virtual = make(map[PublicSessionId]*VirtualSessionData, len(clientVirtual)) + virtual = make(map[api.PublicSessionId]*VirtualSessionData, len(clientVirtual)) } maps.Copy(virtual, clientVirtual) }(client) @@ -694,9 +694,9 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap { return users } - skipSession := make(map[PublicSessionId]bool) + skipSession := make(map[api.PublicSessionId]bool) for _, user := range users { - sessionid, found := api.GetStringMapString[PublicSessionId](user, "sessionId") + sessionid, found := api.GetStringMapString[api.PublicSessionId](user, "sessionId") if !found || sessionid == "" { continue } @@ -811,9 +811,9 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St continue } - sessionId, found := api.GetStringMapString[PublicSessionId](user, "sessionId") + sessionId, found := api.GetStringMapString[api.PublicSessionId](user, "sessionId") if !found { - sessionId, found = api.GetStringMapString[PublicSessionId](user, "sessionid") + sessionId, found = api.GetStringMapString[api.PublicSessionId](user, "sessionid") if !found { continue } @@ -844,12 +844,12 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St changed = r.filterPermissions(changed) users = r.filterPermissions(users) - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "participants", Type: "update", - Update: &RoomEventServerMessage{ + Update: &api.RoomEventServerMessage{ RoomId: r.id, Changed: changed, Users: r.addInternalSessions(users), @@ -868,15 +868,15 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { var notify []*ClientSession if inCall&FlagInCall != 0 { // All connected sessions join the call. - var joined []PublicSessionId + var joined []api.PublicSessionId for _, session := range r.sessions { clientSession, ok := session.(*ClientSession) if !ok { continue } - if session.ClientType() == HelloClientTypeInternal || - session.ClientType() == HelloClientTypeFederation { + if session.ClientType() == api.HelloClientTypeInternal || + session.ClientType() == api.HelloClientTypeFederation { continue } @@ -929,12 +929,12 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { inCallMsg := json.RawMessage(strconv.FormatInt(int64(inCall), 10)) - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "participants", Type: "update", - Update: &RoomEventServerMessage{ + Update: &api.RoomEventServerMessage{ RoomId: r.id, InCall: inCallMsg, All: true, @@ -953,12 +953,12 @@ func (r *Room) PublishUsersChanged(changed []api.StringMap, users []api.StringMa changed = r.filterPermissions(changed) users = r.filterPermissions(users) - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "participants", Type: "update", - Update: &RoomEventServerMessage{ + Update: &api.RoomEventServerMessage{ RoomId: r.id, Changed: changed, Users: r.addInternalSessions(users), @@ -970,15 +970,15 @@ func (r *Room) PublishUsersChanged(changed []api.StringMap, users []api.StringMa } } -func (r *Room) getParticipantsUpdateMessage(users []api.StringMap) *ServerMessage { +func (r *Room) getParticipantsUpdateMessage(users []api.StringMap) *api.ServerMessage { users = r.filterPermissions(users) - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "participants", Type: "update", - Update: &RoomEventServerMessage{ + Update: &api.RoomEventServerMessage{ RoomId: r.id, Users: r.addInternalSessions(users), }, @@ -997,7 +997,7 @@ func (r *Room) NotifySessionResumed(session *ClientSession) { } func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) { - if flags&SessionChangeFlags != 0 && session.ClientType() == HelloClientTypeVirtual { + if flags&SessionChangeFlags != 0 && session.ClientType() == api.HelloClientTypeVirtual { // Only notify if a virtual session has changed. if virtual, ok := session.(*VirtualSession); ok { r.publishSessionFlagsChanged(virtual) @@ -1050,12 +1050,12 @@ func (r *Room) publishUsersChangedWithInternal() { } func (r *Room) publishSessionFlagsChanged(session *VirtualSession) { - message := &ServerMessage{ + message := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "participants", Type: "flags", - Flags: &RoomFlagsServerMessage{ + Flags: &api.RoomFlagsServerMessage{ RoomId: r.id, SessionId: session.PublicId(), Flags: session.Flags(), @@ -1093,7 +1093,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { parsedBackendUrl := parsed - var sid RoomSessionId + var sid api.RoomSessionId var uid string switch sess := session.(type) { case *ClientSession: @@ -1102,7 +1102,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { uid = sess.AuthUserId() case *VirtualSession: // Use our internal generated session id (will be added to Nextcloud). - sid = RoomSessionId(sess.PublicId()) + sid = api.RoomSessionId(sess.PublicId()) uid = sess.UserId() default: continue @@ -1151,12 +1151,12 @@ func (r *Room) publishRoomMessage(message *BackendRoomMessageRequest) { return } - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "message", - Message: &RoomEventMessage{ + Message: &api.RoomEventMessage{ RoomId: r.id, Data: message.Data, }, @@ -1170,12 +1170,12 @@ func (r *Room) publishRoomMessage(message *BackendRoomMessageRequest) { func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { var wg sync.WaitGroup if len(message.SessionsList) > 0 { - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "switchto", - SwitchTo: &EventServerMessageSwitchTo{ + SwitchTo: &api.EventServerMessageSwitchTo{ RoomId: message.RoomId, }, }, @@ -1183,7 +1183,7 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { for _, sessionId := range message.SessionsList { wg.Add(1) - go func(sessionId PublicSessionId) { + go func(sessionId api.PublicSessionId) { defer wg.Done() if err := r.events.PublishSessionMessage(sessionId, r.backend, &AsyncMessage{ @@ -1199,15 +1199,15 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { if len(message.SessionsMap) > 0 { for sessionId, details := range message.SessionsMap { wg.Add(1) - go func(sessionId PublicSessionId, details json.RawMessage) { + go func(sessionId api.PublicSessionId, details json.RawMessage) { defer wg.Done() - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "switchto", - SwitchTo: &EventServerMessageSwitchTo{ + SwitchTo: &api.EventServerMessageSwitchTo{ RoomId: message.RoomId, Details: details, }, @@ -1227,9 +1227,9 @@ func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { } func (r *Room) notifyInternalRoomDeleted() { - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "event", - Event: &EventServerMessage{ + Event: &api.EventServerMessage{ Target: "room", Type: "delete", }, diff --git a/roomsessions.go b/roomsessions.go index 090476c..de7e76f 100644 --- a/roomsessions.go +++ b/roomsessions.go @@ -24,6 +24,8 @@ package signaling import ( "context" "errors" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) var ( @@ -31,9 +33,9 @@ var ( ) type RoomSessions interface { - SetRoomSession(session Session, roomSessionId RoomSessionId) error + SetRoomSession(session Session, roomSessionId api.RoomSessionId) error DeleteRoomSession(session Session) - GetSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) - LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) + GetSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) + LookupSessionId(ctx context.Context, roomSessionId api.RoomSessionId, disconnectReason string) (api.PublicSessionId, error) } diff --git a/roomsessions_builtin.go b/roomsessions_builtin.go index 85bdf92..cb85f7c 100644 --- a/roomsessions_builtin.go +++ b/roomsessions_builtin.go @@ -27,29 +27,30 @@ import ( "sync" "sync/atomic" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" ) type BuiltinRoomSessions struct { mu sync.RWMutex // +checklocks:mu - sessionIdToRoomSession map[PublicSessionId]RoomSessionId + sessionIdToRoomSession map[api.PublicSessionId]api.RoomSessionId // +checklocks:mu - roomSessionToSessionid map[RoomSessionId]PublicSessionId + roomSessionToSessionid map[api.RoomSessionId]api.PublicSessionId clients *GrpcClients } func NewBuiltinRoomSessions(clients *GrpcClients) (RoomSessions, error) { return &BuiltinRoomSessions{ - sessionIdToRoomSession: make(map[PublicSessionId]RoomSessionId), - roomSessionToSessionid: make(map[RoomSessionId]PublicSessionId), + sessionIdToRoomSession: make(map[api.PublicSessionId]api.RoomSessionId), + roomSessionToSessionid: make(map[api.RoomSessionId]api.PublicSessionId), clients: clients, }, nil } -func (r *BuiltinRoomSessions) SetRoomSession(session Session, roomSessionId RoomSessionId) error { +func (r *BuiltinRoomSessions) SetRoomSession(session Session, roomSessionId api.RoomSessionId) error { if roomSessionId == "" { r.DeleteRoomSession(session) return nil @@ -87,7 +88,7 @@ func (r *BuiltinRoomSessions) DeleteRoomSession(session Session) { } } -func (r *BuiltinRoomSessions) GetSessionId(roomSessionId RoomSessionId) (PublicSessionId, error) { +func (r *BuiltinRoomSessions) GetSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { r.mu.RLock() defer r.mu.RUnlock() sid, found := r.roomSessionToSessionid[roomSessionId] @@ -98,7 +99,7 @@ func (r *BuiltinRoomSessions) GetSessionId(roomSessionId RoomSessionId) (PublicS return sid, nil } -func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId RoomSessionId, disconnectReason string) (PublicSessionId, error) { +func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId api.RoomSessionId, disconnectReason string) (api.PublicSessionId, error) { sid, err := r.GetSessionId(roomSessionId) if err == nil { return sid, nil @@ -146,5 +147,5 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId return "", ErrNoSuchRoomSession } - return value.(PublicSessionId), nil + return value.(api.PublicSessionId), nil } diff --git a/roomsessions_test.go b/roomsessions_test.go index a83e518..87bdfc7 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -34,22 +34,22 @@ import ( ) type DummySession struct { - publicId PublicSessionId + publicId api.PublicSessionId } func (s *DummySession) Context() context.Context { return context.Background() } -func (s *DummySession) PrivateId() PrivateSessionId { +func (s *DummySession) PrivateId() api.PrivateSessionId { return "" } -func (s *DummySession) PublicId() PublicSessionId { +func (s *DummySession) PublicId() api.PublicSessionId { return s.publicId } -func (s *DummySession) ClientType() ClientType { +func (s *DummySession) ClientType() api.ClientType { return "" } @@ -88,6 +88,10 @@ func (s *DummySession) GetRoom() *Room { return nil } +func (s *DummySession) IsInRoom(id string) bool { + return false +} + func (s *DummySession) LeaveRoom(notify bool) *Room { return nil } @@ -99,15 +103,15 @@ func (s *DummySession) HasPermission(permission Permission) bool { return false } -func (s *DummySession) SendError(e *Error) bool { +func (s *DummySession) SendError(e *api.Error) bool { return false } -func (s *DummySession) SendMessage(message *ServerMessage) bool { +func (s *DummySession) SendMessage(message *api.ServerMessage) bool { return false } -func checkSession(t *testing.T, sessions RoomSessions, sessionId PublicSessionId, roomSessionId RoomSessionId) Session { +func checkSession(t *testing.T, sessions RoomSessions, sessionId api.PublicSessionId, roomSessionId api.RoomSessionId) Session { session := &DummySession{ publicId: sessionId, } diff --git a/session.go b/session.go index 0a188e8..2a2345b 100644 --- a/session.go +++ b/session.go @@ -51,9 +51,9 @@ var ( type Session interface { Context() context.Context - PrivateId() PrivateSessionId - PublicId() PublicSessionId - ClientType() ClientType + PrivateId() api.PrivateSessionId + PublicId() api.PublicSessionId + ClientType() api.ClientType Data() *SessionIdData UserId() string @@ -67,13 +67,14 @@ type Session interface { SetRoom(room *Room) GetRoom() *Room LeaveRoom(notify bool) *Room + IsInRoom(id string) bool Close() HasPermission(permission Permission) bool - SendError(e *Error) bool - SendMessage(message *ServerMessage) bool + SendError(e *api.Error) bool + SendMessage(message *api.ServerMessage) bool } type SessionWithInCall interface { diff --git a/sessionid_codec.go b/sessionid_codec.go index 0d5ef86..bb5a4e1 100644 --- a/sessionid_codec.go +++ b/sessionid_codec.go @@ -37,6 +37,8 @@ import ( "unsafe" "google.golang.org/protobuf/proto" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -235,14 +237,14 @@ func (c *SessionIdCodec) decodeRaw(name string, value []byte) (*SessionIdData, e return data, nil } -func (c *SessionIdCodec) EncodePrivate(sessionData *SessionIdData) (PrivateSessionId, error) { +func (c *SessionIdCodec) EncodePrivate(sessionData *SessionIdData) (api.PrivateSessionId, error) { id, err := c.encodeRaw(privateSessionName, sessionData) if err != nil { return "", err } defer c.bytesPool.Put(id) - return PrivateSessionId(c.encodeToString(id)), nil + return api.PrivateSessionId(c.encodeToString(id)), nil } func (c *SessionIdCodec) reverseSessionId(data []byte) { @@ -251,7 +253,7 @@ func (c *SessionIdCodec) reverseSessionId(data []byte) { } } -func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (PublicSessionId, error) { +func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (api.PublicSessionId, error) { encoded, err := c.encodeRaw(publicSessionName, sessionData) if err != nil { return "", err @@ -265,10 +267,10 @@ func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (PublicSession c.reverseSessionId(encoded) defer c.bytesPool.Put(encoded) - return PublicSessionId(c.encodeToString(encoded)), nil + return api.PublicSessionId(c.encodeToString(encoded)), nil } -func (c *SessionIdCodec) DecodePrivate(encodedData PrivateSessionId) (*SessionIdData, error) { +func (c *SessionIdCodec) DecodePrivate(encodedData api.PrivateSessionId) (*SessionIdData, error) { decoded, err := c.decodeFromString(string(encodedData)) if err != nil { return nil, fmt.Errorf("invalid session id: %w", err) @@ -278,7 +280,7 @@ func (c *SessionIdCodec) DecodePrivate(encodedData PrivateSessionId) (*SessionId return c.decodeRaw(privateSessionName, decoded) } -func (c *SessionIdCodec) DecodePublic(encodedData PublicSessionId) (*SessionIdData, error) { +func (c *SessionIdCodec) DecodePublic(encodedData api.PublicSessionId) (*SessionIdData, error) { decoded, err := c.decodeFromString(string(encodedData)) if err != nil { return nil, fmt.Errorf("invalid session id: %w", err) diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 47317c8..3236202 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -27,6 +27,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) func TestReverseSessionId(t *testing.T) { @@ -158,11 +160,11 @@ func TestPublicPrivate(t *testing.T) { codec.Put(data) } - if data, err := codec.DecodePublic(PublicSessionId(private)); !assert.Error(err) { + if data, err := codec.DecodePublic(api.PublicSessionId(private)); !assert.Error(err) { assert.Fail("should have failed", "received %+v", data) codec.Put(data) } - if data, err := codec.DecodePrivate(PrivateSessionId(public)); !assert.Error(err) { + if data, err := codec.DecodePrivate(api.PrivateSessionId(public)); !assert.Error(err) { assert.Fail("should have failed", "received %+v", data) codec.Put(data) } diff --git a/testclient_test.go b/testclient_test.go index 2088fb8..2a6b2c9 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -70,7 +70,7 @@ func getWebsocketUrl(url string) string { } } -func getPubliceSessionIdData(h *Hub, publicId PublicSessionId) *SessionIdData { +func getPubliceSessionIdData(h *Hub, publicId api.PublicSessionId) *SessionIdData { decodedPublic := h.decodePublicSessionId(publicId) if decodedPublic == nil { panic("invalid public session id") @@ -78,7 +78,7 @@ func getPubliceSessionIdData(h *Hub, publicId PublicSessionId) *SessionIdData { return decodedPublic } -func checkMessageType(t *testing.T, message *ServerMessage, expectedType string) bool { +func checkMessageType(t *testing.T, message *api.ServerMessage, expectedType string) bool { assert := assert.New(t) if !assert.NotNil(message, "no message received") { return false @@ -115,7 +115,7 @@ func checkMessageType(t *testing.T, message *ServerMessage, expectedType string) return !failed } -func checkMessageSender(t *testing.T, hub *Hub, sender *MessageServerMessageSender, senderType string, hello *HelloServerMessage) bool { +func checkMessageSender(t *testing.T, hub *Hub, sender *api.MessageServerMessageSender, senderType string, hello *api.HelloServerMessage) bool { assert := assert.New(t) return assert.Equal(senderType, sender.Type, "invalid sender type in %+v", sender) && assert.Equal(hello.SessionId, sender.SessionId, "invalid session id, expectd %+v, got %+v in %+v", @@ -126,7 +126,7 @@ func checkMessageSender(t *testing.T, hub *Hub, sender *MessageServerMessageSend assert.Equal(hello.UserId, sender.UserId, "invalid userid in %+v", sender) } -func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) bool { +func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *api.HelloServerMessage, payload any, sender **api.MessageServerMessageSender, recipient **api.MessageClientMessageRecipient) bool { assert := assert.New(t) message, ok := client.RunUntilMessage(ctx) if !ok || @@ -145,15 +145,15 @@ func checkReceiveClientMessageWithSenderAndRecipient(ctx context.Context, t *tes return true } -func checkReceiveClientMessageWithSender(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) bool { +func checkReceiveClientMessageWithSender(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *api.HelloServerMessage, payload any, sender **api.MessageServerMessageSender) bool { return checkReceiveClientMessageWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, sender, nil) } -func checkReceiveClientMessage(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any) bool { +func checkReceiveClientMessage(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *api.HelloServerMessage, payload any) bool { return checkReceiveClientMessageWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, nil, nil) } -func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender, recipient **MessageClientMessageRecipient) bool { +func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *api.HelloServerMessage, payload any, sender **api.MessageServerMessageSender, recipient **api.MessageClientMessageRecipient) bool { assert := assert.New(t) message, ok := client.RunUntilMessage(ctx) if !ok || @@ -172,15 +172,15 @@ func checkReceiveClientControlWithSenderAndRecipient(ctx context.Context, t *tes return true } -func checkReceiveClientControlWithSender(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any, sender **MessageServerMessageSender) bool { // nolint +func checkReceiveClientControlWithSender(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *api.HelloServerMessage, payload any, sender **api.MessageServerMessageSender) bool { // nolint return checkReceiveClientControlWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, sender, nil) } -func checkReceiveClientControl(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *HelloServerMessage, payload any) bool { +func checkReceiveClientControl(ctx context.Context, t *testing.T, client *TestClient, senderType string, hello *api.HelloServerMessage, payload any) bool { return checkReceiveClientControlWithSenderAndRecipient(ctx, t, client, senderType, hello, payload, nil, nil) } -func checkReceiveClientEvent(ctx context.Context, t *testing.T, client *TestClient, eventType string, msg **EventServerMessage) bool { +func checkReceiveClientEvent(ctx context.Context, t *testing.T, client *TestClient, eventType string, msg **api.EventServerMessage) bool { assert := assert.New(t) message, ok := client.RunUntilMessage(ctx) if !ok || @@ -210,7 +210,7 @@ type TestClient struct { messageChan chan []byte readErrorChan chan error - publicId PublicSessionId + publicId api.PublicSessionId } func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub) *TestClient { @@ -272,7 +272,7 @@ func NewTestClient(t *testing.T, server *httptest.Server, hub *Hub) *TestClient return client } -func NewTestClientWithHello(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub, userId string) (*TestClient, *ServerMessage) { +func NewTestClientWithHello(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub, userId string) (*TestClient, *api.ServerMessage) { client := NewTestClient(t, server, hub) t.Cleanup(func() { client.CloseWithBye() @@ -345,7 +345,7 @@ func (c *TestClient) WaitForClientRemoved(ctx context.Context) error { return nil } -func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId PublicSessionId) error { +func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId api.PublicSessionId) error { data := c.hub.decodePublicSessionId(sessionId) if data == nil { return errors.New("Invalid session id passed") @@ -374,7 +374,7 @@ func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId Public func (c *TestClient) WriteJSON(data any) error { if !strings.Contains(c.t.Name(), "HelloUnsupportedVersion") { - if msg, ok := data.(*ClientMessage); ok { + if msg, ok := data.(*api.ClientMessage); ok { if err := msg.CheckValid(); err != nil { return err } @@ -398,7 +398,7 @@ func (c *TestClient) SendHelloV1(userid string) error { params := TestBackendClientAuthParams{ UserId: userid, } - return c.SendHelloParams(c.server.URL, HelloVersionV1, "", nil, params) + return c.SendHelloParams(c.server.URL, api.HelloVersionV1, "", nil, params) } func (c *TestClient) SendHelloV2(userid string) error { @@ -416,7 +416,7 @@ func (c *TestClient) CreateHelloV2TokenWithUserdata(userid string, issuedAt time return "", err } - claims := &HelloV2TokenClaims{ + claims := &api.HelloV2TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: c.server.URL, Subject: userid, @@ -458,18 +458,18 @@ func (c *TestClient) SendHelloV2WithTimesAndFeatures(userid string, issuedAt tim tokenString, err := c.CreateHelloV2Token(userid, issuedAt, expiresAt) require.NoError(c.t, err) - params := HelloV2AuthParams{ + params := api.HelloV2AuthParams{ Token: tokenString, } - return c.SendHelloParams(c.server.URL, HelloVersionV2, "", features, params) + return c.SendHelloParams(c.server.URL, api.HelloVersionV2, "", features, params) } -func (c *TestClient) SendHelloResume(resumeId PrivateSessionId) error { - hello := &ClientMessage{ +func (c *TestClient) SendHelloResume(resumeId api.PrivateSessionId) error { + hello := &api.ClientMessage{ Id: "1234", Type: "hello", - Hello: &HelloClientMessage{ - Version: HelloVersionV1, + Hello: &api.HelloClientMessage{ + Version: api.HelloVersionV1, ResumeId: resumeId, }, } @@ -484,7 +484,7 @@ func (c *TestClient) SendHelloClientWithFeatures(userid string, features []strin params := TestBackendClientAuthParams{ UserId: userid, } - return c.SendHelloParams(c.server.URL, HelloVersionV1, "client", features, params) + return c.SendHelloParams(c.server.URL, api.HelloVersionV1, "client", features, params) } func (c *TestClient) SendHelloInternal() error { @@ -498,25 +498,25 @@ func (c *TestClient) SendHelloInternalWithFeatures(features []string) error { token := hex.EncodeToString(mac.Sum(nil)) backend := c.server.URL - params := ClientTypeInternalAuthParams{ + params := api.ClientTypeInternalAuthParams{ Random: random, Token: token, Backend: backend, } - return c.SendHelloParams("", HelloVersionV1, "internal", features, params) + return c.SendHelloParams("", api.HelloVersionV1, "internal", features, params) } -func (c *TestClient) SendHelloParams(url string, version string, clientType ClientType, features []string, params any) error { +func (c *TestClient) SendHelloParams(url string, version string, clientType api.ClientType, features []string, params any) error { data, err := json.Marshal(params) require.NoError(c.t, err) - hello := &ClientMessage{ + hello := &api.ClientMessage{ Id: "1234", Type: "hello", - Hello: &HelloClientMessage{ + Hello: &api.HelloClientMessage{ Version: version, Features: features, - Auth: &HelloClientMessageAuth{ + Auth: &api.HelloClientMessageAuth{ Type: clientType, Url: url, Params: data, @@ -527,22 +527,22 @@ func (c *TestClient) SendHelloParams(url string, version string, clientType Clie } func (c *TestClient) SendBye() error { - hello := &ClientMessage{ + hello := &api.ClientMessage{ Id: "9876", Type: "bye", - Bye: &ByeClientMessage{}, + Bye: &api.ByeClientMessage{}, } return c.WriteJSON(hello) } -func (c *TestClient) SendMessage(recipient MessageClientMessageRecipient, data any) error { +func (c *TestClient) SendMessage(recipient api.MessageClientMessageRecipient, data any) error { payload, err := json.Marshal(data) require.NoError(c.t, err) - message := &ClientMessage{ + message := &api.ClientMessage{ Id: "abcd", Type: "message", - Message: &MessageClientMessage{ + Message: &api.MessageClientMessage{ Recipient: recipient, Data: payload, }, @@ -550,15 +550,15 @@ func (c *TestClient) SendMessage(recipient MessageClientMessageRecipient, data a return c.WriteJSON(message) } -func (c *TestClient) SendControl(recipient MessageClientMessageRecipient, data any) error { +func (c *TestClient) SendControl(recipient api.MessageClientMessageRecipient, data any) error { payload, err := json.Marshal(data) require.NoError(c.t, err) - message := &ClientMessage{ + message := &api.ClientMessage{ Id: "abcd", Type: "control", - Control: &ControlClientMessage{ - MessageClientMessage: MessageClientMessage{ + Control: &api.ControlClientMessage{ + MessageClientMessage: api.MessageClientMessage{ Recipient: recipient, Data: payload, }, @@ -567,11 +567,11 @@ func (c *TestClient) SendControl(recipient MessageClientMessageRecipient, data a return c.WriteJSON(message) } -func (c *TestClient) SendInternalAddSession(msg *AddSessionInternalClientMessage) error { - message := &ClientMessage{ +func (c *TestClient) SendInternalAddSession(msg *api.AddSessionInternalClientMessage) error { + message := &api.ClientMessage{ Id: "abcd", Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "addsession", AddSession: msg, }, @@ -579,11 +579,11 @@ func (c *TestClient) SendInternalAddSession(msg *AddSessionInternalClientMessage return c.WriteJSON(message) } -func (c *TestClient) SendInternalUpdateSession(msg *UpdateSessionInternalClientMessage) error { - message := &ClientMessage{ +func (c *TestClient) SendInternalUpdateSession(msg *api.UpdateSessionInternalClientMessage) error { + message := &api.ClientMessage{ Id: "abcd", Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "updatesession", UpdateSession: msg, }, @@ -591,11 +591,11 @@ func (c *TestClient) SendInternalUpdateSession(msg *UpdateSessionInternalClientM return c.WriteJSON(message) } -func (c *TestClient) SendInternalRemoveSession(msg *RemoveSessionInternalClientMessage) error { - message := &ClientMessage{ +func (c *TestClient) SendInternalRemoveSession(msg *api.RemoveSessionInternalClientMessage) error { + message := &api.ClientMessage{ Id: "abcd", Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "removesession", RemoveSession: msg, }, @@ -603,11 +603,11 @@ func (c *TestClient) SendInternalRemoveSession(msg *RemoveSessionInternalClientM return c.WriteJSON(message) } -func (c *TestClient) SendInternalDialout(msg *DialoutInternalClientMessage) error { - message := &ClientMessage{ +func (c *TestClient) SendInternalDialout(msg *api.DialoutInternalClientMessage) error { + message := &api.ClientMessage{ Id: "abcd", Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "dialout", Dialout: msg, }, @@ -619,10 +619,10 @@ func (c *TestClient) SetTransientData(key string, value any, ttl time.Duration) payload, err := json.Marshal(value) require.NoError(c.t, err) - message := &ClientMessage{ + message := &api.ClientMessage{ Id: "efgh", Type: "transient", - TransientData: &TransientDataClientMessage{ + TransientData: &api.TransientDataClientMessage{ Type: "set", Key: key, Value: payload, @@ -633,10 +633,10 @@ func (c *TestClient) SetTransientData(key string, value any, ttl time.Duration) } func (c *TestClient) RemoveTransientData(key string) error { - message := &ClientMessage{ + message := &api.ClientMessage{ Id: "ijkl", Type: "transient", - TransientData: &TransientDataClientMessage{ + TransientData: &api.TransientDataClientMessage{ Type: "remove", Key: key, }, @@ -659,20 +659,20 @@ func (c *TestClient) DrainMessages(ctx context.Context) error { return nil } -func (c *TestClient) GetPendingMessages(ctx context.Context) ([]*ServerMessage, error) { - var result []*ServerMessage +func (c *TestClient) GetPendingMessages(ctx context.Context) ([]*api.ServerMessage, error) { + var result []*api.ServerMessage select { case err := <-c.readErrorChan: return nil, err case msg := <-c.messageChan: - var m ServerMessage + var m api.ServerMessage if err := json.Unmarshal(msg, &m); err != nil { return nil, err } result = append(result, &m) n := len(c.messageChan) for range n { - var m ServerMessage + var m api.ServerMessage msg = <-c.messageChan if err := json.Unmarshal(msg, &m); err != nil { return nil, err @@ -694,7 +694,7 @@ func (c *TestClient) RunUntilClosed(ctx context.Context) bool { c.assert.NoError(err, "Received unexpected error") case msg := <-c.messageChan: - var m ServerMessage + var m api.ServerMessage if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { c.assert.Fail("Server should have closed the connection", "received %+v", m) } @@ -709,7 +709,7 @@ func (c *TestClient) RunUntilErrorIs(ctx context.Context, targets ...error) bool select { case err = <-c.readErrorChan: case msg := <-c.messageChan: - var m ServerMessage + var m api.ServerMessage if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { c.assert.Fail("received message", "expected one of errors %+v, got message %+v", targets, m) } @@ -731,13 +731,13 @@ func (c *TestClient) RunUntilErrorIs(ctx context.Context, targets ...error) bool return false } -func (c *TestClient) RunUntilMessage(ctx context.Context) (*ServerMessage, bool) { +func (c *TestClient) RunUntilMessage(ctx context.Context) (*api.ServerMessage, bool) { select { case err := <-c.readErrorChan: c.assert.NoError(err, "error reading while waiting for message") return nil, false case msg := <-c.messageChan: - var m ServerMessage + var m api.ServerMessage if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { return &m, true } @@ -747,7 +747,7 @@ func (c *TestClient) RunUntilMessage(ctx context.Context) (*ServerMessage, bool) return nil, false } -func (c *TestClient) RunUntilMessageOrClosed(ctx context.Context) (*ServerMessage, bool) { +func (c *TestClient) RunUntilMessageOrClosed(ctx context.Context) (*api.ServerMessage, bool) { select { case err := <-c.readErrorChan: if c.assert.Error(err) && websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { @@ -757,7 +757,7 @@ func (c *TestClient) RunUntilMessageOrClosed(ctx context.Context) (*ServerMessag c.assert.NoError(err, "Received unexpected error") return nil, false case msg := <-c.messageChan: - var m ServerMessage + var m api.ServerMessage if err := json.Unmarshal(msg, &m); c.assert.NoError(err, "error decoding received message") { return &m, true } @@ -767,7 +767,7 @@ func (c *TestClient) RunUntilMessageOrClosed(ctx context.Context) (*ServerMessag return nil, false } -func (c *TestClient) RunUntilError(ctx context.Context, code string) (*Error, bool) { +func (c *TestClient) RunUntilError(ctx context.Context, code string) (*api.Error, bool) { message, ok := c.RunUntilMessage(ctx) if !ok || !checkMessageType(c.t, message, "error") || @@ -778,7 +778,7 @@ func (c *TestClient) RunUntilError(ctx context.Context, code string) (*Error, bo return message.Error, true } -func (c *TestClient) RunUntilHello(ctx context.Context) (*ServerMessage, bool) { +func (c *TestClient) RunUntilHello(ctx context.Context) (*api.ServerMessage, bool) { message, ok := c.RunUntilMessage(ctx) if !ok || !checkMessageType(c.t, message, "hello") { @@ -789,15 +789,15 @@ func (c *TestClient) RunUntilHello(ctx context.Context) (*ServerMessage, bool) { return message, true } -func (c *TestClient) JoinRoom(ctx context.Context, roomId string) (*ServerMessage, bool) { - return c.JoinRoomWithRoomSession(ctx, roomId, RoomSessionId(fmt.Sprintf("%s-%s", roomId, c.publicId))) +func (c *TestClient) JoinRoom(ctx context.Context, roomId string) (*api.ServerMessage, bool) { + return c.JoinRoomWithRoomSession(ctx, roomId, api.RoomSessionId(fmt.Sprintf("%s-%s", roomId, c.publicId))) } -func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, roomSessionId RoomSessionId) (message *ServerMessage, ok bool) { - msg := &ClientMessage{ +func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, roomSessionId api.RoomSessionId) (message *api.ServerMessage, ok bool) { + msg := &api.ClientMessage{ Id: "ABCD", Type: "room", - Room: &RoomClientMessage{ + Room: &api.RoomClientMessage{ RoomId: roomId, SessionId: roomSessionId, }, @@ -815,7 +815,7 @@ func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, return message, true } -func checkMessageRoomId(t *testing.T, message *ServerMessage, roomId string) bool { +func checkMessageRoomId(t *testing.T, message *api.ServerMessage, roomId string) bool { return checkMessageType(t, message, "room") && assert.Equal(t, roomId, message.Room.RoomId, "invalid room id in %+v", message) } @@ -825,18 +825,18 @@ func (c *TestClient) RunUntilRoom(ctx context.Context, roomId string) bool { return ok && checkMessageRoomId(c.t, message, roomId) } -func (c *TestClient) checkMessageJoined(message *ServerMessage, hello *HelloServerMessage) bool { +func (c *TestClient) checkMessageJoined(message *api.ServerMessage, hello *api.HelloServerMessage) bool { return c.checkMessageJoinedSession(message, hello.SessionId, hello.UserId) } -func (c *TestClient) checkSingleMessageJoined(message *ServerMessage) bool { +func (c *TestClient) checkSingleMessageJoined(message *api.ServerMessage) bool { return checkMessageType(c.t, message, "event") && c.assert.Equal("room", message.Event.Target, "invalid event target in %+v", message) && c.assert.Equal("join", message.Event.Type, "invalid event type in %+v", message) && c.assert.Len(message.Event.Join, 1, "invalid number of join event entries in %+v", message) } -func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId PublicSessionId, userId string) bool { +func (c *TestClient) checkMessageJoinedSession(message *api.ServerMessage, sessionId api.PublicSessionId, userId string) bool { if !c.checkSingleMessageJoined(message) { return false } @@ -858,10 +858,10 @@ func (c *TestClient) checkMessageJoinedSession(message *ServerMessage, sessionId return !failed } -func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*HelloServerMessage) ([]EventServerMessageSessionEntry, []*ServerMessage, bool) { - received := make([]EventServerMessageSessionEntry, len(hello)) - var ignored []*ServerMessage - hellos := make(map[*HelloServerMessage]int, len(hello)) +func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*api.HelloServerMessage) ([]api.EventServerMessageSessionEntry, []*api.ServerMessage, bool) { + received := make([]api.EventServerMessageSessionEntry, len(hello)) + var ignored []*api.ServerMessage + hellos := make(map[*api.HelloServerMessage]int, len(hello)) for idx, h := range hello { hellos[h] = idx } @@ -905,16 +905,16 @@ func (c *TestClient) RunUntilJoinedAndReturn(ctx context.Context, hello ...*Hell return received, ignored, true } -func (c *TestClient) RunUntilJoined(ctx context.Context, hello ...*HelloServerMessage) bool { +func (c *TestClient) RunUntilJoined(ctx context.Context, hello ...*api.HelloServerMessage) bool { _, unexpected, ok := c.RunUntilJoinedAndReturn(ctx, hello...) return ok && c.assert.Empty(unexpected, "Received unexpected messages: %+v", unexpected) } -func (c *TestClient) checkMessageRoomLeave(message *ServerMessage, hello *HelloServerMessage) bool { +func (c *TestClient) checkMessageRoomLeave(message *api.ServerMessage, hello *api.HelloServerMessage) bool { return c.checkMessageRoomLeaveSession(message, hello.SessionId) } -func (c *TestClient) checkMessageRoomLeaveSession(message *ServerMessage, sessionId PublicSessionId) bool { +func (c *TestClient) checkMessageRoomLeaveSession(message *api.ServerMessage, sessionId api.PublicSessionId) bool { return checkMessageType(c.t, message, "event") && c.assert.Equal("room", message.Event.Target, "invalid target in %+v", message) && c.assert.Equal("leave", message.Event.Type, "invalid event type in %+v", message) && @@ -926,12 +926,12 @@ func (c *TestClient) checkMessageRoomLeaveSession(message *ServerMessage, sessio ) } -func (c *TestClient) RunUntilLeft(ctx context.Context, hello *HelloServerMessage) bool { +func (c *TestClient) RunUntilLeft(ctx context.Context, hello *api.HelloServerMessage) bool { message, ok := c.RunUntilMessage(ctx) return ok && c.checkMessageRoomLeave(message, hello) } -func checkMessageRoomlistUpdate(t *testing.T, message *ServerMessage) (*RoomEventServerMessage, bool) { +func checkMessageRoomlistUpdate(t *testing.T, message *api.ServerMessage) (*api.RoomEventServerMessage, bool) { assert := assert.New(t) if !checkMessageType(t, message, "event") || !assert.Equal("roomlist", message.Event.Target, "invalid event target in %+v", message) || @@ -943,7 +943,7 @@ func checkMessageRoomlistUpdate(t *testing.T, message *ServerMessage) (*RoomEven return message.Event.Update, true } -func (c *TestClient) RunUntilRoomlistUpdate(ctx context.Context) (*RoomEventServerMessage, bool) { +func (c *TestClient) RunUntilRoomlistUpdate(ctx context.Context) (*api.RoomEventServerMessage, bool) { message, ok := c.RunUntilMessage(ctx) if !ok { return nil, false @@ -952,7 +952,7 @@ func (c *TestClient) RunUntilRoomlistUpdate(ctx context.Context) (*RoomEventServ return checkMessageRoomlistUpdate(c.t, message) } -func checkMessageRoomlistDisinvite(t *testing.T, message *ServerMessage) (*RoomDisinviteEventServerMessage, bool) { +func checkMessageRoomlistDisinvite(t *testing.T, message *api.ServerMessage) (*api.RoomDisinviteEventServerMessage, bool) { assert := assert.New(t) if !checkMessageType(t, message, "event") || !assert.Equal("roomlist", message.Event.Target, "invalid event target in %+v", message) || @@ -964,7 +964,7 @@ func checkMessageRoomlistDisinvite(t *testing.T, message *ServerMessage) (*RoomD return message.Event.Disinvite, true } -func (c *TestClient) RunUntilRoomlistDisinvite(ctx context.Context) (*RoomDisinviteEventServerMessage, bool) { +func (c *TestClient) RunUntilRoomlistDisinvite(ctx context.Context) (*api.RoomDisinviteEventServerMessage, bool) { message, ok := c.RunUntilMessage(ctx) if !ok { return nil, false @@ -973,7 +973,7 @@ func (c *TestClient) RunUntilRoomlistDisinvite(ctx context.Context) (*RoomDisinv return checkMessageRoomlistDisinvite(c.t, message) } -func checkMessageParticipantsInCall(t *testing.T, message *ServerMessage) (*RoomEventServerMessage, bool) { +func checkMessageParticipantsInCall(t *testing.T, message *api.ServerMessage) (*api.RoomEventServerMessage, bool) { assert := assert.New(t) if !checkMessageType(t, message, "event") || !assert.Equal("participants", message.Event.Target, "invalid event target in %+v", message) || @@ -985,7 +985,7 @@ func checkMessageParticipantsInCall(t *testing.T, message *ServerMessage) (*Room return message.Event.Update, true } -func checkMessageParticipantFlags(t *testing.T, message *ServerMessage) (*RoomFlagsServerMessage, bool) { +func checkMessageParticipantFlags(t *testing.T, message *api.ServerMessage) (*api.RoomFlagsServerMessage, bool) { assert := assert.New(t) if !checkMessageType(t, message, "event") || !assert.Equal("participants", message.Event.Target, "invalid event target in %+v", message) || @@ -997,7 +997,7 @@ func checkMessageParticipantFlags(t *testing.T, message *ServerMessage) (*RoomFl return message.Event.Flags, true } -func checkMessageRoomMessage(t *testing.T, message *ServerMessage) (*RoomEventMessage, bool) { +func checkMessageRoomMessage(t *testing.T, message *api.ServerMessage) (*api.RoomEventMessage, bool) { assert := assert.New(t) if !checkMessageType(t, message, "event") || !assert.Equal("room", message.Event.Target, "invalid event target in %+v", message) || @@ -1009,7 +1009,7 @@ func checkMessageRoomMessage(t *testing.T, message *ServerMessage) (*RoomEventMe return message.Event.Message, true } -func (c *TestClient) RunUntilRoomMessage(ctx context.Context) (*RoomEventMessage, bool) { +func (c *TestClient) RunUntilRoomMessage(ctx context.Context) (*api.RoomEventMessage, bool) { message, ok := c.RunUntilMessage(ctx) if !ok { return nil, false @@ -1018,7 +1018,7 @@ func (c *TestClient) RunUntilRoomMessage(ctx context.Context) (*RoomEventMessage return checkMessageRoomMessage(c.t, message) } -func checkMessageError(t *testing.T, message *ServerMessage, msgid string) bool { +func checkMessageError(t *testing.T, message *api.ServerMessage, msgid string) bool { return checkMessageType(t, message, "error") && assert.Equal(t, msgid, message.Error.Code, "invalid error code in %+v", message) } @@ -1059,14 +1059,14 @@ func (c *TestClient) RunUntilAnswer(ctx context.Context, answer string) bool { return c.RunUntilAnswerFromSender(ctx, answer, nil) } -func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string, sender *MessageServerMessageSender) bool { +func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string, sender *api.MessageServerMessageSender) bool { message, ok := c.RunUntilMessage(ctx) if !ok || !checkMessageType(c.t, message, "message") { return false } if sender != nil { - if !checkMessageSender(c.t, c.hub, message.Message.Sender, sender.Type, &HelloServerMessage{ + if !checkMessageSender(c.t, c.hub, message.Message.Sender, sender.Type, &api.HelloServerMessage{ SessionId: sender.SessionId, UserId: sender.UserId, }) { @@ -1100,7 +1100,7 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string return true } -func checkMessageTransientSet(t *testing.T, message *ServerMessage, key string, value any, oldValue any) bool { +func checkMessageTransientSet(t *testing.T, message *api.ServerMessage, key string, value any, oldValue any) bool { assert := assert.New(t) return checkMessageType(t, message, "transient") && assert.Equal("set", message.TransientData.Type, "invalid message type in %+v", message) && @@ -1109,7 +1109,7 @@ func checkMessageTransientSet(t *testing.T, message *ServerMessage, key string, assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value in %+v", message) } -func checkMessageTransientRemove(t *testing.T, message *ServerMessage, key string, oldValue any) bool { +func checkMessageTransientRemove(t *testing.T, message *api.ServerMessage, key string, oldValue any) bool { assert := assert.New(t) return checkMessageType(t, message, "transient") && assert.Equal("remove", message.TransientData.Type, "invalid message type in %+v", message) && @@ -1117,14 +1117,14 @@ func checkMessageTransientRemove(t *testing.T, message *ServerMessage, key strin assert.EqualValues(oldValue, message.TransientData.OldValue, "invalid old value in %+v", message) } -func checkMessageTransientInitial(t *testing.T, message *ServerMessage, data api.StringMap) bool { +func checkMessageTransientInitial(t *testing.T, message *api.ServerMessage, data api.StringMap) bool { assert := assert.New(t) return checkMessageType(t, message, "transient") && assert.Equal("initial", message.TransientData.Type, "invalid message type in %+v", message) && assert.Equal(data, message.TransientData.Data, "invalid initial data in %+v", message) } -func checkMessageInCallAll(t *testing.T, message *ServerMessage, roomId string, inCall int) bool { +func checkMessageInCallAll(t *testing.T, message *api.ServerMessage, roomId string, inCall int) bool { assert := assert.New(t) return checkMessageType(t, message, "event") && assert.Equal("update", message.Event.Type, "invalid event type, got %+v", message.Event) && @@ -1134,7 +1134,7 @@ func checkMessageInCallAll(t *testing.T, message *ServerMessage, roomId string, assert.EqualValues(strconv.FormatInt(int64(inCall), 10), message.Event.Update.InCall, "expected incall flags %d, got %+v", inCall, message.Event.Update) } -func checkMessageSwitchTo(t *testing.T, message *ServerMessage, roomId string, details json.RawMessage) (*EventServerMessageSwitchTo, bool) { +func checkMessageSwitchTo(t *testing.T, message *api.ServerMessage, roomId string, details json.RawMessage) (*api.EventServerMessageSwitchTo, bool) { assert := assert.New(t) if !checkMessageType(t, message, "event") || !assert.Equal("switchto", message.Event.Type, "invalid event type, got %+v", message.Event) || @@ -1153,7 +1153,7 @@ func checkMessageSwitchTo(t *testing.T, message *ServerMessage, roomId string, d return message.Event.SwitchTo, true } -func (c *TestClient) RunUntilSwitchTo(ctx context.Context, roomId string, details json.RawMessage) (*EventServerMessageSwitchTo, bool) { +func (c *TestClient) RunUntilSwitchTo(ctx context.Context, roomId string, details json.RawMessage) (*api.EventServerMessageSwitchTo, bool) { message, ok := c.RunUntilMessage(ctx) if !ok { return nil, false diff --git a/testutils_test.go b/testutils_test.go index 4571012..03cc519 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -27,9 +27,11 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) -func WaitForUsersJoined(ctx context.Context, t *testing.T, client1 *TestClient, hello1 *ServerMessage, client2 *TestClient, hello2 *ServerMessage) { +func WaitForUsersJoined(ctx context.Context, t *testing.T, client1 *TestClient, hello1 *api.ServerMessage, client2 *TestClient, hello2 *api.ServerMessage) { t.Helper() // We will receive "joined" events for all clients. The ordering is not // defined as messages are processed and sent by asynchronous event handlers. diff --git a/transient_data.go b/transient_data.go index af3c640..1ec8e1f 100644 --- a/transient_data.go +++ b/transient_data.go @@ -36,7 +36,7 @@ const ( ) type TransientListener interface { - SendMessage(message *ServerMessage) bool + SendMessage(message *api.ServerMessage) bool } type TransientDataEntry struct { @@ -103,7 +103,7 @@ func NewTransientData() *TransientData { } // +checklocks:t.mu -func (t *TransientData) sendMessageToListener(listener TransientListener, message *ServerMessage) { +func (t *TransientData) sendMessageToListener(listener TransientListener, message *api.ServerMessage) { t.mu.Unlock() defer t.mu.Lock() @@ -112,9 +112,9 @@ func (t *TransientData) sendMessageToListener(listener TransientListener, messag // +checklocks:t.mu func (t *TransientData) notifySet(key string, prev, value any) { - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "transient", - TransientData: &TransientDataServerMessage{ + TransientData: &api.TransientDataServerMessage{ Type: "set", Key: key, Value: value, @@ -128,9 +128,9 @@ func (t *TransientData) notifySet(key string, prev, value any) { // +checklocks:t.mu func (t *TransientData) notifyDeleted(key string, prev *TransientDataEntry) { - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "transient", - TransientData: &TransientDataServerMessage{ + TransientData: &api.TransientDataServerMessage{ Type: "remove", Key: key, }, @@ -157,9 +157,9 @@ func (t *TransientData) AddListener(listener TransientListener) { for k, v := range t.data { data[k] = v.Value } - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "transient", - TransientData: &TransientDataServerMessage{ + TransientData: &api.TransientDataServerMessage{ Type: "initial", Data: data, }, @@ -385,9 +385,9 @@ func (t *TransientData) SetInitial(data TransientDataEntries) { if len(msgData) == 0 { return } - msg := &ServerMessage{ + msg := &api.ServerMessage{ Type: "transient", - TransientData: &TransientDataServerMessage{ + TransientData: &api.TransientDataServerMessage{ Type: "initial", Data: msgData, }, diff --git a/transient_data_test.go b/transient_data_test.go index ba2ec6d..950bb5e 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -104,7 +104,7 @@ type MockTransientListener struct { data *TransientData } -func (l *MockTransientListener) SendMessage(message *ServerMessage) bool { +func (l *MockTransientListener) SendMessage(message *api.ServerMessage) bool { close(l.sending) time.Sleep(10 * time.Millisecond) @@ -275,7 +275,7 @@ func Test_TransientMessages(t *testing.T) { _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello2.Hello, hello3.Hello) require.True(ok) - var msg *ServerMessage + var msg *api.ServerMessage if len(ignored) == 0 { msg = MustSucceed1(t, client3.RunUntilMessage, ctx) } else if len(ignored) == 1 { @@ -392,7 +392,7 @@ func Test_TransientSessionData(t *testing.T) { client1.CloseWithBye() assert.NoError(client1.WaitForClientRemoved(ctx)) - var messages []*ServerMessage + var messages []*api.ServerMessage for range 2 { if msg, ok := client2.RunUntilMessage(ctx); ok { messages = append(messages, msg) @@ -413,7 +413,7 @@ func Test_TransientSessionData(t *testing.T) { _, ignored, ok := client3.RunUntilJoinedAndReturn(ctx, hello2.Hello, hello3.Hello) require.True(ok) - var msg *ServerMessage + var msg *api.ServerMessage if len(ignored) == 0 { msg = MustSucceed1(t, client3.RunUntilMessage, ctx) } else if len(ignored) == 1 { diff --git a/virtualsession.go b/virtualsession.go index 112c441..fd130ea 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -43,30 +43,30 @@ type VirtualSession struct { logger log.Logger hub *Hub session *ClientSession - privateId PrivateSessionId - publicId PublicSessionId + privateId api.PrivateSessionId + publicId api.PublicSessionId data *SessionIdData ctx context.Context closeFunc context.CancelFunc room atomic.Pointer[Room] - sessionId PublicSessionId + sessionId api.PublicSessionId userId string userData json.RawMessage inCall Flags flags Flags - options *AddSessionOptions + options *api.AddSessionOptions parseUserData func() (api.StringMap, error) asyncCh AsyncChannel } -func GetVirtualSessionId(session Session, sessionId PublicSessionId) PublicSessionId { +func GetVirtualSessionId(session Session, sessionId api.PublicSessionId) api.PublicSessionId { return session.PublicId() + "|" + sessionId } -func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publicId PublicSessionId, data *SessionIdData, msg *AddSessionInternalClientMessage) (*VirtualSession, error) { +func NewVirtualSession(session *ClientSession, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, msg *api.AddSessionInternalClientMessage) (*VirtualSession, error) { ctx := log.NewLoggerContext(session.Context(), session.hub.logger) ctx, closeFunc := context.WithCancel(ctx) @@ -95,7 +95,7 @@ func NewVirtualSession(session *ClientSession, privateId PrivateSessionId, publi if msg.InCall != nil { result.SetInCall(*msg.InCall) - } else if !session.HasFeature(ClientFeatureInternalInCall) { + } else if !session.HasFeature(api.ClientFeatureInternalInCall) { result.SetInCall(FlagInCall | FlagWithPhone) } if msg.Flags != 0 { @@ -110,16 +110,16 @@ func (s *VirtualSession) Context() context.Context { return s.ctx } -func (s *VirtualSession) PrivateId() PrivateSessionId { +func (s *VirtualSession) PrivateId() api.PrivateSessionId { return s.privateId } -func (s *VirtualSession) PublicId() PublicSessionId { +func (s *VirtualSession) PublicId() api.PublicSessionId { return s.publicId } -func (s *VirtualSession) ClientType() ClientType { - return HelloClientTypeVirtual +func (s *VirtualSession) ClientType() api.ClientType { + return api.HelloClientTypeVirtual } func (s *VirtualSession) GetInCall() int { @@ -169,7 +169,7 @@ func (s *VirtualSession) ParsedUserData() (api.StringMap, error) { func (s *VirtualSession) SetRoom(room *Room) { s.room.Store(room) if room != nil { - if err := s.hub.roomSessions.SetRoomSession(s, RoomSessionId(s.PublicId())); err != nil { + if err := s.hub.roomSessions.SetRoomSession(s, api.RoomSessionId(s.PublicId())); err != nil { s.logger.Printf("Error adding virtual room session %s: %s", s.PublicId(), err) } } else { @@ -181,6 +181,11 @@ func (s *VirtualSession) GetRoom() *Room { return s.room.Load() } +func (s *VirtualSession) IsInRoom(id string) bool { + room := s.GetRoom() + return room != nil && room.Id() == id +} + func (s *VirtualSession) LeaveRoom(notify bool) *Room { room := s.GetRoom() if room == nil { @@ -214,7 +219,7 @@ func (s *VirtualSession) Close() { s.CloseWithFeedback(nil, nil) } -func (s *VirtualSession) CloseWithFeedback(session Session, message *ClientMessage) { +func (s *VirtualSession) CloseWithFeedback(session Session, message *api.ClientMessage) { s.closeFunc() room := s.GetRoom() @@ -228,13 +233,13 @@ func (s *VirtualSession) CloseWithFeedback(session Session, message *ClientMessa } } -func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, message *ClientMessage) { +func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, message *api.ClientMessage) { ctx := log.NewLoggerContext(context.Background(), s.logger) ctx, cancel := context.WithTimeout(ctx, s.hub.backendTimeout) defer cancel() if options := s.Options(); options != nil && options.ActorId != "" && options.ActorType != "" { - request := NewBackendClientRoomRequest(room.Id(), s.UserId(), RoomSessionId(s.PublicId())) + request := NewBackendClientRoomRequest(room.Id(), s.UserId(), api.RoomSessionId(s.PublicId())) request.Room.Action = "leave" request.Room.ActorId = options.ActorId request.Room.ActorType = options.ActorType @@ -244,7 +249,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa virtualSessionId := GetVirtualSessionId(s.session, s.PublicId()) s.logger.Printf("Could not leave virtual session %s at backend %s: %s", virtualSessionId, s.BackendUrl(), err) if session != nil && message != nil { - reply := message.NewErrorServerMessage(NewError("remove_failed", "Could not remove virtual session from backend.")) + reply := message.NewErrorServerMessage(api.NewError("remove_failed", "Could not remove virtual session from backend.")) session.SendMessage(reply) } return @@ -254,13 +259,13 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa virtualSessionId := GetVirtualSessionId(s.session, s.PublicId()) if session != nil && message != nil && (response.Error == nil || response.Error.Code != "no_such_room") { s.logger.Printf("Could not leave virtual session %s at backend %s: %+v", virtualSessionId, s.BackendUrl(), response.Error) - reply := message.NewErrorServerMessage(NewError("remove_failed", response.Error.Error())) + reply := message.NewErrorServerMessage(api.NewError("remove_failed", response.Error.Error())) session.SendMessage(reply) } return } } else { - request := NewBackendClientSessionRequest(room.Id(), "remove", s.PublicId(), &AddSessionInternalClientMessage{ + request := NewBackendClientSessionRequest(room.Id(), "remove", s.PublicId(), &api.AddSessionInternalClientMessage{ UserId: s.userId, User: s.userData, }) @@ -269,7 +274,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa if err != nil { s.logger.Printf("Could not remove virtual session %s from backend %s: %s", s.PublicId(), s.BackendUrl(), err) if session != nil && message != nil { - reply := message.NewErrorServerMessage(NewError("remove_failed", "Could not remove virtual session from backend.")) + reply := message.NewErrorServerMessage(api.NewError("remove_failed", "Could not remove virtual session from backend.")) session.SendMessage(reply) } } @@ -284,7 +289,7 @@ func (s *VirtualSession) Session() *ClientSession { return s.session } -func (s *VirtualSession) SessionId() PublicSessionId { +func (s *VirtualSession) SessionId() api.PublicSessionId { return s.sessionId } @@ -304,7 +309,7 @@ func (s *VirtualSession) Flags() uint32 { return s.flags.Get() } -func (s *VirtualSession) Options() *AddSessionOptions { +func (s *VirtualSession) Options() *api.AddSessionOptions { return s.options } @@ -327,7 +332,7 @@ func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { message.Message.Message.Recipient.Type == "session" && message.Message.Message.Recipient.SessionId == s.PublicId() { // The client should see his session id as recipient. - message.Message.Message.Recipient = &MessageClientMessageRecipient{ + message.Message.Message.Recipient = &api.MessageClientMessageRecipient{ Type: "session", SessionId: s.SessionId(), UserId: s.UserId(), @@ -356,10 +361,10 @@ func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { s.session.processAsyncMessage(&AsyncMessage{ Type: "message", SendTime: message.SendTime, - Message: &ServerMessage{ + Message: &api.ServerMessage{ Type: "control", - Control: &ControlServerMessage{ - Recipient: &MessageClientMessageRecipient{ + Control: &api.ControlServerMessage{ + Recipient: &api.MessageClientMessageRecipient{ Type: "session", SessionId: s.SessionId(), UserId: s.UserId(), @@ -375,7 +380,7 @@ func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { message.Message.Control.Recipient.Type == "session" && message.Message.Control.Recipient.SessionId == s.PublicId() { // The client should see his session id as recipient. - message.Message.Control.Recipient = &MessageClientMessageRecipient{ + message.Message.Control.Recipient = &api.MessageClientMessageRecipient{ Type: "session", SessionId: s.SessionId(), UserId: s.UserId(), @@ -386,10 +391,10 @@ func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { } } -func (s *VirtualSession) SendError(e *Error) bool { +func (s *VirtualSession) SendError(e *api.Error) bool { return s.session.SendError(e) } -func (s *VirtualSession) SendMessage(message *ServerMessage) bool { +func (s *VirtualSession) SendMessage(message *api.ServerMessage) bool { return s.session.SendMessage(message) } diff --git a/virtualsession_test.go b/virtualsession_test.go index 83b060a..031d9af 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -68,14 +68,14 @@ func TestVirtualSession(t *testing.T) { // Ignore "join" events. assert.NoError(client.DrainMessages(ctx)) - internalSessionId := PublicSessionId("session1") + internalSessionId := api.PublicSessionId("session1") userId := "user1" - msgAdd := &ClientMessage{ + msgAdd := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "addsession", - AddSession: &AddSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + AddSession: &api.AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -92,7 +92,7 @@ func TestVirtualSession(t *testing.T) { sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session, "Could not get virtual session %s", sessionId) { - assert.Equal(HelloClientTypeVirtual, session.ClientType()) + assert.Equal(api.HelloClientTypeVirtual, session.ClientType()) sid := session.(*VirtualSession).SessionId() assert.Equal(internalSessionId, sid) } @@ -116,12 +116,12 @@ func TestVirtualSession(t *testing.T) { } newFlags := uint32(FLAG_TALKING) - msgFlags := &ClientMessage{ + msgFlags := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "updatesession", - UpdateSession: &UpdateSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + UpdateSession: &api.UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -144,7 +144,7 @@ func TestVirtualSession(t *testing.T) { require.Equal(roomId, roomMsg.Room.RoomId) gotFlags := false - var receivedMessages []*ServerMessage + var receivedMessages []*api.ServerMessage for !gotFlags { messages, err := client2.GetPendingMessages(ctx) if err != nil { @@ -175,7 +175,7 @@ func TestVirtualSession(t *testing.T) { // When sending to a virtual session, the message is sent to the actual // client and contains a "Recipient" block with the internal session id. - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "session", SessionId: sessionId, } @@ -197,12 +197,12 @@ func TestVirtualSession(t *testing.T) { assert.Equal(data, payload) } - msgRemove := &ClientMessage{ + msgRemove := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "removesession", - RemoveSession: &RemoveSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + RemoveSession: &api.RemoveSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -251,20 +251,20 @@ func TestVirtualSessionActorInformation(t *testing.T) { // Ignore "join" events. assert.NoError(client.DrainMessages(ctx)) - internalSessionId := PublicSessionId("session1") + internalSessionId := api.PublicSessionId("session1") userId := "user1" - msgAdd := &ClientMessage{ + msgAdd := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "addsession", - AddSession: &AddSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + AddSession: &api.AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, UserId: userId, Flags: FLAG_MUTED_SPEAKING, - Options: &AddSessionOptions{ + Options: &api.AddSessionOptions{ ActorId: "actor-id", ActorType: "actor-type", }, @@ -279,7 +279,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session, "Could not get virtual session %s", sessionId) { - assert.Equal(HelloClientTypeVirtual, session.ClientType()) + assert.Equal(api.HelloClientTypeVirtual, session.ClientType()) sid := session.(*VirtualSession).SessionId() assert.Equal(internalSessionId, sid) } @@ -303,12 +303,12 @@ func TestVirtualSessionActorInformation(t *testing.T) { } newFlags := uint32(FLAG_TALKING) - msgFlags := &ClientMessage{ + msgFlags := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "updatesession", - UpdateSession: &UpdateSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + UpdateSession: &api.UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -331,7 +331,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { require.Equal(roomId, roomMsg.Room.RoomId) gotFlags := false - var receivedMessages []*ServerMessage + var receivedMessages []*api.ServerMessage for !gotFlags { messages, err := client2.GetPendingMessages(ctx) if err != nil { @@ -362,7 +362,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { // When sending to a virtual session, the message is sent to the actual // client and contains a "Recipient" block with the internal session id. - recipient := MessageClientMessageRecipient{ + recipient := api.MessageClientMessageRecipient{ Type: "session", SessionId: sessionId, } @@ -384,12 +384,12 @@ func TestVirtualSessionActorInformation(t *testing.T) { assert.Equal(data, payload) } - msgRemove := &ClientMessage{ + msgRemove := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "removesession", - RemoveSession: &RemoveSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + RemoveSession: &api.RemoveSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -403,11 +403,11 @@ func TestVirtualSessionActorInformation(t *testing.T) { } } -func checkHasEntryWithInCall(t *testing.T, message *RoomEventServerMessage, sessionId PublicSessionId, entryType string, inCall int) bool { +func checkHasEntryWithInCall(t *testing.T, message *api.RoomEventServerMessage, sessionId api.PublicSessionId, entryType string, inCall int) bool { assert := assert.New(t) found := false for _, entry := range message.Users { - if sid, ok := api.GetStringMapString[PublicSessionId](entry, "sessionId"); ok && sid == sessionId { + if sid, ok := api.GetStringMapString[api.PublicSessionId](entry, "sessionId"); ok && sid == sessionId { if value, found := api.GetStringMapEntry[bool](entry, entryType); !assert.True(found, "entry %s not found or invalid in %+v", entryType, entry) || !assert.True(value, "entry %s invalid in %+v", entryType, entry) { return false @@ -443,7 +443,7 @@ func TestVirtualSessionCustomInCall(t *testing.T) { clientInternal := NewTestClient(t, server, hub) defer clientInternal.CloseWithBye() features := []string{ - ClientFeatureInternalInCall, + api.ClientFeatureInternalInCall, } require.NoError(clientInternal.SendHelloInternalWithFeatures(features)) @@ -474,14 +474,14 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } client.RunUntilJoined(ctx, helloInternal.Hello, hello.Hello) - internalSessionId := PublicSessionId("session1") + internalSessionId := api.PublicSessionId("session1") userId := "user1" - msgAdd := &ClientMessage{ + msgAdd := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "addsession", - AddSession: &AddSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + AddSession: &api.AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -498,7 +498,7 @@ func TestVirtualSessionCustomInCall(t *testing.T) { sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session) { - assert.Equal(HelloClientTypeVirtual, session.ClientType()) + assert.Equal(api.HelloClientTypeVirtual, session.ClientType()) sid := session.(*VirtualSession).SessionId() assert.Equal(internalSessionId, sid) } @@ -521,11 +521,11 @@ func TestVirtualSessionCustomInCall(t *testing.T) { } // The internal session can change its "inCall" flags - msgInCall := &ClientMessage{ + msgInCall := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "incall", - InCall: &InCallInternalClientMessage{ + InCall: &api.InCallInternalClientMessage{ InCall: FlagInCall | FlagWithAudio, }, }, @@ -542,12 +542,12 @@ func TestVirtualSessionCustomInCall(t *testing.T) { // The internal session can change the "inCall" flags of a virtual session newInCall := FlagInCall | FlagWithPhone - msgInCall2 := &ClientMessage{ + msgInCall2 := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "updatesession", - UpdateSession: &UpdateSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + UpdateSession: &api.UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -601,14 +601,14 @@ func TestVirtualSessionCleanup(t *testing.T) { // Ignore "join" events. assert.NoError(client.DrainMessages(ctx)) - internalSessionId := PublicSessionId("session1") + internalSessionId := api.PublicSessionId("session1") userId := "user1" - msgAdd := &ClientMessage{ + msgAdd := &api.ClientMessage{ Type: "internal", - Internal: &InternalClientMessage{ + Internal: &api.InternalClientMessage{ Type: "addsession", - AddSession: &AddSessionInternalClientMessage{ - CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + AddSession: &api.AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ SessionId: internalSessionId, RoomId: roomId, }, @@ -625,7 +625,7 @@ func TestVirtualSessionCleanup(t *testing.T) { sessionId := msg1.Event.Join[0].SessionId session := hub.GetSessionByPublicId(sessionId) if assert.NotNil(session) { - assert.Equal(HelloClientTypeVirtual, session.ClientType()) + assert.Equal(api.HelloClientTypeVirtual, session.ClientType()) sid := session.(*VirtualSession).SessionId() assert.Equal(internalSessionId, sid) } From 61491a786c9f74653b72fa7b6f6a0e0a61c338a5 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 16:35:18 +0100 Subject: [PATCH 414/549] Update generated files. --- .../signaling_easyjson.go | 727 ++++++++++-------- api_async_easyjson.go | 11 +- api_backend_easyjson.go | 26 +- api_proxy_easyjson.go | 10 +- 4 files changed, 426 insertions(+), 348 deletions(-) rename api_signaling_easyjson.go => api/signaling_easyjson.go (79%) diff --git a/api_signaling_easyjson.go b/api/signaling_easyjson.go similarity index 79% rename from api_signaling_easyjson.go rename to api/signaling_easyjson.go index d85860a..ff1a12b 100644 --- a/api_signaling_easyjson.go +++ b/api/signaling_easyjson.go @@ -1,6 +1,6 @@ // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package signaling +package api import ( json "encoding/json" @@ -8,7 +8,6 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" - api "github.com/strukturag/nextcloud-spreed-signaling/api" time "time" ) @@ -20,7 +19,7 @@ var ( _ easyjson.Marshaler ) -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexer.Lexer, out *WelcomeServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(in *jlexer.Lexer, out *WelcomeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -83,7 +82,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwriter.Writer, in WelcomeServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(out *jwriter.Writer, in WelcomeServerMessage) { out.RawByte('{') first := true _ = first @@ -117,27 +116,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwri // MarshalJSON supports json.Marshaler interface func (v WelcomeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v WelcomeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *WelcomeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *WelcomeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlexer.Lexer, out *UpdateSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(in *jlexer.Lexer, out *UpdateSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -201,7 +200,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwriter.Writer, in UpdateSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(out *jwriter.Writer, in UpdateSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -242,27 +241,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwr // MarshalJSON supports json.Marshaler interface func (v UpdateSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling1(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v UpdateSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling1(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *UpdateSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *UpdateSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling1(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *TransientDataServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(in *jlexer.Lexer, out *TransientDataServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -310,7 +309,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex } else { in.Delim('{') if !in.IsDelim('}') { - out.Data = make(api.StringMap) + out.Data = make(StringMap) } else { out.Data = nil } @@ -340,7 +339,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in TransientDataServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(out *jwriter.Writer, in TransientDataServerMessage) { out.RawByte('{') first := true _ = first @@ -407,27 +406,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwr // MarshalJSON supports json.Marshaler interface func (v TransientDataServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TransientDataServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TransientDataServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TransientDataServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlexer.Lexer, out *TransientDataClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(in *jlexer.Lexer, out *TransientDataClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -477,7 +476,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwriter.Writer, in TransientDataClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(out *jwriter.Writer, in TransientDataClientMessage) { out.RawByte('{') first := true _ = first @@ -507,27 +506,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwr // MarshalJSON supports json.Marshaler interface func (v TransientDataClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling3(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TransientDataClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling3(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TransientDataClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling3(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TransientDataClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling3(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlexer.Lexer, out *ServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(in *jlexer.Lexer, out *ServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -717,7 +716,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwriter.Writer, in ServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(out *jwriter.Writer, in ServerMessage) { out.RawByte('{') first := true _ = first @@ -798,27 +797,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwr // MarshalJSON supports json.Marshaler interface func (v ServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling4(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling4(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling4(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlexer.Lexer, out *RoomServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(in *jlexer.Lexer, out *RoomServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -870,7 +869,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwriter.Writer, in RoomServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(out *jwriter.Writer, in RoomServerMessage) { out.RawByte('{') first := true _ = first @@ -895,27 +894,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling5(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling5(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling5(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling5(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlexer.Lexer, out *RoomFlagsServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(in *jlexer.Lexer, out *RoomFlagsServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -957,7 +956,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwriter.Writer, in RoomFlagsServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(out *jwriter.Writer, in RoomFlagsServerMessage) { out.RawByte('{') first := true _ = first @@ -982,27 +981,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomFlagsServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling6(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomFlagsServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling6(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomFlagsServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomFlagsServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *RoomFederationMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(in *jlexer.Lexer, out *RoomFederationMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1050,7 +1049,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in RoomFederationMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(out *jwriter.Writer, in RoomFederationMessage) { out.RawByte('{') first := true _ = first @@ -1080,27 +1079,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomFederationMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomFederationMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomFederationMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomFederationMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *RoomEventServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(in *jlexer.Lexer, out *RoomEventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1144,21 +1143,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]api.StringMap, 0, 8) + out.Changed = make([]StringMap, 0, 8) } else { - out.Changed = []api.StringMap{} + out.Changed = []StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v6 api.StringMap + var v6 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v6 = make(api.StringMap) + v6 = make(StringMap) } else { v6 = nil } @@ -1191,21 +1190,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]api.StringMap, 0, 8) + out.Users = make([]StringMap, 0, 8) } else { - out.Users = []api.StringMap{} + out.Users = []StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v8 api.StringMap + var v8 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v8 = make(api.StringMap) + v8 = make(StringMap) } else { v8 = nil } @@ -1246,7 +1245,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in RoomEventServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(out *jwriter.Writer, in RoomEventServerMessage) { out.RawByte('{') first := true _ = first @@ -1348,27 +1347,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomEventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *RoomEventMessageDataChat) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(in *jlexer.Lexer, out *RoomEventMessageDataChat) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1406,7 +1405,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in RoomEventMessageDataChat) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(out *jwriter.Writer, in RoomEventMessageDataChat) { out.RawByte('{') first := true _ = first @@ -1432,27 +1431,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomEventMessageDataChat) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessageDataChat) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessageDataChat) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessageDataChat) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *RoomEventMessageData) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(in *jlexer.Lexer, out *RoomEventMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1496,7 +1495,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in RoomEventMessageData) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(out *jwriter.Writer, in RoomEventMessageData) { out.RawByte('{') first := true _ = first @@ -1516,27 +1515,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomEventMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *RoomEventMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(in *jlexer.Lexer, out *RoomEventMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1574,7 +1573,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in RoomEventMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(out *jwriter.Writer, in RoomEventMessage) { out.RawByte('{') first := true _ = first @@ -1594,27 +1593,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomEventMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *RoomErrorDetails) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(in *jlexer.Lexer, out *RoomErrorDetails) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1652,7 +1651,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in RoomErrorDetails) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(out *jwriter.Writer, in RoomErrorDetails) { out.RawByte('{') first := true _ = first @@ -1671,27 +1670,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomErrorDetails) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomErrorDetails) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomErrorDetails) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomErrorDetails) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *RoomDisinviteEventServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *jlexer.Lexer, out *RoomDisinviteEventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1741,21 +1740,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim('[') if out.Changed == nil { if !in.IsDelim(']') { - out.Changed = make([]api.StringMap, 0, 8) + out.Changed = make([]StringMap, 0, 8) } else { - out.Changed = []api.StringMap{} + out.Changed = []StringMap{} } } else { out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v16 api.StringMap + var v16 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v16 = make(api.StringMap) + v16 = make(StringMap) } else { v16 = nil } @@ -1788,21 +1787,21 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Delim('[') if out.Users == nil { if !in.IsDelim(']') { - out.Users = make([]api.StringMap, 0, 8) + out.Users = make([]StringMap, 0, 8) } else { - out.Users = []api.StringMap{} + out.Users = []StringMap{} } } else { out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v18 api.StringMap + var v18 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v18 = make(api.StringMap) + v18 = make(StringMap) } else { v18 = nil } @@ -1843,7 +1842,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in RoomDisinviteEventServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(out *jwriter.Writer, in RoomDisinviteEventServerMessage) { out.RawByte('{') first := true _ = first @@ -1950,27 +1949,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomDisinviteEventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomDisinviteEventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomDisinviteEventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomDisinviteEventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *RoomClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(in *jlexer.Lexer, out *RoomClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2020,7 +2019,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in RoomClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(out *jwriter.Writer, in RoomClientMessage) { out.RawByte('{') first := true _ = first @@ -2045,27 +2044,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *RoomBandwidth) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(in *jlexer.Lexer, out *RoomBandwidth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2083,13 +2082,13 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle if in.IsNull() { in.Skip() } else { - out.MaxStreamBitrate = api.Bandwidth(in.Uint64()) + out.MaxStreamBitrate = Bandwidth(in.Uint64()) } case "maxscreenbitrate": if in.IsNull() { in.Skip() } else { - out.MaxScreenBitrate = api.Bandwidth(in.Uint64()) + out.MaxScreenBitrate = Bandwidth(in.Uint64()) } default: in.SkipRecursive() @@ -2101,7 +2100,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in RoomBandwidth) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(out *jwriter.Writer, in RoomBandwidth) { out.RawByte('{') first := true _ = first @@ -2121,27 +2120,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomBandwidth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomBandwidth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomBandwidth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2183,7 +2182,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -2214,27 +2213,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw // MarshalJSON supports json.Marshaler interface func (v RemoveSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RemoveSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *MessageServerMessageSender) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(in *jlexer.Lexer, out *MessageServerMessageSender) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2276,7 +2275,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in MessageServerMessageSender) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(out *jwriter.Writer, in MessageServerMessageSender) { out.RawByte('{') first := true _ = first @@ -2301,27 +2300,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageSender) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageSender) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageSender) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageSender) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *MessageServerMessageData) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(in *jlexer.Lexer, out *MessageServerMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2351,7 +2350,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in MessageServerMessageData) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(out *jwriter.Writer, in MessageServerMessageData) { out.RawByte('{') first := true _ = first @@ -2366,27 +2365,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *MessageServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(in *jlexer.Lexer, out *MessageServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2446,7 +2445,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in MessageServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(out *jwriter.Writer, in MessageServerMessage) { out.RawByte('{') first := true _ = first @@ -2475,27 +2474,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *MessageClientMessageRecipient) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(in *jlexer.Lexer, out *MessageClientMessageRecipient) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2537,7 +2536,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in MessageClientMessageRecipient) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(out *jwriter.Writer, in MessageClientMessageRecipient) { out.RawByte('{') first := true _ = first @@ -2562,27 +2561,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageRecipient) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageRecipient) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *MessageClientMessageData) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(in *jlexer.Lexer, out *MessageClientMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2619,7 +2618,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Skip() } else { in.Delim('{') - out.Payload = make(api.StringMap) + out.Payload = make(StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() @@ -2640,7 +2639,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle if in.IsNull() { in.Skip() } else { - out.Bitrate = api.Bandwidth(in.Uint64()) + out.Bitrate = Bandwidth(in.Uint64()) } case "audiocodec": if in.IsNull() { @@ -2676,7 +2675,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in MessageClientMessageData) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(out *jwriter.Writer, in MessageClientMessageData) { out.RawByte('{') first := true _ = first @@ -2753,27 +2752,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *MessageClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(in *jlexer.Lexer, out *MessageClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2811,7 +2810,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in MessageClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(out *jwriter.Writer, in MessageClientMessage) { out.RawByte('{') first := true _ = first @@ -2831,27 +2830,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *InternalServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(in *jlexer.Lexer, out *InternalServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2895,7 +2894,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in InternalServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(out *jwriter.Writer, in InternalServerMessage) { out.RawByte('{') first := true _ = first @@ -2915,27 +2914,105 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *InternalServerDialoutRequest) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(in *jlexer.Lexer, out *InternalServerDialoutRequestContents) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "number": + if in.IsNull() { + in.Skip() + } else { + out.Number = string(in.String()) + } + case "options": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Options).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(out *jwriter.Writer, in InternalServerDialoutRequestContents) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"number\":" + out.RawString(prefix[1:]) + out.String(string(in.Number)) + } + if len(in.Options) != 0 { + const prefix string = ",\"options\":" + out.RawString(prefix) + out.Raw((in.Options).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v InternalServerDialoutRequestContents) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v InternalServerDialoutRequestContents) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *InternalServerDialoutRequestContents) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *InternalServerDialoutRequestContents) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(l, v) +} +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(in *jlexer.Lexer, out *InternalServerDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2967,7 +3044,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle out.Request = nil } else { if out.Request == nil { - out.Request = new(BackendRoomDialoutRequest) + out.Request = new(InternalServerDialoutRequestContents) } if in.IsNull() { in.Skip() @@ -2985,7 +3062,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in InternalServerDialoutRequest) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(out *jwriter.Writer, in InternalServerDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3014,27 +3091,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalServerDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *InternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(in *jlexer.Lexer, out *InternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3134,7 +3211,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in InternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(out *jwriter.Writer, in InternalClientMessage) { out.RawByte('{') first := true _ = first @@ -3174,27 +3251,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *InCallInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(in *jlexer.Lexer, out *InCallInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3224,7 +3301,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in InCallInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(out *jwriter.Writer, in InCallInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -3239,27 +3316,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v InCallInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InCallInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *HelloV2TokenClaims) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(in *jlexer.Lexer, out *HelloV2TokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3365,7 +3442,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in HelloV2TokenClaims) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(out *jwriter.Writer, in HelloV2TokenClaims) { out.RawByte('{') first := true _ = first @@ -3451,27 +3528,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloV2TokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2TokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *HelloV2AuthParams) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(in *jlexer.Lexer, out *HelloV2AuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3501,7 +3578,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in HelloV2AuthParams) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(out *jwriter.Writer, in HelloV2AuthParams) { out.RawByte('{') first := true _ = first @@ -3516,27 +3593,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloV2AuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2AuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *HelloServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(in *jlexer.Lexer, out *HelloServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3598,7 +3675,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in HelloServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(out *jwriter.Writer, in HelloServerMessage) { out.RawByte('{') first := true _ = first @@ -3633,27 +3710,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *HelloClientMessageAuth) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(in *jlexer.Lexer, out *HelloClientMessageAuth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3697,7 +3774,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in HelloClientMessageAuth) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(out *jwriter.Writer, in HelloClientMessageAuth) { out.RawByte('{') first := true _ = first @@ -3728,27 +3805,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloClientMessageAuth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessageAuth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *HelloClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(in *jlexer.Lexer, out *HelloClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3825,7 +3902,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in HelloClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(out *jwriter.Writer, in HelloClientMessage) { out.RawByte('{') first := true _ = first @@ -3864,27 +3941,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *FederationTokenClaims) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(in *jlexer.Lexer, out *FederationTokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3990,7 +4067,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in FederationTokenClaims) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(out *jwriter.Writer, in FederationTokenClaims) { out.RawByte('{') first := true _ = first @@ -4076,27 +4153,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v FederationTokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationTokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *FederationAuthParams) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(in *jlexer.Lexer, out *FederationAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4126,7 +4203,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in FederationAuthParams) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(out *jwriter.Writer, in FederationAuthParams) { out.RawByte('{') first := true _ = first @@ -4141,27 +4218,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v FederationAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4199,7 +4276,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in EventServerMessageSwitchTo) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(out *jwriter.Writer, in EventServerMessageSwitchTo) { out.RawByte('{') first := true _ = first @@ -4219,27 +4296,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSwitchTo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSwitchTo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4322,7 +4399,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in EventServerMessageSessionEntry) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(out *jwriter.Writer, in EventServerMessageSessionEntry) { out.RawByte('{') first := true _ = first @@ -4371,27 +4448,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSessionEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSessionEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *EventServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4606,7 +4683,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in EventServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -4703,27 +4780,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *Error) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(in *jlexer.Lexer, out *Error) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4767,7 +4844,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in Error) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(out *jwriter.Writer, in Error) { out.RawByte('{') first := true _ = first @@ -4792,27 +4869,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jw // MarshalJSON supports json.Marshaler interface func (v Error) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v Error) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *Error) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4866,7 +4943,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4901,27 +4978,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *DialoutInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(in *jlexer.Lexer, out *DialoutInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4985,7 +5062,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in DialoutInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(out *jwriter.Writer, in DialoutInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5015,27 +5092,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ControlServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(in *jlexer.Lexer, out *ControlServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5095,7 +5172,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ControlServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(out *jwriter.Writer, in ControlServerMessage) { out.RawByte('{') first := true _ = first @@ -5124,27 +5201,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *ControlClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(in *jlexer.Lexer, out *ControlClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5182,7 +5259,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in ControlClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(out *jwriter.Writer, in ControlClientMessage) { out.RawByte('{') first := true _ = first @@ -5202,27 +5279,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5258,7 +5335,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in CommonSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(out *jwriter.Writer, in CommonSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5278,27 +5355,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jw // MarshalJSON supports json.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5340,7 +5417,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ClientTypeInternalAuthParams) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(out *jwriter.Writer, in ClientTypeInternalAuthParams) { out.RawByte('{') first := true _ = first @@ -5365,27 +5442,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *ClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(in *jlexer.Lexer, out *ClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5519,7 +5596,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in ClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(out *jwriter.Writer, in ClientMessage) { out.RawByte('{') first := true _ = first @@ -5580,27 +5657,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *ByeServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5630,7 +5707,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in ByeServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -5645,27 +5722,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *ByeClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5689,7 +5766,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in ByeClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -5699,27 +5776,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jlexer.Lexer, out *AnswerOfferMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(in *jlexer.Lexer, out *AnswerOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5762,7 +5839,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Skip() } else { in.Delim('{') - out.Payload = make(api.StringMap) + out.Payload = make(StringMap) for !in.IsDelim('}') { key := string(in.String()) in.WantColon() @@ -5795,7 +5872,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jwriter.Writer, in AnswerOfferMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(out *jwriter.Writer, in AnswerOfferMessage) { out.RawByte('{') first := true _ = first @@ -5857,27 +5934,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jw // MarshalJSON supports json.Marshaler interface func (v AnswerOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AnswerOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jlexer.Lexer, out *AddSessionOptions) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(in *jlexer.Lexer, out *AddSessionOptions) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5913,7 +5990,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jwriter.Writer, in AddSessionOptions) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(out *jwriter.Writer, in AddSessionOptions) { out.RawByte('{') first := true _ = first @@ -5939,27 +6016,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionOptions) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionOptions) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionOptions) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionOptions) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -6043,7 +6120,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(out *jwriter.Writer, in AddSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(out *jwriter.Writer, in AddSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -6114,23 +6191,23 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(l, v) } diff --git a/api_async_easyjson.go b/api_async_easyjson.go index 508e444..2436e63 100644 --- a/api_async_easyjson.go +++ b/api_async_easyjson.go @@ -7,6 +7,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + api "github.com/strukturag/nextcloud-spreed-signaling/api" ) // suppress unused package warning @@ -41,7 +42,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe if in.IsNull() { in.Skip() } else { - out.SessionId = PublicSessionId(in.String()) + out.SessionId = api.PublicSessionId(in.String()) } case "data": if in.IsNull() { @@ -49,7 +50,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe out.Data = nil } else { if out.Data == nil { - out.Data = new(MessageClientMessageData) + out.Data = new(api.MessageClientMessageData) } if in.IsNull() { in.Skip() @@ -146,13 +147,13 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex if in.IsNull() { in.Skip() } else { - out.SessionId = PublicSessionId(in.String()) + out.SessionId = api.PublicSessionId(in.String()) } case "clienttype": if in.IsNull() { in.Skip() } else { - out.ClientType = ClientType(in.String()) + out.ClientType = api.ClientType(in.String()) } default: in.SkipRecursive() @@ -243,7 +244,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex out.Message = nil } else { if out.Message == nil { - out.Message = new(ServerMessage) + out.Message = new(api.ServerMessage) } if in.IsNull() { in.Skip() diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index cb244db..d76ece6 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -1618,7 +1618,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle if in.IsNull() { in.Skip() } else { - out.SessionId = PublicSessionId(in.String()) + out.SessionId = api.PublicSessionId(in.String()) } case "connected": if in.IsNull() { @@ -2261,11 +2261,11 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle out.SessionsList = (out.SessionsList)[:0] } for !in.IsDelim(']') { - var v31 PublicSessionId + var v31 api.PublicSessionId if in.IsNull() { in.Skip() } else { - v31 = PublicSessionId(in.String()) + v31 = api.PublicSessionId(in.String()) } out.SessionsList = append(out.SessionsList, v31) in.WantComma() @@ -2283,7 +2283,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle out.SessionsMap = nil } for !in.IsDelim('}') { - key := PublicSessionId(in.String()) + key := api.PublicSessionId(in.String()) in.WantColon() var v32 json.RawMessage if in.IsNull() { @@ -3144,19 +3144,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Delim('[') if out.SessionIds == nil { if !in.IsDelim(']') { - out.SessionIds = make([]RoomSessionId, 0, 4) + out.SessionIds = make([]api.RoomSessionId, 0, 4) } else { - out.SessionIds = []RoomSessionId{} + out.SessionIds = []api.RoomSessionId{} } } else { out.SessionIds = (out.SessionIds)[:0] } for !in.IsDelim(']') { - var v63 RoomSessionId + var v63 api.RoomSessionId if in.IsNull() { in.Skip() } else { - v63 = RoomSessionId(in.String()) + v63 = api.RoomSessionId(in.String()) } out.SessionIds = append(out.SessionIds, v63) in.WantComma() @@ -3327,7 +3327,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle out.Error = nil } else { if out.Error == nil { - out.Error = new(Error) + out.Error = new(api.Error) } if in.IsNull() { in.Skip() @@ -3665,7 +3665,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle if in.IsNull() { in.Skip() } else { - out.SessionId = RoomSessionId(in.String()) + out.SessionId = api.RoomSessionId(in.String()) } default: in.SkipRecursive() @@ -3996,7 +3996,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle if in.IsNull() { in.Skip() } else { - out.SessionId = PublicSessionId(in.String()) + out.SessionId = api.PublicSessionId(in.String()) } case "userid": if in.IsNull() { @@ -4277,7 +4277,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle if in.IsNull() { in.Skip() } else { - out.SessionId = RoomSessionId(in.String()) + out.SessionId = api.RoomSessionId(in.String()) } case "actorid": if in.IsNull() { @@ -4479,7 +4479,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle out.Error = nil } else { if out.Error == nil { - out.Error = new(Error) + out.Error = new(api.Error) } if in.IsNull() { in.Skip() diff --git a/api_proxy_easyjson.go b/api_proxy_easyjson.go index b527f17..c79385b 100644 --- a/api_proxy_easyjson.go +++ b/api_proxy_easyjson.go @@ -245,7 +245,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex out.Error = nil } else { if out.Error == nil { - out.Error = new(Error) + out.Error = new(api.Error) } if in.IsNull() { in.Skip() @@ -1065,7 +1065,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex if in.IsNull() { in.Skip() } else { - out.SessionId = PublicSessionId(in.String()) + out.SessionId = api.PublicSessionId(in.String()) } case "server": if in.IsNull() { @@ -1073,7 +1073,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex out.Server = nil } else { if out.Server == nil { - out.Server = new(WelcomeServerMessage) + out.Server = new(api.WelcomeServerMessage) } if in.IsNull() { in.Skip() @@ -1160,7 +1160,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex if in.IsNull() { in.Skip() } else { - out.ResumeId = PublicSessionId(in.String()) + out.ResumeId = api.PublicSessionId(in.String()) } case "features": if in.IsNull() { @@ -1882,7 +1882,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle if in.IsNull() { in.Skip() } else { - out.PublisherId = PublicSessionId(in.String()) + out.PublisherId = api.PublicSessionId(in.String()) } case "clientId": if in.IsNull() { From 98764f2782709c7abeadb7480cbc067e4e7c7331 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 19:53:03 +0100 Subject: [PATCH 415/549] Move confiuration helpers to config module. --- .codecov.yml | 4 +++ backend_server.go | 13 +++---- backend_storage_static.go | 47 +++++++++++++------------ client/main.go | 7 ++-- config.go => config/config.go | 2 +- config_test.go => config/config_test.go | 2 +- geoip.go | 5 +-- grpc_server.go | 7 ++-- hub.go | 35 +++++++++--------- mcu_proxy.go | 5 +-- proxy/main.go | 14 ++++---- proxy/proxy_server.go | 9 ++--- proxy/proxy_tokens_static.go | 6 ++-- proxy_config_static.go | 7 ++-- server/main.go | 45 +++++++++++------------ 15 files changed, 111 insertions(+), 97 deletions(-) rename config.go => config/config.go (99%) rename config_test.go => config/config_test.go (99%) diff --git a/.codecov.yml b/.codecov.yml index 48adc51..832fbf1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -30,6 +30,10 @@ component_management: name: client paths: - client/** + - component_id: module_config + name: config + paths: + - config/** - component_id: module_container name: container paths: diff --git a/backend_server.go b/backend_server.go index 94530ff..f94bef6 100644 --- a/backend_server.go +++ b/backend_server.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -87,11 +88,11 @@ type BackendServer struct { buffers pool.BufferPool } -func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { +func NewBackendServer(ctx context.Context, cfg *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) { logger := log.LoggerFromContext(ctx) - turnapikey, _ := GetStringOptionWithEnv(config, "turn", "apikey") - turnsecret, _ := GetStringOptionWithEnv(config, "turn", "secret") - turnservers, _ := config.GetString("turn", "servers") + turnapikey, _ := config.GetStringOptionWithEnv(cfg, "turn", "apikey") + turnsecret, _ := config.GetStringOptionWithEnv(cfg, "turn", "secret") + turnservers, _ := cfg.GetString("turn", "servers") // TODO(jojo): Make the validity for TURN credentials configurable. turnvalid := 24 * time.Hour @@ -111,7 +112,7 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, } } - statsAllowed, _ := config.GetString("stats", "allowed_ips") + statsAllowed, _ := cfg.GetString("stats", "allowed_ips") statsAllowedIps, err := container.ParseIPList(statsAllowed) if err != nil { return nil, err @@ -129,7 +130,7 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub, return nil, err } - debug, _ := config.GetBool("app", "debug") + debug, _ := cfg.GetBool("app", "debug") result := &BackendServer{ logger: logger, diff --git a/backend_storage_static.go b/backend_storage_static.go index 2624a56..f0ebebc 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -29,6 +29,7 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -45,11 +46,11 @@ type backendStorageStatic struct { compatBackend *Backend } -func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { - allowAll, _ := config.GetBool("backend", "allowall") - allowHttp, _ := config.GetBool("backend", "allowhttp") - commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") - sessionLimit, err := config.GetInt("backend", "sessionlimit") +func NewBackendStorageStatic(logger log.Logger, cfg *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { + allowAll, _ := cfg.GetBool("backend", "allowall") + allowHttp, _ := cfg.GetBool("backend", "allowhttp") + commonSecret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret") + sessionLimit, err := cfg.GetInt("backend", "sessionlimit") if err != nil || sessionLimit < 0 { sessionLimit = 0 } @@ -59,11 +60,11 @@ func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats numBackends := 0 if allowAll { logger.Println("WARNING: All backend hostnames are allowed, only use for development!") - maxStreamBitrate, err := config.GetInt("backend", "maxstreambitrate") + maxStreamBitrate, err := cfg.GetInt("backend", "maxstreambitrate") if err != nil || maxStreamBitrate < 0 { maxStreamBitrate = 0 } - maxScreenBitrate, err := config.GetInt("backend", "maxscreenbitrate") + maxScreenBitrate, err := cfg.GetInt("backend", "maxscreenbitrate") if err != nil || maxScreenBitrate < 0 { maxScreenBitrate = 0 } @@ -85,9 +86,9 @@ func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats updateBackendStats(compatBackend) backendsById[compatBackend.id] = compatBackend numBackends++ - } else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { + } else if backendIds, _ := cfg.GetString("backend", "backends"); backendIds != "" { added := make(map[string]*Backend) - for host, configuredBackends := range getConfiguredHosts(logger, backendIds, config, commonSecret) { + for host, configuredBackends := range getConfiguredHosts(logger, backendIds, cfg, commonSecret) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { added[be.id] = be @@ -100,7 +101,7 @@ func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats be.counted = true } numBackends += len(added) - } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { + } else if allowedUrls, _ := cfg.GetString("backend", "allowed"); allowedUrls != "" { // Old-style configuration, only hosts are configured and are using a common secret. allowMap := make(map[string]bool) for u := range internal.SplitEntries(allowedUrls, ",") { @@ -117,11 +118,11 @@ func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats if len(allowMap) == 0 { logger.Println("WARNING: No backend hostnames are allowed, check your configuration!") } else { - maxStreamBitrate, err := config.GetInt("backend", "maxstreambitrate") + maxStreamBitrate, err := cfg.GetInt("backend", "maxstreambitrate") if err != nil || maxStreamBitrate < 0 { maxStreamBitrate = 0 } - maxScreenBitrate, err := config.GetInt("backend", "maxscreenbitrate") + maxScreenBitrate, err := cfg.GetInt("backend", "maxscreenbitrate") if err != nil || maxScreenBitrate < 0 { maxScreenBitrate = 0 } @@ -297,11 +298,11 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } -func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { +func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { hosts = make(map[string][]*Backend) seenUrls := make(map[string]string) for _, id := range getConfiguredBackendIDs(backendIds) { - secret, _ := GetStringOptionWithEnv(config, id, "secret") + secret, _ := config.GetStringOptionWithEnv(cfg, id, "secret") if secret == "" && commonSecret != "" { logger.Printf("Backend %s has no own shared secret set, using common shared secret", id) secret = commonSecret @@ -311,7 +312,7 @@ func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.Con continue } - sessionLimit, err := config.GetInt(id, "sessionlimit") + sessionLimit, err := cfg.GetInt(id, "sessionlimit") if err != nil || sessionLimit < 0 { sessionLimit = 0 } @@ -319,20 +320,20 @@ func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.Con logger.Printf("Backend %s allows a maximum of %d sessions", id, sessionLimit) } - maxStreamBitrate, err := config.GetInt(id, "maxstreambitrate") + maxStreamBitrate, err := cfg.GetInt(id, "maxstreambitrate") if err != nil || maxStreamBitrate < 0 { maxStreamBitrate = 0 } - maxScreenBitrate, err := config.GetInt(id, "maxscreenbitrate") + maxScreenBitrate, err := cfg.GetInt(id, "maxscreenbitrate") if err != nil || maxScreenBitrate < 0 { maxScreenBitrate = 0 } var urls []string - if u, _ := GetStringOptionWithEnv(config, id, "urls"); u != "" { + if u, _ := config.GetStringOptionWithEnv(cfg, id, "urls"); u != "" { urls = slices.Sorted(internal.SplitEntries(u, ",")) urls = slices.Compact(urls) - } else if u, _ := GetStringOptionWithEnv(config, id, "url"); u != "" { + } else if u, _ := config.GetStringOptionWithEnv(cfg, id, "url"); u != "" { if u = strings.TrimSpace(u); u != "" { urls = []string{u} } @@ -391,7 +392,7 @@ func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.Con return hosts } -func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { +func (s *backendStorageStatic) Reload(cfg *goconf.ConfigFile) { s.mu.Lock() defer s.mu.Unlock() @@ -400,10 +401,10 @@ func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) { return } - commonSecret, _ := GetStringOptionWithEnv(config, "backend", "secret") + commonSecret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret") - if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { - configuredHosts := getConfiguredHosts(s.logger, backendIds, config, commonSecret) + if backendIds, _ := cfg.GetString("backend", "backends"); backendIds != "" { + configuredHosts := getConfiguredHosts(s.logger, backendIds, cfg, commonSecret) // remove backends that are no longer configured seen := make(map[string]seenState) diff --git a/client/main.go b/client/main.go index 9156623..dd38d7f 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/api" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -60,7 +61,7 @@ var ( addr = flag.String("addr", "localhost:28080", "http service address") - config = flag.String("config", "server.conf", "config file to use") + configFlag = flag.String("config", "server.conf", "config file to use") cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") @@ -508,12 +509,12 @@ func main() { os.Exit(0) } - config, err := goconf.ReadConfigFile(*config) + cfg, err := goconf.ReadConfigFile(*configFlag) if err != nil { log.Fatal("Could not read configuration: ", err) } - secret, _ := signaling.GetStringOptionWithEnv(config, "backend", "secret") + secret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret") backendSecret = []byte(secret) log.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) diff --git a/config.go b/config/config.go similarity index 99% rename from config.go rename to config/config.go index c3a006e..25e8833 100644 --- a/config.go +++ b/config/config.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 config import ( "errors" diff --git a/config_test.go b/config/config_test.go similarity index 99% rename from config_test.go rename to config/config_test.go index f0cc619..7727138 100644 --- a/config_test.go +++ b/config/config_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 config import ( "testing" diff --git a/geoip.go b/geoip.go index 326e23f..4da1aa4 100644 --- a/geoip.go +++ b/geoip.go @@ -39,6 +39,7 @@ import ( "github.com/dlintw/goconf" "github.com/oschwald/maxminddb-golang" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -274,9 +275,9 @@ func IsValidContinent(continent string) bool { } } -func LoadGeoIPOverrides(ctx context.Context, config *goconf.ConfigFile, ignoreErrors bool) (map[*net.IPNet]string, error) { +func LoadGeoIPOverrides(ctx context.Context, cfg *goconf.ConfigFile, ignoreErrors bool) (map[*net.IPNet]string, error) { logger := log.LoggerFromContext(ctx) - options, _ := GetStringOptions(config, "geoip-overrides", true) + options, _ := config.GetStringOptions(cfg, "geoip-overrides", true) if len(options) == 0 { return nil, nil } diff --git a/grpc_server.go b/grpc_server.go index 307c091..e66fcd9 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -39,6 +39,7 @@ import ( status "google.golang.org/grpc/status" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -86,9 +87,9 @@ type GrpcServer struct { hub GrpcServerHub } -func NewGrpcServer(ctx context.Context, config *goconf.ConfigFile, version string) (*GrpcServer, error) { +func NewGrpcServer(ctx context.Context, cfg *goconf.ConfigFile, version string) (*GrpcServer, error) { var listener net.Listener - if addr, _ := GetStringOptionWithEnv(config, "grpc", "listen"); addr != "" { + if addr, _ := config.GetStringOptionWithEnv(cfg, "grpc", "listen"); addr != "" { var err error listener, err = net.Listen("tcp", addr) if err != nil { @@ -97,7 +98,7 @@ func NewGrpcServer(ctx context.Context, config *goconf.ConfigFile, version strin } logger := log.LoggerFromContext(ctx) - creds, err := NewReloadableCredentials(logger, config, true) + creds, err := NewReloadableCredentials(logger, cfg, true) if err != nil { return nil, err } diff --git a/hub.go b/hub.go index 8f41a36..5c97b7d 100644 --- a/hub.go +++ b/hub.go @@ -53,6 +53,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -222,9 +223,9 @@ type Hub struct { blockedCandidates atomic.Pointer[container.IPList] } -func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { +func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { logger := log.LoggerFromContext(ctx) - hashKey, _ := GetStringOptionWithEnv(config, "sessions", "hashkey") + hashKey, _ := config.GetStringOptionWithEnv(cfg, "sessions", "hashkey") switch len(hashKey) { case 32: case 64: @@ -232,7 +233,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, logger.Printf("WARNING: The sessions hash key should be 32 or 64 bytes but is %d bytes", len(hashKey)) } - blockKey, _ := GetStringOptionWithEnv(config, "sessions", "blockkey") + blockKey, _ := config.GetStringOptionWithEnv(cfg, "sessions", "blockkey") blockBytes := []byte(blockKey) switch len(blockKey) { case 0: @@ -249,51 +250,51 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, return nil, fmt.Errorf("error creating session id codec: %w", err) } - internalClientsSecret, _ := GetStringOptionWithEnv(config, "clients", "internalsecret") + internalClientsSecret, _ := config.GetStringOptionWithEnv(cfg, "clients", "internalsecret") if internalClientsSecret == "" { logger.Println("WARNING: No shared secret has been set for internal clients.") } - maxConcurrentRequestsPerHost, _ := config.GetInt("backend", "connectionsperhost") + maxConcurrentRequestsPerHost, _ := cfg.GetInt("backend", "connectionsperhost") if maxConcurrentRequestsPerHost <= 0 { maxConcurrentRequestsPerHost = defaultMaxConcurrentRequestsPerHost } - backend, err := NewBackendClient(ctx, config, maxConcurrentRequestsPerHost, version, etcdClient) + backend, err := NewBackendClient(ctx, cfg, maxConcurrentRequestsPerHost, version, etcdClient) if err != nil { return nil, err } logger.Printf("Using a maximum of %d concurrent backend connections per host", maxConcurrentRequestsPerHost) - backendTimeoutSeconds, _ := config.GetInt("backend", "timeout") + backendTimeoutSeconds, _ := cfg.GetInt("backend", "timeout") if backendTimeoutSeconds <= 0 { backendTimeoutSeconds = defaultBackendTimeoutSeconds } backendTimeout := time.Duration(backendTimeoutSeconds) * time.Second logger.Printf("Using a timeout of %s for backend connections", backendTimeout) - mcuTimeoutSeconds, _ := config.GetInt("mcu", "timeout") + mcuTimeoutSeconds, _ := cfg.GetInt("mcu", "timeout") if mcuTimeoutSeconds <= 0 { mcuTimeoutSeconds = defaultMcuTimeoutSeconds } mcuTimeout := time.Duration(mcuTimeoutSeconds) * time.Second - allowSubscribeAnyStream, _ := config.GetBool("app", "allowsubscribeany") + allowSubscribeAnyStream, _ := cfg.GetBool("app", "allowsubscribeany") if allowSubscribeAnyStream { logger.Printf("WARNING: Allow subscribing any streams, this is insecure and should only be enabled for testing") } - trustedProxies, _ := config.GetString("app", "trustedproxies") + trustedProxies, _ := cfg.GetString("app", "trustedproxies") trustedProxiesIps, err := container.ParseIPList(trustedProxies) if err != nil { return nil, err } - skipFederationVerify, _ := config.GetBool("federation", "skipverify") + skipFederationVerify, _ := cfg.GetBool("federation", "skipverify") if skipFederationVerify { logger.Println("WARNING: Federation target verification is disabled!") } - federationTimeoutSeconds, _ := config.GetInt("federation", "timeout") + federationTimeoutSeconds, _ := cfg.GetInt("federation", "timeout") if federationTimeoutSeconds <= 0 { federationTimeoutSeconds = defaultFederationTimeoutSeconds } @@ -321,12 +322,12 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, return nil, err } - geoipUrl, _ := config.GetString("geoip", "url") + geoipUrl, _ := cfg.GetString("geoip", "url") if geoipUrl == "default" || geoipUrl == "none" { geoipUrl = "" } if geoipUrl == "" { - if geoipLicense, _ := config.GetString("geoip", "license"); geoipLicense != "" { + if geoipLicense, _ := cfg.GetString("geoip", "license"); geoipLicense != "" { geoipUrl = GetGeoIpDownloadUrl(geoipLicense) } } @@ -347,7 +348,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, logger.Printf("Not using GeoIP database") } - geoipOverrides, err := LoadGeoIPOverrides(ctx, config, false) + geoipOverrides, err := LoadGeoIPOverrides(ctx, cfg, false) if err != nil { return nil, err } @@ -418,7 +419,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, skipFederationVerify: skipFederationVerify, federationTimeout: federationTimeout, } - if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { + if value, _ := cfg.GetString("mcu", "allowedcandidates"); value != "" { allowed, err := container.ParseIPList(value) if err != nil { return nil, fmt.Errorf("invalid allowedcandidates: %w", err) @@ -429,7 +430,7 @@ func NewHub(ctx context.Context, config *goconf.ConfigFile, events AsyncEvents, } else { logger.Printf("No candidates allowlist") } - if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { + if value, _ := cfg.GetString("mcu", "blockedcandidates"); value != "" { blocked, err := container.ParseIPList(value) if err != nil { return nil, fmt.Errorf("invalid blockedcandidates: %w", err) diff --git a/mcu_proxy.go b/mcu_proxy.go index 3e471bc..0383a91 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -47,6 +47,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -1589,8 +1590,8 @@ func (m *mcuProxy) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { return m.settings.MaxStreamBitrate(), m.settings.MaxScreenBitrate() } -func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error { - options, err := GetStringOptions(config, "continent-overrides", false) +func (m *mcuProxy) loadContinentsMap(cfg *goconf.ConfigFile) error { + options, err := config.GetStringOptions(cfg, "continent-overrides", false) if err != nil { return err } diff --git a/proxy/main.go b/proxy/main.go index 76755ad..2f406e6 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -37,7 +37,7 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" - signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -78,7 +78,7 @@ func main() { logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) - config, err := goconf.ReadConfigFile(*configFlag) + cfg, err := goconf.ReadConfigFile(*configFlag) if err != nil { logger.Fatal("Could not read configuration: ", err) } @@ -87,22 +87,22 @@ func main() { r := mux.NewRouter() - proxy, err := NewProxyServer(stopCtx, r, version, config) + proxy, err := NewProxyServer(stopCtx, r, version, cfg) if err != nil { logger.Fatal(err) } - if err := proxy.Start(config); err != nil { + if err := proxy.Start(cfg); err != nil { logger.Fatal(err) } defer proxy.Stop() - if addr, _ := signaling.GetStringOptionWithEnv(config, "http", "listen"); addr != "" { - readTimeout, _ := config.GetInt("http", "readtimeout") + if addr, _ := config.GetStringOptionWithEnv(cfg, "http", "listen"); addr != "" { + readTimeout, _ := cfg.GetInt("http", "readtimeout") if readTimeout <= 0 { readTimeout = defaultReadTimeout } - writeTimeout, _ := config.GetInt("http", "writetimeout") + writeTimeout, _ := cfg.GetInt("http", "writetimeout") if writeTimeout <= 0 { writeTimeout = defaultWriteTimeout } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 7c12372..a0e7d4b 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -52,6 +52,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -424,13 +425,13 @@ func (s *ProxyServer) checkOrigin(r *http.Request) bool { return true } -func (s *ProxyServer) Start(config *goconf.ConfigFile) error { - s.url, _ = signaling.GetStringOptionWithEnv(config, "mcu", "url") +func (s *ProxyServer) Start(cfg *goconf.ConfigFile) error { + s.url, _ = config.GetStringOptionWithEnv(cfg, "mcu", "url") if s.url == "" { return errors.New("no MCU server url configured") } - mcuType, _ := config.GetString("mcu", "type") + mcuType, _ := cfg.GetString("mcu", "type") if mcuType == "" { mcuType = signaling.McuTypeDefault } @@ -447,7 +448,7 @@ func (s *ProxyServer) Start(config *goconf.ConfigFile) error { for { switch mcuType { case signaling.McuTypeJanus: - mcu, err = signaling.NewMcuJanus(ctx, s.url, config) + mcu, err = signaling.NewMcuJanus(ctx, s.url, cfg) if err == nil { signaling.RegisterJanusMcuStats() } diff --git a/proxy/proxy_tokens_static.go b/proxy/proxy_tokens_static.go index ab28f04..2abb43c 100644 --- a/proxy/proxy_tokens_static.go +++ b/proxy/proxy_tokens_static.go @@ -30,7 +30,7 @@ import ( "github.com/dlintw/goconf" "github.com/golang-jwt/jwt/v5" - signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -64,8 +64,8 @@ func (t *tokensStatic) Get(id string) (*ProxyToken, error) { return token, nil } -func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error { - options, err := signaling.GetStringOptions(config, "tokens", ignoreErrors) +func (t *tokensStatic) load(cfg *goconf.ConfigFile, ignoreErrors bool) error { + options, err := config.GetStringOptions(cfg, "tokens", ignoreErrors) if err != nil { return err } diff --git a/proxy_config_static.go b/proxy_config_static.go index 2c81fa5..36424bb 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -30,6 +30,7 @@ import ( "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -70,11 +71,11 @@ func NewProxyConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy Mc return result, nil } -func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool) error { +func (p *proxyConfigStatic) configure(cfg *goconf.ConfigFile, fromReload bool) error { p.mu.Lock() defer p.mu.Unlock() - dnsDiscovery, _ := config.GetBool("mcu", "dnsdiscovery") + dnsDiscovery, _ := cfg.GetBool("mcu", "dnsdiscovery") if dnsDiscovery != p.dnsDiscovery { if !dnsDiscovery { for _, ips := range p.connectionsMap { @@ -89,7 +90,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool remove := maps.Clone(p.connectionsMap) - mcuUrl, _ := GetStringOptionWithEnv(config, "mcu", "url") + mcuUrl, _ := config.GetStringOptionWithEnv(cfg, "mcu", "url") for u := range internal.SplitEntries(mcuUrl, " ") { if existing, found := remove[u]; found { // Proxy connection still exists in new configuration diff --git a/server/main.go b/server/main.go index 4f2b7a4..022ef7c 100644 --- a/server/main.go +++ b/server/main.go @@ -42,6 +42,7 @@ import ( "github.com/gorilla/mux" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" @@ -168,7 +169,7 @@ func main() { logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid()) - config, err := goconf.ReadConfigFile(*configFlag) + cfg, err := goconf.ReadConfigFile(*configFlag) if err != nil { logger.Fatal("Could not read configuration: ", err) } @@ -177,7 +178,7 @@ func main() { signaling.RegisterStats() - natsUrl, _ := signaling.GetStringOptionWithEnv(config, "nats", "url") + natsUrl, _ := config.GetStringOptionWithEnv(cfg, "nats", "url") if natsUrl == "" { natsUrl = nats.DefaultURL } @@ -203,7 +204,7 @@ func main() { } defer dnsMonitor.Stop() - etcdClient, err := signaling.NewEtcdClient(logger, config, "mcu") + etcdClient, err := signaling.NewEtcdClient(logger, cfg, "mcu") if err != nil { logger.Fatalf("Could not create etcd client: %s", err) } @@ -213,7 +214,7 @@ func main() { } }() - rpcServer, err := signaling.NewGrpcServer(stopCtx, config, version) + rpcServer, err := signaling.NewGrpcServer(stopCtx, cfg, version) if err != nil { logger.Fatalf("Could not create RPC server: %s", err) } @@ -224,20 +225,20 @@ func main() { }() defer rpcServer.Close() - rpcClients, err := signaling.NewGrpcClients(stopCtx, config, etcdClient, dnsMonitor, version) + rpcClients, err := signaling.NewGrpcClients(stopCtx, cfg, etcdClient, dnsMonitor, version) if err != nil { logger.Fatalf("Could not create RPC clients: %s", err) } defer rpcClients.Close() r := mux.NewRouter() - hub, err := signaling.NewHub(stopCtx, config, events, rpcServer, rpcClients, etcdClient, r, version) + hub, err := signaling.NewHub(stopCtx, cfg, events, rpcServer, rpcClients, etcdClient, r, version) if err != nil { logger.Fatal("Could not create hub: ", err) } - mcuUrl, _ := signaling.GetStringOptionWithEnv(config, "mcu", "url") - mcuType, _ := config.GetString("mcu", "type") + mcuUrl, _ := config.GetStringOptionWithEnv(cfg, "mcu", "url") + mcuType, _ := cfg.GetString("mcu", "type") if mcuType == "" && mcuUrl != "" { logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) mcuType = signaling.McuTypeJanus @@ -256,11 +257,11 @@ func main() { ctx := context.TODO() switch mcuType { case signaling.McuTypeJanus: - mcu, err = signaling.NewMcuJanus(ctx, mcuUrl, config) + mcu, err = signaling.NewMcuJanus(ctx, mcuUrl, cfg) signaling.UnregisterProxyMcuStats() signaling.RegisterJanusMcuStats() case signaling.McuTypeProxy: - mcu, err = signaling.NewMcuProxy(ctx, config, etcdClient, rpcClients, dnsMonitor) + mcu, err = signaling.NewMcuProxy(ctx, cfg, etcdClient, rpcClients, dnsMonitor) signaling.UnregisterJanusMcuStats() signaling.RegisterProxyMcuStats() default: @@ -285,11 +286,11 @@ func main() { switch sig { case syscall.SIGHUP: logger.Printf("Received SIGHUP, reloading %s", *configFlag) - if config, err = goconf.ReadConfigFile(*configFlag); err != nil { + if cfg, err = goconf.ReadConfigFile(*configFlag); err != nil { logger.Printf("Could not read configuration from %s: %s", *configFlag, err) } else { - mcuUrl, _ = signaling.GetStringOptionWithEnv(config, "mcu", "url") - mcuType, _ = config.GetString("mcu", "type") + mcuUrl, _ = config.GetStringOptionWithEnv(cfg, "mcu", "url") + mcuType, _ = cfg.GetString("mcu", "type") if mcuType == "" && mcuUrl != "" { logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) mcuType = signaling.McuTypeJanus @@ -316,7 +317,7 @@ func main() { go hub.Run() defer hub.Stop() - server, err := signaling.NewBackendServer(stopCtx, config, hub, version) + server, err := signaling.NewBackendServer(stopCtx, cfg, hub, version) if err != nil { logger.Fatal("Could not create backend server: ", err) } @@ -328,18 +329,18 @@ func main() { logger: logger, } - if saddr, _ := signaling.GetStringOptionWithEnv(config, "https", "listen"); saddr != "" { - cert, _ := config.GetString("https", "certificate") - key, _ := config.GetString("https", "key") + if saddr, _ := config.GetStringOptionWithEnv(cfg, "https", "listen"); saddr != "" { + cert, _ := cfg.GetString("https", "certificate") + key, _ := cfg.GetString("https", "key") if cert == "" || key == "" { logger.Fatal("Need a certificate and key for the HTTPS listener") } - readTimeout, _ := config.GetInt("https", "readtimeout") + readTimeout, _ := cfg.GetInt("https", "readtimeout") if readTimeout <= 0 { readTimeout = defaultReadTimeout } - writeTimeout, _ := config.GetInt("https", "writetimeout") + writeTimeout, _ := cfg.GetInt("https", "writetimeout") if writeTimeout <= 0 { writeTimeout = defaultWriteTimeout } @@ -366,12 +367,12 @@ func main() { } } - if addr, _ := signaling.GetStringOptionWithEnv(config, "http", "listen"); addr != "" { - readTimeout, _ := config.GetInt("http", "readtimeout") + if addr, _ := config.GetStringOptionWithEnv(cfg, "http", "listen"); addr != "" { + readTimeout, _ := cfg.GetInt("http", "readtimeout") if readTimeout <= 0 { readTimeout = defaultReadTimeout } - writeTimeout, _ := config.GetInt("http", "writetimeout") + writeTimeout, _ := cfg.GetInt("http", "writetimeout") if writeTimeout <= 0 { writeTimeout = defaultWriteTimeout } From ff69ee5c91f8ba1ee14757be3e0065a03e0825a0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 20:20:45 +0100 Subject: [PATCH 416/549] Move backend object to talk package. --- .codecov.yml | 4 + api_backend.go | 71 ----- api_backend_test.go | 107 -------- async_events.go | 25 +- async_events_nats.go | 33 +-- async_events_nats_test.go | 6 +- backend_client.go | 6 +- backend_configuration.go | 150 +---------- backend_configuration_stats_prometheus.go | 26 -- backend_configuration_test.go | 45 ++-- backend_server.go | 21 +- backend_storage_etcd.go | 56 ++-- backend_storage_static.go | 194 ++++--------- clientsession.go | 11 +- clientsession_test.go | 8 +- etcd/api.go | 99 +++++++ etcd/api_test.go | 135 ++++++++++ grpc_server.go | 5 +- hub.go | 30 +-- hub_test.go | 8 +- mcu_proxy_test.go | 5 +- room.go | 9 +- roomsessions_test.go | 3 +- session.go | 3 +- sessionid_codec_test.go | 17 +- talk/backend.go | 314 ++++++++++++++++++++++ talk/backend_stats_prometheus.go | 52 ++++ virtualsession.go | 3 +- virtualsession_test.go | 17 +- 29 files changed, 823 insertions(+), 640 deletions(-) create mode 100644 etcd/api.go create mode 100644 etcd/api_test.go create mode 100644 talk/backend.go create mode 100644 talk/backend_stats_prometheus.go diff --git a/.codecov.yml b/.codecov.yml index 832fbf1..58b6c01 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -38,6 +38,10 @@ component_management: name: container paths: - container/** + - component_id: module_etcd + name: etcd + paths: + - etcd/** - component_id: module_internal name: internal paths: diff --git a/api_backend.go b/api_backend.go index 6b061de..1a1a236 100644 --- a/api_backend.go +++ b/api_backend.go @@ -28,16 +28,12 @@ import ( "crypto/subtle" "encoding/hex" "encoding/json" - "errors" "fmt" "net/http" - "net/url" "regexp" - "slices" "time" "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -426,73 +422,6 @@ type TurnCredentials struct { URIs []string `json:"uris"` } -// Information on a backend in the etcd cluster. - -type BackendInformationEtcd struct { - // Compat setting. - Url string `json:"url,omitempty"` - - Urls []string `json:"urls,omitempty"` - parsedUrls []*url.URL - Secret string `json:"secret"` - - MaxStreamBitrate api.Bandwidth `json:"maxstreambitrate,omitempty"` - MaxScreenBitrate api.Bandwidth `json:"maxscreenbitrate,omitempty"` - - SessionLimit uint64 `json:"sessionlimit,omitempty"` -} - -func (p *BackendInformationEtcd) CheckValid() (err error) { - if p.Secret == "" { - return errors.New("secret missing") - } - - if len(p.Urls) > 0 { - slices.Sort(p.Urls) - p.Urls = slices.Compact(p.Urls) - seen := make(map[string]bool) - outIdx := 0 - for _, u := range p.Urls { - parsedUrl, err := url.Parse(u) - if err != nil { - return fmt.Errorf("invalid url %s: %w", u, err) - } - - var changed bool - if parsedUrl, changed = internal.CanonicalizeUrl(parsedUrl); changed { - u = parsedUrl.String() - } - p.Urls[outIdx] = u - if seen[u] { - continue - } - seen[u] = true - p.parsedUrls = append(p.parsedUrls, parsedUrl) - outIdx++ - } - if len(p.Urls) != outIdx { - clear(p.Urls[outIdx:]) - p.Urls = p.Urls[:outIdx] - } - } else if p.Url != "" { - parsedUrl, err := url.Parse(p.Url) - if err != nil { - return fmt.Errorf("invalid url: %w", err) - } - var changed bool - if parsedUrl, changed = internal.CanonicalizeUrl(parsedUrl); changed { - p.Url = parsedUrl.String() - } - - p.Urls = append(p.Urls, p.Url) - p.parsedUrls = append(p.parsedUrls, parsedUrl) - } else { - return errors.New("urls missing") - } - - return nil -} - type BackendServerInfoVideoRoom struct { Name string `json:"name,omitempty"` Version string `json:"version,omitempty"` diff --git a/api_backend_test.go b/api_backend_test.go index 1a46212..724075d 100644 --- a/api_backend_test.go +++ b/api_backend_test.go @@ -72,110 +72,3 @@ func TestValidNumbers(t *testing.T) { assert.False(isValidNumber(number), "number %s should not be valid", number) } } - -func TestValidateBackendInformationEtcd(t *testing.T) { - t.Parallel() - assert := assert.New(t) - - testcases := []struct { - b BackendInformationEtcd - expectedError string - expectedUrls []string - }{ - { - b: BackendInformationEtcd{}, - expectedError: "secret missing", - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - }, - expectedError: "urls missing", - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Url: "https://foo\n", - }, - expectedError: "invalid url", - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo\n"}, - }, - expectedError: "invalid url", - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo", "https://foo\n"}, - }, - expectedError: "invalid url", - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Url: "https://foo:443", - }, - expectedUrls: []string{"https://foo"}, - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo:443"}, - }, - expectedUrls: []string{"https://foo"}, - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Url: "https://foo:8443", - }, - expectedUrls: []string{"https://foo:8443"}, - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo:8443"}, - }, - expectedUrls: []string{"https://foo:8443"}, - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo", "https://bar", "https://foo"}, - }, - expectedUrls: []string{"https://bar", "https://foo"}, - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo", "https://bar", "https://foo:443", "https://zaz"}, - }, - expectedUrls: []string{"https://bar", "https://foo", "https://zaz"}, - }, - { - b: BackendInformationEtcd{ - Secret: "verysecret", - Urls: []string{"https://foo:443", "https://bar", "https://foo", "https://zaz"}, - }, - expectedUrls: []string{"https://bar", "https://foo", "https://zaz"}, - }, - } - - for idx, tc := range testcases { - if tc.expectedError == "" { - if assert.NoError(tc.b.CheckValid(), "failed for testcase %d", idx) { - assert.Equal(tc.expectedUrls, tc.b.Urls, "failed for testcase %d", idx) - var urls []string - for _, u := range tc.b.parsedUrls { - urls = append(urls, u.String()) - } - assert.Equal(tc.expectedUrls, urls, "failed for testcase %d", idx) - } - } else { - assert.ErrorContains(tc.b.CheckValid(), tc.expectedError, "failed for testcase %d, got %+v", idx, tc.b.parsedUrls) - } - } -} diff --git a/async_events.go b/async_events.go index 9699853..d5fb7ab 100644 --- a/async_events.go +++ b/async_events.go @@ -28,6 +28,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -47,22 +48,22 @@ type AsyncEventListener interface { type AsyncEvents interface { Close(ctx context.Context) error - RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error - UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error + RegisterBackendRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error + UnregisterBackendRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error - RegisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error - UnregisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error + RegisterRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error + UnregisterRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error - RegisterUserListener(userId string, backend *Backend, listener AsyncEventListener) error - UnregisterUserListener(userId string, backend *Backend, listener AsyncEventListener) error + RegisterUserListener(userId string, backend *talk.Backend, listener AsyncEventListener) error + UnregisterUserListener(userId string, backend *talk.Backend, listener AsyncEventListener) error - RegisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error - UnregisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error + RegisterSessionListener(sessionId api.PublicSessionId, backend *talk.Backend, listener AsyncEventListener) error + UnregisterSessionListener(sessionId api.PublicSessionId, backend *talk.Backend, listener AsyncEventListener) error - PublishBackendRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error - PublishRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error - PublishUserMessage(userId string, backend *Backend, message *AsyncMessage) error - PublishSessionMessage(sessionId api.PublicSessionId, backend *Backend, message *AsyncMessage) error + PublishBackendRoomMessage(roomId string, backend *talk.Backend, message *AsyncMessage) error + PublishRoomMessage(roomId string, backend *talk.Backend, message *AsyncMessage) error + PublishUserMessage(userId string, backend *talk.Backend, message *AsyncMessage) error + PublishSessionMessage(sessionId api.PublicSessionId, backend *talk.Backend, message *AsyncMessage) error } func NewAsyncEvents(ctx context.Context, url string) (AsyncEvents, error) { diff --git a/async_events_nats.go b/async_events_nats.go index 1f363c6..4bc5788 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -30,9 +30,10 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) -func GetSubjectForBackendRoomId(roomId string, backend *Backend) string { +func GetSubjectForBackendRoomId(roomId string, backend *talk.Backend) string { if backend == nil || backend.IsCompat() { return nats.GetEncodedSubject("backend.room", roomId) } @@ -40,7 +41,7 @@ func GetSubjectForBackendRoomId(roomId string, backend *Backend) string { return nats.GetEncodedSubject("backend.room", roomId+"|"+backend.Id()) } -func GetSubjectForRoomId(roomId string, backend *Backend) string { +func GetSubjectForRoomId(roomId string, backend *talk.Backend) string { if backend == nil || backend.IsCompat() { return nats.GetEncodedSubject("room", roomId) } @@ -48,7 +49,7 @@ func GetSubjectForRoomId(roomId string, backend *Backend) string { return nats.GetEncodedSubject("room", roomId+"|"+backend.Id()) } -func GetSubjectForUserId(userId string, backend *Backend) string { +func GetSubjectForUserId(userId string, backend *talk.Backend) string { if backend == nil || backend.IsCompat() { return nats.GetEncodedSubject("user", userId) } @@ -56,7 +57,7 @@ func GetSubjectForUserId(userId string, backend *Backend) string { return nats.GetEncodedSubject("user", userId+"|"+backend.Id()) } -func GetSubjectForSessionId(sessionId api.PublicSessionId, backend *Backend) string { +func GetSubjectForSessionId(sessionId api.PublicSessionId, backend *talk.Backend) string { return string("session." + sessionId) } @@ -189,7 +190,7 @@ func (e *asyncEventsNats) unregisterListener(key string, subscriptions asyncEven return sub.Unsubscribe() } -func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForBackendRoomId(roomId, backend) e.mu.Lock() @@ -198,7 +199,7 @@ func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Ba return e.registerListener(key, e.backendRoomSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForBackendRoomId(roomId, backend) e.mu.Lock() @@ -207,7 +208,7 @@ func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend * return e.unregisterListener(key, e.backendRoomSubscriptions, listener) } -func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForRoomId(roomId, backend) e.mu.Lock() @@ -216,7 +217,7 @@ func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *Backend, return e.registerListener(key, e.roomSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForRoomId(roomId, backend) e.mu.Lock() @@ -225,7 +226,7 @@ func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *Backend return e.unregisterListener(key, e.roomSubscriptions, listener) } -func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForUserId(roomId, backend) e.mu.Lock() @@ -234,7 +235,7 @@ func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *Backend, return e.registerListener(key, e.userSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForUserId(roomId, backend) e.mu.Lock() @@ -243,7 +244,7 @@ func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend return e.unregisterListener(key, e.userSubscriptions, listener) } -func (e *asyncEventsNats) RegisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) RegisterSessionListener(sessionId api.PublicSessionId, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() @@ -252,7 +253,7 @@ func (e *asyncEventsNats) RegisterSessionListener(sessionId api.PublicSessionId, return e.registerListener(key, e.sessionSubscriptions, listener) } -func (e *asyncEventsNats) UnregisterSessionListener(sessionId api.PublicSessionId, backend *Backend, listener AsyncEventListener) error { +func (e *asyncEventsNats) UnregisterSessionListener(sessionId api.PublicSessionId, backend *talk.Backend, listener AsyncEventListener) error { key := GetSubjectForSessionId(sessionId, backend) e.mu.Lock() @@ -266,22 +267,22 @@ func (e *asyncEventsNats) publish(subject string, message *AsyncMessage) error { return e.client.Publish(subject, message) } -func (e *asyncEventsNats) PublishBackendRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error { +func (e *asyncEventsNats) PublishBackendRoomMessage(roomId string, backend *talk.Backend, message *AsyncMessage) error { subject := GetSubjectForBackendRoomId(roomId, backend) return e.publish(subject, message) } -func (e *asyncEventsNats) PublishRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error { +func (e *asyncEventsNats) PublishRoomMessage(roomId string, backend *talk.Backend, message *AsyncMessage) error { subject := GetSubjectForRoomId(roomId, backend) return e.publish(subject, message) } -func (e *asyncEventsNats) PublishUserMessage(userId string, backend *Backend, message *AsyncMessage) error { +func (e *asyncEventsNats) PublishUserMessage(userId string, backend *talk.Backend, message *AsyncMessage) error { subject := GetSubjectForUserId(userId, backend) return e.publish(subject, message) } -func (e *asyncEventsNats) PublishSessionMessage(sessionId api.PublicSessionId, backend *Backend, message *AsyncMessage) error { +func (e *asyncEventsNats) PublishSessionMessage(sessionId api.PublicSessionId, backend *talk.Backend, message *AsyncMessage) error { subject := GetSubjectForSessionId(sessionId, backend) return e.publish(subject, message) } diff --git a/async_events_nats_test.go b/async_events_nats_test.go index dadd9e2..24bd50f 100644 --- a/async_events_nats_test.go +++ b/async_events_nats_test.go @@ -26,13 +26,13 @@ import ( "time" "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func Benchmark_GetSubjectForSessionId(b *testing.B) { require := require.New(b) - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), diff --git a/backend_client.go b/backend_client.go index 1032d49..2d22e91 100644 --- a/backend_client.go +++ b/backend_client.go @@ -98,15 +98,15 @@ func (b *BackendClient) Reload(config *goconf.ConfigFile) { b.backends.Reload(config) } -func (b *BackendClient) GetCompatBackend() *Backend { +func (b *BackendClient) GetCompatBackend() *talk.Backend { return b.backends.GetCompatBackend() } -func (b *BackendClient) GetBackend(u *url.URL) *Backend { +func (b *BackendClient) GetBackend(u *url.URL) *talk.Backend { return b.backends.GetBackend(u) } -func (b *BackendClient) GetBackends() []*Backend { +func (b *BackendClient) GetBackends() []*talk.Backend { return b.backends.GetBackends() } diff --git a/backend_configuration.go b/backend_configuration.go index 41a7495..99569dc 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -22,7 +22,6 @@ package signaling import ( - "bytes" "fmt" "net/url" "slices" @@ -31,9 +30,9 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -43,134 +42,13 @@ const ( DefaultBackendType = BackendTypeStatic ) -var ( - SessionLimitExceeded = api.NewError("session_limit_exceeded", "Too many sessions connected for this backend.") -) - -type Backend struct { - id string - urls []string - secret []byte - - allowHttp bool - - maxStreamBitrate api.Bandwidth - maxScreenBitrate api.Bandwidth - - sessionLimit uint64 - sessionsLock sync.Mutex - // +checklocks:sessionsLock - sessions map[api.PublicSessionId]bool - - counted bool -} - -func (b *Backend) Id() string { - return b.id -} - -func (b *Backend) Secret() []byte { - return b.secret -} - -func (b *Backend) IsCompat() bool { - return len(b.urls) == 0 -} - -func (b *Backend) Equal(other *Backend) bool { - if b == other { - return true - } else if b == nil || other == nil { - return false - } - - return b.id == other.id && - b.allowHttp == other.allowHttp && - b.maxStreamBitrate == other.maxStreamBitrate && - b.maxScreenBitrate == other.maxScreenBitrate && - b.sessionLimit == other.sessionLimit && - bytes.Equal(b.secret, other.secret) && - slices.Equal(b.urls, other.urls) -} - -func (b *Backend) IsUrlAllowed(u *url.URL) bool { - switch u.Scheme { - case "https": - return true - case "http": - return b.allowHttp - default: - return false - } -} - -func (b *Backend) HasUrl(url string) bool { - if b.IsCompat() { - // Old-style configuration, only hosts are configured. - return true - } - - for _, u := range b.urls { - if strings.HasPrefix(url, u) { - return true - } - } - - return false -} - -func (b *Backend) Urls() []string { - return b.urls -} - -func (b *Backend) Limit() int { - return int(b.sessionLimit) -} - -func (b *Backend) Len() int { - b.sessionsLock.Lock() - defer b.sessionsLock.Unlock() - return len(b.sessions) -} - -func (b *Backend) AddSession(session Session) error { - if session.ClientType() == api.HelloClientTypeInternal || session.ClientType() == api.HelloClientTypeVirtual { - // Internal and virtual sessions are not counting to the limit. - return nil - } - - if b.sessionLimit == 0 { - // Not limited - return nil - } - - b.sessionsLock.Lock() - defer b.sessionsLock.Unlock() - if b.sessions == nil { - b.sessions = make(map[api.PublicSessionId]bool) - } else if uint64(len(b.sessions)) >= b.sessionLimit { - statsBackendLimitExceededTotal.WithLabelValues(b.id).Inc() - return SessionLimitExceeded - } - - b.sessions[session.PublicId()] = true - return nil -} - -func (b *Backend) RemoveSession(session Session) { - b.sessionsLock.Lock() - defer b.sessionsLock.Unlock() - - delete(b.sessions, session.PublicId()) -} - type BackendStorage interface { Close() - Reload(config *goconf.ConfigFile) + Reload(cfg *goconf.ConfigFile) - GetCompatBackend() *Backend - GetBackend(u *url.URL) *Backend - GetBackends() []*Backend + GetCompatBackend() *talk.Backend + GetBackend(u *url.URL) *talk.Backend + GetBackends() []*talk.Backend } type BackendStorageStats interface { @@ -183,29 +61,29 @@ type BackendStorageStats interface { type backendStorageCommon struct { mu sync.RWMutex // +checklocks:mu - backends map[string][]*Backend + backends map[string][]*talk.Backend stats BackendStorageStats // +checklocksignore: Only written to from constructor } -func (s *backendStorageCommon) GetBackends() []*Backend { +func (s *backendStorageCommon) GetBackends() []*talk.Backend { s.mu.RLock() defer s.mu.RUnlock() - var result []*Backend + var result []*talk.Backend for _, entries := range s.backends { result = append(result, entries...) } - slices.SortFunc(result, func(a, b *Backend) int { + slices.SortFunc(result, func(a, b *talk.Backend) int { return strings.Compare(a.Id(), b.Id()) }) - result = slices.CompactFunc(result, func(a, b *Backend) bool { + result = slices.CompactFunc(result, func(a, b *talk.Backend) bool { return a.Id() == b.Id() }) return result } -func (s *backendStorageCommon) getBackendLocked(u *url.URL) *Backend { +func (s *backendStorageCommon) getBackendLocked(u *url.URL) *talk.Backend { s.mu.RLock() defer s.mu.RUnlock() @@ -299,16 +177,16 @@ func (b *BackendConfiguration) Reload(config *goconf.ConfigFile) { b.storage.Reload(config) } -func (b *BackendConfiguration) GetCompatBackend() *Backend { +func (b *BackendConfiguration) GetCompatBackend() *talk.Backend { return b.storage.GetCompatBackend() } -func (b *BackendConfiguration) GetBackend(u *url.URL) *Backend { +func (b *BackendConfiguration) GetBackend(u *url.URL) *talk.Backend { u, _ = internal.CanonicalizeUrl(u) return b.storage.GetBackend(u) } -func (b *BackendConfiguration) GetBackends() []*Backend { +func (b *BackendConfiguration) GetBackends() []*talk.Backend { return b.storage.GetBackends() } diff --git a/backend_configuration_stats_prometheus.go b/backend_configuration_stats_prometheus.go index 1fbef3c..b1e8bf7 100644 --- a/backend_configuration_stats_prometheus.go +++ b/backend_configuration_stats_prometheus.go @@ -28,18 +28,6 @@ import ( ) var ( - statsBackendLimit = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "backend", - Name: "session_limit", - Help: "The session limit of a backend", - }, []string{"backend"}) - statsBackendLimitExceededTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ // +checklocksignore: Global readonly variable. - Namespace: "signaling", - Subsystem: "backend", - Name: "session_limit_exceeded_total", - Help: "The number of times the session limit exceeded", - }, []string{"backend"}) statsBackendsCurrent = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "signaling", Subsystem: "backend", @@ -48,8 +36,6 @@ var ( }) backendConfigurationStats = []prometheus.Collector{ - statsBackendLimit, - statsBackendLimitExceededTotal, statsBackendsCurrent, } ) @@ -57,15 +43,3 @@ var ( func RegisterBackendConfigurationStats() { metrics.RegisterAll(backendConfigurationStats...) } - -func updateBackendStats(backend *Backend) { - if backend.sessionLimit > 0 { - statsBackendLimit.WithLabelValues(backend.id).Set(float64(backend.sessionLimit)) - } else { - statsBackendLimit.DeleteLabelValues(backend.id) - } -} - -func deleteBackendStats(backend *Backend) { - statsBackendLimit.DeleteLabelValues(backend.id) -} diff --git a/backend_configuration_test.go b/backend_configuration_test.go index 74012a9..e460035 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -34,6 +34,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) { @@ -486,9 +487,9 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { } } -func sortBackends(backends []*Backend) []*Backend { +func sortBackends(backends []*talk.Backend) []*talk.Backend { result := slices.Clone(backends) - slices.SortFunc(result, func(a, b *Backend) int { + slices.SortFunc(result, func(a, b *talk.Backend) int { return strings.Compare(a.Id(), b.Id()) }) return result @@ -534,8 +535,8 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { require.NoError(storage.WaitForInitialized(ctx)) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && - assert.Equal([]string{url1}, backends[0].urls) && - assert.Equal(initialSecret1, string(backends[0].secret)) { + assert.Equal([]string{url1}, backends[0].Urls()) && + assert.Equal(initialSecret1, string(backends[0].Secret())) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) } @@ -546,8 +547,8 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { <-ch assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && - assert.Equal([]string{url1}, backends[0].urls) && - assert.Equal(secret1, string(backends[0].secret)) { + assert.Equal([]string{url1}, backends[0].Urls()) && + assert.Equal(secret1, string(backends[0].Secret())) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) } @@ -561,10 +562,10 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { <-ch assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && - assert.Equal([]string{url1}, backends[0].urls) && - assert.Equal(secret1, string(backends[0].secret)) && - assert.Equal([]string{url2}, backends[1].urls) && - assert.Equal(secret2, string(backends[1].secret)) { + assert.Equal([]string{url1}, backends[0].Urls()) && + assert.Equal(secret1, string(backends[0].Secret())) && + assert.Equal([]string{url2}, backends[1].Urls()) && + assert.Equal(secret2, string(backends[1].Secret())) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) } else if backend := cfg.GetBackend(mustParse(url2)); assert.NotNil(backend) { @@ -580,12 +581,12 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { <-ch assert.Equal(3, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 3) && - assert.Equal([]string{url1}, backends[0].urls) && - assert.Equal(secret1, string(backends[0].secret)) && - assert.Equal([]string{url2}, backends[1].urls) && - assert.Equal(secret2, string(backends[1].secret)) && - assert.Equal([]string{url3}, backends[2].urls) && - assert.Equal(secret3, string(backends[2].secret)) { + assert.Equal([]string{url1}, backends[0].Urls()) && + assert.Equal(secret1, string(backends[0].Secret())) && + assert.Equal([]string{url2}, backends[1].Urls()) && + assert.Equal(secret2, string(backends[1].Secret())) && + assert.Equal([]string{url3}, backends[2].Urls()) && + assert.Equal(secret3, string(backends[2].Secret())) { if backend := cfg.GetBackend(mustParse(url1)); assert.NotNil(backend) { assert.Equal(backends[0], backend) } else if backend := cfg.GetBackend(mustParse(url2)); assert.NotNil(backend) { @@ -600,10 +601,10 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { <-ch assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) { - assert.Equal([]string{url2}, backends[0].urls) - assert.Equal(secret2, string(backends[0].secret)) - assert.Equal([]string{url3}, backends[1].urls) - assert.Equal(secret3, string(backends[1].secret)) + assert.Equal([]string{url2}, backends[0].Urls()) + assert.Equal(secret2, string(backends[0].Secret())) + assert.Equal([]string{url3}, backends[1].Urls()) + assert.Equal(secret3, string(backends[1].Secret())) } drainWakeupChannel(ch) @@ -611,8 +612,8 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { <-ch assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { - assert.Equal([]string{url3}, backends[0].urls) - assert.Equal(secret3, string(backends[0].secret)) + assert.Equal([]string{url3}, backends[0].Urls()) + assert.Equal(secret3, string(backends[0].Secret())) } storage.mu.RLock() diff --git a/backend_server.go b/backend_server.go index f94bef6..3b25831 100644 --- a/backend_server.go +++ b/backend_server.go @@ -55,6 +55,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -327,7 +328,7 @@ func (b *BackendServer) parseRequestBody(f func(context.Context, http.ResponseWr } } -func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids []string, properties json.RawMessage) { +func (b *BackendServer) sendRoomInvite(roomid string, backend *talk.Backend, userids []string, properties json.RawMessage) { msg := &AsyncMessage{ Type: "message", Message: &api.ServerMessage{ @@ -349,7 +350,7 @@ func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids } } -func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reason string, userids []string, sessionids []api.RoomSessionId) { +func (b *BackendServer) sendRoomDisinvite(roomid string, backend *talk.Backend, reason string, userids []string, sessionids []api.RoomSessionId) { msg := &AsyncMessage{ Type: "message", Message: &api.ServerMessage{ @@ -398,7 +399,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reaso wg.Wait() } -func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified_userids []string, all_userids []string, properties json.RawMessage) { +func (b *BackendServer) sendRoomUpdate(roomid string, backend *talk.Backend, notified_userids []string, all_userids []string, properties json.RawMessage) { msg := &AsyncMessage{ Type: "message", Message: &api.ServerMessage{ @@ -499,7 +500,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container. return result } -func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomIncall(roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { if !request.InCall.All { timeout := time.Second @@ -524,7 +525,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid string, backend *Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { timeout := time.Second // Convert (Nextcloud) session ids to signaling session ids. @@ -588,7 +589,7 @@ loop: return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomMessage(roomid string, backend *Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomMessage(roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { message := &AsyncMessage{ Type: "room", Room: request, @@ -596,7 +597,7 @@ func (b *BackendServer) sendRoomMessage(roomid string, backend *Backend, request return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, backend *Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { timeout := time.Second // Convert (Nextcloud) session ids to signaling session ids. @@ -728,7 +729,7 @@ func isNumeric(s string) bool { return checkNumeric.MatchString(s) } -func (b *BackendServer) startDialoutInSession(ctx context.Context, session *ClientSession, roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { +func (b *BackendServer) startDialoutInSession(ctx context.Context, session *ClientSession, roomid string, backend *talk.Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { url := backendUrl if url != "" && url[len(url)-1] != '/' { url += "/" @@ -809,7 +810,7 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie } } -func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { +func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend *talk.Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { if err := request.Dialout.ValidateNumber(); err != nil { return returnDialoutError(http.StatusBadRequest, err) } @@ -860,7 +861,7 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, v := mux.Vars(r) roomid := v["roomid"] - var backend *Backend + var backend *talk.Backend backendUrl := r.Header.Get(HeaderBackendServer) if backendUrl != "" { if u, err := url.Parse(backendUrl); err == nil { diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 86a241e..acb4b7c 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -33,7 +33,9 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type backendStorageEtcd struct { @@ -42,7 +44,7 @@ type backendStorageEtcd struct { logger log.Logger etcdClient *EtcdClient keyPrefix string - keyInfos map[string]*BackendInformationEtcd + keyInfos map[string]*etcd.BackendInformationEtcd initializedCtx context.Context initializedFunc context.CancelFunc @@ -66,13 +68,13 @@ func NewBackendStorageEtcd(logger log.Logger, config *goconf.ConfigFile, etcdCli closeCtx, closeFunc := context.WithCancel(context.Background()) result := &backendStorageEtcd{ backendStorageCommon: backendStorageCommon{ - backends: make(map[string][]*Backend), + backends: make(map[string][]*talk.Backend), stats: stats, }, logger: logger, etcdClient: etcdClient, keyPrefix: keyPrefix, - keyInfos: make(map[string]*BackendInformationEtcd), + keyInfos: make(map[string]*etcd.BackendInformationEtcd), initializedCtx: initializedCtx, initializedFunc: initializedFunc, @@ -173,7 +175,7 @@ func (s *backendStorageEtcd) getBackends(ctx context.Context, client *EtcdClient } func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { - var info BackendInformationEtcd + var info etcd.BackendInformationEtcd if err := json.Unmarshal(data, &info); err != nil { s.logger.Printf("Could not decode backend information %s: %s", string(data), err) return @@ -183,34 +185,20 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data return } - allowHttp := slices.ContainsFunc(info.parsedUrls, func(u *url.URL) bool { - return u.Scheme == "http" - }) - - backend := &Backend{ - id: key, - urls: info.Urls, - secret: []byte(info.Secret), - - allowHttp: allowHttp, - - maxStreamBitrate: info.MaxStreamBitrate, - maxScreenBitrate: info.MaxScreenBitrate, - sessionLimit: info.SessionLimit, - } + backend := talk.NewBackendFromEtcd(key, &info) s.mu.Lock() defer s.mu.Unlock() s.keyInfos[key] = &info added := false - for idx, u := range info.parsedUrls { + for idx, u := range info.ParsedUrls { host := u.Host entries, found := s.backends[host] if !found { // Simple case, first backend for this host s.logger.Printf("Added backend %s (from %s)", info.Urls[idx], key) - s.backends[host] = []*Backend{backend} + s.backends[host] = []*talk.Backend{backend} added = true continue } @@ -218,7 +206,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data // Was the backend changed? replaced := false for idx, entry := range entries { - if entry.id == key { + if entry.Id() == key { s.logger.Printf("Updated backend %s (from %s)", info.Urls[idx], key) entries[idx] = backend replaced = true @@ -233,7 +221,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data added = true } } - updateBackendStats(backend) + backend.UpdateStats() if added { s.stats.IncBackends() } @@ -250,15 +238,15 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev } delete(s.keyInfos, key) - var deleted map[string][]*Backend + var deleted map[string][]*talk.Backend seen := make(map[string]bool) - for idx, u := range info.parsedUrls { + for idx, u := range info.ParsedUrls { host := u.Host entries, found := s.backends[host] if !found { if d, ok := deleted[host]; ok { - if slices.ContainsFunc(d, func(b *Backend) bool { - return slices.Contains(b.urls, u.String()) + if slices.ContainsFunc(d, func(b *talk.Backend) bool { + return slices.Contains(b.Urls(), u.String()) }) { s.logger.Printf("Removing backend %s (from %s)", info.Urls[idx], key) } @@ -267,18 +255,18 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prev } s.logger.Printf("Removing backend %s (from %s)", info.Urls[idx], key) - newEntries := make([]*Backend, 0, len(entries)-1) + newEntries := make([]*talk.Backend, 0, len(entries)-1) for _, entry := range entries { - if entry.id == key { - if len(info.parsedUrls) > 1 { + if entry.Id() == key { + if len(info.ParsedUrls) > 1 { if deleted == nil { - deleted = make(map[string][]*Backend) + deleted = make(map[string][]*talk.Backend) } deleted[host] = append(deleted[host], entry) } if !seen[entry.Id()] { seen[entry.Id()] = true - updateBackendStats(entry) + entry.UpdateStats() s.stats.DecBackends() } continue @@ -304,11 +292,11 @@ func (s *backendStorageEtcd) Reload(config *goconf.ConfigFile) { // Backend updates are processed through etcd. } -func (s *backendStorageEtcd) GetCompatBackend() *Backend { +func (s *backendStorageEtcd) GetCompatBackend() *talk.Backend { return nil } -func (s *backendStorageEtcd) GetBackend(u *url.URL) *Backend { +func (s *backendStorageEtcd) GetBackend(u *url.URL) *talk.Backend { s.mu.RLock() defer s.mu.RUnlock() diff --git a/backend_storage_static.go b/backend_storage_static.go index f0ebebc..ea2e89a 100644 --- a/backend_storage_static.go +++ b/backend_storage_static.go @@ -28,77 +28,53 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type backendStorageStatic struct { backendStorageCommon logger log.Logger - backendsById map[string]*Backend + backendsById map[string]*talk.Backend // Deprecated allowAll bool commonSecret []byte - compatBackend *Backend + compatBackend *talk.Backend } func NewBackendStorageStatic(logger log.Logger, cfg *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { allowAll, _ := cfg.GetBool("backend", "allowall") - allowHttp, _ := cfg.GetBool("backend", "allowhttp") commonSecret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret") - sessionLimit, err := cfg.GetInt("backend", "sessionlimit") - if err != nil || sessionLimit < 0 { - sessionLimit = 0 - } - backends := make(map[string][]*Backend) - backendsById := make(map[string]*Backend) - var compatBackend *Backend + backends := make(map[string][]*talk.Backend) + backendsById := make(map[string]*talk.Backend) + var compatBackend *talk.Backend numBackends := 0 if allowAll { logger.Println("WARNING: All backend hostnames are allowed, only use for development!") - maxStreamBitrate, err := cfg.GetInt("backend", "maxstreambitrate") - if err != nil || maxStreamBitrate < 0 { - maxStreamBitrate = 0 - } - maxScreenBitrate, err := cfg.GetInt("backend", "maxscreenbitrate") - if err != nil || maxScreenBitrate < 0 { - maxScreenBitrate = 0 - } - compatBackend = &Backend{ - id: "compat", - secret: []byte(commonSecret), - - allowHttp: allowHttp, - - sessionLimit: uint64(sessionLimit), - counted: true, - - maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), - maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), - } - if sessionLimit > 0 { + compatBackend = talk.NewCompatBackend(cfg) + if sessionLimit := compatBackend.Limit(); sessionLimit > 0 { logger.Printf("Allow a maximum of %d sessions", sessionLimit) } - updateBackendStats(compatBackend) - backendsById[compatBackend.id] = compatBackend + compatBackend.UpdateStats() + backendsById[compatBackend.Id()] = compatBackend numBackends++ } else if backendIds, _ := cfg.GetString("backend", "backends"); backendIds != "" { - added := make(map[string]*Backend) + added := make(map[string]*talk.Backend) for host, configuredBackends := range getConfiguredHosts(logger, backendIds, cfg, commonSecret) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { - added[be.id] = be + added[be.Id()] = be } } for _, be := range added { - logger.Printf("Backend %s added for %s", be.id, strings.Join(be.urls, ", ")) - backendsById[be.id] = be - updateBackendStats(be) - be.counted = true + logger.Printf("Backend %s added for %s", be.Id(), strings.Join(be.Urls(), ", ")) + backendsById[be.Id()] = be + be.UpdateStats() + be.Count() } numBackends += len(added) } else if allowedUrls, _ := cfg.GetString("backend", "allowed"); allowedUrls != "" { @@ -118,40 +94,21 @@ func NewBackendStorageStatic(logger log.Logger, cfg *goconf.ConfigFile, stats Ba if len(allowMap) == 0 { logger.Println("WARNING: No backend hostnames are allowed, check your configuration!") } else { - maxStreamBitrate, err := cfg.GetInt("backend", "maxstreambitrate") - if err != nil || maxStreamBitrate < 0 { - maxStreamBitrate = 0 - } - maxScreenBitrate, err := cfg.GetInt("backend", "maxscreenbitrate") - if err != nil || maxScreenBitrate < 0 { - maxScreenBitrate = 0 - } - compatBackend = &Backend{ - id: "compat", - secret: []byte(commonSecret), - - allowHttp: allowHttp, - - sessionLimit: uint64(sessionLimit), - counted: true, - - maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), - maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), - } + compatBackend = talk.NewCompatBackend(cfg) hosts := make([]string, 0, len(allowMap)) for host := range allowMap { hosts = append(hosts, host) - backends[host] = []*Backend{compatBackend} + backends[host] = []*talk.Backend{compatBackend} } if len(hosts) > 1 { logger.Println("WARNING: Using deprecated backend configuration. Please migrate the \"allowed\" setting to the new \"backends\" configuration.") } logger.Printf("Allowed backend hostnames: %s", hosts) - if sessionLimit > 0 { + if sessionLimit := compatBackend.Limit(); sessionLimit > 0 { logger.Printf("Allow a maximum of %d sessions", sessionLimit) } - updateBackendStats(compatBackend) - backendsById[compatBackend.id] = compatBackend + compatBackend.UpdateStats() + backendsById[compatBackend.Id()] = compatBackend numBackends++ } } @@ -189,15 +146,14 @@ func (s *backendStorageStatic) RemoveBackendsForHost(host string, seen map[strin } seen[backend.Id()] = seenDeleted - urls := slices.DeleteFunc(backend.urls, func(s string) bool { + urls := slices.DeleteFunc(backend.Urls(), func(s string) bool { return !strings.Contains(s, "://"+host) }) - s.logger.Printf("Backend %s removed for %s", backend.id, strings.Join(urls, ", ")) - if len(urls) == len(backend.urls) && backend.counted { - deleteBackendStats(backend) + s.logger.Printf("Backend %s removed for %s", backend.Id(), strings.Join(urls, ", ")) + if len(urls) == len(backend.Urls()) && backend.Uncount() { + backend.DeleteStats() delete(s.backendsById, backend.Id()) deleted++ - backend.counted = false } } s.stats.RemoveBackends(deleted) @@ -215,7 +171,7 @@ const ( ) // +checklocks:s.mu -func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen map[string]seenState) { +func (s *backendStorageStatic) UpsertHost(host string, backends []*talk.Backend, seen map[string]seenState) { for existingIndex, existingBackend := range s.backends[host] { found := false index := 0 @@ -224,16 +180,16 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen found = true backends = slices.Delete(backends, index, index+1) break - } else if newBackend.id == existingBackend.id { + } else if newBackend.Id() == existingBackend.Id() { found = true s.backends[host][existingIndex] = newBackend backends = slices.Delete(backends, index, index+1) - if seen[newBackend.id] != seenUpdated { - seen[newBackend.id] = seenUpdated - s.logger.Printf("Backend %s updated for %s", newBackend.id, strings.Join(newBackend.urls, ", ")) - updateBackendStats(newBackend) - newBackend.counted = existingBackend.counted - s.backendsById[newBackend.id] = newBackend + if seen[newBackend.Id()] != seenUpdated { + seen[newBackend.Id()] = seenUpdated + s.logger.Printf("Backend %s updated for %s", newBackend.Id(), strings.Join(newBackend.Urls(), ", ")) + newBackend.UpdateStats() + newBackend.CopyCount(existingBackend) + s.backendsById[newBackend.Id()] = newBackend } break } @@ -242,17 +198,16 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen if !found { removed := s.backends[host][existingIndex] s.backends[host] = slices.Delete(s.backends[host], existingIndex, existingIndex+1) - if seen[removed.id] != seenDeleted { - seen[removed.id] = seenDeleted - urls := slices.DeleteFunc(removed.urls, func(s string) bool { + if seen[removed.Id()] != seenDeleted { + seen[removed.Id()] = seenDeleted + urls := slices.DeleteFunc(removed.Urls(), func(s string) bool { return !strings.Contains(s, "://"+host) }) - s.logger.Printf("Backend %s removed for %s", removed.id, strings.Join(urls, ", ")) - if len(urls) == len(removed.urls) && removed.counted { - deleteBackendStats(removed) + s.logger.Printf("Backend %s removed for %s", removed.Id(), strings.Join(urls, ", ")) + if len(urls) == len(removed.Urls()) && removed.Uncount() { + removed.DeleteStats() delete(s.backendsById, removed.Id()) s.stats.DecBackends() - removed.counted = false } } } @@ -262,22 +217,21 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen addedBackends := 0 for _, added := range backends { - if seen[added.id] == seenAdded { + if seen[added.Id()] == seenAdded { continue } - seen[added.id] = seenAdded - if prev, found := s.backendsById[added.id]; found { - added.counted = prev.counted + seen[added.Id()] = seenAdded + if prev, found := s.backendsById[added.Id()]; found { + added.CopyCount(prev) } else { - s.backendsById[added.id] = added + s.backendsById[added.Id()] = added } - s.logger.Printf("Backend %s added for %s", added.id, strings.Join(added.urls, ", ")) - if !added.counted { - updateBackendStats(added) + s.logger.Printf("Backend %s added for %s", added.Id(), strings.Join(added.Urls(), ", ")) + if added.Count() { + added.UpdateStats() addedBackends++ - added.counted = true } } s.stats.AddBackends(addedBackends) @@ -298,37 +252,10 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } -func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { - hosts = make(map[string][]*Backend) +func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.ConfigFile, commonSecret string) (hosts map[string][]*talk.Backend) { + hosts = make(map[string][]*talk.Backend) seenUrls := make(map[string]string) for _, id := range getConfiguredBackendIDs(backendIds) { - secret, _ := config.GetStringOptionWithEnv(cfg, id, "secret") - if secret == "" && commonSecret != "" { - logger.Printf("Backend %s has no own shared secret set, using common shared secret", id) - secret = commonSecret - } - if secret == "" { - logger.Printf("Backend %s is missing or incomplete, skipping", id) - continue - } - - sessionLimit, err := cfg.GetInt(id, "sessionlimit") - if err != nil || sessionLimit < 0 { - sessionLimit = 0 - } - if sessionLimit > 0 { - logger.Printf("Backend %s allows a maximum of %d sessions", id, sessionLimit) - } - - maxStreamBitrate, err := cfg.GetInt(id, "maxstreambitrate") - if err != nil || maxStreamBitrate < 0 { - maxStreamBitrate = 0 - } - maxScreenBitrate, err := cfg.GetInt(id, "maxscreenbitrate") - if err != nil || maxScreenBitrate < 0 { - maxScreenBitrate = 0 - } - var urls []string if u, _ := config.GetStringOptionWithEnv(cfg, id, "urls"); u != "" { urls = slices.Sorted(internal.SplitEntries(u, ",")) @@ -344,14 +271,14 @@ func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.Config continue } - backend := &Backend{ - id: id, - secret: []byte(secret), + backend, err := talk.NewBackendFromConfig(logger, id, cfg, commonSecret) + if err != nil { + logger.Printf("%s", err) + continue + } - maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), - maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), - - sessionLimit: uint64(sessionLimit), + if sessionLimit := backend.Limit(); sessionLimit > 0 { + logger.Printf("Backend %s allows a maximum of %d sessions", id, sessionLimit) } added := make(map[string]bool) @@ -377,10 +304,7 @@ func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.Config } seenUrls[u] = id - backend.urls = append(backend.urls, u) - if parsed.Scheme == "http" { - backend.allowHttp = true - } + backend.AddUrl(parsed) if !added[parsed.Host] { hosts[parsed.Host] = append(hosts[parsed.Host], backend) @@ -427,14 +351,14 @@ func (s *backendStorageStatic) Reload(cfg *goconf.ConfigFile) { } } -func (s *backendStorageStatic) GetCompatBackend() *Backend { +func (s *backendStorageStatic) GetCompatBackend() *talk.Backend { s.mu.RLock() defer s.mu.RUnlock() return s.compatBackend } -func (s *backendStorageStatic) GetBackend(u *url.URL) *Backend { +func (s *backendStorageStatic) GetBackend(u *url.URL) *talk.Backend { s.mu.RLock() defer s.mu.RUnlock() diff --git a/clientsession.go b/clientsession.go index 059f173..7612ac4 100644 --- a/clientsession.go +++ b/clientsession.go @@ -39,6 +39,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -76,7 +77,7 @@ type ClientSession struct { // +checklocks:mu permissions map[Permission]bool - backend *Backend + backend *talk.Backend backendUrl string parsedBackendUrl *url.URL @@ -119,7 +120,7 @@ type ClientSession struct { responseHandlers map[string]ResponseHandlerFunc } -func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, backend *Backend, hello *api.HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { +func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, backend *talk.Backend, hello *api.HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { ctx := log.NewLoggerContext(context.Background(), hub.logger) ctx, closeFunc := context.WithCancel(ctx) s := &ClientSession{ @@ -284,7 +285,7 @@ func (s *ClientSession) SetPermissions(permissions []Permission) { s.logger.Printf("Permissions of session %s changed: %s", s.PublicId(), permissions) } -func (s *ClientSession) Backend() *Backend { +func (s *ClientSession) Backend() *talk.Backend { return s.backend } @@ -947,9 +948,9 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea if backend := s.Backend(); backend != nil { var maxBitrate api.Bandwidth if streamType == StreamTypeScreen { - maxBitrate = backend.maxScreenBitrate + maxBitrate = backend.MaxScreenBitrate() } else { - maxBitrate = backend.maxStreamBitrate + maxBitrate = backend.MaxStreamBitrate() } if settings.Bitrate <= 0 { settings.Bitrate = maxBitrate diff --git a/clientsession_test.go b/clientsession_test.go index c37007a..a16f0d6 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -105,8 +105,8 @@ func TestBandwidth_Backend(t *testing.T) { backend := hub.backend.GetBackend(u) require.NotNil(backend, "Could not get backend") - backend.maxScreenBitrate = 1000 - backend.maxStreamBitrate = 2000 + backend.SetMaxScreenBitrate(1000) + backend.SetMaxStreamBitrate(2000) ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -157,9 +157,9 @@ func TestBandwidth_Backend(t *testing.T) { var expectBitrate api.Bandwidth if streamType == StreamTypeVideo { - expectBitrate = backend.maxStreamBitrate + expectBitrate = backend.MaxStreamBitrate() } else { - expectBitrate = backend.maxScreenBitrate + expectBitrate = backend.MaxScreenBitrate() } assert.Equal(expectBitrate, pub.settings.Bitrate) }) diff --git a/etcd/api.go b/etcd/api.go new file mode 100644 index 0000000..60b5ad1 --- /dev/null +++ b/etcd/api.go @@ -0,0 +1,99 @@ +/** + * 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 etcd + +import ( + "errors" + "fmt" + "net/url" + "slices" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" +) + +// Information on a backend in the etcd cluster. + +type BackendInformationEtcd struct { + // Compat setting. + Url string `json:"url,omitempty"` + + Urls []string `json:"urls,omitempty"` + ParsedUrls []*url.URL `json:"-"` + Secret string `json:"secret"` + + MaxStreamBitrate api.Bandwidth `json:"maxstreambitrate,omitempty"` + MaxScreenBitrate api.Bandwidth `json:"maxscreenbitrate,omitempty"` + + SessionLimit uint64 `json:"sessionlimit,omitempty"` +} + +func (p *BackendInformationEtcd) CheckValid() (err error) { + if p.Secret == "" { + return errors.New("secret missing") + } + + if len(p.Urls) > 0 { + slices.Sort(p.Urls) + p.Urls = slices.Compact(p.Urls) + seen := make(map[string]bool) + outIdx := 0 + for _, u := range p.Urls { + parsedUrl, err := url.Parse(u) + if err != nil { + return fmt.Errorf("invalid url %s: %w", u, err) + } + + var changed bool + if parsedUrl, changed = internal.CanonicalizeUrl(parsedUrl); changed { + u = parsedUrl.String() + } + p.Urls[outIdx] = u + if seen[u] { + continue + } + seen[u] = true + p.ParsedUrls = append(p.ParsedUrls, parsedUrl) + outIdx++ + } + if len(p.Urls) != outIdx { + clear(p.Urls[outIdx:]) + p.Urls = p.Urls[:outIdx] + } + } else if p.Url != "" { + parsedUrl, err := url.Parse(p.Url) + if err != nil { + return fmt.Errorf("invalid url: %w", err) + } + var changed bool + if parsedUrl, changed = internal.CanonicalizeUrl(parsedUrl); changed { + p.Url = parsedUrl.String() + } + + p.Urls = append(p.Urls, p.Url) + p.ParsedUrls = append(p.ParsedUrls, parsedUrl) + } else { + return errors.New("urls missing") + } + + return nil +} diff --git a/etcd/api_test.go b/etcd/api_test.go new file mode 100644 index 0000000..887a4ee --- /dev/null +++ b/etcd/api_test.go @@ -0,0 +1,135 @@ +/** + * 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 etcd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateBackendInformationEtcd(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + testcases := []struct { + b BackendInformationEtcd + expectedError string + expectedUrls []string + }{ + { + b: BackendInformationEtcd{}, + expectedError: "secret missing", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + }, + expectedError: "urls missing", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Url: "https://foo\n", + }, + expectedError: "invalid url", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo\n"}, + }, + expectedError: "invalid url", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo", "https://foo\n"}, + }, + expectedError: "invalid url", + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Url: "https://foo:443", + }, + expectedUrls: []string{"https://foo"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo:443"}, + }, + expectedUrls: []string{"https://foo"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Url: "https://foo:8443", + }, + expectedUrls: []string{"https://foo:8443"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo:8443"}, + }, + expectedUrls: []string{"https://foo:8443"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo", "https://bar", "https://foo"}, + }, + expectedUrls: []string{"https://bar", "https://foo"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo", "https://bar", "https://foo:443", "https://zaz"}, + }, + expectedUrls: []string{"https://bar", "https://foo", "https://zaz"}, + }, + { + b: BackendInformationEtcd{ + Secret: "verysecret", + Urls: []string{"https://foo:443", "https://bar", "https://foo", "https://zaz"}, + }, + expectedUrls: []string{"https://bar", "https://foo", "https://zaz"}, + }, + } + + for idx, tc := range testcases { + if tc.expectedError == "" { + if assert.NoError(tc.b.CheckValid(), "failed for testcase %d", idx) { + assert.Equal(tc.expectedUrls, tc.b.Urls, "failed for testcase %d", idx) + var urls []string + for _, u := range tc.b.ParsedUrls { + urls = append(urls, u.String()) + } + assert.Equal(tc.expectedUrls, urls, "failed for testcase %d", idx) + } + } else { + assert.ErrorContains(tc.b.CheckValid(), tc.expectedError, "failed for testcase %d, got %+v", idx, tc.b.ParsedUrls) + } + } +} diff --git a/grpc_server.go b/grpc_server.go index e66fcd9..b3a887f 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -41,6 +41,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -65,9 +66,9 @@ type GrpcServerHub interface { GetSessionByResumeId(resumeId api.PrivateSessionId) Session GetSessionByPublicId(sessionId api.PublicSessionId) Session GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) - GetRoomForBackend(roomId string, backend *Backend) *Room + GetRoomForBackend(roomId string, backend *talk.Backend) *Room - GetBackend(u *url.URL) *Backend + GetBackend(u *url.URL) *talk.Backend CreateProxyToken(publisherId string) (string, error) } diff --git a/hub.go b/hub.go index 5c97b7d..5e11afb 100644 --- a/hub.go +++ b/hub.go @@ -742,7 +742,7 @@ func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api. return h.roomSessions.GetSessionId(roomSessionId) } -func (h *Hub) GetDialoutSessions(roomId string, backend *Backend) (result []*ClientSession) { +func (h *Hub) GetDialoutSessions(roomId string, backend *talk.Backend) (result []*ClientSession) { h.mu.RLock() defer h.mu.RUnlock() for session := range h.dialoutSessions { @@ -758,7 +758,7 @@ func (h *Hub) GetDialoutSessions(roomId string, backend *Backend) (result []*Cli return } -func (h *Hub) GetBackend(u *url.URL) *Backend { +func (h *Hub) GetBackend(u *url.URL) *talk.Backend { if u == nil { return h.backend.GetCompatBackend() } @@ -942,7 +942,7 @@ func (h *Hub) unregisterRemoteSession(session *RemoteSession) { delete(h.remoteSessions, session) } -func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData { +func (h *Hub) newSessionIdData(backend *talk.Backend) *SessionIdData { sid := h.sid.Add(1) for sid == 0 { sid = h.sid.Add(1) @@ -955,7 +955,7 @@ func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData { return sessionIdData } -func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backend *Backend, auth *BackendClientResponse) { +func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backend *talk.Backend, auth *BackendClientResponse) { if !c.IsConnected() { // Client disconnected while waiting for "hello" response. return @@ -1036,9 +1036,9 @@ func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backe wg.Wait() if totalCount.Load() > limit { backend.RemoveSession(session) - h.logger.Printf("Error adding session %s to backend %s: %s", session.PublicId(), backend.Id(), SessionLimitExceeded) + h.logger.Printf("Error adding session %s to backend %s: %s", session.PublicId(), backend.Id(), talk.SessionLimitExceeded) session.Close() - client.SendMessage(message.NewWrappedErrorServerMessage(SessionLimitExceeded)) + client.SendMessage(message.NewWrappedErrorServerMessage(talk.SessionLimitExceeded)) return } } @@ -1365,7 +1365,7 @@ func (h *Hub) processHello(client HandlerClient, message *api.ClientMessage) { } } -func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*Backend, *BackendClientResponse, error) { +func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *BackendClientResponse, error) { url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { @@ -1389,7 +1389,7 @@ func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message return backend, &auth, nil } -func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*Backend, *BackendClientResponse, error) { +func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *BackendClientResponse, error) { url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { @@ -1546,7 +1546,7 @@ func (h *Hub) processHelloClient(client HandlerClient, message *api.ClientMessag // Make sure the client must send another "hello" in case of errors. defer h.startExpectHello(client) - var authFunc func(context.Context, HandlerClient, *api.ClientMessage) (*Backend, *BackendClientResponse, error) + var authFunc func(context.Context, HandlerClient, *api.ClientMessage) (*talk.Backend, *BackendClientResponse, error) switch message.Hello.Version { case api.HelloVersionV1: // Auth information contains a ticket that must be validated against the @@ -1616,7 +1616,7 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *api.ClientMess h.processRegister(client, message, backend, auth) } -func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId, backend *Backend) { +func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId, backend *talk.Backend) { sessionId, err := h.roomSessions.LookupSessionId(ctx, roomSessionId, "room_session_reconnected") if err == ErrNoSuchRoomSession { return @@ -1680,8 +1680,8 @@ func (h *Hub) sendRoom(session *ClientSession, message *api.ClientMessage, room var backendStreamBitrate api.Bandwidth var backendScreenBitrate api.Bandwidth if backend := room.Backend(); backend != nil { - backendStreamBitrate = backend.maxStreamBitrate - backendScreenBitrate = backend.maxScreenBitrate + backendStreamBitrate = backend.MaxStreamBitrate() + backendScreenBitrate = backend.MaxScreenBitrate() } var maxStreamBitrate api.Bandwidth @@ -1967,7 +1967,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { return count, &wg } -func (h *Hub) GetRoomForBackend(id string, backend *Backend) *Room { +func (h *Hub) GetRoomForBackend(id string, backend *talk.Backend) *Room { internalRoomId := getRoomIdForBackend(id, backend) h.ru.RLock() @@ -1986,7 +1986,7 @@ func (h *Hub) removeRoom(room *Room) { h.roomPing.DeleteRoom(room.Id()) } -func (h *Hub) CreateRoom(id string, properties json.RawMessage, backend *Backend) (*Room, error) { +func (h *Hub) CreateRoom(id string, properties json.RawMessage, backend *talk.Backend) (*Room, error) { h.ru.Lock() defer h.ru.Unlock() @@ -1994,7 +1994,7 @@ func (h *Hub) CreateRoom(id string, properties json.RawMessage, backend *Backend } // +checklocks:h.ru -func (h *Hub) createRoomLocked(id string, properties json.RawMessage, backend *Backend) (*Room, error) { +func (h *Hub) createRoomLocked(id string, properties json.RawMessage, backend *talk.Backend) (*Room, error) { // Note the write lock must be held. room, err := NewRoom(id, properties, h, h.events, backend) if err != nil { diff --git a/hub_test.go b/hub_test.go index d68654b..79ba5b2 100644 --- a/hub_test.go +++ b/hub_test.go @@ -822,9 +822,7 @@ func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { for range numDecodeCaches { decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) } - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), @@ -851,9 +849,7 @@ func Benchmark_DecodePublicSessionIdCached(b *testing.B) { for range numDecodeCaches { decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) } - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 6e7df14..fd7bb00 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -52,6 +52,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -1787,11 +1788,11 @@ func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSe return "", nil } -func (h *mockGrpcServerHub) GetBackend(u *url.URL) *Backend { +func (h *mockGrpcServerHub) GetBackend(u *url.URL) *talk.Backend { return nil } -func (h *mockGrpcServerHub) GetRoomForBackend(roomId string, backend *Backend) *Room { +func (h *mockGrpcServerHub) GetRoomForBackend(roomId string, backend *talk.Backend) *Room { return nil } diff --git a/room.go b/room.go index b4e82a2..dbc9705 100644 --- a/room.go +++ b/room.go @@ -39,6 +39,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" ) const ( @@ -70,7 +71,7 @@ type Room struct { logger log.Logger hub *Hub events AsyncEvents - backend *Backend + backend *talk.Backend // +checklocks:mu properties json.RawMessage @@ -101,7 +102,7 @@ type Room struct { transientData *TransientData } -func getRoomIdForBackend(id string, backend *Backend) string { +func getRoomIdForBackend(id string, backend *talk.Backend) string { if id == "" { return "" } @@ -109,7 +110,7 @@ func getRoomIdForBackend(id string, backend *Backend) string { return backend.Id() + "|" + id } -func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEvents, backend *Backend) (*Room, error) { +func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEvents, backend *talk.Backend) (*Room, error) { room := &Room{ id: roomId, logger: hub.logger, @@ -158,7 +159,7 @@ func (r *Room) Properties() json.RawMessage { return r.properties } -func (r *Room) Backend() *Backend { +func (r *Room) Backend() *talk.Backend { return r.backend } diff --git a/roomsessions_test.go b/roomsessions_test.go index 87bdfc7..5d33169 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type DummySession struct { @@ -69,7 +70,7 @@ func (s *DummySession) ParsedUserData() (api.StringMap, error) { return nil, nil } -func (s *DummySession) Backend() *Backend { +func (s *DummySession) Backend() *talk.Backend { return nil } diff --git a/session.go b/session.go index 2a2345b..adc93d4 100644 --- a/session.go +++ b/session.go @@ -28,6 +28,7 @@ import ( "sync" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type Permission string @@ -60,7 +61,7 @@ type Session interface { UserData() json.RawMessage ParsedUserData() (api.StringMap, error) - Backend() *Backend + Backend() *talk.Backend BackendUrl() string ParsedBackendUrl() *url.URL diff --git a/sessionid_codec_test.go b/sessionid_codec_test.go index 3236202..7504c27 100644 --- a/sessionid_codec_test.go +++ b/sessionid_codec_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func TestReverseSessionId(t *testing.T) { @@ -47,9 +48,7 @@ func TestReverseSessionId(t *testing.T) { func Benchmark_EncodePrivateSessionId(b *testing.B) { require := require.New(b) - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), @@ -66,9 +65,7 @@ func Benchmark_EncodePrivateSessionId(b *testing.B) { func Benchmark_DecodePrivateSessionId(b *testing.B) { require := require.New(b) - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), @@ -89,9 +86,7 @@ func Benchmark_DecodePrivateSessionId(b *testing.B) { func Benchmark_EncodePublicSessionId(b *testing.B) { require := require.New(b) - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), @@ -108,9 +103,7 @@ func Benchmark_EncodePublicSessionId(b *testing.B) { func Benchmark_DecodePublicSessionId(b *testing.B) { require := require.New(b) - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) data := &SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), diff --git a/talk/backend.go b/talk/backend.go new file mode 100644 index 0000000..3a664f8 --- /dev/null +++ b/talk/backend.go @@ -0,0 +1,314 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2020 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 ( + "bytes" + "fmt" + "net/url" + "slices" + "strings" + "sync" + + "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +var ( + SessionLimitExceeded = api.NewError("session_limit_exceeded", "Too many sessions connected for this backend.") // +checklocksignore: Global readonly variable. +) + +func init() { + registerBackendStats() +} + +type Backend struct { + id string + urls []string + secret []byte + + allowHttp bool + + maxStreamBitrate api.Bandwidth + maxScreenBitrate api.Bandwidth + + sessionLimit uint64 + sessionsLock sync.Mutex + // +checklocks:sessionsLock + sessions map[api.PublicSessionId]bool + + counted bool +} + +func NewCompatBackend(cfg *goconf.ConfigFile) *Backend { + if cfg == nil { + return &Backend{ + id: "compat", + } + } + + allowHttp, _ := cfg.GetBool("backend", "allowhttp") + commonSecret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret") + sessionLimit, err := cfg.GetInt("backend", "sessionlimit") + if err != nil || sessionLimit < 0 { + sessionLimit = 0 + } + maxStreamBitrate, err := cfg.GetInt("backend", "maxstreambitrate") + if err != nil || maxStreamBitrate < 0 { + maxStreamBitrate = 0 + } + maxScreenBitrate, err := cfg.GetInt("backend", "maxscreenbitrate") + if err != nil || maxScreenBitrate < 0 { + maxScreenBitrate = 0 + } + + return &Backend{ + id: "compat", + secret: []byte(commonSecret), + + allowHttp: allowHttp, + + sessionLimit: uint64(sessionLimit), + counted: true, + + maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), + maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), + } +} + +func NewBackendFromConfig(logger log.Logger, id string, cfg *goconf.ConfigFile, commonSecret string) (*Backend, error) { + secret, _ := config.GetStringOptionWithEnv(cfg, id, "secret") + if secret == "" && commonSecret != "" { + logger.Printf("Backend %s has no own shared secret set, using common shared secret", id) + secret = commonSecret + } + if secret == "" { + return nil, fmt.Errorf("backend %s is missing or incomplete, skipping", id) + } + + sessionLimit, err := cfg.GetInt(id, "sessionlimit") + if err != nil || sessionLimit < 0 { + sessionLimit = 0 + } + maxStreamBitrate, err := cfg.GetInt(id, "maxstreambitrate") + if err != nil || maxStreamBitrate < 0 { + maxStreamBitrate = 0 + } + maxScreenBitrate, err := cfg.GetInt(id, "maxscreenbitrate") + if err != nil || maxScreenBitrate < 0 { + maxScreenBitrate = 0 + } + + return &Backend{ + id: id, + secret: []byte(secret), + + maxStreamBitrate: api.BandwidthFromBits(uint64(maxStreamBitrate)), + maxScreenBitrate: api.BandwidthFromBits(uint64(maxScreenBitrate)), + + sessionLimit: uint64(sessionLimit), + }, nil +} + +func NewBackendFromEtcd(key string, info *etcd.BackendInformationEtcd) *Backend { + allowHttp := slices.ContainsFunc(info.ParsedUrls, func(u *url.URL) bool { + return u.Scheme == "http" + }) + + return &Backend{ + id: key, + urls: info.Urls, + secret: []byte(info.Secret), + + allowHttp: allowHttp, + + maxStreamBitrate: info.MaxStreamBitrate, + maxScreenBitrate: info.MaxScreenBitrate, + sessionLimit: info.SessionLimit, + } +} + +func (b *Backend) Id() string { + return b.id +} + +func (b *Backend) Secret() []byte { + return b.secret +} + +func (b *Backend) IsCompat() bool { + return len(b.urls) == 0 +} + +func (b *Backend) Equal(other *Backend) bool { + if b == other { + return true + } else if b == nil || other == nil { + return false + } + + return b.id == other.id && + b.allowHttp == other.allowHttp && + b.maxStreamBitrate == other.maxStreamBitrate && + b.maxScreenBitrate == other.maxScreenBitrate && + b.sessionLimit == other.sessionLimit && + bytes.Equal(b.secret, other.secret) && + slices.Equal(b.urls, other.urls) +} + +func (b *Backend) IsUrlAllowed(u *url.URL) bool { + switch u.Scheme { + case "https": + return true + case "http": + return b.allowHttp + default: + return false + } +} + +func (b *Backend) HasUrl(url string) bool { + if b.IsCompat() { + // Old-style configuration, only hosts are configured. + return true + } + + for _, u := range b.urls { + if strings.HasPrefix(url, u) { + return true + } + } + + return false +} + +func (b *Backend) Urls() []string { + return b.urls +} + +func (b *Backend) AddUrl(u *url.URL) { + b.urls = append(b.urls, u.String()) + if u.Scheme == "http" { + b.allowHttp = true + } +} + +func (b *Backend) Limit() int { + return int(b.sessionLimit) +} + +func (b *Backend) SetMaxStreamBitrate(bitrate api.Bandwidth) { + b.maxStreamBitrate = bitrate +} + +func (b *Backend) MaxStreamBitrate() api.Bandwidth { + return b.maxStreamBitrate +} + +func (b *Backend) SetMaxScreenBitrate(bitrate api.Bandwidth) { + b.maxScreenBitrate = bitrate +} + +func (b *Backend) MaxScreenBitrate() api.Bandwidth { + return b.maxScreenBitrate +} + +func (b *Backend) CopyCount(other *Backend) { + b.counted = other.counted +} + +func (b *Backend) Count() bool { + if b.counted { + return false + } + + b.counted = true + return true +} + +func (b *Backend) Uncount() bool { + if !b.counted { + return false + } + + b.counted = false + return true +} + +func (b *Backend) Len() int { + b.sessionsLock.Lock() + defer b.sessionsLock.Unlock() + return len(b.sessions) +} + +type BackendSession interface { + PublicId() api.PublicSessionId + ClientType() api.ClientType +} + +func (b *Backend) AddSession(session BackendSession) error { + if session.ClientType() == api.HelloClientTypeInternal || session.ClientType() == api.HelloClientTypeVirtual { + // Internal and virtual sessions are not counting to the limit. + return nil + } + + if b.sessionLimit == 0 { + // Not limited + return nil + } + + b.sessionsLock.Lock() + defer b.sessionsLock.Unlock() + if b.sessions == nil { + b.sessions = make(map[api.PublicSessionId]bool) + } else if uint64(len(b.sessions)) >= b.sessionLimit { + registerBackendStats() + statsBackendLimitExceededTotal.WithLabelValues(b.id).Inc() + return SessionLimitExceeded + } + + b.sessions[session.PublicId()] = true + return nil +} + +func (b *Backend) RemoveSession(session BackendSession) { + b.sessionsLock.Lock() + defer b.sessionsLock.Unlock() + + delete(b.sessions, session.PublicId()) +} + +func (b *Backend) UpdateStats() { + if b.sessionLimit > 0 { + statsBackendLimit.WithLabelValues(b.id).Set(float64(b.sessionLimit)) + } else { + statsBackendLimit.DeleteLabelValues(b.id) + } +} + +func (b *Backend) DeleteStats() { + statsBackendLimit.DeleteLabelValues(b.id) +} diff --git a/talk/backend_stats_prometheus.go b/talk/backend_stats_prometheus.go new file mode 100644 index 0000000..1deeb3b --- /dev/null +++ b/talk/backend_stats_prometheus.go @@ -0,0 +1,52 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 ( + "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" +) + +var ( + statsBackendLimit = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "backend", + Name: "session_limit", + Help: "The session limit of a backend", + }, []string{"backend"}) + statsBackendLimitExceededTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ // +checklocksignore: Global readonly variable. + Namespace: "signaling", + Subsystem: "backend", + Name: "session_limit_exceeded_total", + Help: "The number of times the session limit exceeded", + }, []string{"backend"}) + + backendStats = []prometheus.Collector{ + statsBackendLimit, + statsBackendLimitExceededTotal, + } +) + +func registerBackendStats() { + metrics.RegisterAll(backendStats...) +} diff --git a/virtualsession.go b/virtualsession.go index fd130ea..2c5e11b 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -31,6 +31,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -138,7 +139,7 @@ func (s *VirtualSession) Data() *SessionIdData { return s.data } -func (s *VirtualSession) Backend() *Backend { +func (s *VirtualSession) Backend() *talk.Backend { return s.session.Backend() } diff --git a/virtualsession_test.go b/virtualsession_test.go index 031d9af..b69a8cf 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func TestVirtualSession(t *testing.T) { @@ -41,9 +42,7 @@ func TestVirtualSession(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() @@ -224,9 +223,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() @@ -433,9 +430,7 @@ func TestVirtualSessionCustomInCall(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() @@ -574,9 +569,7 @@ func TestVirtualSessionCleanup(t *testing.T) { roomId := "the-room-id" emptyProperties := json.RawMessage("{}") - backend := &Backend{ - id: "compat", - } + backend := talk.NewCompatBackend(nil) room, err := hub.CreateRoom(roomId, emptyProperties, backend) require.NoError(err) defer room.Close() From 407577ee8d203d0d5a430942aa9b77d2c93d4147 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 20:20:53 +0100 Subject: [PATCH 417/549] Update generated files. --- api_backend_easyjson.go | 305 ++++++++++------------------------------ 1 file changed, 72 insertions(+), 233 deletions(-) diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index d76ece6..961402e 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -3723,168 +3723,7 @@ func (v *BackendPingEntry) UnmarshalJSON(data []byte) error { func (v *BackendPingEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendInformationEtcd) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "url": - if in.IsNull() { - in.Skip() - } else { - out.Url = string(in.String()) - } - case "urls": - if in.IsNull() { - in.Skip() - out.Urls = nil - } else { - in.Delim('[') - if out.Urls == nil { - if !in.IsDelim(']') { - out.Urls = make([]string, 0, 4) - } else { - out.Urls = []string{} - } - } else { - out.Urls = (out.Urls)[:0] - } - for !in.IsDelim(']') { - var v74 string - if in.IsNull() { - in.Skip() - } else { - v74 = string(in.String()) - } - out.Urls = append(out.Urls, v74) - in.WantComma() - } - in.Delim(']') - } - case "secret": - if in.IsNull() { - in.Skip() - } else { - out.Secret = string(in.String()) - } - case "maxstreambitrate": - if in.IsNull() { - in.Skip() - } else { - out.MaxStreamBitrate = api.Bandwidth(in.Uint64()) - } - case "maxscreenbitrate": - if in.IsNull() { - in.Skip() - } else { - out.MaxScreenBitrate = api.Bandwidth(in.Uint64()) - } - case "sessionlimit": - if in.IsNull() { - in.Skip() - } else { - out.SessionLimit = uint64(in.Uint64()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendInformationEtcd) { - out.RawByte('{') - first := true - _ = first - if in.Url != "" { - const prefix string = ",\"url\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Url)) - } - if len(in.Urls) != 0 { - const prefix string = ",\"urls\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v75, v76 := range in.Urls { - if v75 > 0 { - out.RawByte(',') - } - out.String(string(v76)) - } - out.RawByte(']') - } - } - { - const prefix string = ",\"secret\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Secret)) - } - if in.MaxStreamBitrate != 0 { - const prefix string = ",\"maxstreambitrate\":" - out.RawString(prefix) - out.Uint64(uint64(in.MaxStreamBitrate)) - } - if in.MaxScreenBitrate != 0 { - const prefix string = ",\"maxscreenbitrate\":" - out.RawString(prefix) - out.Uint64(uint64(in.MaxScreenBitrate)) - } - if in.SessionLimit != 0 { - const prefix string = ",\"sessionlimit\":" - out.RawString(prefix) - out.Uint64(uint64(in.SessionLimit)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v BackendInformationEtcd) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v BackendInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *BackendInformationEtcd) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *BackendInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) -} -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientSessionResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendClientSessionResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3920,7 +3759,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientSessionResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendClientSessionResponse) { out.RawByte('{') first := true _ = first @@ -3940,27 +3779,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientSessionRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientSessionRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4022,7 +3861,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientSessionRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientSessionRequest) { out.RawByte('{') first := true _ = first @@ -4062,27 +3901,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientRoomResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4147,13 +3986,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle *out.Permissions = (*out.Permissions)[:0] } for !in.IsDelim(']') { - var v77 Permission + var v74 Permission if in.IsNull() { in.Skip() } else { - v77 = Permission(in.String()) + v74 = Permission(in.String()) } - *out.Permissions = append(*out.Permissions, v77) + *out.Permissions = append(*out.Permissions, v74) in.WantComma() } in.Delim(']') @@ -4169,7 +4008,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientRoomResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientRoomResponse) { out.RawByte('{') first := true _ = first @@ -4200,11 +4039,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw out.RawString("null") } else { out.RawByte('[') - for v78, v79 := range *in.Permissions { - if v78 > 0 { + for v75, v76 := range *in.Permissions { + if v75 > 0 { out.RawByte(',') } - out.String(string(v79)) + out.String(string(v76)) } out.RawByte(']') } @@ -4215,27 +4054,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientRoomRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4307,7 +4146,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientRoomRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientRoomRequest) { out.RawByte('{') first := true _ = first @@ -4357,27 +4196,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientRingResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientRingResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4413,7 +4252,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientRingResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientRingResponse) { out.RawByte('{') first := true _ = first @@ -4433,27 +4272,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRingResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRingResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4553,7 +4392,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientResponse) { out.RawByte('{') first := true _ = first @@ -4593,27 +4432,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4699,7 +4538,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientRequest) { out.RawByte('{') first := true _ = first @@ -4734,27 +4573,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientPingRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientPingRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4796,13 +4635,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle out.Entries = (out.Entries)[:0] } for !in.IsDelim(']') { - var v80 BackendPingEntry + var v77 BackendPingEntry if in.IsNull() { in.Skip() } else { - (v80).UnmarshalEasyJSON(in) + (v77).UnmarshalEasyJSON(in) } - out.Entries = append(out.Entries, v80) + out.Entries = append(out.Entries, v77) in.WantComma() } in.Delim(']') @@ -4817,7 +4656,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientPingRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientPingRequest) { out.RawByte('{') first := true _ = first @@ -4838,11 +4677,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw out.RawString("null") } else { out.RawByte('[') - for v81, v82 := range in.Entries { - if v81 > 0 { + for v78, v79 := range in.Entries { + if v78 > 0 { out.RawByte(',') } - (v82).MarshalEasyJSON(out) + (v79).MarshalEasyJSON(out) } out.RawByte(']') } @@ -4853,27 +4692,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientPingRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientPingRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *BackendClientAuthResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientAuthResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4917,7 +4756,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in BackendClientAuthResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientAuthResponse) { out.RawByte('{') first := true _ = first @@ -4942,27 +4781,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *BackendClientAuthRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *BackendClientAuthRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5000,7 +4839,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in BackendClientAuthRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in BackendClientAuthRequest) { out.RawByte('{') first := true _ = first @@ -5020,23 +4859,23 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) } From 3e18e6a4fa1354ae848676657b0d162cac5c335f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 11 Dec 2025 20:34:15 +0100 Subject: [PATCH 418/549] Move function to check for address already in use to test package. --- etcd_client_test.go | 25 ++--------------- grpc_server_test.go | 3 ++- proxy/proxy_tokens_etcd_test.go | 25 ++--------------- test/network.go | 48 +++++++++++++++++++++++++++++++++ test/network_test.go | 43 +++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 47 deletions(-) create mode 100644 test/network.go create mode 100644 test/network_test.go diff --git a/etcd_client_test.go b/etcd_client_test.go index dcda596..da8b383 100644 --- a/etcd_client_test.go +++ b/etcd_client_test.go @@ -25,14 +25,11 @@ import ( "context" "crypto/rand" "crypto/rsa" - "errors" "net" "net/url" "os" "path" - "runtime" "strconv" - "syscall" "testing" "time" @@ -48,31 +45,13 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) var ( etcdListenUrl = "http://localhost:8080" ) -func isErrorAddressAlreadyInUse(err error) bool { - var eOsSyscall *os.SyscallError - if !errors.As(err, &eOsSyscall) { - return false - } - var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr) - if !errors.As(eOsSyscall, &errErrno) { - return false - } - if errErrno == syscall.EADDRINUSE { - return true - } - const WSAEADDRINUSE = 10048 - if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE { - return true - } - return false -} - func NewEtcdForTestWithTls(t *testing.T, withTLS bool) (*embed.Etcd, string, string) { t.Helper() @@ -124,7 +103,7 @@ func NewEtcdForTestWithTls(t *testing.T, withTLS bool) (*embed.Etcd, string, str cfg.AdvertisePeerUrls = []url.URL{*peerListener} cfg.InitialCluster = "signalingtest=" + peerListener.String() etcd, err = embed.StartEtcd(cfg) - if isErrorAddressAlreadyInUse(err) { + if test.IsErrorAddressAlreadyInUse(err) { continue } diff --git a/grpc_server_test.go b/grpc_server_test.go index a7051d6..4b46258 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -43,6 +43,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) func (s *GrpcServer) WaitForCertificateReload(ctx context.Context, counter uint64) error { @@ -71,7 +72,7 @@ func NewGrpcServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (se config.AddOption("grpc", "listen", addr) var err error server, err = NewGrpcServer(ctx, config, "0.0.0") - if isErrorAddressAlreadyInUse(err) { + if test.IsErrorAddressAlreadyInUse(err) { continue } diff --git a/proxy/proxy_tokens_etcd_test.go b/proxy/proxy_tokens_etcd_test.go index 03da903..308fd86 100644 --- a/proxy/proxy_tokens_etcd_test.go +++ b/proxy/proxy_tokens_etcd_test.go @@ -27,13 +27,10 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" - "errors" "net" "net/url" "os" - "runtime" "strconv" - "syscall" "testing" "github.com/dlintw/goconf" @@ -45,31 +42,13 @@ import ( "go.uber.org/zap/zaptest" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) var ( etcdListenUrl = "http://localhost:8080" ) -func isErrorAddressAlreadyInUse(err error) bool { - var eOsSyscall *os.SyscallError - if !errors.As(err, &eOsSyscall) { - return false - } - var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr) - if !errors.As(eOsSyscall, &errErrno) { - return false - } - if errErrno == syscall.EADDRINUSE { - return true - } - const WSAEADDRINUSE = 10048 - if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE { - return true - } - return false -} - func newEtcdForTesting(t *testing.T) *embed.Etcd { cfg := embed.NewConfig() cfg.Dir = t.TempDir() @@ -92,7 +71,7 @@ func newEtcdForTesting(t *testing.T) *embed.Etcd { peerListener.Host = net.JoinHostPort("localhost", strconv.Itoa(port+2)) cfg.ListenPeerUrls = []url.URL{*peerListener} etcd, err = embed.StartEtcd(cfg) - if isErrorAddressAlreadyInUse(err) { + if test.IsErrorAddressAlreadyInUse(err) { continue } diff --git a/test/network.go b/test/network.go new file mode 100644 index 0000000..ec7d582 --- /dev/null +++ b/test/network.go @@ -0,0 +1,48 @@ +/** + * 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 test + +import ( + "errors" + "os" + "runtime" + "syscall" +) + +func IsErrorAddressAlreadyInUse(err error) bool { + var eOsSyscall *os.SyscallError + if !errors.As(err, &eOsSyscall) { + return false + } + var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr) + if !errors.As(eOsSyscall, &errErrno) { + return false + } + if errErrno == syscall.EADDRINUSE { + return true + } + const WSAEADDRINUSE = 10048 + if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE { + return true + } + return false +} diff --git a/test/network_test.go b/test/network_test.go new file mode 100644 index 0000000..69a24fe --- /dev/null +++ b/test/network_test.go @@ -0,0 +1,43 @@ +/** + * 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 test + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsErrorAddressAlreadyInUse(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + listener1, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(err) + + listener2, err := net.Listen("tcp", listener1.Addr().String()) + assert.Nil(listener2) + assert.True(IsErrorAddressAlreadyInUse(err), "expected address already in use, got %+v", err) +} From 675652044735a85e80b751463148246931ad0aba Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 15 Dec 2025 10:04:32 +0100 Subject: [PATCH 419/549] Move etcd client code to "etcd" package. --- Makefile | 2 +- api_backend.go | 14 +- backend_client.go | 3 +- backend_configuration.go | 5 +- backend_configuration_test.go | 27 +- backend_storage_etcd.go | 14 +- backend_storage_etcd_test.go | 12 +- etcd/api.go | 32 ++ etcd_client.go => etcd/client.go | 51 +-- etcd_client_test.go => etcd/client_test.go | 58 +-- etcd/etcdtest/etcdtest.go | 466 +++++++++++++++++++++ etcd/etcdtest/etcdtest_test.go | 319 ++++++++++++++ grpc_client.go | 15 +- grpc_client_test.go | 41 +- hub.go | 5 +- mcu_proxy.go | 3 +- mcu_proxy_test.go | 75 ++-- proxy/proxy_tokens_etcd.go | 6 +- proxy_config_etcd.go | 15 +- proxy_config_etcd_test.go | 26 +- server/main.go | 3 +- 21 files changed, 1004 insertions(+), 188 deletions(-) rename etcd_client.go => etcd/client.go (83%) rename etcd_client_test.go => etcd/client_test.go (87%) create mode 100644 etcd/etcdtest/etcdtest.go create mode 100644 etcd/etcdtest/etcdtest_test.go diff --git a/Makefile b/Makefile index 28f78fe..b726693 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 api/signaling.go talk/ocs.go)) +EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go api/signaling.go */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 1a1a236..322d075 100644 --- a/api_backend.go +++ b/api_backend.go @@ -34,6 +34,7 @@ import ( "time" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" ) const ( @@ -503,13 +504,6 @@ type BackendServerInfoGrpc struct { Version string `json:"version,omitempty"` } -type BackendServerInfoEtcd struct { - Endpoints []string `json:"endpoints"` - - Active string `json:"active,omitempty"` - Connected *bool `json:"connected,omitempty"` -} - type BackendServerInfo struct { Version string `json:"version"` Features []string `json:"features"` @@ -517,7 +511,7 @@ type BackendServerInfo struct { Sfu *BackendServerInfoSfu `json:"sfu,omitempty"` Dialout []BackendServerInfoDialout `json:"dialout,omitempty"` - Nats *BackendServerInfoNats `json:"nats,omitempty"` - Grpc []BackendServerInfoGrpc `json:"grpc,omitempty"` - Etcd *BackendServerInfoEtcd `json:"etcd,omitempty"` + Nats *BackendServerInfoNats `json:"nats,omitempty"` + Grpc []BackendServerInfoGrpc `json:"grpc,omitempty"` + Etcd *etcd.BackendServerInfoEtcd `json:"etcd,omitempty"` } diff --git a/backend_client.go b/backend_client.go index 2d22e91..54a7def 100644 --- a/backend_client.go +++ b/backend_client.go @@ -33,6 +33,7 @@ import ( "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -59,7 +60,7 @@ type BackendClient struct { buffers pool.BufferPool } -func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) { +func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient etcd.Client) (*BackendClient, error) { logger := log.LoggerFromContext(ctx) backends, err := NewBackendConfiguration(logger, config, etcdClient) if err != nil { diff --git a/backend_configuration.go b/backend_configuration.go index 99569dc..6329930 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -30,6 +30,7 @@ import ( "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -135,11 +136,11 @@ var ( defaultBackendStats = &prometheusBackendStats{} ) -func NewBackendConfiguration(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient) (*BackendConfiguration, error) { +func NewBackendConfiguration(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client) (*BackendConfiguration, error) { return NewBackendConfigurationWithStats(logger, config, etcdClient, nil) } -func NewBackendConfigurationWithStats(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (*BackendConfiguration, error) { +func NewBackendConfigurationWithStats(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, stats BackendStorageStats) (*BackendConfiguration, error) { backendType, _ := config.GetString("backend", "backendtype") if backendType == "" { backendType = DefaultBackendType diff --git a/backend_configuration_test.go b/backend_configuration_test.go index e460035..a452df4 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -510,13 +511,13 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) - etcd, client := NewEtcdClientForTest(t) + embedEtcd, client := etcdtest.NewClientForTest(t) url1 := "https://domain1.invalid/foo" initialSecret1 := string(testBackendSecret) + "-backend1-initial" secret1 := string(testBackendSecret) + "-backend1" - SetEtcdValue(etcd, "/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+initialSecret1+"\"}")) + embedEtcd.SetValue("/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+initialSecret1+"\"}")) config := goconf.NewConfigFile() config.AddOption("backend", "backendtype", "etcd") @@ -543,7 +544,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { } drainWakeupChannel(ch) - SetEtcdValue(etcd, "/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+secret1+"\"}")) + embedEtcd.SetValue("/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+secret1+"\"}")) <-ch assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && @@ -558,7 +559,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { secret2 := string(testBackendSecret) + "-backend2" drainWakeupChannel(ch) - SetEtcdValue(etcd, "/backends/2_two", []byte("{\"url\":\""+url2+"\",\"secret\":\""+secret2+"\"}")) + embedEtcd.SetValue("/backends/2_two", []byte("{\"url\":\""+url2+"\",\"secret\":\""+secret2+"\"}")) <-ch assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && @@ -577,7 +578,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { secret3 := string(testBackendSecret) + "-backend3" drainWakeupChannel(ch) - SetEtcdValue(etcd, "/backends/3_three", []byte("{\"url\":\""+url3+"\",\"secret\":\""+secret3+"\"}")) + embedEtcd.SetValue("/backends/3_three", []byte("{\"url\":\""+url3+"\",\"secret\":\""+secret3+"\"}")) <-ch assert.Equal(3, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 3) && @@ -597,7 +598,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { } drainWakeupChannel(ch) - DeleteEtcdValue(etcd, "/backends/1_one") + embedEtcd.DeleteValue("/backends/1_one") <-ch assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) { @@ -608,7 +609,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { } drainWakeupChannel(ch) - DeleteEtcdValue(etcd, "/backends/2_two") + embedEtcd.DeleteValue("/backends/2_two") <-ch assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { @@ -760,13 +761,13 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { logger := log.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) - etcd, client := NewEtcdClientForTest(t) + embedEtcd, client := etcdtest.NewClientForTest(t) url1 := "https://domain1.invalid/foo" initialSecret1 := string(testBackendSecret) + "-backend1-initial" secret1 := string(testBackendSecret) + "-backend1" - SetEtcdValue(etcd, "/backends/1_one", []byte("{\"urls\":[\""+url1+"\"],\"secret\":\""+initialSecret1+"\"}")) + embedEtcd.SetValue("/backends/1_one", []byte("{\"urls\":[\""+url1+"\"],\"secret\":\""+initialSecret1+"\"}")) config := goconf.NewConfigFile() config.AddOption("backend", "backendtype", "etcd") @@ -796,7 +797,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { url2 := "https://domain1.invalid/bar" drainWakeupChannel(ch) - SetEtcdValue(etcd, "/backends/1_one", []byte("{\"urls\":[\""+url1+"\",\""+url2+"\"],\"secret\":\""+secret1+"\"}")) + embedEtcd.SetValue("/backends/1_one", []byte("{\"urls\":[\""+url1+"\",\""+url2+"\"],\"secret\":\""+secret1+"\"}")) <-ch assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) && @@ -816,7 +817,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { url4 := "https://domain3.invalid/foo" drainWakeupChannel(ch) - SetEtcdValue(etcd, "/backends/3_three", []byte("{\"urls\":[\""+url3+"\",\""+url4+"\"],\"secret\":\""+secret3+"\"}")) + embedEtcd.SetValue("/backends/3_three", []byte("{\"urls\":[\""+url3+"\",\""+url4+"\"],\"secret\":\""+secret3+"\"}")) <-ch assert.Equal(2, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 2) && @@ -836,7 +837,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { } drainWakeupChannel(ch) - DeleteEtcdValue(etcd, "/backends/1_one") + embedEtcd.DeleteValue("/backends/1_one") <-ch assert.Equal(1, stats.value) if backends := sortBackends(cfg.GetBackends()); assert.Len(backends, 1) { @@ -845,7 +846,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { } drainWakeupChannel(ch) - DeleteEtcdValue(etcd, "/backends/3_three") + embedEtcd.DeleteValue("/backends/3_three") <-ch assert.Equal(0, stats.value) diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index acb4b7c..03a04ac 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -42,7 +42,7 @@ type backendStorageEtcd struct { backendStorageCommon logger log.Logger - etcdClient *EtcdClient + etcdClient etcd.Client keyPrefix string keyInfos map[string]*etcd.BackendInformationEtcd @@ -54,7 +54,7 @@ type backendStorageEtcd struct { closeFunc context.CancelFunc } -func NewBackendStorageEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, stats BackendStorageStats) (BackendStorage, error) { +func NewBackendStorageEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, stats BackendStorageStats) (BackendStorage, error) { if etcdClient == nil || !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } @@ -106,7 +106,7 @@ func (s *backendStorageEtcd) wakeupForTesting() { } } -func (s *backendStorageEtcd) EtcdClientCreated(client *EtcdClient) { +func (s *backendStorageEtcd) EtcdClientCreated(client etcd.Client) { go func() { if err := client.WaitForConnection(s.closeCtx); err != nil { if errors.Is(err, context.Canceled) { @@ -164,17 +164,17 @@ func (s *backendStorageEtcd) EtcdClientCreated(client *EtcdClient) { }() } -func (s *backendStorageEtcd) EtcdWatchCreated(client *EtcdClient, key string) { +func (s *backendStorageEtcd) EtcdWatchCreated(client etcd.Client, key string) { } -func (s *backendStorageEtcd) getBackends(ctx context.Context, client *EtcdClient, keyPrefix string) (*clientv3.GetResponse, error) { +func (s *backendStorageEtcd) getBackends(ctx context.Context, client etcd.Client, keyPrefix string) (*clientv3.GetResponse, error) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() return client.Get(ctx, keyPrefix, clientv3.WithPrefix()) } -func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { +func (s *backendStorageEtcd) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { var info etcd.BackendInformationEtcd if err := json.Unmarshal(data, &info); err != nil { s.logger.Printf("Could not decode backend information %s: %s", string(data), err) @@ -228,7 +228,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data s.wakeupForTesting() } -func (s *backendStorageEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prevValue []byte) { +func (s *backendStorageEtcd) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { s.mu.Lock() defer s.mu.Unlock() diff --git a/backend_storage_etcd_test.go b/backend_storage_etcd_test.go index 255882a..0ff8efe 100644 --- a/backend_storage_etcd_test.go +++ b/backend_storage_etcd_test.go @@ -26,8 +26,9 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" - "go.etcd.io/etcd/server/v3/embed" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -46,21 +47,20 @@ func (s *backendStorageEtcd) getWakeupChannelForTesting() <-chan struct{} { } type testListener struct { - etcd *embed.Etcd + etcd *etcdtest.TestServer closed chan struct{} } -func (tl *testListener) EtcdClientCreated(client *EtcdClient) { - tl.etcd.Server.Stop() +func (tl *testListener) EtcdClientCreated(client etcd.Client) { close(tl.closed) } func Test_BackendStorageEtcdNoLeak(t *testing.T) { // nolint:paralleltest logger := log.NewLoggerForTest(t) test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - etcd, client := NewEtcdClientForTest(t) + embedEtcd, client := etcdtest.NewClientForTest(t) tl := &testListener{ - etcd: etcd, + etcd: embedEtcd, closed: make(chan struct{}), } client.AddListener(tl) diff --git a/etcd/api.go b/etcd/api.go index 60b5ad1..557269a 100644 --- a/etcd/api.go +++ b/etcd/api.go @@ -22,6 +22,7 @@ package etcd import ( + "context" "errors" "fmt" "net/url" @@ -29,8 +30,32 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + clientv3 "go.etcd.io/etcd/client/v3" ) +type ClientListener interface { + EtcdClientCreated(client Client) +} + +type ClientWatcher interface { + EtcdWatchCreated(client Client, key string) + EtcdKeyUpdated(client Client, key string, value []byte, prevValue []byte) + EtcdKeyDeleted(client Client, key string, prevValue []byte) +} + +type Client interface { + IsConfigured() bool + WaitForConnection(ctx context.Context) error + GetServerInfoEtcd() *BackendServerInfoEtcd + Close() error + + AddListener(listener ClientListener) + RemoveListener(listener ClientListener) + + Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) + Watch(ctx context.Context, key string, nextRevision int64, watcher ClientWatcher, opts ...clientv3.OpOption) (int64, error) +} + // Information on a backend in the etcd cluster. type BackendInformationEtcd struct { @@ -97,3 +122,10 @@ func (p *BackendInformationEtcd) CheckValid() (err error) { return nil } + +type BackendServerInfoEtcd struct { + Endpoints []string `json:"endpoints"` + + Active string `json:"active,omitempty"` + Connected *bool `json:"connected,omitempty"` +} diff --git a/etcd_client.go b/etcd/client.go similarity index 83% rename from etcd_client.go rename to etcd/client.go index ef498ab..a9cbc93 100644 --- a/etcd_client.go +++ b/etcd/client.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 etcd import ( "context" @@ -43,28 +43,23 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" ) -type EtcdClientListener interface { - EtcdClientCreated(client *EtcdClient) -} +var ( + initialWaitDelay = time.Second + maxWaitDelay = 8 * time.Second +) -type EtcdClientWatcher interface { - EtcdWatchCreated(client *EtcdClient, key string) - EtcdKeyUpdated(client *EtcdClient, key string, value []byte, prevValue []byte) - EtcdKeyDeleted(client *EtcdClient, key string, prevValue []byte) -} - -type EtcdClient struct { +type etcdClient struct { logger log.Logger compatSection string mu sync.Mutex client atomic.Value // +checklocks:mu - listeners map[EtcdClientListener]bool + listeners map[ClientListener]bool } -func NewEtcdClient(logger log.Logger, config *goconf.ConfigFile, compatSection string) (*EtcdClient, error) { - result := &EtcdClient{ +func NewClient(logger log.Logger, config *goconf.ConfigFile, compatSection string) (Client, error) { + result := &etcdClient{ logger: logger, compatSection: compatSection, } @@ -75,7 +70,7 @@ func NewEtcdClient(logger log.Logger, config *goconf.ConfigFile, compatSection s return result, nil } -func (c *EtcdClient) GetServerInfoEtcd() *BackendServerInfoEtcd { +func (c *etcdClient) GetServerInfoEtcd() *BackendServerInfoEtcd { client := c.getEtcdClient() if client == nil { return nil @@ -94,7 +89,7 @@ func (c *EtcdClient) GetServerInfoEtcd() *BackendServerInfoEtcd { return result } -func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, option string) string { +func (c *etcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, option string) string { value, _ := config.GetString("etcd", option) if value == "" && c.compatSection != "" { value, _ = config.GetString(c.compatSection, option) @@ -106,7 +101,7 @@ func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, opti return value } -func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { +func (c *etcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { var endpoints []string if endpointsString := c.getConfigStringWithFallback(config, "endpoints"); endpointsString != "" { endpoints = slices.Collect(internal.SplitEntries(endpointsString, ",")) @@ -189,7 +184,7 @@ func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error { return nil } -func (c *EtcdClient) Close() error { +func (c *etcdClient) Close() error { client := c.getEtcdClient() if client != nil { return client.Close() @@ -198,11 +193,11 @@ func (c *EtcdClient) Close() error { return nil } -func (c *EtcdClient) IsConfigured() bool { +func (c *etcdClient) IsConfigured() bool { return c.getEtcdClient() != nil } -func (c *EtcdClient) getEtcdClient() *clientv3.Client { +func (c *etcdClient) getEtcdClient() *clientv3.Client { client := c.client.Load() if client == nil { return nil @@ -211,14 +206,14 @@ func (c *EtcdClient) getEtcdClient() *clientv3.Client { return client.(*clientv3.Client) } -func (c *EtcdClient) syncClient(ctx context.Context) error { +func (c *etcdClient) syncClient(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() return c.getEtcdClient().Sync(ctx) } -func (c *EtcdClient) notifyListeners() { +func (c *etcdClient) notifyListeners() { c.mu.Lock() defer c.mu.Unlock() @@ -227,12 +222,12 @@ func (c *EtcdClient) notifyListeners() { } } -func (c *EtcdClient) AddListener(listener EtcdClientListener) { +func (c *etcdClient) AddListener(listener ClientListener) { c.mu.Lock() defer c.mu.Unlock() if c.listeners == nil { - c.listeners = make(map[EtcdClientListener]bool) + c.listeners = make(map[ClientListener]bool) } c.listeners[listener] = true if client := c.getEtcdClient(); client != nil { @@ -240,14 +235,14 @@ func (c *EtcdClient) AddListener(listener EtcdClientListener) { } } -func (c *EtcdClient) RemoveListener(listener EtcdClientListener) { +func (c *etcdClient) RemoveListener(listener ClientListener) { c.mu.Lock() defer c.mu.Unlock() delete(c.listeners, listener) } -func (c *EtcdClient) WaitForConnection(ctx context.Context) error { +func (c *etcdClient) WaitForConnection(ctx context.Context) error { backoff, err := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) if err != nil { return err @@ -276,11 +271,11 @@ func (c *EtcdClient) WaitForConnection(ctx context.Context) error { } } -func (c *EtcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { +func (c *etcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { return c.getEtcdClient().Get(ctx, key, opts...) } -func (c *EtcdClient) Watch(ctx context.Context, key string, nextRevision int64, watcher EtcdClientWatcher, opts ...clientv3.OpOption) (int64, error) { +func (c *etcdClient) Watch(ctx context.Context, key string, nextRevision int64, watcher ClientWatcher, opts ...clientv3.OpOption) (int64, error) { c.logger.Printf("Wait for leader and start watching on %s (rev=%d)", key, nextRevision) opts = append(opts, clientv3.WithRev(nextRevision), clientv3.WithPrevKV()) ch := c.getEtcdClient().Watch(clientv3.WithRequireLeader(ctx), key, opts...) diff --git a/etcd_client_test.go b/etcd/client_test.go similarity index 87% rename from etcd_client_test.go rename to etcd/client_test.go index da8b383..b789125 100644 --- a/etcd_client_test.go +++ b/etcd/client_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 etcd import ( "context" @@ -48,6 +48,10 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/test" ) +const ( + testTimeout = 10 * time.Second +) + var ( etcdListenUrl = "http://localhost:8080" ) @@ -129,7 +133,7 @@ func NewEtcdForTest(t *testing.T) *embed.Etcd { return etcd } -func NewEtcdClientForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { +func NewClientForTest(t *testing.T) (*embed.Etcd, Client) { etcd := NewEtcdForTest(t) config := goconf.NewConfigFile() @@ -137,7 +141,7 @@ func NewEtcdClientForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { config.AddOption("etcd", "loglevel", "error") logger := log.NewLoggerForTest(t) - client, err := NewEtcdClient(logger, config, "") + client, err := NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, client.Close()) @@ -145,7 +149,7 @@ func NewEtcdClientForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { return etcd, client } -func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { +func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, Client) { etcd, keyfile, certfile := NewEtcdForTestWithTls(t, true) config := goconf.NewConfigFile() @@ -156,7 +160,7 @@ func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { config.AddOption("etcd", "cacert", certfile) logger := log.NewLoggerForTest(t) - client, err := NewEtcdClient(logger, config, "") + client, err := NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, client.Close()) @@ -164,14 +168,14 @@ func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, *EtcdClient) { return etcd, client } -func SetEtcdValue(etcd *embed.Etcd, key string, value []byte) { +func SetValue(etcd *embed.Etcd, key string, value []byte) { if kv := etcd.Server.KV(); kv != nil { kv.Put([]byte(key), value, lease.NoLease) kv.Commit() } } -func DeleteEtcdValue(etcd *embed.Etcd, key string) { +func DeleteValue(etcd *embed.Etcd, key string) { if kv := etcd.Server.KV(); kv != nil { kv.DeleteRange([]byte(key), nil) kv.Commit() @@ -184,7 +188,7 @@ func Test_EtcdClient_Get(t *testing.T) { ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) - etcd, client := NewEtcdClientForTest(t) + etcd, client := NewClientForTest(t) ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() @@ -194,9 +198,7 @@ func Test_EtcdClient_Get(t *testing.T) { assert.Equal([]string{ etcd.Config().ListenClientUrls[0].String(), }, info.Endpoints) - if connected := info.Connected; assert.NotNil(connected) { - assert.False(*connected) - } + assert.NotNil(info.Connected) } require.NoError(client.WaitForConnection(ctx)) @@ -215,7 +217,7 @@ func Test_EtcdClient_Get(t *testing.T) { assert.EqualValues(0, response.Count) } - SetEtcdValue(etcd, "foo", []byte("bar")) + SetValue(etcd, "foo", []byte("bar")) if response, err := client.Get(ctx, "foo"); assert.NoError(err) { if assert.EqualValues(1, response.Count) { @@ -241,9 +243,7 @@ func Test_EtcdClientTLS_Get(t *testing.T) { assert.Equal([]string{ etcd.Config().ListenClientUrls[0].String(), }, info.Endpoints) - if connected := info.Connected; assert.NotNil(connected) { - assert.False(*connected) - } + assert.NotNil(info.Connected) } require.NoError(client.WaitForConnection(ctx)) @@ -262,7 +262,7 @@ func Test_EtcdClientTLS_Get(t *testing.T) { assert.EqualValues(0, response.Count) } - SetEtcdValue(etcd, "foo", []byte("bar")) + SetValue(etcd, "foo", []byte("bar")) if response, err := client.Get(ctx, "foo"); assert.NoError(err) { if assert.EqualValues(1, response.Count) { @@ -277,15 +277,15 @@ func Test_EtcdClient_GetPrefix(t *testing.T) { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) - etcd, client := NewEtcdClientForTest(t) + etcd, client := NewClientForTest(t) if response, err := client.Get(ctx, "foo"); assert.NoError(err) { assert.EqualValues(0, response.Count) } - SetEtcdValue(etcd, "foo", []byte("1")) - SetEtcdValue(etcd, "foo/lala", []byte("2")) - SetEtcdValue(etcd, "lala/foo", []byte("3")) + SetValue(etcd, "foo", []byte("1")) + SetValue(etcd, "foo/lala", []byte("2")) + SetValue(etcd, "lala/foo", []byte("3")) if response, err := client.Get(ctx, "foo", clientv3.WithPrefix()); assert.NoError(err) { if assert.EqualValues(2, response.Count) { @@ -332,7 +332,7 @@ func (l *EtcdClientTestListener) Close() { l.cancel() } -func (l *EtcdClientTestListener) EtcdClientCreated(client *EtcdClient) { +func (l *EtcdClientTestListener) EtcdClientCreated(client Client) { go func() { assert := assert.New(l.t) if err := client.WaitForConnection(l.ctx); !assert.NoError(err) { @@ -358,10 +358,10 @@ func (l *EtcdClientTestListener) EtcdClientCreated(client *EtcdClient) { }() } -func (l *EtcdClientTestListener) EtcdWatchCreated(client *EtcdClient, key string) { +func (l *EtcdClientTestListener) EtcdWatchCreated(client Client, key string) { } -func (l *EtcdClientTestListener) EtcdKeyUpdated(client *EtcdClient, key string, value []byte, prevValue []byte) { +func (l *EtcdClientTestListener) EtcdKeyUpdated(client Client, key string, value []byte, prevValue []byte) { evt := etcdEvent{ t: clientv3.EventTypePut, key: string(key), @@ -373,7 +373,7 @@ func (l *EtcdClientTestListener) EtcdKeyUpdated(client *EtcdClient, key string, l.events <- evt } -func (l *EtcdClientTestListener) EtcdKeyDeleted(client *EtcdClient, key string, prevValue []byte) { +func (l *EtcdClientTestListener) EtcdKeyDeleted(client Client, key string, prevValue []byte) { evt := etcdEvent{ t: clientv3.EventTypeDelete, key: string(key), @@ -389,9 +389,9 @@ func Test_EtcdClient_Watch(t *testing.T) { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) - etcd, client := NewEtcdClientForTest(t) + etcd, client := NewClientForTest(t) - SetEtcdValue(etcd, "foo/a", []byte("1")) + SetValue(etcd, "foo/a", []byte("1")) listener := NewEtcdClientTestListener(ctx, t) defer listener.Close() @@ -401,19 +401,19 @@ func Test_EtcdClient_Watch(t *testing.T) { <-listener.initial - SetEtcdValue(etcd, "foo/b", []byte("2")) + SetValue(etcd, "foo/b", []byte("2")) event := <-listener.events assert.Equal(clientv3.EventTypePut, event.t) assert.Equal("foo/b", event.key) assert.Equal("2", event.value) - SetEtcdValue(etcd, "foo/a", []byte("3")) + SetValue(etcd, "foo/a", []byte("3")) event = <-listener.events assert.Equal(clientv3.EventTypePut, event.t) assert.Equal("foo/a", event.key) assert.Equal("3", event.value) - DeleteEtcdValue(etcd, "foo/a") + DeleteValue(etcd, "foo/a") event = <-listener.events assert.Equal(clientv3.EventTypeDelete, event.t) assert.Equal("foo/a", event.key) diff --git a/etcd/etcdtest/etcdtest.go b/etcd/etcdtest/etcdtest.go new file mode 100644 index 0000000..4a8603d --- /dev/null +++ b/etcd/etcdtest/etcdtest.go @@ -0,0 +1,466 @@ +/** + * 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 etcdtest + +import ( + "bytes" + "context" + "errors" + "net" + "net/url" + "os" + "slices" + "strconv" + "strings" + "sync" + "testing" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.etcd.io/etcd/api/v3/etcdserverpb" + "go.etcd.io/etcd/api/v3/mvccpb" + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/server/v3/embed" + "go.etcd.io/etcd/server/v3/lease" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" + + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" +) + +var ( + etcdListenUrl = "http://localhost:8080" +) + +type Server struct { + embed *embed.Etcd +} + +func (s *Server) URL() *url.URL { + return &s.embed.Config().ListenClientUrls[0] +} + +func (s *Server) SetValue(key string, value []byte) { + if kv := s.embed.Server.KV(); kv != nil { + kv.Put([]byte(key), value, lease.NoLease) + kv.Commit() + } +} + +func (s *Server) DeleteValue(key string) { + if kv := s.embed.Server.KV(); kv != nil { + kv.DeleteRange([]byte(key), nil) + kv.Commit() + } +} + +func NewServerForTest(t *testing.T) *Server { + t.Helper() + require := require.New(t) + cfg := embed.NewConfig() + cfg.Dir = t.TempDir() + os.Chmod(cfg.Dir, 0700) // nolint + cfg.LogLevel = "warn" + cfg.Name = "signalingtest" + cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))) + + u, err := url.Parse(etcdListenUrl) + require.NoError(err) + + // Find a free port to bind the server to. + var etcd *embed.Etcd + for port := 50000; port < 50100; port++ { + u.Host = net.JoinHostPort("localhost", strconv.Itoa(port)) + cfg.ListenClientUrls = []url.URL{*u} + cfg.AdvertiseClientUrls = []url.URL{*u} + httpListener := u + httpListener.Host = net.JoinHostPort("localhost", strconv.Itoa(port+1)) + cfg.ListenClientHttpUrls = []url.URL{*httpListener} + peerListener := u + peerListener.Host = net.JoinHostPort("localhost", strconv.Itoa(port+2)) + cfg.ListenPeerUrls = []url.URL{*peerListener} + cfg.AdvertisePeerUrls = []url.URL{*peerListener} + cfg.InitialCluster = "signalingtest=" + peerListener.String() + etcd, err = embed.StartEtcd(cfg) + if test.IsErrorAddressAlreadyInUse(err) { + continue + } + + require.NoError(err) + break + } + require.NotNil(etcd, "could not find free port") + + t.Cleanup(func() { + etcd.Close() + <-etcd.Server.StopNotify() + }) + // Wait for server to be ready. + <-etcd.Server.ReadyNotify() + + server := &Server{ + embed: etcd, + } + return server +} + +func NewEtcdClientForTest(t *testing.T, server *Server) etcd.Client { + t.Helper() + + logger := log.NewLoggerForTest(t) + + config := goconf.NewConfigFile() + config.AddOption("etcd", "endpoints", server.URL().String()) + + client, err := etcd.NewClient(logger, config, "") + require.NoError(t, err) + + t.Cleanup(func() { + assert.NoError(t, client.Close()) + }) + + return client +} + +type testWatch struct { + key string + op clientv3.Op + rev int64 + + watcher etcd.ClientWatcher +} + +type testClient struct { + mu sync.Mutex + server *TestServer + + // +checklocks:mu + closed bool + closeCh chan struct{} + processCh chan func() + // +checklocks:mu + listeners []etcd.ClientListener + // +checklocks:mu + watchers []*testWatch +} + +func newTestClient(server *TestServer) *testClient { + client := &testClient{ + server: server, + closeCh: make(chan struct{}), + processCh: make(chan func(), 1), + } + go func() { + defer close(client.closeCh) + for { + f := <-client.processCh + if f == nil { + return + } + + f() + } + }() + return client +} + +func (c *testClient) IsConfigured() bool { + return true +} + +func (c *testClient) WaitForConnection(ctx context.Context) error { + return nil +} + +func (c *testClient) GetServerInfoEtcd() *etcd.BackendServerInfoEtcd { + return &etcd.BackendServerInfoEtcd{} +} + +func (c *testClient) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil + } + + c.closed = true + c.server.removeClient(c) + close(c.processCh) + <-c.closeCh + return nil +} + +func (c *testClient) AddListener(listener etcd.ClientListener) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return + } + + c.listeners = append(c.listeners, listener) + c.processCh <- func() { + listener.EtcdClientCreated(c) + } +} + +func (c *testClient) RemoveListener(listener etcd.ClientListener) { + c.mu.Lock() + defer c.mu.Unlock() + + c.listeners = slices.DeleteFunc(c.listeners, func(l etcd.ClientListener) bool { + return l == listener + }) +} + +func (c *testClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { + keys, values, revision := c.server.getValues(key, 0, opts...) + response := &clientv3.GetResponse{ + Count: int64(len(values)), + Header: &etcdserverpb.ResponseHeader{ + Revision: revision, + }, + } + for idx, key := range keys { + response.Kvs = append(response.Kvs, &mvccpb.KeyValue{ + Key: []byte(key), + Value: values[idx], + }) + } + return response, nil +} + +func (c *testClient) notifyUpdated(key string, oldValue []byte, newValue []byte) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return + } + + for _, w := range c.watchers { + if withPrefix := w.op.IsOptsWithPrefix(); (withPrefix && strings.HasPrefix(key, w.key)) || (!withPrefix && key == w.key) { + c.processCh <- func() { + w.watcher.EtcdKeyUpdated(c, key, newValue, oldValue) + } + } + } +} + +func (c *testClient) notifyDeleted(key string, oldValue []byte) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return + } + + for _, w := range c.watchers { + if withPrefix := w.op.IsOptsWithPrefix(); (withPrefix && strings.HasPrefix(key, w.key)) || (!withPrefix && key == w.key) { + c.processCh <- func() { + w.watcher.EtcdKeyDeleted(c, key, oldValue) + } + } + } +} + +func (c *testClient) addWatcher(w *testWatch, opts ...clientv3.OpOption) error { + keys, values, _ := c.server.getValues(w.key, w.rev, opts...) + + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return errors.New("closed") + } + + c.watchers = append(c.watchers, w) + c.processCh <- func() { + w.watcher.EtcdWatchCreated(c, w.key) + } + + for idx, key := range keys { + c.processCh <- func() { + w.watcher.EtcdKeyUpdated(c, key, values[idx], nil) + } + } + + return nil +} + +func (c *testClient) Watch(ctx context.Context, key string, nextRevision int64, watcher etcd.ClientWatcher, opts ...clientv3.OpOption) (int64, error) { + w := &testWatch{ + key: key, + rev: nextRevision, + + watcher: watcher, + } + for _, o := range opts { + o(&w.op) + } + + if err := c.addWatcher(w, opts...); err != nil { + return 0, err + } + + select { + case <-c.closeCh: + // Client is closed. + case <-ctx.Done(): + // Watch context was cancelled / timed out. + } + return c.server.getRevision(), nil +} + +type testServerValue struct { + value []byte + revision int64 +} + +type TestServer struct { + t *testing.T + mu sync.Mutex + // +checklocks:mu + clients []*testClient + // +checklocks:mu + values map[string]*testServerValue + // +checklocks:mu + revision int64 +} + +func (s *TestServer) newClient() *testClient { + client := newTestClient(s) + s.addClient(client) + return client +} + +func (s *TestServer) addClient(client *testClient) { + s.mu.Lock() + defer s.mu.Unlock() + + s.clients = append(s.clients, client) +} + +func (s *TestServer) removeClient(client *testClient) { + s.mu.Lock() + defer s.mu.Unlock() + + s.clients = slices.DeleteFunc(s.clients, func(c *testClient) bool { + return c == client + }) +} + +func (s *TestServer) getRevision() int64 { + s.mu.Lock() + defer s.mu.Unlock() + + return s.revision +} + +func (s *TestServer) getValues(key string, minRevision int64, opts ...clientv3.OpOption) (keys []string, values [][]byte, revision int64) { + s.mu.Lock() + defer s.mu.Unlock() + + var op clientv3.Op + for _, o := range opts { + o(&op) + } + if op.IsOptsWithPrefix() { + for k, value := range s.values { + if minRevision > 0 && value.revision < minRevision { + continue + } + if strings.HasPrefix(k, key) { + keys = append(keys, k) + values = append(values, value.value) + } + } + } else { + if value, found := s.values[key]; found && (minRevision == 0 || value.revision >= minRevision) { + keys = append(keys, key) + values = append(values, value.value) + } + } + + revision = s.revision + return +} + +func (s *TestServer) SetValue(key string, value []byte) { + s.mu.Lock() + defer s.mu.Unlock() + + prev, found := s.values[key] + if found && bytes.Equal(prev.value, value) { + return + } + + if s.values == nil { + s.values = make(map[string]*testServerValue) + } + if prev == nil { + prev = &testServerValue{} + s.values[key] = prev + } + s.revision++ + prevValue := prev.value + prev.value = value + prev.revision = s.revision + + for _, c := range s.clients { + c.notifyUpdated(key, prevValue, value) + } +} + +func (s *TestServer) DeleteValue(key string) { + s.mu.Lock() + defer s.mu.Unlock() + + prev, found := s.values[key] + if !found { + return + } + + delete(s.values, key) + s.revision++ + + for _, c := range s.clients { + c.notifyDeleted(key, prev.value) + } +} + +func NewClientForTest(t *testing.T) (*TestServer, etcd.Client) { + t.Helper() + server := &TestServer{ + t: t, + revision: 1, + } + client := server.newClient() + t.Cleanup(func() { + assert.NoError(t, client.Close()) + }) + return server, client +} diff --git a/etcd/etcdtest/etcdtest_test.go b/etcd/etcdtest/etcdtest_test.go new file mode 100644 index 0000000..2302c73 --- /dev/null +++ b/etcd/etcdtest/etcdtest_test.go @@ -0,0 +1,319 @@ +/** + * 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 etcdtest + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + clientv3 "go.etcd.io/etcd/client/v3" + + "github.com/strukturag/nextcloud-spreed-signaling/etcd" +) + +var ( + testTimeout = 10 * time.Second +) + +type updateEvent struct { + key string + value string + prev []byte +} + +type deleteEvent struct { + key string + prev []byte +} + +type testWatcher struct { + created chan struct{} + updated chan updateEvent + deleted chan deleteEvent +} + +func newTestWatcher() *testWatcher { + return &testWatcher{ + created: make(chan struct{}), + updated: make(chan updateEvent), + deleted: make(chan deleteEvent), + } +} + +func (w *testWatcher) EtcdWatchCreated(client etcd.Client, key string) { + close(w.created) +} + +func (w *testWatcher) EtcdKeyUpdated(client etcd.Client, key string, value []byte, prevValue []byte) { + w.updated <- updateEvent{ + key: key, + value: string(value), + prev: prevValue, + } +} + +func (w *testWatcher) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { + w.deleted <- deleteEvent{ + key: key, + prev: prevValue, + } +} + +type serverInterface interface { + SetValue(key string, value []byte) + DeleteValue(key string) +} + +type testClientListener struct { + called chan struct{} +} + +func (l *testClientListener) EtcdClientCreated(c etcd.Client) { + close(l.called) +} + +func testServerWatch(t *testing.T, server serverInterface, client etcd.Client) { + require := require.New(t) + assert := assert.New(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + + assert.True(client.IsConfigured(), "should be configured") + require.NoError(client.WaitForConnection(ctx)) + + listener := &testClientListener{ + called: make(chan struct{}), + } + client.AddListener(listener) + defer client.RemoveListener(listener) + + select { + case <-listener.called: + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + watcher := newTestWatcher() + + go func() { + if _, err := client.Watch(cancelCtx, "foo", 0, watcher); err != nil { + assert.ErrorIs(err, context.Canceled) + } + }() + + select { + case <-watcher.created: + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + key := "foo" + value := "bar" + server.SetValue("foo", []byte(value)) + select { + case evt := <-watcher.updated: + assert.Equal(key, evt.key) + assert.Equal(value, evt.value) + assert.Empty(evt.prev) + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + if response, err := client.Get(ctx, "foo"); assert.NoError(err) { + assert.EqualValues(1, response.Count) + if assert.Len(response.Kvs, 1) { + assert.Equal(key, string(response.Kvs[0].Key)) + assert.Equal(value, string(response.Kvs[0].Value)) + } + } + if response, err := client.Get(ctx, "f"); assert.NoError(err) { + assert.EqualValues(0, response.Count) + assert.Empty(response.Kvs) + } + if response, err := client.Get(ctx, "f", clientv3.WithPrefix()); assert.NoError(err) { + assert.EqualValues(1, response.Count) + if assert.Len(response.Kvs, 1) { + assert.Equal(key, string(response.Kvs[0].Key)) + assert.Equal(value, string(response.Kvs[0].Value)) + } + } + + server.DeleteValue("foo") + select { + case evt := <-watcher.deleted: + assert.Equal(key, evt.key) + assert.Equal(value, string(evt.prev)) + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + select { + case evt := <-watcher.updated: + assert.Fail("unexpected update event", "got %+v", evt) + case evt := <-watcher.deleted: + assert.Fail("unexpected deleted event", "got %+v", evt) + default: + } +} + +func TestServerWatch_Mock(t *testing.T) { + t.Parallel() + + server, client := NewClientForTest(t) + testServerWatch(t, server, client) +} + +func TestServerWatch_Real(t *testing.T) { + t.Parallel() + + server := NewServerForTest(t) + client := NewEtcdClientForTest(t, server) + testServerWatch(t, server, client) +} + +func testServerWatchInitialData(t *testing.T, server serverInterface, client etcd.Client) { + require := require.New(t) + assert := assert.New(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + key := "foo" + value := "bar" + server.SetValue("foo", []byte(value)) + + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + + watcher := newTestWatcher() + + go func() { + if _, err := client.Watch(cancelCtx, "foo", 1, watcher); err != nil { + assert.ErrorIs(err, context.Canceled) + } + }() + + select { + case <-watcher.created: + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + select { + case evt := <-watcher.updated: + assert.Equal(key, evt.key) + assert.Equal(value, evt.value) + assert.Empty(evt.prev) + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + select { + case evt := <-watcher.updated: + assert.Fail("unexpected update event", "got %+v", evt) + case evt := <-watcher.deleted: + assert.Fail("unexpected deleted event", "got %+v", evt) + default: + } +} + +func TestServerWatchInitialData_Mock(t *testing.T) { + t.Parallel() + + server, client := NewClientForTest(t) + testServerWatchInitialData(t, server, client) +} + +func TestServerWatchInitialData_Real(t *testing.T) { + t.Parallel() + + server := NewServerForTest(t) + client := NewEtcdClientForTest(t, server) + testServerWatchInitialData(t, server, client) +} + +func testServerWatchInitialOldData(t *testing.T, server serverInterface, client etcd.Client) { + require := require.New(t) + assert := assert.New(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + key := "foo" + value := "bar" + server.SetValue("foo", []byte(value)) + + response, err := client.Get(ctx, key) + require.NoError(err) + + if assert.EqualValues(1, response.Count) && assert.Len(response.Kvs, 1) { + assert.Equal(key, string(response.Kvs[0].Key)) + assert.Equal(value, string(response.Kvs[0].Value)) + } + + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + + watcher := newTestWatcher() + + go func() { + if _, err := client.Watch(cancelCtx, "foo", response.Header.GetRevision()+1, watcher); err != nil { + assert.ErrorIs(err, context.Canceled) + } + }() + + select { + case <-watcher.created: + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + + select { + case evt := <-watcher.updated: + assert.Fail("unexpected update event", "got %+v", evt) + case evt := <-watcher.deleted: + assert.Fail("unexpected deleted event", "got %+v", evt) + default: + } +} + +func TestServerWatchInitialOldData_Mock(t *testing.T) { + t.Parallel() + + server, client := NewClientForTest(t) + testServerWatchInitialOldData(t, server, client) +} + +func TestServerWatchInitialOldData_Real(t *testing.T) { + t.Parallel() + + server := NewServerForTest(t) + client := NewEtcdClientForTest(t, server) + testServerWatchInitialOldData(t, server, client) +} diff --git a/grpc_client.go b/grpc_client.go index 82f79c5..f6c154e 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -45,6 +45,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -466,7 +467,7 @@ type GrpcClients struct { // +checklocks:mu dnsDiscovery bool - etcdClient *EtcdClient // +checklocksignore: Only written to from constructor. + etcdClient etcd.Client // +checklocksignore: Only written to from constructor. targetPrefix string // +checklocks:mu targetInformation map[string]*GrpcTargetInformationEtcd @@ -482,7 +483,7 @@ type GrpcClients struct { closeFunc context.CancelFunc // +checklocksignore: No locking necessary. } -func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient *EtcdClient, dnsMonitor *DnsMonitor, version string) (*GrpcClients, error) { +func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, dnsMonitor *DnsMonitor, version string) (*GrpcClients, error) { initializedCtx, initializedFunc := context.WithCancel(context.Background()) closeCtx, closeFunc := context.WithCancel(context.Background()) result := &GrpcClients{ @@ -823,7 +824,7 @@ func (c *GrpcClients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool return nil } -func (c *GrpcClients) EtcdClientCreated(client *EtcdClient) { +func (c *GrpcClients) EtcdClientCreated(client etcd.Client) { go func() { if err := client.WaitForConnection(c.closeCtx); err != nil { if errors.Is(err, context.Canceled) { @@ -879,17 +880,17 @@ func (c *GrpcClients) EtcdClientCreated(client *EtcdClient) { }() } -func (c *GrpcClients) EtcdWatchCreated(client *EtcdClient, key string) { +func (c *GrpcClients) EtcdWatchCreated(client etcd.Client, key string) { } -func (c *GrpcClients) getGrpcTargets(ctx context.Context, client *EtcdClient, targetPrefix string) (*clientv3.GetResponse, error) { +func (c *GrpcClients) getGrpcTargets(ctx context.Context, client etcd.Client, targetPrefix string) (*clientv3.GetResponse, error) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() return client.Get(ctx, targetPrefix, clientv3.WithPrefix()) } -func (c *GrpcClients) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { +func (c *GrpcClients) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { var info GrpcTargetInformationEtcd if err := json.Unmarshal(data, &info); err != nil { c.logger.Printf("Could not decode GRPC target %s=%s: %s", key, string(data), err) @@ -938,7 +939,7 @@ func (c *GrpcClients) EtcdKeyUpdated(client *EtcdClient, key string, data []byte c.wakeupForTesting() } -func (c *GrpcClients) EtcdKeyDeleted(client *EtcdClient, key string, prevValue []byte) { +func (c *GrpcClients) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { c.mu.Lock() defer c.mu.Unlock() diff --git a/grpc_client_test.go b/grpc_client_test.go index 3885aaf..4fe72a2 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -34,8 +34,9 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.etcd.io/etcd/server/v3/embed" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -54,7 +55,7 @@ func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { return ch } -func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient *EtcdClient, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { +func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) @@ -75,15 +76,15 @@ func NewGrpcClientsForTest(t *testing.T, addr string, lookup *mockDnsLookup) (*G return NewGrpcClientsForTestWithConfig(t, config, nil, lookup) } -func NewGrpcClientsWithEtcdForTest(t *testing.T, etcd *embed.Etcd, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { +func NewGrpcClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { config := goconf.NewConfigFile() - config.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) + config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) config.AddOption("grpc", "targettype", "etcd") config.AddOption("grpc", "targetprefix", "/grpctargets") logger := log.NewLoggerForTest(t) - etcdClient, err := NewEtcdClient(logger, config, "") + etcdClient, err := etcd.NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, etcdClient.Close()) @@ -120,12 +121,12 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest _, addr1 := NewGrpcServerForTest(t) _, addr2 := NewGrpcServerForTest(t) - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) + client, _ := NewGrpcClientsWithEtcdForTest(t, embedEtcd, nil) ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() require.NoError(t, client.WaitForInitialized(ctx)) @@ -140,8 +141,8 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) - etcd := NewEtcdForTest(t) - client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) + embedEtcd := etcdtest.NewServerForTest(t) + client, _ := NewGrpcClientsWithEtcdForTest(t, embedEtcd, nil) ch := client.getWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) @@ -151,7 +152,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { drainWakeupChannel(ch) _, addr1 := NewGrpcServerForTest(t) - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr1, clients[0].Target()) @@ -159,7 +160,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { drainWakeupChannel(ch) _, addr2 := NewGrpcServerForTest(t) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 2) { assert.Equal(addr1, clients[0].Target()) @@ -167,7 +168,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { } drainWakeupChannel(ch) - DeleteEtcdValue(etcd, "/grpctargets/one") + embedEtcd.DeleteValue("/grpctargets/one") waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr2, clients[0].Target()) @@ -175,7 +176,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { drainWakeupChannel(ch) _, addr3 := NewGrpcServerForTest(t) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr3+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr3+"\"}")) waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr3, clients[0].Target()) @@ -187,8 +188,8 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) - etcd := NewEtcdForTest(t) - client, _ := NewGrpcClientsWithEtcdForTest(t, etcd, nil) + embedEtcd := etcdtest.NewServerForTest(t) + client, _ := NewGrpcClientsWithEtcdForTest(t, embedEtcd, nil) ch := client.getWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) @@ -198,7 +199,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { drainWakeupChannel(ch) _, addr1 := NewGrpcServerForTest(t) - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr1, clients[0].Target()) @@ -207,7 +208,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { drainWakeupChannel(ch) server2, addr2 := NewGrpcServerForTest(t) server2.serverId = GrpcServerId - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) waitForEvent(ctx, t, ch) client.selfCheckWaitGroup.Wait() if clients := client.GetClients(); assert.Len(clients, 1) { @@ -215,7 +216,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { } drainWakeupChannel(ch) - DeleteEtcdValue(etcd, "/grpctargets/two") + embedEtcd.DeleteValue("/grpctargets/two") waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr1, clients[0].Target()) diff --git a/hub.go b/hub.go index 5e11afb..1db57fa 100644 --- a/hub.go +++ b/hub.go @@ -55,6 +55,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -210,7 +211,7 @@ type Hub struct { geoipOverrides atomic.Pointer[map[*net.IPNet]string] geoipUpdating atomic.Bool - etcdClient *EtcdClient + etcdClient etcd.Client rpcServer *GrpcServer rpcClients *GrpcClients @@ -223,7 +224,7 @@ type Hub struct { blockedCandidates atomic.Pointer[container.IPList] } -func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { +func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { logger := log.LoggerFromContext(ctx) hashKey, _ := config.GetStringOptionWithEnv(cfg, "sessions", "hashkey") switch len(hashKey) { diff --git a/mcu_proxy.go b/mcu_proxy.go index 0383a91..1575ba5 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -48,6 +48,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -1512,7 +1513,7 @@ type mcuProxy struct { rpcClients *GrpcClients } -func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient *EtcdClient, rpcClients *GrpcClients, dnsMonitor *DnsMonitor) (Mcu, error) { +func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, rpcClients *GrpcClients, dnsMonitor *DnsMonitor) (Mcu, error) { logger := log.LoggerFromContext(ctx) urlType, _ := config.GetString("mcu", "urltype") if urlType == "" { diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index fd7bb00..bd81f3b 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -47,9 +47,10 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.etcd.io/etcd/server/v3/embed" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -846,7 +847,7 @@ func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler } type proxyTestOptions struct { - etcd *embed.Etcd + etcd *etcdtest.Server servers []*TestProxyServerHandler } @@ -854,7 +855,7 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i t.Helper() require := require.New(t) if options.etcd == nil { - options.etcd = NewEtcdForTest(t) + options.etcd = etcdtest.NewServerForTest(t) } grpcClients, dnsMonitor := NewGrpcClientsWithEtcdForTest(t, options.etcd, lookup) @@ -891,12 +892,12 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i cfg.AddOption("mcu", "token_key", privkeyFile) etcdConfig := goconf.NewConfigFile() - etcdConfig.AddOption("etcd", "endpoints", options.etcd.Config().ListenClientUrls[0].String()) + etcdConfig.AddOption("etcd", "endpoints", options.etcd.URL().String()) etcdConfig.AddOption("etcd", "loglevel", "error") logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - etcdClient, err := NewEtcdClient(logger, etcdConfig, "") + etcdClient, err := etcd.NewClient(logger, etcdConfig, "") require.NoError(err) t.Cleanup(func() { assert.NoError(t, etcdClient.Close()) @@ -1808,7 +1809,7 @@ func (h *mockGrpcServerHub) CreateProxyToken(publisherId string) (string, error) func Test_ProxyRemotePublisher(t *testing.T) { t.Parallel() - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -1818,14 +1819,14 @@ func Test_ProxyRemotePublisher(t *testing.T) { grpcServer1.hub = hub1 grpcServer2.hub = hub2 - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -1833,7 +1834,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -1887,7 +1888,7 @@ func Test_ProxyRemotePublisher(t *testing.T) { func Test_ProxyMultipleRemotePublisher(t *testing.T) { t.Parallel() - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -1900,16 +1901,16 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { grpcServer2.hub = hub2 grpcServer3.hub = hub3 - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - SetEtcdValue(etcd, "/grpctargets/three", []byte("{\"address\":\""+addr3+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/three", []byte("{\"address\":\""+addr3+"\"}")) server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "US") server3 := NewProxyServerForTest(t, "US") mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -1918,7 +1919,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -1927,7 +1928,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { }, 2, nil) hub2.proxy.Store(mcu2) mcu3, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -1993,7 +1994,7 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { func Test_ProxyRemotePublisherWait(t *testing.T) { t.Parallel() - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -2003,14 +2004,14 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { grpcServer1.hub = hub1 grpcServer2.hub = hub2 - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -2018,7 +2019,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, server2, @@ -2088,7 +2089,7 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { func Test_ProxyRemotePublisherTemporary(t *testing.T) { t.Parallel() - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -2098,21 +2099,21 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { grpcServer1.hub = hub1 grpcServer2.hub = hub2 - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, }, }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server2, }, @@ -2196,7 +2197,7 @@ loop: func Test_ProxyConnectToken(t *testing.T) { t.Parallel() - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -2206,8 +2207,8 @@ func Test_ProxyConnectToken(t *testing.T) { grpcServer1.hub = hub1 grpcServer2.hub = hub2 - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "DE") @@ -2216,14 +2217,14 @@ func Test_ProxyConnectToken(t *testing.T) { // i.e. they are only known to their local proxy, not the one of the other // signaling server - so the connection token must be passed between them. mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, }, }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server2, }, @@ -2276,7 +2277,7 @@ func Test_ProxyConnectToken(t *testing.T) { func Test_ProxyPublisherToken(t *testing.T) { t.Parallel() - etcd := NewEtcdForTest(t) + embedEtcd := etcdtest.NewServerForTest(t) grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) @@ -2286,8 +2287,8 @@ func Test_ProxyPublisherToken(t *testing.T) { grpcServer1.hub = hub1 grpcServer2.hub = hub2 - SetEtcdValue(etcd, "/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - SetEtcdValue(etcd, "/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) server1 := NewProxyServerForTest(t, "DE") server2 := NewProxyServerForTest(t, "US") @@ -2298,14 +2299,14 @@ func Test_ProxyPublisherToken(t *testing.T) { // Also the subscriber is connecting from a different country, so a remote // stream will be created that needs a valid token from the remote proxy. mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server1, }, }, 1, nil) hub1.proxy.Store(mcu1) mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: etcd, + etcd: embedEtcd, servers: []*TestProxyServerHandler{ server2, }, diff --git a/proxy/proxy_tokens_etcd.go b/proxy/proxy_tokens_etcd.go index 00a94c2..22d22d9 100644 --- a/proxy/proxy_tokens_etcd.go +++ b/proxy/proxy_tokens_etcd.go @@ -33,8 +33,8 @@ import ( "github.com/dlintw/goconf" "github.com/golang-jwt/jwt/v5" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -49,14 +49,14 @@ type tokenCacheEntry struct { type tokensEtcd struct { logger log.Logger - client *signaling.EtcdClient + client etcd.Client tokenFormats atomic.Value tokenCache *container.LruCache[*tokenCacheEntry] } func NewProxyTokensEtcd(logger log.Logger, config *goconf.ConfigFile) (ProxyTokens, error) { - client, err := signaling.NewEtcdClient(logger, config, "tokens") + client, err := etcd.NewClient(logger, config, "tokens") if err != nil { return nil, err } diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index 354e771..69e7b43 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -32,6 +32,7 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -40,7 +41,7 @@ type proxyConfigEtcd struct { mu sync.Mutex proxy McuProxy // +checklocksignore: Only written to from constructor. - client *EtcdClient + client etcd.Client keyPrefix string // +checklocks:mu keyInfos map[string]*ProxyInformationEtcd @@ -51,7 +52,7 @@ type proxyConfigEtcd struct { closeFunc context.CancelFunc } -func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient *EtcdClient, proxy McuProxy) (ProxyConfig, error) { +func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, proxy McuProxy) (ProxyConfig, error) { if !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } @@ -100,7 +101,7 @@ func (p *proxyConfigEtcd) Stop() { p.closeFunc() } -func (p *proxyConfigEtcd) EtcdClientCreated(client *EtcdClient) { +func (p *proxyConfigEtcd) EtcdClientCreated(client etcd.Client) { go func() { if err := client.WaitForConnection(p.closeCtx); err != nil { if errors.Is(err, context.Canceled) { @@ -159,17 +160,17 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client *EtcdClient) { }() } -func (p *proxyConfigEtcd) EtcdWatchCreated(client *EtcdClient, key string) { +func (p *proxyConfigEtcd) EtcdWatchCreated(client etcd.Client, key string) { } -func (p *proxyConfigEtcd) getProxyUrls(ctx context.Context, client *EtcdClient, keyPrefix string) (*clientv3.GetResponse, error) { +func (p *proxyConfigEtcd) getProxyUrls(ctx context.Context, client etcd.Client, keyPrefix string) (*clientv3.GetResponse, error) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() return client.Get(ctx, keyPrefix, clientv3.WithPrefix()) } -func (p *proxyConfigEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data []byte, prevValue []byte) { +func (p *proxyConfigEtcd) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { var info ProxyInformationEtcd if err := json.Unmarshal(data, &info); err != nil { p.logger.Printf("Could not decode proxy information %s: %s", string(data), err) @@ -210,7 +211,7 @@ func (p *proxyConfigEtcd) EtcdKeyUpdated(client *EtcdClient, key string, data [] } } -func (p *proxyConfigEtcd) EtcdKeyDeleted(client *EtcdClient, key string, prevValue []byte) { +func (p *proxyConfigEtcd) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { p.mu.Lock() defer p.mu.Unlock() diff --git a/proxy_config_etcd_test.go b/proxy_config_etcd_test.go index 938ae9c..ef9a453 100644 --- a/proxy_config_etcd_test.go +++ b/proxy_config_etcd_test.go @@ -29,8 +29,8 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" - "go.etcd.io/etcd/server/v3/embed" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -40,9 +40,9 @@ type TestProxyInformationEtcd struct { OtherData string `json:"otherdata,omitempty"` } -func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*embed.Etcd, ProxyConfig) { +func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.TestServer, ProxyConfig) { t.Helper() - etcd, client := NewEtcdClientForTest(t) + embedEtcd, client := etcdtest.NewClientForTest(t) cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "keyprefix", "proxies/") logger := log.NewLoggerForTest(t) @@ -51,24 +51,24 @@ func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*embed.Etcd, ProxyConfig) t.Cleanup(func() { p.Stop() }) - return etcd, p + return embedEtcd, p } -func SetEtcdProxy(t *testing.T, etcd *embed.Etcd, path string, proxy *TestProxyInformationEtcd) { +func SetEtcdProxy(t *testing.T, server *etcdtest.TestServer, path string, proxy *TestProxyInformationEtcd) { t.Helper() data, _ := json.Marshal(proxy) - SetEtcdValue(etcd, path, data) + server.SetValue(path, data) } func TestProxyConfigEtcd(t *testing.T) { t.Parallel() proxy := newMcuProxyForConfig(t) - etcd, config := newProxyConfigEtcd(t, proxy) + embedEtcd, config := newProxyConfigEtcd(t, proxy) ctx, cancel := context.WithTimeout(t.Context(), time.Second) defer cancel() - SetEtcdProxy(t, etcd, "proxies/a", &TestProxyInformationEtcd{ + SetEtcdProxy(t, embedEtcd, "proxies/a", &TestProxyInformationEtcd{ Address: "https://foo/", }) proxy.Expect("add", "https://foo/") @@ -76,31 +76,31 @@ func TestProxyConfigEtcd(t *testing.T) { proxy.WaitForEvents(ctx) proxy.Expect("add", "https://bar/") - SetEtcdProxy(t, etcd, "proxies/b", &TestProxyInformationEtcd{ + SetEtcdProxy(t, embedEtcd, "proxies/b", &TestProxyInformationEtcd{ Address: "https://bar/", }) proxy.WaitForEvents(ctx) proxy.Expect("keep", "https://bar/") - SetEtcdProxy(t, etcd, "proxies/b", &TestProxyInformationEtcd{ + SetEtcdProxy(t, embedEtcd, "proxies/b", &TestProxyInformationEtcd{ Address: "https://bar/", OtherData: "ignore-me", }) proxy.WaitForEvents(ctx) proxy.Expect("remove", "https://foo/") - DeleteEtcdValue(etcd, "proxies/a") + embedEtcd.DeleteValue("proxies/a") proxy.WaitForEvents(ctx) proxy.Expect("remove", "https://bar/") proxy.Expect("add", "https://baz/") - SetEtcdProxy(t, etcd, "proxies/b", &TestProxyInformationEtcd{ + SetEtcdProxy(t, embedEtcd, "proxies/b", &TestProxyInformationEtcd{ Address: "https://baz/", }) proxy.WaitForEvents(ctx) // Adding the same hostname multiple times should not trigger an event. - SetEtcdProxy(t, etcd, "proxies/c", &TestProxyInformationEtcd{ + SetEtcdProxy(t, embedEtcd, "proxies/c", &TestProxyInformationEtcd{ Address: "https://baz/", }) time.Sleep(100 * time.Millisecond) diff --git a/server/main.go b/server/main.go index 022ef7c..e1e8a33 100644 --- a/server/main.go +++ b/server/main.go @@ -43,6 +43,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" @@ -204,7 +205,7 @@ func main() { } defer dnsMonitor.Stop() - etcdClient, err := signaling.NewEtcdClient(logger, cfg, "mcu") + etcdClient, err := etcd.NewClient(logger, cfg, "mcu") if err != nil { logger.Fatalf("Could not create etcd client: %s", err) } From f407b98443ffabcae91875c756f3138a6e0d7263 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 15 Dec 2025 10:04:46 +0100 Subject: [PATCH 420/549] Update generated files. --- api_backend_easyjson.go | 856 +++++++++++++++++----------------------- etcd/api_easyjson.go | 308 +++++++++++++++ 2 files changed, 673 insertions(+), 491 deletions(-) create mode 100644 etcd/api_easyjson.go diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 961402e..6dc0a11 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -8,6 +8,7 @@ import ( jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" api "github.com/strukturag/nextcloud-spreed-signaling/api" + etcd "github.com/strukturag/nextcloud-spreed-signaling/etcd" time "time" ) @@ -1473,134 +1474,7 @@ func (v *BackendServerInfoGrpc) UnmarshalJSON(data []byte) error { func (v *BackendServerInfoGrpc) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendServerInfoEtcd) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "endpoints": - if in.IsNull() { - in.Skip() - out.Endpoints = nil - } else { - in.Delim('[') - if out.Endpoints == nil { - if !in.IsDelim(']') { - out.Endpoints = make([]string, 0, 4) - } else { - out.Endpoints = []string{} - } - } else { - out.Endpoints = (out.Endpoints)[:0] - } - for !in.IsDelim(']') { - var v13 string - if in.IsNull() { - in.Skip() - } else { - v13 = string(in.String()) - } - out.Endpoints = append(out.Endpoints, v13) - in.WantComma() - } - in.Delim(']') - } - case "active": - if in.IsNull() { - in.Skip() - } else { - out.Active = string(in.String()) - } - case "connected": - if in.IsNull() { - in.Skip() - out.Connected = nil - } else { - if out.Connected == nil { - out.Connected = new(bool) - } - if in.IsNull() { - in.Skip() - } else { - *out.Connected = bool(in.Bool()) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendServerInfoEtcd) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"endpoints\":" - out.RawString(prefix[1:]) - if in.Endpoints == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v14, v15 := range in.Endpoints { - if v14 > 0 { - out.RawByte(',') - } - out.String(string(v15)) - } - out.RawByte(']') - } - } - if in.Active != "" { - const prefix string = ",\"active\":" - out.RawString(prefix) - out.String(string(in.Active)) - } - if in.Connected != nil { - const prefix string = ",\"connected\":" - out.RawString(prefix) - out.Bool(bool(*in.Connected)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v BackendServerInfoEtcd) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v BackendServerInfoEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *BackendServerInfoEtcd) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *BackendServerInfoEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) -} -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendServerInfoDialout) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendServerInfoDialout) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1660,13 +1534,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle out.Features = (out.Features)[:0] } for !in.IsDelim(']') { - var v16 string + var v13 string if in.IsNull() { in.Skip() } else { - v16 = string(in.String()) + v13 = string(in.String()) } - out.Features = append(out.Features, v16) + out.Features = append(out.Features, v13) in.WantComma() } in.Delim(']') @@ -1681,7 +1555,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendServerInfoDialout) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendServerInfoDialout) { out.RawByte('{') first := true _ = first @@ -1715,11 +1589,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw out.RawString(prefix) { out.RawByte('[') - for v17, v18 := range in.Features { - if v17 > 0 { + for v14, v15 := range in.Features { + if v14 > 0 { out.RawByte(',') } - out.String(string(v18)) + out.String(string(v15)) } out.RawByte(']') } @@ -1730,27 +1604,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoDialout) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoDialout) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendServerInfo) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendServerInfo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1786,13 +1660,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle out.Features = (out.Features)[:0] } for !in.IsDelim(']') { - var v19 string + var v16 string if in.IsNull() { in.Skip() } else { - v19 = string(in.String()) + v16 = string(in.String()) } - out.Features = append(out.Features, v19) + out.Features = append(out.Features, v16) in.WantComma() } in.Delim(']') @@ -1827,13 +1701,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle out.Dialout = (out.Dialout)[:0] } for !in.IsDelim(']') { - var v20 BackendServerInfoDialout + var v17 BackendServerInfoDialout if in.IsNull() { in.Skip() } else { - (v20).UnmarshalEasyJSON(in) + (v17).UnmarshalEasyJSON(in) } - out.Dialout = append(out.Dialout, v20) + out.Dialout = append(out.Dialout, v17) in.WantComma() } in.Delim(']') @@ -1868,13 +1742,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle out.Grpc = (out.Grpc)[:0] } for !in.IsDelim(']') { - var v21 BackendServerInfoGrpc + var v18 BackendServerInfoGrpc if in.IsNull() { in.Skip() } else { - (v21).UnmarshalEasyJSON(in) + (v18).UnmarshalEasyJSON(in) } - out.Grpc = append(out.Grpc, v21) + out.Grpc = append(out.Grpc, v18) in.WantComma() } in.Delim(']') @@ -1885,7 +1759,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle out.Etcd = nil } else { if out.Etcd == nil { - out.Etcd = new(BackendServerInfoEtcd) + out.Etcd = new(etcd.BackendServerInfoEtcd) } if in.IsNull() { in.Skip() @@ -1903,7 +1777,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendServerInfo) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendServerInfo) { out.RawByte('{') first := true _ = first @@ -1919,11 +1793,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw out.RawString("null") } else { out.RawByte('[') - for v22, v23 := range in.Features { - if v22 > 0 { + for v19, v20 := range in.Features { + if v19 > 0 { out.RawByte(',') } - out.String(string(v23)) + out.String(string(v20)) } out.RawByte(']') } @@ -1938,11 +1812,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw out.RawString(prefix) { out.RawByte('[') - for v24, v25 := range in.Dialout { - if v24 > 0 { + for v21, v22 := range in.Dialout { + if v21 > 0 { out.RawByte(',') } - (v25).MarshalEasyJSON(out) + (v22).MarshalEasyJSON(out) } out.RawByte(']') } @@ -1957,11 +1831,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw out.RawString(prefix) { out.RawByte('[') - for v26, v27 := range in.Grpc { - if v26 > 0 { + for v23, v24 := range in.Grpc { + if v23 > 0 { out.RawByte(',') } - (v27).MarshalEasyJSON(out) + (v24).MarshalEasyJSON(out) } out.RawByte(']') } @@ -1977,27 +1851,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2027,13 +1901,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v28 string + var v25 string if in.IsNull() { in.Skip() } else { - v28 = string(in.String()) + v25 = string(in.String()) } - out.UserIds = append(out.UserIds, v28) + out.UserIds = append(out.UserIds, v25) in.WantComma() } in.Delim(']') @@ -2056,7 +1930,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendRoomUpdateRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendRoomUpdateRequest) { out.RawByte('{') first := true _ = first @@ -2066,11 +1940,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v29, v30 := range in.UserIds { - if v29 > 0 { + for v26, v27 := range in.UserIds { + if v26 > 0 { out.RawByte(',') } - out.String(string(v30)) + out.String(string(v27)) } out.RawByte(']') } @@ -2091,27 +1965,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomUpdateRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomUpdateRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendRoomTransientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendRoomTransientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2161,7 +2035,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendRoomTransientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendRoomTransientRequest) { out.RawByte('{') first := true _ = first @@ -2197,27 +2071,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomTransientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomTransientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2261,13 +2135,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle out.SessionsList = (out.SessionsList)[:0] } for !in.IsDelim(']') { - var v31 api.PublicSessionId + var v28 api.PublicSessionId if in.IsNull() { in.Skip() } else { - v31 = api.PublicSessionId(in.String()) + v28 = api.PublicSessionId(in.String()) } - out.SessionsList = append(out.SessionsList, v31) + out.SessionsList = append(out.SessionsList, v28) in.WantComma() } in.Delim(']') @@ -2285,15 +2159,15 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle for !in.IsDelim('}') { key := api.PublicSessionId(in.String()) in.WantColon() - var v32 json.RawMessage + var v29 json.RawMessage if in.IsNull() { in.Skip() } else { if data := in.Raw(); in.Ok() { - in.AddError((v32).UnmarshalJSON(data)) + in.AddError((v29).UnmarshalJSON(data)) } } - (out.SessionsMap)[key] = v32 + (out.SessionsMap)[key] = v29 in.WantComma() } in.Delim('}') @@ -2308,7 +2182,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { out.RawByte('{') first := true _ = first @@ -2327,11 +2201,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw out.RawString(prefix) { out.RawByte('[') - for v33, v34 := range in.SessionsList { - if v33 > 0 { + for v30, v31 := range in.SessionsList { + if v30 > 0 { out.RawByte(',') } - out.String(string(v34)) + out.String(string(v31)) } out.RawByte(']') } @@ -2341,16 +2215,16 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw out.RawString(prefix) { out.RawByte('{') - v35First := true - for v35Name, v35Value := range in.SessionsMap { - if v35First { - v35First = false + v32First := true + for v32Name, v32Value := range in.SessionsMap { + if v32First { + v32First = false } else { out.RawByte(',') } - out.String(string(v35Name)) + out.String(string(v32Name)) out.RawByte(':') - out.Raw((v35Value).MarshalJSON()) + out.Raw((v32Value).MarshalJSON()) } out.RawByte('}') } @@ -2361,27 +2235,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2411,33 +2285,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v36 api.StringMap + var v33 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v36 = make(api.StringMap) + v33 = make(api.StringMap) } else { - v36 = nil + v33 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v37 interface{} - if m, ok := v37.(easyjson.Unmarshaler); ok { + var v34 interface{} + if m, ok := v34.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v37.(json.Unmarshaler); ok { + } else if m, ok := v34.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v37 = in.Interface() + v34 = in.Interface() } - (v36)[key] = v37 + (v33)[key] = v34 in.WantComma() } in.Delim('}') } - out.Changed = append(out.Changed, v36) + out.Changed = append(out.Changed, v33) in.WantComma() } in.Delim(']') @@ -2458,33 +2332,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v38 api.StringMap + var v35 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v38 = make(api.StringMap) + v35 = make(api.StringMap) } else { - v38 = nil + v35 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v39 interface{} - if m, ok := v39.(easyjson.Unmarshaler); ok { + var v36 interface{} + if m, ok := v36.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v39.(json.Unmarshaler); ok { + } else if m, ok := v36.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v39 = in.Interface() + v36 = in.Interface() } - (v38)[key] = v39 + (v35)[key] = v36 in.WantComma() } in.Delim('}') } - out.Users = append(out.Users, v38) + out.Users = append(out.Users, v35) in.WantComma() } in.Delim(']') @@ -2499,7 +2373,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomParticipantsRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendRoomParticipantsRequest) { out.RawByte('{') first := true _ = first @@ -2509,7 +2383,48 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v40, v41 := range in.Changed { + for v37, v38 := range in.Changed { + if v37 > 0 { + out.RawByte(',') + } + if v38 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + out.RawString(`null`) + } else { + out.RawByte('{') + v39First := true + for v39Name, v39Value := range v38 { + if v39First { + v39First = false + } else { + out.RawByte(',') + } + out.String(string(v39Name)) + out.RawByte(':') + if m, ok := v39Value.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := v39Value.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(v39Value)) + } + } + out.RawByte('}') + } + } + out.RawByte(']') + } + } + if len(in.Users) != 0 { + const prefix string = ",\"users\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + { + out.RawByte('[') + for v40, v41 := range in.Users { if v40 > 0 { out.RawByte(',') } @@ -2540,74 +2455,33 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw out.RawByte(']') } } - if len(in.Users) != 0 { - const prefix string = ",\"users\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v43, v44 := range in.Users { - if v43 > 0 { - out.RawByte(',') - } - if v44 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { - out.RawString(`null`) - } else { - out.RawByte('{') - v45First := true - for v45Name, v45Value := range v44 { - if v45First { - v45First = false - } else { - out.RawByte(',') - } - out.String(string(v45Name)) - out.RawByte(':') - if m, ok := v45Value.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := v45Value.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(v45Value)) - } - } - out.RawByte('}') - } - } - out.RawByte(']') - } - } out.RawByte('}') } // MarshalJSON supports json.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomMessageRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2639,7 +2513,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomMessageRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomMessageRequest) { out.RawByte('{') first := true _ = first @@ -2655,27 +2529,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomInviteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomInviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2705,13 +2579,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v46 string + var v43 string if in.IsNull() { in.Skip() } else { - v46 = string(in.String()) + v43 = string(in.String()) } - out.UserIds = append(out.UserIds, v46) + out.UserIds = append(out.UserIds, v43) in.WantComma() } in.Delim(']') @@ -2732,13 +2606,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle out.AllUserIds = (out.AllUserIds)[:0] } for !in.IsDelim(']') { - var v47 string + var v44 string if in.IsNull() { in.Skip() } else { - v47 = string(in.String()) + v44 = string(in.String()) } - out.AllUserIds = append(out.AllUserIds, v47) + out.AllUserIds = append(out.AllUserIds, v44) in.WantComma() } in.Delim(']') @@ -2761,7 +2635,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomInviteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomInviteRequest) { out.RawByte('{') first := true _ = first @@ -2771,11 +2645,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v48, v49 := range in.UserIds { - if v48 > 0 { + for v45, v46 := range in.UserIds { + if v45 > 0 { out.RawByte(',') } - out.String(string(v49)) + out.String(string(v46)) } out.RawByte(']') } @@ -2790,11 +2664,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw } { out.RawByte('[') - for v50, v51 := range in.AllUserIds { - if v50 > 0 { + for v47, v48 := range in.AllUserIds { + if v47 > 0 { out.RawByte(',') } - out.String(string(v51)) + out.String(string(v48)) } out.RawByte(']') } @@ -2815,27 +2689,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendRoomInCallRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomInCallRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2879,33 +2753,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v52 api.StringMap + var v49 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v52 = make(api.StringMap) + v49 = make(api.StringMap) } else { - v52 = nil + v49 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v53 interface{} - if m, ok := v53.(easyjson.Unmarshaler); ok { + var v50 interface{} + if m, ok := v50.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v53.(json.Unmarshaler); ok { + } else if m, ok := v50.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v53 = in.Interface() + v50 = in.Interface() } - (v52)[key] = v53 + (v49)[key] = v50 in.WantComma() } in.Delim('}') } - out.Changed = append(out.Changed, v52) + out.Changed = append(out.Changed, v49) in.WantComma() } in.Delim(']') @@ -2926,33 +2800,33 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v54 api.StringMap + var v51 api.StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v54 = make(api.StringMap) + v51 = make(api.StringMap) } else { - v54 = nil + v51 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v55 interface{} - if m, ok := v55.(easyjson.Unmarshaler); ok { + var v52 interface{} + if m, ok := v52.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v55.(json.Unmarshaler); ok { + } else if m, ok := v52.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v55 = in.Interface() + v52 = in.Interface() } - (v54)[key] = v55 + (v51)[key] = v52 in.WantComma() } in.Delim('}') } - out.Users = append(out.Users, v54) + out.Users = append(out.Users, v51) in.WantComma() } in.Delim(']') @@ -2967,7 +2841,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendRoomInCallRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomInCallRequest) { out.RawByte('{') first := true _ = first @@ -2997,7 +2871,48 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw } { out.RawByte('[') - for v56, v57 := range in.Changed { + for v53, v54 := range in.Changed { + if v53 > 0 { + out.RawByte(',') + } + if v54 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + out.RawString(`null`) + } else { + out.RawByte('{') + v55First := true + for v55Name, v55Value := range v54 { + if v55First { + v55First = false + } else { + out.RawByte(',') + } + out.String(string(v55Name)) + out.RawByte(':') + if m, ok := v55Value.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := v55Value.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(v55Value)) + } + } + out.RawByte('}') + } + } + out.RawByte(']') + } + } + if len(in.Users) != 0 { + const prefix string = ",\"users\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + { + out.RawByte('[') + for v56, v57 := range in.Users { if v56 > 0 { out.RawByte(',') } @@ -3028,74 +2943,33 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw out.RawByte(']') } } - if len(in.Users) != 0 { - const prefix string = ",\"users\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v59, v60 := range in.Users { - if v59 > 0 { - out.RawByte(',') - } - if v60 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { - out.RawString(`null`) - } else { - out.RawByte('{') - v61First := true - for v61Name, v61Value := range v60 { - if v61First { - v61First = false - } else { - out.RawByte(',') - } - out.String(string(v61Name)) - out.RawByte(':') - if m, ok := v61Value.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := v61Value.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(v61Value)) - } - } - out.RawByte('}') - } - } - out.RawByte(']') - } - } out.RawByte('}') } // MarshalJSON supports json.Marshaler interface func (v BackendRoomInCallRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInCallRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3125,13 +2999,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v62 string + var v59 string if in.IsNull() { in.Skip() } else { - v62 = string(in.String()) + v59 = string(in.String()) } - out.UserIds = append(out.UserIds, v62) + out.UserIds = append(out.UserIds, v59) in.WantComma() } in.Delim(']') @@ -3152,13 +3026,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle out.SessionIds = (out.SessionIds)[:0] } for !in.IsDelim(']') { - var v63 api.RoomSessionId + var v60 api.RoomSessionId if in.IsNull() { in.Skip() } else { - v63 = api.RoomSessionId(in.String()) + v60 = api.RoomSessionId(in.String()) } - out.SessionIds = append(out.SessionIds, v63) + out.SessionIds = append(out.SessionIds, v60) in.WantComma() } in.Delim(']') @@ -3179,13 +3053,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle out.AllUserIds = (out.AllUserIds)[:0] } for !in.IsDelim(']') { - var v64 string + var v61 string if in.IsNull() { in.Skip() } else { - v64 = string(in.String()) + v61 = string(in.String()) } - out.AllUserIds = append(out.AllUserIds, v64) + out.AllUserIds = append(out.AllUserIds, v61) in.WantComma() } in.Delim(']') @@ -3208,7 +3082,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendRoomDisinviteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendRoomDisinviteRequest) { out.RawByte('{') first := true _ = first @@ -3218,11 +3092,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v65, v66 := range in.UserIds { - if v65 > 0 { + for v62, v63 := range in.UserIds { + if v62 > 0 { out.RawByte(',') } - out.String(string(v66)) + out.String(string(v63)) } out.RawByte(']') } @@ -3237,11 +3111,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw } { out.RawByte('[') - for v67, v68 := range in.SessionIds { - if v67 > 0 { + for v64, v65 := range in.SessionIds { + if v64 > 0 { out.RawByte(',') } - out.String(string(v68)) + out.String(string(v65)) } out.RawByte(']') } @@ -3256,11 +3130,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw } { out.RawByte('[') - for v69, v70 := range in.AllUserIds { - if v69 > 0 { + for v66, v67 := range in.AllUserIds { + if v66 > 0 { out.RawByte(',') } - out.String(string(v70)) + out.String(string(v67)) } out.RawByte(']') } @@ -3281,27 +3155,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3345,7 +3219,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendRoomDialoutResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendRoomDialoutResponse) { out.RawByte('{') first := true _ = first @@ -3371,27 +3245,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3429,7 +3303,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendRoomDialoutRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendRoomDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3449,27 +3323,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendRoomDialoutError) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendRoomDialoutError) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3505,7 +3379,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendRoomDialoutError) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendRoomDialoutError) { out.RawByte('{') first := true _ = first @@ -3525,27 +3399,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutError) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutError) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3575,13 +3449,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle out.UserIds = (out.UserIds)[:0] } for !in.IsDelim(']') { - var v71 string + var v68 string if in.IsNull() { in.Skip() } else { - v71 = string(in.String()) + v68 = string(in.String()) } - out.UserIds = append(out.UserIds, v71) + out.UserIds = append(out.UserIds, v68) in.WantComma() } in.Delim(']') @@ -3596,7 +3470,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendRoomDeleteRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendRoomDeleteRequest) { out.RawByte('{') first := true _ = first @@ -3606,11 +3480,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw out.RawString(prefix[1:]) { out.RawByte('[') - for v72, v73 := range in.UserIds { - if v72 > 0 { + for v69, v70 := range in.UserIds { + if v69 > 0 { out.RawByte(',') } - out.String(string(v73)) + out.String(string(v70)) } out.RawByte(']') } @@ -3621,27 +3495,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDeleteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDeleteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendPingEntry) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendPingEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3677,7 +3551,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendPingEntry) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendPingEntry) { out.RawByte('{') first := true _ = first @@ -3703,27 +3577,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendPingEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendPingEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendPingEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendPingEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendClientSessionResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendClientSessionResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3759,7 +3633,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendClientSessionResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendClientSessionResponse) { out.RawByte('{') first := true _ = first @@ -3779,27 +3653,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientSessionRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendClientSessionRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3861,7 +3735,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientSessionRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendClientSessionRequest) { out.RawByte('{') first := true _ = first @@ -3901,27 +3775,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientRoomResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3986,13 +3860,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle *out.Permissions = (*out.Permissions)[:0] } for !in.IsDelim(']') { - var v74 Permission + var v71 Permission if in.IsNull() { in.Skip() } else { - v74 = Permission(in.String()) + v71 = Permission(in.String()) } - *out.Permissions = append(*out.Permissions, v74) + *out.Permissions = append(*out.Permissions, v71) in.WantComma() } in.Delim(']') @@ -4008,7 +3882,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientRoomResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientRoomResponse) { out.RawByte('{') first := true _ = first @@ -4039,11 +3913,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw out.RawString("null") } else { out.RawByte('[') - for v75, v76 := range *in.Permissions { - if v75 > 0 { + for v72, v73 := range *in.Permissions { + if v72 > 0 { out.RawByte(',') } - out.String(string(v76)) + out.String(string(v73)) } out.RawByte(']') } @@ -4054,27 +3928,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientRoomRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4146,7 +4020,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientRoomRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientRoomRequest) { out.RawByte('{') first := true _ = first @@ -4196,27 +4070,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientRingResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientRingResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4252,7 +4126,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientRingResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientRingResponse) { out.RawByte('{') first := true _ = first @@ -4272,27 +4146,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRingResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRingResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4392,7 +4266,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientResponse) { out.RawByte('{') first := true _ = first @@ -4432,27 +4306,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4538,7 +4412,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientRequest) { out.RawByte('{') first := true _ = first @@ -4573,27 +4447,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientPingRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientPingRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4635,13 +4509,13 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle out.Entries = (out.Entries)[:0] } for !in.IsDelim(']') { - var v77 BackendPingEntry + var v74 BackendPingEntry if in.IsNull() { in.Skip() } else { - (v77).UnmarshalEasyJSON(in) + (v74).UnmarshalEasyJSON(in) } - out.Entries = append(out.Entries, v77) + out.Entries = append(out.Entries, v74) in.WantComma() } in.Delim(']') @@ -4656,7 +4530,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientPingRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientPingRequest) { out.RawByte('{') first := true _ = first @@ -4677,11 +4551,11 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw out.RawString("null") } else { out.RawByte('[') - for v78, v79 := range in.Entries { - if v78 > 0 { + for v75, v76 := range in.Entries { + if v75 > 0 { out.RawByte(',') } - (v79).MarshalEasyJSON(out) + (v76).MarshalEasyJSON(out) } out.RawByte(']') } @@ -4692,27 +4566,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientPingRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientPingRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientAuthResponse) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientAuthResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4756,7 +4630,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientAuthResponse) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientAuthResponse) { out.RawByte('{') first := true _ = first @@ -4781,27 +4655,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *BackendClientAuthRequest) { +func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientAuthRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4839,7 +4713,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in BackendClientAuthRequest) { +func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientAuthRequest) { out.RawByte('{') first := true _ = first @@ -4859,23 +4733,23 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } diff --git a/etcd/api_easyjson.go b/etcd/api_easyjson.go new file mode 100644 index 0000000..f580a59 --- /dev/null +++ b/etcd/api_easyjson.go @@ -0,0 +1,308 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package etcd + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" + api "github.com/strukturag/nextcloud-spreed-signaling/api" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(in *jlexer.Lexer, out *BackendServerInfoEtcd) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "endpoints": + if in.IsNull() { + in.Skip() + out.Endpoints = nil + } else { + in.Delim('[') + if out.Endpoints == nil { + if !in.IsDelim(']') { + out.Endpoints = make([]string, 0, 4) + } else { + out.Endpoints = []string{} + } + } else { + out.Endpoints = (out.Endpoints)[:0] + } + for !in.IsDelim(']') { + var v1 string + if in.IsNull() { + in.Skip() + } else { + v1 = string(in.String()) + } + out.Endpoints = append(out.Endpoints, v1) + in.WantComma() + } + in.Delim(']') + } + case "active": + if in.IsNull() { + in.Skip() + } else { + out.Active = string(in.String()) + } + case "connected": + if in.IsNull() { + in.Skip() + out.Connected = nil + } else { + if out.Connected == nil { + out.Connected = new(bool) + } + if in.IsNull() { + in.Skip() + } else { + *out.Connected = bool(in.Bool()) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(out *jwriter.Writer, in BackendServerInfoEtcd) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"endpoints\":" + out.RawString(prefix[1:]) + if in.Endpoints == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v2, v3 := range in.Endpoints { + if v2 > 0 { + out.RawByte(',') + } + out.String(string(v3)) + } + out.RawByte(']') + } + } + if in.Active != "" { + const prefix string = ",\"active\":" + out.RawString(prefix) + out.String(string(in.Active)) + } + if in.Connected != nil { + const prefix string = ",\"connected\":" + out.RawString(prefix) + out.Bool(bool(*in.Connected)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoEtcd) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoEtcd) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoEtcd) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(in *jlexer.Lexer, out *BackendInformationEtcd) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "url": + if in.IsNull() { + in.Skip() + } else { + out.Url = string(in.String()) + } + case "urls": + if in.IsNull() { + in.Skip() + out.Urls = nil + } else { + in.Delim('[') + if out.Urls == nil { + if !in.IsDelim(']') { + out.Urls = make([]string, 0, 4) + } else { + out.Urls = []string{} + } + } else { + out.Urls = (out.Urls)[:0] + } + for !in.IsDelim(']') { + var v4 string + if in.IsNull() { + in.Skip() + } else { + v4 = string(in.String()) + } + out.Urls = append(out.Urls, v4) + in.WantComma() + } + in.Delim(']') + } + case "secret": + if in.IsNull() { + in.Skip() + } else { + out.Secret = string(in.String()) + } + case "maxstreambitrate": + if in.IsNull() { + in.Skip() + } else { + out.MaxStreamBitrate = api.Bandwidth(in.Uint64()) + } + case "maxscreenbitrate": + if in.IsNull() { + in.Skip() + } else { + out.MaxScreenBitrate = api.Bandwidth(in.Uint64()) + } + case "sessionlimit": + if in.IsNull() { + in.Skip() + } else { + out.SessionLimit = uint64(in.Uint64()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(out *jwriter.Writer, in BackendInformationEtcd) { + out.RawByte('{') + first := true + _ = first + if in.Url != "" { + const prefix string = ",\"url\":" + first = false + out.RawString(prefix[1:]) + out.String(string(in.Url)) + } + if len(in.Urls) != 0 { + const prefix string = ",\"urls\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + { + out.RawByte('[') + for v5, v6 := range in.Urls { + if v5 > 0 { + out.RawByte(',') + } + out.String(string(v6)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"secret\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Secret)) + } + if in.MaxStreamBitrate != 0 { + const prefix string = ",\"maxstreambitrate\":" + out.RawString(prefix) + out.Uint64(uint64(in.MaxStreamBitrate)) + } + if in.MaxScreenBitrate != 0 { + const prefix string = ",\"maxscreenbitrate\":" + out.RawString(prefix) + out.Uint64(uint64(in.MaxScreenBitrate)) + } + if in.SessionLimit != 0 { + const prefix string = ",\"sessionlimit\":" + out.RawString(prefix) + out.Uint64(uint64(in.SessionLimit)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendInformationEtcd) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendInformationEtcd) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(l, v) +} From 80040aaa6dd297b645e4186120400e3b46d8688c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 15 Dec 2025 10:33:16 +0100 Subject: [PATCH 421/549] Move DNS monitor to "dns" package. --- .codecov.yml | 4 + dns_monitor.go => dns/monitor.go | 78 ++++++------ dns_monitor_test.go => dns/monitor_test.go | 135 ++++++--------------- dns/test_helpers.go | 98 +++++++++++++++ grpc_client.go | 9 +- grpc_client_test.go | 23 ++-- mcu_proxy.go | 3 +- mcu_proxy_test.go | 13 +- proxy_config_static.go | 9 +- proxy_config_static_test.go | 13 +- server/main.go | 3 +- 11 files changed, 213 insertions(+), 175 deletions(-) rename dns_monitor.go => dns/monitor.go (76%) rename dns_monitor_test.go => dns/monitor_test.go (66%) create mode 100644 dns/test_helpers.go diff --git a/.codecov.yml b/.codecov.yml index 58b6c01..d5787fd 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -38,6 +38,10 @@ component_management: name: container paths: - container/** + - component_id: module_dns + name: dns + paths: + - dns/** - component_id: module_etcd name: etcd paths: diff --git a/dns_monitor.go b/dns/monitor.go similarity index 76% rename from dns_monitor.go rename to dns/monitor.go index 33bc12b..03d7021 100644 --- a/dns_monitor.go +++ b/dns/monitor.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 dns import ( "context" @@ -35,22 +35,22 @@ import ( ) const ( - defaultDnsMonitorInterval = time.Second + defaultMonitorInterval = time.Second ) -type DnsMonitorCallback = func(entry *DnsMonitorEntry, all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) +type MonitorCallback = func(entry *MonitorEntry, all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) -type DnsMonitorEntry struct { - entry atomic.Pointer[dnsMonitorEntry] +type MonitorEntry struct { + entry atomic.Pointer[monitorEntry] url string - callback DnsMonitorCallback + callback MonitorCallback } -func (e *DnsMonitorEntry) URL() string { +func (e *MonitorEntry) URL() string { return e.url } -type dnsMonitorEntry struct { +type monitorEntry struct { hostname string hostIP net.IP @@ -58,10 +58,10 @@ type dnsMonitorEntry struct { // +checklocks:mu ips []net.IP // +checklocks:mu - entries map[*DnsMonitorEntry]bool + entries map[*MonitorEntry]bool } -func (e *dnsMonitorEntry) clearRemoved() bool { +func (e *monitorEntry) clearRemoved() bool { e.mu.Lock() defer e.mu.Unlock() @@ -76,7 +76,7 @@ func (e *dnsMonitorEntry) clearRemoved() bool { return deleted && len(e.entries) == 0 } -func (e *dnsMonitorEntry) setIPs(ips []net.IP, fromIP bool) { +func (e *monitorEntry) setIPs(ips []net.IP, fromIP bool) { e.mu.Lock() defer e.mu.Unlock() @@ -133,14 +133,14 @@ func (e *dnsMonitorEntry) setIPs(ips []net.IP, fromIP bool) { } } -func (e *dnsMonitorEntry) addEntry(entry *DnsMonitorEntry) { +func (e *monitorEntry) addEntry(entry *MonitorEntry) { e.mu.Lock() defer e.mu.Unlock() e.entries[entry] = true } -func (e *dnsMonitorEntry) removeEntry(entry *DnsMonitorEntry) bool { +func (e *monitorEntry) removeEntry(entry *MonitorEntry) bool { e.mu.Lock() defer e.mu.Unlock() @@ -149,18 +149,18 @@ func (e *dnsMonitorEntry) removeEntry(entry *DnsMonitorEntry) bool { } // +checklocks:e.mu -func (e *dnsMonitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) { +func (e *monitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) { for entry := range e.entries { entry.callback(entry, all, add, keep, remove) } } -type DnsMonitorLookupFunc func(hostname string) ([]net.IP, error) +type MonitorLookupFunc func(hostname string) ([]net.IP, error) -type DnsMonitor struct { +type Monitor struct { logger log.Logger interval time.Duration - lookupFunc DnsMonitorLookupFunc + lookupFunc MonitorLookupFunc stopCtx context.Context stopFunc func() @@ -168,25 +168,22 @@ type DnsMonitor struct { mu sync.RWMutex cond *sync.Cond - hostnames map[string]*dnsMonitorEntry + hostnames map[string]*monitorEntry tickerWaiting atomic.Bool hasRemoved atomic.Bool - - // Can be overwritten from tests. - checkHostnames func() } -func NewDnsMonitor(logger log.Logger, interval time.Duration, lookupFunc DnsMonitorLookupFunc) (*DnsMonitor, error) { +func NewMonitor(logger log.Logger, interval time.Duration, lookupFunc MonitorLookupFunc) (*Monitor, error) { if interval < 0 { - interval = defaultDnsMonitorInterval + interval = defaultMonitorInterval } if lookupFunc == nil { lookupFunc = net.LookupIP } stopCtx, stopFunc := context.WithCancel(context.Background()) - monitor := &DnsMonitor{ + monitor := &Monitor{ logger: logger, interval: interval, lookupFunc: lookupFunc, @@ -195,25 +192,24 @@ func NewDnsMonitor(logger log.Logger, interval time.Duration, lookupFunc DnsMoni stopFunc: stopFunc, stopped: make(chan struct{}), - hostnames: make(map[string]*dnsMonitorEntry), + hostnames: make(map[string]*monitorEntry), } monitor.cond = sync.NewCond(&monitor.mu) - monitor.checkHostnames = monitor.doCheckHostnames return monitor, nil } -func (m *DnsMonitor) Start() error { +func (m *Monitor) Start() error { go m.run() return nil } -func (m *DnsMonitor) Stop() { +func (m *Monitor) Stop() { m.stopFunc() m.cond.Signal() <-m.stopped } -func (m *DnsMonitor) Add(target string, callback DnsMonitorCallback) (*DnsMonitorEntry, error) { +func (m *Monitor) Add(target string, callback MonitorCallback) (*MonitorEntry, error) { var hostname string if strings.Contains(target, "://") { // Full URL passed. @@ -233,17 +229,17 @@ func (m *DnsMonitor) Add(target string, callback DnsMonitorCallback) (*DnsMonito m.mu.Lock() defer m.mu.Unlock() - e := &DnsMonitorEntry{ + e := &MonitorEntry{ url: target, callback: callback, } entry, found := m.hostnames[hostname] if !found { - entry = &dnsMonitorEntry{ + entry = &monitorEntry{ hostname: hostname, hostIP: net.ParseIP(hostname), - entries: make(map[*DnsMonitorEntry]bool), + entries: make(map[*MonitorEntry]bool), } m.hostnames[hostname] = entry } @@ -253,7 +249,7 @@ func (m *DnsMonitor) Add(target string, callback DnsMonitorCallback) (*DnsMonito return e, nil } -func (m *DnsMonitor) Remove(entry *DnsMonitorEntry) { +func (m *Monitor) Remove(entry *MonitorEntry) { oldEntry := entry.entry.Swap(nil) if oldEntry == nil { // Already removed. @@ -283,7 +279,7 @@ func (m *DnsMonitor) Remove(entry *DnsMonitorEntry) { } } -func (m *DnsMonitor) clearRemoved() { +func (m *Monitor) clearRemoved() { if !m.hasRemoved.CompareAndSwap(true, false) { return } @@ -298,7 +294,7 @@ func (m *DnsMonitor) clearRemoved() { } } -func (m *DnsMonitor) waitForEntries() (waited bool) { +func (m *Monitor) waitForEntries() (waited bool) { m.mu.Lock() defer m.mu.Unlock() @@ -309,7 +305,7 @@ func (m *DnsMonitor) waitForEntries() (waited bool) { return } -func (m *DnsMonitor) run() { +func (m *Monitor) run() { ticker := time.NewTicker(m.interval) defer ticker.Stop() defer close(m.stopped) @@ -320,7 +316,7 @@ func (m *DnsMonitor) run() { if m.stopCtx.Err() == nil { // Initial check when a new entry was added. More checks will be // triggered by the Ticker. - m.checkHostnames() + m.CheckHostnames() continue } } @@ -330,12 +326,12 @@ func (m *DnsMonitor) run() { case <-m.stopCtx.Done(): return case <-ticker.C: - m.checkHostnames() + m.CheckHostnames() } } } -func (m *DnsMonitor) doCheckHostnames() { +func (m *Monitor) CheckHostnames() { m.clearRemoved() m.mu.RLock() @@ -346,7 +342,7 @@ func (m *DnsMonitor) doCheckHostnames() { } } -func (m *DnsMonitor) checkHostname(entry *dnsMonitorEntry) { +func (m *Monitor) checkHostname(entry *monitorEntry) { if len(entry.hostIP) > 0 { entry.setIPs([]net.IP{entry.hostIP}, true) return @@ -361,7 +357,7 @@ func (m *DnsMonitor) checkHostname(entry *dnsMonitorEntry) { entry.setIPs(ips, false) } -func (m *DnsMonitor) waitForTicker(ctx context.Context) error { +func (m *Monitor) WaitForTicker(ctx context.Context) error { for !m.tickerWaiting.Load() { time.Sleep(time.Millisecond) if err := ctx.Err(); err != nil { diff --git a/dns_monitor_test.go b/dns/monitor_test.go similarity index 66% rename from dns_monitor_test.go rename to dns/monitor_test.go index fdab473..f8d8546 100644 --- a/dns_monitor_test.go +++ b/dns/monitor_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 dns import ( "context" @@ -33,117 +33,50 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/strukturag/nextcloud-spreed-signaling/log" ) -type mockDnsLookup struct { - sync.RWMutex - - // +checklocks:RWMutex - ips map[string][]net.IP -} - -func newMockDnsLookupForTest(t *testing.T) *mockDnsLookup { - t.Helper() - mock := &mockDnsLookup{ - ips: make(map[string][]net.IP), - } - return mock -} - -func (m *mockDnsLookup) Set(host string, ips []net.IP) { - m.Lock() - defer m.Unlock() - - m.ips[host] = ips -} - -func (m *mockDnsLookup) Get(host string) []net.IP { - m.Lock() - defer m.Unlock() - - return m.ips[host] -} - -func (m *mockDnsLookup) lookup(host string) ([]net.IP, error) { - m.RLock() - defer m.RUnlock() - - ips, found := m.ips[host] - if !found { - return nil, &net.DNSError{ - Err: "could not resolve " + host, - Name: host, - IsNotFound: true, - } - } - - return append([]net.IP{}, ips...), nil -} - -func newDnsMonitorForTest(t *testing.T, interval time.Duration, lookup *mockDnsLookup) *DnsMonitor { - t.Helper() - require := require.New(t) - - logger := log.NewLoggerForTest(t) - var lookupFunc DnsMonitorLookupFunc - if lookup != nil { - lookupFunc = lookup.lookup - } - monitor, err := NewDnsMonitor(logger, interval, lookupFunc) - require.NoError(err) - - t.Cleanup(func() { - monitor.Stop() - }) - - require.NoError(monitor.Start()) - return monitor -} - -type dnsMonitorReceiverRecord struct { +type monitorReceiverRecord struct { all []net.IP add []net.IP keep []net.IP remove []net.IP } -func (r *dnsMonitorReceiverRecord) Equal(other *dnsMonitorReceiverRecord) bool { +func (r *monitorReceiverRecord) Equal(other *monitorReceiverRecord) bool { return r == other || (reflect.DeepEqual(r.add, other.add) && reflect.DeepEqual(r.keep, other.keep) && reflect.DeepEqual(r.remove, other.remove)) } -func (r *dnsMonitorReceiverRecord) String() string { +func (r *monitorReceiverRecord) String() string { return fmt.Sprintf("all=%v, add=%v, keep=%v, remove=%v", r.all, r.add, r.keep, r.remove) } var ( - expectNone = &dnsMonitorReceiverRecord{} // +checklocksignore: Global readonly variable. + expectNone = &monitorReceiverRecord{} // +checklocksignore: Global readonly variable. ) -type dnsMonitorReceiver struct { +type monitorReceiver struct { sync.Mutex t *testing.T // +checklocks:Mutex - expected *dnsMonitorReceiverRecord + expected *monitorReceiverRecord // +checklocks:Mutex - received *dnsMonitorReceiverRecord + received *monitorReceiverRecord } -func newDnsMonitorReceiverForTest(t *testing.T) *dnsMonitorReceiver { - return &dnsMonitorReceiver{ +func newMonitorReceiverForTest(t *testing.T) *monitorReceiver { + return &monitorReceiver{ t: t, } } -func (r *dnsMonitorReceiver) OnLookup(entry *DnsMonitorEntry, all, add, keep, remove []net.IP) { +func (r *monitorReceiver) OnLookup(entry *MonitorEntry, all, add, keep, remove []net.IP) { r.Lock() defer r.Unlock() - received := &dnsMonitorReceiverRecord{ + received := &monitorReceiverRecord{ all: all, add: add, keep: keep, @@ -169,7 +102,7 @@ func (r *dnsMonitorReceiver) OnLookup(entry *DnsMonitorEntry, all, add, keep, re r.expected = nil } -func (r *dnsMonitorReceiver) WaitForExpected(ctx context.Context) { +func (r *monitorReceiver) WaitForExpected(ctx context.Context) { r.t.Helper() r.Lock() defer r.Unlock() @@ -188,7 +121,7 @@ func (r *dnsMonitorReceiver) WaitForExpected(ctx context.Context) { } } -func (r *dnsMonitorReceiver) Expect(all, add, keep, remove []net.IP) { +func (r *monitorReceiver) Expect(all, add, keep, remove []net.IP) { r.t.Helper() r.Lock() defer r.Unlock() @@ -197,7 +130,7 @@ func (r *dnsMonitorReceiver) Expect(all, add, keep, remove []net.IP) { assert.Fail(r.t, "didn't get previous message", "expected %v", r.expected) } - expected := &dnsMonitorReceiverRecord{ + expected := &monitorReceiverRecord{ all: all, add: add, keep: keep, @@ -211,7 +144,7 @@ func (r *dnsMonitorReceiver) Expect(all, add, keep, remove []net.IP) { r.expected = expected } -func (r *dnsMonitorReceiver) ExpectNone() { +func (r *monitorReceiver) ExpectNone() { r.t.Helper() r.Lock() defer r.Unlock() @@ -223,14 +156,14 @@ func (r *dnsMonitorReceiver) ExpectNone() { r.expected = expectNone } -func TestDnsMonitor(t *testing.T) { +func TestMonitor(t *testing.T) { t.Parallel() - lookup := newMockDnsLookupForTest(t) + lookup := NewMockLookupForTest(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval, lookup) + monitor := NewMonitorForTest(t, interval, lookup) ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.1.1") @@ -241,7 +174,7 @@ func TestDnsMonitor(t *testing.T) { } lookup.Set("foo", ips1) - rec1 := newDnsMonitorReceiverForTest(t) + rec1 := newMonitorReceiverForTest(t) rec1.Expect(ips1, ips1, nil, nil) entry1, err := monitor.Add("https://foo:12345", rec1.OnLookup) @@ -292,20 +225,20 @@ func TestDnsMonitor(t *testing.T) { time.Sleep(5 * interval) } -func TestDnsMonitorIP(t *testing.T) { +func TestMonitorIP(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval, nil) + monitor := NewMonitorForTest(t, interval, nil) ip := "192.168.0.1" ips := []net.IP{ net.ParseIP(ip), } - rec1 := newDnsMonitorReceiverForTest(t) + rec1 := newMonitorReceiverForTest(t) rec1.Expect(ips, ips, nil, nil) entry, err := monitor.Add(ip+":12345", rec1.OnLookup) @@ -318,15 +251,15 @@ func TestDnsMonitorIP(t *testing.T) { time.Sleep(5 * interval) } -func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { +func TestMonitorNoLookupIfEmpty(t *testing.T) { t.Parallel() interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval, nil) + monitor := NewMonitorForTest(t, interval, nil) var checked atomic.Bool - monitor.checkHostnames = func() { + monitor.lookupFunc = func(hostname string) ([]net.IP, error) { checked.Store(true) - monitor.doCheckHostnames() + return net.LookupIP(hostname) } time.Sleep(10 * interval) @@ -335,20 +268,20 @@ func TestDnsMonitorNoLookupIfEmpty(t *testing.T) { type deadlockMonitorReceiver struct { t *testing.T - monitor *DnsMonitor // +checklocksignore: Only written to from constructor. + monitor *Monitor // +checklocksignore: Only written to from constructor. mu sync.RWMutex wg sync.WaitGroup // +checklocks:mu - entry *DnsMonitorEntry + entry *MonitorEntry started chan struct{} // +checklocks:mu triggered bool closed atomic.Bool } -func newDeadlockMonitorReceiver(t *testing.T, monitor *DnsMonitor) *deadlockMonitorReceiver { +func newDeadlockMonitorReceiver(t *testing.T, monitor *Monitor) *deadlockMonitorReceiver { return &deadlockMonitorReceiver{ t: t, monitor: monitor, @@ -356,7 +289,7 @@ func newDeadlockMonitorReceiver(t *testing.T, monitor *DnsMonitor) *deadlockMoni } } -func (r *deadlockMonitorReceiver) OnLookup(entry *DnsMonitorEntry, all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) { +func (r *deadlockMonitorReceiver) OnLookup(entry *MonitorEntry, all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) { if !assert.False(r.t, r.closed.Load(), "received lookup after closed") { return } @@ -404,15 +337,15 @@ func (r *deadlockMonitorReceiver) Close() { r.wg.Wait() } -func TestDnsMonitorDeadlock(t *testing.T) { +func TestMonitorDeadlock(t *testing.T) { t.Parallel() - lookup := newMockDnsLookupForTest(t) + lookup := NewMockLookupForTest(t) ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") lookup.Set("foo", []net.IP{ip1}) interval := time.Millisecond - monitor := newDnsMonitorForTest(t, interval, lookup) + monitor := NewMonitorForTest(t, interval, lookup) r := newDeadlockMonitorReceiver(t, monitor) r.Start() diff --git a/dns/test_helpers.go b/dns/test_helpers.go new file mode 100644 index 0000000..030bb49 --- /dev/null +++ b/dns/test_helpers.go @@ -0,0 +1,98 @@ +/** + * 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 dns + +import ( + "net" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +type MockLookup struct { + sync.RWMutex + + // +checklocks:RWMutex + ips map[string][]net.IP +} + +func NewMockLookupForTest(t *testing.T) *MockLookup { + t.Helper() + mock := &MockLookup{ + ips: make(map[string][]net.IP), + } + return mock +} + +func (m *MockLookup) Set(host string, ips []net.IP) { + m.Lock() + defer m.Unlock() + + m.ips[host] = ips +} + +func (m *MockLookup) Get(host string) []net.IP { + m.Lock() + defer m.Unlock() + + return m.ips[host] +} + +func (m *MockLookup) lookup(host string) ([]net.IP, error) { + m.RLock() + defer m.RUnlock() + + ips, found := m.ips[host] + if !found { + return nil, &net.DNSError{ + Err: "could not resolve " + host, + Name: host, + IsNotFound: true, + } + } + + return append([]net.IP{}, ips...), nil +} + +func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *MockLookup) *Monitor { + t.Helper() + require := require.New(t) + + logger := log.NewLoggerForTest(t) + var lookupFunc MonitorLookupFunc + if lookup != nil { + lookupFunc = lookup.lookup + } + monitor, err := NewMonitor(logger, interval, lookupFunc) + require.NoError(err) + + t.Cleanup(func() { + monitor.Stop() + }) + + require.NoError(monitor.Start()) + return monitor +} diff --git a/grpc_client.go b/grpc_client.go index f6c154e..9e49772 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -45,6 +45,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -450,7 +451,7 @@ func (c *GrpcClient) ProxySession(ctx context.Context, sessionId api.PublicSessi type grpcClientsList struct { clients []*GrpcClient - entry *DnsMonitorEntry + entry *dns.MonitorEntry } type GrpcClients struct { @@ -463,7 +464,7 @@ type GrpcClients struct { // +checklocks:mu clients []*GrpcClient - dnsMonitor *DnsMonitor + dnsMonitor *dns.Monitor // +checklocks:mu dnsDiscovery bool @@ -483,7 +484,7 @@ type GrpcClients struct { closeFunc context.CancelFunc // +checklocksignore: No locking necessary. } -func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, dnsMonitor *DnsMonitor, version string) (*GrpcClients, error) { +func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, dnsMonitor *dns.Monitor, version string) (*GrpcClients, error) { initializedCtx, initializedFunc := context.WithCancel(context.Background()) closeCtx, closeFunc := context.WithCancel(context.Background()) result := &GrpcClients{ @@ -743,7 +744,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo return nil } -func (c *GrpcClients) onLookup(entry *DnsMonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { +func (c *GrpcClients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { c.mu.Lock() defer c.mu.Unlock() diff --git a/grpc_client_test.go b/grpc_client_test.go index 4fe72a2..2a2a3ee 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -35,6 +35,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/internal" @@ -55,8 +56,8 @@ func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { return ch } -func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { - dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually +func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*GrpcClients, *dns.Monitor) { + dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) client, err := NewGrpcClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") @@ -68,7 +69,7 @@ func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, et return client, dnsMonitor } -func NewGrpcClientsForTest(t *testing.T, addr string, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { +func NewGrpcClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*GrpcClients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("grpc", "targets", addr) config.AddOption("grpc", "dnsdiscovery", "true") @@ -76,7 +77,7 @@ func NewGrpcClientsForTest(t *testing.T, addr string, lookup *mockDnsLookup) (*G return NewGrpcClientsForTestWithConfig(t, config, nil, lookup) } -func NewGrpcClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *mockDnsLookup) (*GrpcClients, *DnsMonitor) { +func NewGrpcClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dns.MockLookup) (*GrpcClients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) @@ -229,7 +230,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) - lookup := newMockDnsLookupForTest(t) + lookup := dns.NewMockLookupForTest(t) target := "testgrpc:12345" ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") @@ -243,12 +244,12 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest defer cancel() // Wait for initial check to be done to make sure internal dnsmonitor goroutine is waiting. - if err := dnsMonitor.waitForTicker(ctx); err != nil { + if err := dnsMonitor.WaitForTicker(ctx); err != nil { require.NoError(err) } drainWakeupChannel(ch) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(targetWithIp1, clients[0].Target()) assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) @@ -256,7 +257,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest lookup.Set("testgrpc", []net.IP{ip1, ip2}) drainWakeupChannel(ch) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 2) { @@ -268,7 +269,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest lookup.Set("testgrpc", []net.IP{ip2}) drainWakeupChannel(ch) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { @@ -281,7 +282,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { t.Parallel() assert := assert.New(t) - lookup := newMockDnsLookupForTest(t) + lookup := dns.NewMockLookupForTest(t) target := "testgrpc:12345" ip1 := net.ParseIP("192.168.0.1") targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) @@ -299,7 +300,7 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { lookup.Set("testgrpc", []net.IP{ip1}) drainWakeupChannel(ch) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() waitForEvent(testCtx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { diff --git a/mcu_proxy.go b/mcu_proxy.go index 1575ba5..930cdfe 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -48,6 +48,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -1513,7 +1514,7 @@ type mcuProxy struct { rpcClients *GrpcClients } -func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, rpcClients *GrpcClients, dnsMonitor *DnsMonitor) (Mcu, error) { +func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, rpcClients *GrpcClients, dnsMonitor *dns.Monitor) (Mcu, error) { logger := log.LoggerFromContext(ctx) urlType, _ := config.GetString("mcu", "urltype") if urlType == "" { diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index bd81f3b..40f5881 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -49,6 +49,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/internal" @@ -851,7 +852,7 @@ type proxyTestOptions struct { servers []*TestProxyServerHandler } -func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int, lookup *mockDnsLookup) (*mcuProxy, *goconf.ConfigFile) { +func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int, lookup *dns.MockLookup) (*mcuProxy, *goconf.ConfigFile) { t.Helper() require := require.New(t) if options.etcd == nil { @@ -939,7 +940,7 @@ func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx i return proxy, cfg } -func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int, lookup *mockDnsLookup) *mcuProxy { +func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int, lookup *dns.MockLookup) *mcuProxy { t.Helper() proxy, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ @@ -948,7 +949,7 @@ func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandl return proxy } -func newMcuProxyForTest(t *testing.T, idx int, lookup *mockDnsLookup) *mcuProxy { +func newMcuProxyForTest(t *testing.T, idx int, lookup *dns.MockLookup) *mcuProxy { t.Helper() server := NewProxyServerForTest(t, "DE") @@ -1034,7 +1035,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { assert := assert.New(t) require := require.New(t) - lookup := newMockDnsLookupForTest(t) + lookup := dns.NewMockLookupForTest(t) server1 := NewProxyServerForTest(t, "DE") server1.server.Start() @@ -1091,7 +1092,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { ip1, ip2, }) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() // Wait until connection is established. waitCtx, cancel := context.WithTimeout(ctx, time.Second) @@ -1121,7 +1122,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { lookup.Set(u1.Hostname(), []net.IP{ ip2, }) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() // Removing the connections takes a short while (asynchronously, closed when unused). waitCtx, cancel = context.WithTimeout(ctx, time.Second) diff --git a/proxy_config_static.go b/proxy_config_static.go index 36424bb..f12f94a 100644 --- a/proxy_config_static.go +++ b/proxy_config_static.go @@ -31,6 +31,7 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -38,7 +39,7 @@ import ( type ipList struct { hostname string - entry *DnsMonitorEntry + entry *dns.MonitorEntry ips []net.IP } @@ -47,7 +48,7 @@ type proxyConfigStatic struct { mu sync.Mutex proxy McuProxy - dnsMonitor *DnsMonitor + dnsMonitor *dns.Monitor // +checklocks:mu dnsDiscovery bool @@ -55,7 +56,7 @@ type proxyConfigStatic struct { connectionsMap map[string]*ipList } -func NewProxyConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *DnsMonitor) (ProxyConfig, error) { +func NewProxyConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *dns.Monitor) (ProxyConfig, error) { result := &proxyConfigStatic{ logger: logger, proxy: proxy, @@ -192,7 +193,7 @@ func (p *proxyConfigStatic) Reload(config *goconf.ConfigFile) error { return p.configure(config, true) } -func (p *proxyConfigStatic) onLookup(entry *DnsMonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { +func (p *proxyConfigStatic) onLookup(entry *dns.MonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { p.mu.Lock() defer p.mu.Unlock() diff --git a/proxy_config_static_test.go b/proxy_config_static_test.go index 484045b..3d3aef1 100644 --- a/proxy_config_static_test.go +++ b/proxy_config_static_test.go @@ -30,16 +30,17 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/log" ) -func newProxyConfigStatic(t *testing.T, proxy McuProxy, dns bool, lookup *mockDnsLookup, urls ...string) (ProxyConfig, *DnsMonitor) { +func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dns.MockLookup, urls ...string) (ProxyConfig, *dns.Monitor) { cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "url", strings.Join(urls, " ")) - if dns { + if dnsDiscovery { cfg.AddOption("mcu", "dnsdiscovery", "true") } - dnsMonitor := newDnsMonitorForTest(t, time.Hour, lookup) // will be updated manually + dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := log.NewLoggerForTest(t) p, err := NewProxyConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) @@ -77,7 +78,7 @@ func TestProxyConfigStaticSimple(t *testing.T) { func TestProxyConfigStaticDNS(t *testing.T) { t.Parallel() - lookup := newMockDnsLookupForTest(t) + lookup := dns.NewMockLookupForTest(t) proxy := newMcuProxyForConfig(t) config, dnsMonitor := newProxyConfigStatic(t, proxy, true, lookup, "https://foo/") require.NoError(t, config.Start()) @@ -89,7 +90,7 @@ func TestProxyConfigStaticDNS(t *testing.T) { net.ParseIP("10.1.2.3"), }) proxy.Expect("add", "https://foo/", lookup.Get("foo")...) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() lookup.Set("foo", []net.IP{ net.ParseIP("192.168.0.1"), @@ -99,7 +100,7 @@ func TestProxyConfigStaticDNS(t *testing.T) { proxy.Expect("keep", "https://foo/", net.ParseIP("192.168.0.1")) proxy.Expect("add", "https://foo/", net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.2")) proxy.Expect("remove", "https://foo/", net.ParseIP("10.1.2.3")) - dnsMonitor.checkHostnames() + dnsMonitor.CheckHostnames() proxy.Expect("add", "https://bar/") proxy.Expect("remove", "https://foo/", lookup.Get("foo")...) diff --git a/server/main.go b/server/main.go index e1e8a33..341f395 100644 --- a/server/main.go +++ b/server/main.go @@ -43,6 +43,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -196,7 +197,7 @@ func main() { } }() - dnsMonitor, err := signaling.NewDnsMonitor(logger, dnsMonitorInterval, nil) + dnsMonitor, err := dns.NewMonitor(logger, dnsMonitorInterval, nil) if err != nil { logger.Fatal("Could not create DNS monitor: ", err) } From 2275a5542e2d367f02482a12b5e297c006c5943f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 15 Dec 2025 10:56:39 +0100 Subject: [PATCH 422/549] Move certificate / pool reloader to "security" package. --- .codecov.yml | 4 ++ certificate_reloader_test.go | 47 ------------------- grpc_common.go | 13 ++--- grpc_common_test.go | 17 ++++++- .../certificate_reloader.go | 19 ++++---- .../internal/file_watcher.go | 4 +- .../internal/file_watcher_test.go | 22 ++++----- 7 files changed, 49 insertions(+), 77 deletions(-) delete mode 100644 certificate_reloader_test.go rename certificate_reloader.go => security/certificate_reloader.go (86%) rename file_watcher.go => security/internal/file_watcher.go (98%) rename file_watcher_test.go => security/internal/file_watcher_test.go (96%) diff --git a/.codecov.yml b/.codecov.yml index d5787fd..7f3ccd4 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -74,6 +74,10 @@ component_management: name: proxy paths: - proxy/** + - component_id: module_security + name: security + paths: + - security/** - component_id: module_server name: server paths: diff --git a/certificate_reloader_test.go b/certificate_reloader_test.go deleted file mode 100644 index f8be0a4..0000000 --- a/certificate_reloader_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2022 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" - "time" -) - -func (r *CertificateReloader) WaitForReload(ctx context.Context, counter uint64) error { - for counter == r.GetReloadCounter() { - if err := ctx.Err(); err != nil { - return err - } - time.Sleep(time.Millisecond) - } - return nil -} - -func (r *CertPoolReloader) WaitForReload(ctx context.Context, counter uint64) error { - for counter == r.GetReloadCounter() { - if err := ctx.Err(); err != nil { - return err - } - time.Sleep(time.Millisecond) - } - return nil -} diff --git a/grpc_common.go b/grpc_common.go index 5c49300..c6ec5c4 100644 --- a/grpc_common.go +++ b/grpc_common.go @@ -32,13 +32,14 @@ import ( "google.golang.org/grpc/credentials/insecure" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/security" ) type reloadableCredentials struct { config *tls.Config - loader *CertificateReloader - pool *CertPoolReloader + loader *security.CertificateReloader + pool *security.CertPoolReloader } func (c *reloadableCredentials) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { @@ -151,18 +152,18 @@ func NewReloadableCredentials(logger log.Logger, config *goconf.ConfigFile, serv cfg := &tls.Config{ NextProtos: []string{"h2"}, } - var loader *CertificateReloader + var loader *security.CertificateReloader var err error if certificateFile != "" && keyFile != "" { - loader, err = NewCertificateReloader(logger, certificateFile, keyFile) + loader, err = security.NewCertificateReloader(logger, certificateFile, keyFile) if err != nil { return nil, fmt.Errorf("invalid GRPC %s certificate / key in %s / %s: %w", prefix, certificateFile, keyFile, err) } } - var pool *CertPoolReloader + var pool *security.CertPoolReloader if caFile != "" { - pool, err = NewCertPoolReloader(logger, caFile) + pool, err = security.NewCertPoolReloader(logger, caFile) if err != nil { return nil, err } diff --git a/grpc_common_test.go b/grpc_common_test.go index f2ed944..27d8d23 100644 --- a/grpc_common_test.go +++ b/grpc_common_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "errors" + "time" ) func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context, counter uint64) error { @@ -31,7 +32,13 @@ func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context, co return errors.New("no certificate loaded") } - return c.loader.WaitForReload(ctx, counter) + for counter == c.loader.GetReloadCounter() { + if err := ctx.Err(); err != nil { + return err + } + time.Sleep(time.Millisecond) + } + return nil } func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context, counter uint64) error { @@ -39,5 +46,11 @@ func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context, count return errors.New("no certificate pool loaded") } - return c.pool.WaitForReload(ctx, counter) + for counter == c.pool.GetReloadCounter() { + if err := ctx.Err(); err != nil { + return err + } + time.Sleep(time.Millisecond) + } + return nil } diff --git a/certificate_reloader.go b/security/certificate_reloader.go similarity index 86% rename from certificate_reloader.go rename to security/certificate_reloader.go index cfc74a8..fde5185 100644 --- a/certificate_reloader.go +++ b/security/certificate_reloader.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 security import ( "crypto/tls" @@ -30,16 +30,17 @@ import ( "testing" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/security/internal" ) type CertificateReloader struct { logger log.Logger certFile string - certWatcher *FileWatcher + certWatcher *internal.FileWatcher keyFile string - keyWatcher *FileWatcher + keyWatcher *internal.FileWatcher certificate atomic.Pointer[tls.Certificate] @@ -52,7 +53,7 @@ func NewCertificateReloader(logger log.Logger, certFile string, keyFile string) return nil, fmt.Errorf("could not load certificate / key: %w", err) } - deduplicate := defaultDeduplicateWatchEvents + deduplicate := internal.DefaultDeduplicateWatchEvents if testing.Testing() { deduplicate = 0 } @@ -63,11 +64,11 @@ func NewCertificateReloader(logger log.Logger, certFile string, keyFile string) keyFile: keyFile, } reloader.certificate.Store(&pair) - reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload, deduplicate) + reloader.certWatcher, err = internal.NewFileWatcher(reloader.logger, certFile, reloader.reload, deduplicate) if err != nil { return nil, err } - reloader.keyWatcher, err = NewFileWatcher(reloader.logger, keyFile, reloader.reload, deduplicate) + reloader.keyWatcher, err = internal.NewFileWatcher(reloader.logger, keyFile, reloader.reload, deduplicate) if err != nil { reloader.certWatcher.Close() // nolint return nil, err @@ -113,7 +114,7 @@ type CertPoolReloader struct { logger log.Logger certFile string - certWatcher *FileWatcher + certWatcher *internal.FileWatcher pool atomic.Pointer[x509.CertPool] @@ -140,7 +141,7 @@ func NewCertPoolReloader(logger log.Logger, certFile string) (*CertPoolReloader, return nil, err } - deduplicate := defaultDeduplicateWatchEvents + deduplicate := internal.DefaultDeduplicateWatchEvents if testing.Testing() { deduplicate = 0 } @@ -150,7 +151,7 @@ func NewCertPoolReloader(logger log.Logger, certFile string) (*CertPoolReloader, certFile: certFile, } reloader.pool.Store(pool) - reloader.certWatcher, err = NewFileWatcher(reloader.logger, certFile, reloader.reload, deduplicate) + reloader.certWatcher, err = internal.NewFileWatcher(reloader.logger, certFile, reloader.reload, deduplicate) if err != nil { return nil, err } diff --git a/file_watcher.go b/security/internal/file_watcher.go similarity index 98% rename from file_watcher.go rename to security/internal/file_watcher.go index dc1e824..338c472 100644 --- a/file_watcher.go +++ b/security/internal/file_watcher.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 internal import ( "context" @@ -37,7 +37,7 @@ import ( ) const ( - defaultDeduplicateWatchEvents = 100 * time.Millisecond + DefaultDeduplicateWatchEvents = 100 * time.Millisecond ) type FileWatcherCallback func(filename string) diff --git a/file_watcher_test.go b/security/internal/file_watcher_test.go similarity index 96% rename from file_watcher_test.go rename to security/internal/file_watcher_test.go index a8e96e3..948099b 100644 --- a/file_watcher_test.go +++ b/security/internal/file_watcher_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 internal import ( "context" @@ -35,7 +35,7 @@ import ( ) var ( - testWatcherNoEventTimeout = 2 * defaultDeduplicateWatchEvents + testWatcherNoEventTimeout = 2 * DefaultDeduplicateWatchEvents ) func TestFileWatcher_NotExist(t *testing.T) { @@ -43,7 +43,7 @@ func TestFileWatcher_NotExist(t *testing.T) { assert := assert.New(t) tmpdir := t.TempDir() logger := log.NewLoggerForTest(t) - if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}, defaultDeduplicateWatchEvents); !assert.ErrorIs(err, os.ErrNotExist) { + if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}, DefaultDeduplicateWatchEvents); !assert.ErrorIs(err, os.ErrNotExist) { if w != nil { assert.NoError(w.Close()) } @@ -62,7 +62,7 @@ func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -105,7 +105,7 @@ func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest modified := make(chan struct{}) w, err := NewFileWatcher(logger, "./"+path.Base(filename), func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -147,7 +147,7 @@ func TestFileWatcher_Rename(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -191,7 +191,7 @@ func TestFileWatcher_Symlink(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -226,7 +226,7 @@ func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -263,7 +263,7 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -294,7 +294,7 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() @@ -348,7 +348,7 @@ func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} - }, defaultDeduplicateWatchEvents) + }, DefaultDeduplicateWatchEvents) require.NoError(err) defer w.Close() From 231f7c8af430ece1183a41866f26498871cac80d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 15 Dec 2025 13:49:08 +0100 Subject: [PATCH 423/549] Move geo related code to "geoip" package. With that, add dedicated types for "Country" and "Continent". --- .codecov.yml | 4 + Makefile | 8 +- api/signaling.go | 7 +- api/signaling_easyjson.go | 3 +- api_backend.go | 3 +- api_backend_easyjson.go | 3 +- client.go | 36 +---- continentmap.go => geoip/continentmap.go | 4 +- geoip/geoip.go | 98 ++++++++++++ geoip.go => geoip/maxmind.go | 127 ++-------------- geoip_test.go => geoip/maxmind_test.go | 38 ++--- geoip/overrides.go | 130 ++++++++++++++++ geoip/overrides_test.go | 182 +++++++++++++++++++++++ grpc_client.go | 5 +- grpc_remote_client.go | 7 +- hub.go | 54 +++---- hub_test.go | 11 +- mcu_common.go | 3 +- mcu_common_test.go | 5 +- mcu_janus_test.go | 5 +- mcu_proxy.go | 53 +++---- mcu_proxy_test.go | 17 ++- proxy/proxy_remote.go | 5 +- proxy/proxy_server.go | 11 +- remotesession.go | 5 +- scripts/get_continent_map.py | 4 +- 26 files changed, 564 insertions(+), 264 deletions(-) rename continentmap.go => geoip/continentmap.go (98%) create mode 100644 geoip/geoip.go rename geoip.go => geoip/maxmind.go (63%) rename geoip_test.go => geoip/maxmind_test.go (84%) create mode 100644 geoip/overrides.go create mode 100644 geoip/overrides_test.go diff --git a/.codecov.yml b/.codecov.yml index 7f3ccd4..d790054 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -46,6 +46,10 @@ component_management: name: etcd paths: - etcd/** + - component_id: module_geoip + name: geoip + paths: + - geoip/** - component_id: module_internal name: internal paths: diff --git a/Makefile b/Makefile index b726693..f9085d9 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ GRPC_PROTO_GO_FILES := $(addsuffix .pb.go,$(GRPC_PROTO_FILES)) $(addsuffix _grpc TEST_GO_FILES := $(wildcard *_test.go)) EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go api/signaling.go */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)) +COMMON_GO_FILES := $(filter-out geoip/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)) CLIENT_GO_FILES := $(filter-out $(CLIENT_TEST_GO_FILES),$(wildcard client/*.go)) SERVER_TEST_GO_FILES := $(wildcard server/*_test.go)) @@ -92,7 +92,7 @@ $(GOPATHBIN)/protoc-gen-go-grpc: go.mod go.sum $(GOPATHBIN)/checklocks: go.mod go.sum $(GO) install gvisor.dev/gvisor/tools/checklocks/cmd/checklocks@go -continentmap.go: +geoip/continentmap.go: $(CURDIR)/scripts/get_continent_map.py $@ check-continentmap: @@ -100,7 +100,7 @@ check-continentmap: TMP=$$(mktemp -d) ;\ echo Make sure to remove $$TMP on error ;\ $(CURDIR)/scripts/get_continent_map.py $$TMP/continentmap.go ;\ - diff -u continentmap.go $$TMP/continentmap.go ;\ + diff -u geoip/continentmap.go $$TMP/continentmap.go ;\ rm -rf $$TMP get: @@ -215,6 +215,6 @@ tarball: vendor | $(TMPDIR) dist: tarball .NOTPARALLEL: $(EASYJSON_GO_FILES) -.PHONY: continentmap.go common vendor +.PHONY: geoip/continentmap.go common vendor .SECONDARY: $(EASYJSON_GO_FILES) $(PROTO_GO_FILES) .DELETE_ON_ERROR: diff --git a/api/signaling.go b/api/signaling.go index 04a7988..0c7df41 100644 --- a/api/signaling.go +++ b/api/signaling.go @@ -37,6 +37,7 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" ) @@ -301,9 +302,9 @@ func (e *Error) Error() string { } type WelcomeServerMessage struct { - Version string `json:"version"` - Features []string `json:"features,omitempty"` - Country string `json:"country,omitempty"` + Version string `json:"version"` + Features []string `json:"features,omitempty"` + Country geoip.Country `json:"country,omitempty"` } func NewWelcomeServerMessage(version string, feature ...string) *WelcomeServerMessage { diff --git a/api/signaling_easyjson.go b/api/signaling_easyjson.go index ff1a12b..4a4ce0e 100644 --- a/api/signaling_easyjson.go +++ b/api/signaling_easyjson.go @@ -8,6 +8,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + geoip "github.com/strukturag/nextcloud-spreed-signaling/geoip" time "time" ) @@ -70,7 +71,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(in *jle if in.IsNull() { in.Skip() } else { - out.Country = string(in.String()) + out.Country = geoip.Country(in.String()) } default: in.SkipRecursive() diff --git a/api_backend.go b/api_backend.go index 322d075..bb720b8 100644 --- a/api_backend.go +++ b/api_backend.go @@ -35,6 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" ) const ( @@ -458,7 +459,7 @@ type BackendServerInfoSfuProxy struct { Version string `json:"version,omitempty"` Features []string `json:"features,omitempty"` - Country string `json:"country,omitempty"` + Country geoip.Country `json:"country,omitempty"` Load *uint64 `json:"load,omitempty"` Bandwidth *EventProxyServerBandwidth `json:"bandwidth,omitempty"` } diff --git a/api_backend_easyjson.go b/api_backend_easyjson.go index 6dc0a11..9e8ef43 100644 --- a/api_backend_easyjson.go +++ b/api_backend_easyjson.go @@ -9,6 +9,7 @@ import ( jwriter "github.com/mailru/easyjson/jwriter" api "github.com/strukturag/nextcloud-spreed-signaling/api" etcd "github.com/strukturag/nextcloud-spreed-signaling/etcd" + geoip "github.com/strukturag/nextcloud-spreed-signaling/geoip" time "time" ) @@ -769,7 +770,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex if in.IsNull() { in.Skip() } else { - out.Country = string(in.String()) + out.Country = geoip.Country(in.String()) } case "load": if in.IsNull() { diff --git a/client.go b/client.go index fcb86a3..0c024b2 100644 --- a/client.go +++ b/client.go @@ -38,6 +38,7 @@ import ( "github.com/mailru/easyjson" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" @@ -57,33 +58,10 @@ const ( maxMessageSize = 64 * 1024 ) -var ( - noCountry = "no-country" - - loopback = "loopback" - - unknownCountry = "unknown-country" -) - func init() { RegisterClientStats() } -func IsValidCountry(country string) bool { - switch country { - case "": - fallthrough - case noCountry: - fallthrough - case loopback: - fallthrough - case unknownCountry: - return false - default: - return true - } -} - var ( InvalidFormat = api.NewError("invalid_format", "Invalid data format.") @@ -99,7 +77,7 @@ type WritableClientMessage interface { type HandlerClient interface { Context() context.Context RemoteAddr() string - Country() string + Country() geoip.Country UserAgent() string IsConnected() bool IsAuthenticated() bool @@ -122,7 +100,7 @@ type ClientHandler interface { } type ClientGeoIpHandler interface { - OnLookupCountry(HandlerClient) string + OnLookupCountry(HandlerClient) geoip.Country } type Client struct { @@ -132,7 +110,7 @@ type Client struct { addr string agent string closed atomic.Int32 - country *string + country *geoip.Country logRTT bool handlerMu sync.RWMutex @@ -246,13 +224,13 @@ func (c *Client) UserAgent() string { return c.agent } -func (c *Client) Country() string { +func (c *Client) Country() geoip.Country { if c.country == nil { - var country string + var country geoip.Country if handler, ok := c.getHandler().(ClientGeoIpHandler); ok { country = handler.OnLookupCountry(c) } else { - country = unknownCountry + country = geoip.UnknownCountry } c.country = &country } diff --git a/continentmap.go b/geoip/continentmap.go similarity index 98% rename from continentmap.go rename to geoip/continentmap.go index a99b944..75673bd 100644 --- a/continentmap.go +++ b/geoip/continentmap.go @@ -1,10 +1,10 @@ -package signaling +package geoip // This file has been automatically generated, do not modify. // Source: https://raw.githubusercontent.com/datasets/country-codes/refs/heads/main/data/country-codes.csv var ( - ContinentMap = map[string][]string{ + ContinentMap = map[Country][]Continent{ "AD": {"EU"}, "AE": {"AS"}, "AF": {"AS"}, diff --git a/geoip/geoip.go b/geoip/geoip.go new file mode 100644 index 0000000..a020a38 --- /dev/null +++ b/geoip/geoip.go @@ -0,0 +1,98 @@ +/** + * 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 geoip + +import "strings" + +type ( + Country string + Continent string +) + +var ( + NoCountry = Country("no-country") + noCountryUpper = Country(strings.ToUpper("no-country")) + + Loopback = Country("loopback") + loopbackUpper = Country(strings.ToUpper("loopback")) + + UnknownCountry = Country("unknown-country") + unknownCountryUpper = Country(strings.ToUpper("unknown-country")) +) + +func IsValidCountry(country Country) bool { + switch country { + case "": + fallthrough + case NoCountry: + fallthrough + case noCountryUpper: + fallthrough + case Loopback: + fallthrough + case loopbackUpper: + fallthrough + case UnknownCountry: + fallthrough + case unknownCountryUpper: + return false + default: + return true + } +} + +func LookupContinents(country Country) []Continent { + continents, found := ContinentMap[country] + if !found { + return nil + } + + return continents +} + +func IsValidContinent(continent Continent) bool { + switch continent { + case "AF": + // Africa + fallthrough + case "AN": + // Antartica + fallthrough + case "AS": + // Asia + fallthrough + case "EU": + // Europe + fallthrough + case "NA": + // North America + fallthrough + case "SA": + // South America + fallthrough + case "OC": + // Oceania + return true + default: + return false + } +} diff --git a/geoip.go b/geoip/maxmind.go similarity index 63% rename from geoip.go rename to geoip/maxmind.go index 4da1aa4..d5f1c8c 100644 --- a/geoip.go +++ b/geoip/maxmind.go @@ -19,12 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package geoip import ( "archive/tar" "compress/gzip" - "context" "errors" "fmt" "io" @@ -36,10 +35,8 @@ import ( "sync/atomic" "time" - "github.com/dlintw/goconf" "github.com/oschwald/maxminddb-golang" - "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -47,7 +44,7 @@ var ( ErrDatabaseNotInitialized = errors.New("GeoIP database not initialized yet") ) -func GetGeoIpDownloadUrl(license string) string { +func GetMaxMindDownloadUrl(license string) string { if license == "" { return "" } @@ -59,7 +56,7 @@ func GetGeoIpDownloadUrl(license string) string { return result } -type GeoLookup struct { +type Lookup struct { logger log.Logger url string isFile bool @@ -71,16 +68,16 @@ type GeoLookup struct { reader atomic.Pointer[maxminddb.Reader] } -func NewGeoLookupFromUrl(logger log.Logger, url string) (*GeoLookup, error) { - geoip := &GeoLookup{ +func NewLookupFromUrl(logger log.Logger, url string) (*Lookup, error) { + geoip := &Lookup{ logger: logger, url: url, } return geoip, nil } -func NewGeoLookupFromFile(logger log.Logger, filename string) (*GeoLookup, error) { - geoip := &GeoLookup{ +func NewLookupFromFile(logger log.Logger, filename string) (*Lookup, error) { + geoip := &Lookup{ logger: logger, url: filename, isFile: true, @@ -92,13 +89,13 @@ func NewGeoLookupFromFile(logger log.Logger, filename string) (*GeoLookup, error return geoip, nil } -func (g *GeoLookup) Close() { +func (g *Lookup) Close() { if reader := g.reader.Swap(nil); reader != nil { reader.Close() } } -func (g *GeoLookup) Update() error { +func (g *Lookup) Update() error { if g.isFile { return g.updateFile() } @@ -106,7 +103,7 @@ func (g *GeoLookup) Update() error { return g.updateUrl() } -func (g *GeoLookup) updateFile() error { +func (g *Lookup) updateFile() error { info, err := os.Stat(g.url) if err != nil { return err @@ -136,7 +133,7 @@ func (g *GeoLookup) updateFile() error { return nil } -func (g *GeoLookup) updateUrl() error { +func (g *Lookup) updateUrl() error { request, err := http.NewRequest("GET", g.url, nil) if err != nil { return err @@ -219,7 +216,7 @@ func (g *GeoLookup) updateUrl() error { return nil } -func (g *GeoLookup) LookupCountry(ip net.IP) (string, error) { +func (g *Lookup) LookupCountry(ip net.IP) (Country, error) { var record struct { Country struct { ISOCode string `maxminddb:"iso_code"` @@ -235,103 +232,5 @@ func (g *GeoLookup) LookupCountry(ip net.IP) (string, error) { return "", err } - return record.Country.ISOCode, nil -} - -func LookupContinents(country string) []string { - continents, found := ContinentMap[country] - if !found { - return nil - } - - return continents -} - -func IsValidContinent(continent string) bool { - switch continent { - case "AF": - // Africa - fallthrough - case "AN": - // Antartica - fallthrough - case "AS": - // Asia - fallthrough - case "EU": - // Europe - fallthrough - case "NA": - // North America - fallthrough - case "SA": - // South America - fallthrough - case "OC": - // Oceania - return true - default: - return false - } -} - -func LoadGeoIPOverrides(ctx context.Context, cfg *goconf.ConfigFile, ignoreErrors bool) (map[*net.IPNet]string, error) { - logger := log.LoggerFromContext(ctx) - options, _ := config.GetStringOptions(cfg, "geoip-overrides", true) - if len(options) == 0 { - return nil, nil - } - - var err error - geoipOverrides := make(map[*net.IPNet]string, len(options)) - for option, value := range options { - var ip net.IP - var ipNet *net.IPNet - if strings.Contains(option, "/") { - _, ipNet, err = net.ParseCIDR(option) - if err != nil { - if ignoreErrors { - logger.Printf("could not parse CIDR %s (%s), skipping", option, err) - continue - } - - return nil, fmt.Errorf("could not parse CIDR %s: %s", option, err) - } - } else { - ip = net.ParseIP(option) - if ip == nil { - if ignoreErrors { - logger.Printf("could not parse IP %s, skipping", option) - continue - } - - return nil, fmt.Errorf("could not parse IP %s", option) - } - - var mask net.IPMask - if ipv4 := ip.To4(); ipv4 != nil { - mask = net.CIDRMask(32, 32) - } else { - mask = net.CIDRMask(128, 128) - } - ipNet = &net.IPNet{ - IP: ip, - Mask: mask, - } - } - - value = strings.ToUpper(strings.TrimSpace(value)) - if value == "" { - logger.Printf("IP %s doesn't have a country assigned, skipping", option) - continue - } else if !IsValidCountry(value) { - logger.Printf("Country %s for IP %s is invalid, skipping", value, option) - continue - } - - logger.Printf("Using country %s for %s", value, ipNet) - geoipOverrides[ipNet] = value - } - - return geoipOverrides, nil + return Country(record.Country.ISOCode), nil } diff --git a/geoip_test.go b/geoip/maxmind_test.go similarity index 84% rename from geoip_test.go rename to geoip/maxmind_test.go index de59b78..82d4657 100644 --- a/geoip_test.go +++ b/geoip/maxmind_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 geoip import ( "archive/tar" @@ -39,8 +39,8 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" ) -func testGeoLookupReader(t *testing.T, reader *GeoLookup) { - tests := map[string]string{ +func testLookupReader(t *testing.T, reader *Lookup) { + tests := map[string]Country{ // Example from maxminddb-golang code. "81.2.69.142": "GB", // Local addresses don't have a country assigned. @@ -59,7 +59,7 @@ func testGeoLookupReader(t *testing.T, reader *GeoLookup) { } } -func GetGeoIpUrlForTest(t *testing.T) string { +func GetIpUrlForTest(t *testing.T) string { t.Helper() var geoIpUrl string @@ -72,29 +72,29 @@ func GetGeoIpUrlForTest(t *testing.T) string { if license == "" { t.Skip("No MaxMind GeoLite2 license was set in MAXMIND_GEOLITE2_LICENSE environment variable.") } - geoIpUrl = GetGeoIpDownloadUrl(license) + geoIpUrl = GetMaxMindDownloadUrl(license) } return geoIpUrl } -func TestGeoLookup(t *testing.T) { +func TestLookup(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) require := require.New(t) - reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) + reader, err := NewLookupFromUrl(logger, GetIpUrlForTest(t)) require.NoError(err) defer reader.Close() require.NoError(reader.Update()) - testGeoLookupReader(t, reader) + testLookupReader(t, reader) } -func TestGeoLookupCaching(t *testing.T) { +func TestLookupCaching(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) require := require.New(t) - reader, err := NewGeoLookupFromUrl(logger, GetGeoIpUrlForTest(t)) + reader, err := NewLookupFromUrl(logger, GetIpUrlForTest(t)) require.NoError(err) defer reader.Close() @@ -105,9 +105,9 @@ func TestGeoLookupCaching(t *testing.T) { require.NoError(reader.Update()) } -func TestGeoLookupContinent(t *testing.T) { +func TestLookupContinent(t *testing.T) { t.Parallel() - tests := map[string][]string{ + tests := map[Country][]Continent{ "AU": {"OC"}, "DE": {"EU"}, "RU": {"EU"}, @@ -116,7 +116,7 @@ func TestGeoLookupContinent(t *testing.T) { } for country, expected := range tests { - t.Run(country, func(t *testing.T) { + t.Run(string(country), func(t *testing.T) { t.Parallel() continents := LookupContinents(country) if !assert.Len(t, continents, len(expected), "Continents didn't match for %s: got %s, expected %s", country, continents, expected) { @@ -131,19 +131,19 @@ func TestGeoLookupContinent(t *testing.T) { } } -func TestGeoLookupCloseEmpty(t *testing.T) { +func TestLookupCloseEmpty(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) - reader, err := NewGeoLookupFromUrl(logger, "ignore-url") + reader, err := NewLookupFromUrl(logger, "ignore-url") require.NoError(t, err) reader.Close() } -func TestGeoLookupFromFile(t *testing.T) { +func TestLookupFromFile(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) require := require.New(t) - geoIpUrl := GetGeoIpUrlForTest(t) + geoIpUrl := GetIpUrlForTest(t) resp, err := http.Get(geoIpUrl) require.NoError(err) @@ -196,11 +196,11 @@ func TestGeoLookupFromFile(t *testing.T) { require.True(foundDatabase, "Did not find GeoIP database in download from %s", geoIpUrl) - reader, err := NewGeoLookupFromFile(logger, tmpfile.Name()) + reader, err := NewLookupFromFile(logger, tmpfile.Name()) require.NoError(err) defer reader.Close() - testGeoLookupReader(t, reader) + testLookupReader(t, reader) } func TestIsValidContinent(t *testing.T) { diff --git a/geoip/overrides.go b/geoip/overrides.go new file mode 100644 index 0000000..a15faa7 --- /dev/null +++ b/geoip/overrides.go @@ -0,0 +1,130 @@ +/** + * 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 geoip + +import ( + "context" + "fmt" + "maps" + "net" + "strings" + "sync/atomic" + + "github.com/dlintw/goconf" + "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +type Overrides map[*net.IPNet]Country + +func (o Overrides) Lookup(ip net.IP) (Country, bool) { + for overrideNet, country := range o { + if overrideNet.Contains(ip) { + return country, true + } + } + + return UnknownCountry, false +} + +type AtomicOverrides struct { + value atomic.Pointer[Overrides] +} + +func (a *AtomicOverrides) Store(value Overrides) { + if len(value) == 0 { + a.value.Store(nil) + } else { + v := maps.Clone(value) + a.value.Store(&v) + } +} + +func (a *AtomicOverrides) Load() Overrides { + value := a.value.Load() + if value == nil { + return nil + } + + return *value +} + +func LoadOverrides(ctx context.Context, cfg *goconf.ConfigFile, ignoreErrors bool) (Overrides, error) { + logger := log.LoggerFromContext(ctx) + options, _ := config.GetStringOptions(cfg, "geoip-overrides", true) + if len(options) == 0 { + return nil, nil + } + + var err error + geoipOverrides := make(Overrides, len(options)) + for option, value := range options { + var ip net.IP + var ipNet *net.IPNet + if strings.Contains(option, "/") { + _, ipNet, err = net.ParseCIDR(option) + if err != nil { + if ignoreErrors { + logger.Printf("could not parse CIDR %s (%s), skipping", option, err) + continue + } + + return nil, fmt.Errorf("could not parse CIDR %s: %w", option, err) + } + } else { + ip = net.ParseIP(option) + if ip == nil { + if ignoreErrors { + logger.Printf("could not parse IP %s, skipping", option) + continue + } + + return nil, fmt.Errorf("could not parse IP %s", option) + } + + var mask net.IPMask + if ipv4 := ip.To4(); ipv4 != nil { + mask = net.CIDRMask(32, 32) + } else { + mask = net.CIDRMask(128, 128) + } + ipNet = &net.IPNet{ + IP: ip, + Mask: mask, + } + } + + value = strings.ToUpper(strings.TrimSpace(value)) + if value == "" { + logger.Printf("IP %s doesn't have a country assigned, skipping", option) + continue + } else if !IsValidCountry(Country(value)) { + logger.Printf("Country %s for IP %s is invalid, skipping", value, option) + continue + } + + logger.Printf("Using country %s for %s", value, ipNet) + geoipOverrides[ipNet] = Country(value) + } + + return geoipOverrides, nil +} diff --git a/geoip/overrides_test.go b/geoip/overrides_test.go new file mode 100644 index 0000000..68845c2 --- /dev/null +++ b/geoip/overrides_test.go @@ -0,0 +1,182 @@ +/** + * 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 geoip + +import ( + "maps" + "net" + "testing" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +func mustSucceed1[T any, A1 any](t *testing.T, f func(a1 A1) (T, bool), a1 A1) T { + t.Helper() + result, ok := f(a1) + if !ok { + t.FailNow() + } + return result +} + +func TestOverridesEmpty(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + config := goconf.NewConfigFile() + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + + overrides, err := LoadOverrides(ctx, config, true) + require.NoError(err) + assert.Empty(overrides) +} + +func TestOverrides(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + config := goconf.NewConfigFile() + config.AddOption("geoip-overrides", "10.1.0.0/16", "DE") + config.AddOption("geoip-overrides", "2001:db8::/48", "FR") + config.AddOption("geoip-overrides", "2001:db9::3", "CH") + config.AddOption("geoip-overrides", "10.3.4.5", "custom") + config.AddOption("geoip-overrides", "10.4.5.6", "loopback") + config.AddOption("geoip-overrides", "192.168.1.0", "") + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + + overrides, err := LoadOverrides(ctx, config, true) + require.NoError(err) + + if assert.Len(overrides, 4) { + assert.EqualValues("DE", mustSucceed1(t, overrides.Lookup, net.ParseIP("10.1.2.3"))) + assert.EqualValues("DE", mustSucceed1(t, overrides.Lookup, net.ParseIP("10.1.3.4"))) + assert.EqualValues("FR", mustSucceed1(t, overrides.Lookup, net.ParseIP("2001:db8::1"))) + assert.EqualValues("FR", mustSucceed1(t, overrides.Lookup, net.ParseIP("2001:db8::2"))) + assert.EqualValues("CH", mustSucceed1(t, overrides.Lookup, net.ParseIP("2001:db9::3"))) + assert.EqualValues("CUSTOM", mustSucceed1(t, overrides.Lookup, net.ParseIP("10.3.4.5"))) + + country, ok := overrides.Lookup(net.ParseIP("10.4.5.6")) + assert.False(ok, "expected no country, got %s", country) + + country, ok = overrides.Lookup(net.ParseIP("192.168.1.0")) + assert.False(ok, "expected no country, got %s", country) + } +} + +func TestOverridesInvalidIgnoreErrors(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + config := goconf.NewConfigFile() + config.AddOption("geoip-overrides", "invalid-ip", "DE") + config.AddOption("geoip-overrides", "300.1.2.3/8", "DE") + config.AddOption("geoip-overrides", "10.2.0.0/16", "FR") + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + + overrides, err := LoadOverrides(ctx, config, true) + require.NoError(err) + + if assert.Len(overrides, 1) { + assert.EqualValues("FR", mustSucceed1(t, overrides.Lookup, net.ParseIP("10.2.3.4"))) + assert.EqualValues("FR", mustSucceed1(t, overrides.Lookup, net.ParseIP("10.2.4.5"))) + + country, ok := overrides.Lookup(net.ParseIP("10.3.4.5")) + assert.False(ok, "expected no country, got %s", country) + + country, ok = overrides.Lookup(net.ParseIP("192.168.1.0")) + assert.False(ok, "expected no country, got %s", country) + } +} + +func TestOverridesInvalidIPReturnErrors(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + config := goconf.NewConfigFile() + config.AddOption("geoip-overrides", "invalid-ip", "DE") + config.AddOption("geoip-overrides", "10.2.0.0/16", "FR") + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + + overrides, err := LoadOverrides(ctx, config, false) + assert.ErrorContains(err, "could not parse IP", err) + assert.Empty(overrides) +} + +func TestOverridesInvalidCIDRReturnErrors(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + config := goconf.NewConfigFile() + config.AddOption("geoip-overrides", "300.1.2.3/8", "DE") + config.AddOption("geoip-overrides", "10.2.0.0/16", "FR") + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + + overrides, err := LoadOverrides(ctx, config, false) + var e *net.ParseError + assert.ErrorAs(err, &e) + assert.Empty(overrides) +} + +func TestAtomicOverrides(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + overrides := make(Overrides) + overrides[&net.IPNet{ + IP: net.ParseIP("10.1.2.3."), + Mask: net.CIDRMask(32, 32), + }] = "DE" + + var value AtomicOverrides + assert.Nil(value.Load()) + value.Store(make(Overrides)) + assert.Nil(value.Load()) + value.Store(overrides) + if o := value.Load(); assert.NotEmpty(o) { + assert.Equal(overrides, o) + } + // Updating the overrides doesn't change the stored value. + overrides2 := maps.Clone(overrides) + overrides[&net.IPNet{ + IP: net.ParseIP("10.1.2.3."), + Mask: net.CIDRMask(32, 32), + }] = "FR" + if o := value.Load(); assert.NotEmpty(o) { + assert.Equal(overrides2, o) + } +} diff --git a/grpc_client.go b/grpc_client.go index 9e49772..07912fb 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -47,6 +47,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -369,7 +370,7 @@ func (c *GrpcClient) GetTransientData(ctx context.Context, room *Room) (Transien type ProxySessionReceiver interface { RemoteAddr() string - Country() string + Country() geoip.Country UserAgent() string OnProxyMessage(message *ServerSessionMessage) error @@ -429,7 +430,7 @@ func (c *GrpcClient) ProxySession(ctx context.Context, sessionId api.PublicSessi md := metadata.Pairs( "sessionId", string(sessionId), "remoteAddr", receiver.RemoteAddr(), - "country", receiver.Country(), + "country", string(receiver.Country()), "userAgent", receiver.UserAgent(), ) client, err := c.impl.ProxySession(metadata.NewOutgoingContext(ctx, md), grpc.WaitForReady(true)) diff --git a/grpc_remote_client.go b/grpc_remote_client.go index b04f52b..85fbd95 100644 --- a/grpc_remote_client.go +++ b/grpc_remote_client.go @@ -34,6 +34,7 @@ import ( "google.golang.org/grpc/status" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -57,7 +58,7 @@ type remoteGrpcClient struct { sessionId string remoteAddr string - country string + country geoip.Country userAgent string closeCtx context.Context @@ -82,7 +83,7 @@ func newRemoteGrpcClient(hub *Hub, request RpcSessions_ProxySessionServer) (*rem sessionId: getMD(md, "sessionId"), remoteAddr: getMD(md, "remoteAddr"), - country: getMD(md, "country"), + country: geoip.Country(getMD(md, "country")), userAgent: getMD(md, "userAgent"), closeCtx: closeCtx, @@ -131,7 +132,7 @@ func (c *remoteGrpcClient) UserAgent() string { return c.userAgent } -func (c *remoteGrpcClient) Country() string { +func (c *remoteGrpcClient) Country() geoip.Country { return c.country } diff --git a/hub.go b/hub.go index 1db57fa..f15d9e6 100644 --- a/hub.go +++ b/hub.go @@ -56,6 +56,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -207,8 +208,8 @@ type Hub struct { backend *BackendClient trustedProxies atomic.Pointer[container.IPList] - geoip *GeoLookup - geoipOverrides atomic.Pointer[map[*net.IPNet]string] + geoip *geoip.Lookup + geoipOverrides geoip.AtomicOverrides geoipUpdating atomic.Bool etcdClient etcd.Client @@ -329,18 +330,18 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpc } if geoipUrl == "" { if geoipLicense, _ := cfg.GetString("geoip", "license"); geoipLicense != "" { - geoipUrl = GetGeoIpDownloadUrl(geoipLicense) + geoipUrl = geoip.GetMaxMindDownloadUrl(geoipLicense) } } - var geoip *GeoLookup + var geoipLookup *geoip.Lookup if geoipUrl != "" { if geoipUrl, found := strings.CutPrefix(geoipUrl, "file://"); found { logger.Printf("Using GeoIP database from %s", geoipUrl) - geoip, err = NewGeoLookupFromFile(logger, geoipUrl) + geoipLookup, err = geoip.NewLookupFromFile(logger, geoipUrl) } else { logger.Printf("Downloading GeoIP database from %s", geoipUrl) - geoip, err = NewGeoLookupFromUrl(logger, geoipUrl) + geoipLookup, err = geoip.NewLookupFromUrl(logger, geoipUrl) } if err != nil { return nil, err @@ -349,7 +350,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpc logger.Printf("Not using GeoIP database") } - geoipOverrides, err := LoadGeoIPOverrides(ctx, cfg, false) + geoipOverrides, err := geoip.LoadOverrides(ctx, cfg, false) if err != nil { return nil, err } @@ -409,7 +410,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpc backendTimeout: backendTimeout, backend: backend, - geoip: geoip, + geoip: geoipLookup, etcdClient: etcdClient, rpcServer: rpcServer, @@ -444,9 +445,8 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpc } hub.trustedProxies.Store(trustedProxiesIps) - if len(geoipOverrides) > 0 { - hub.geoipOverrides.Store(&geoipOverrides) - } + hub.geoipOverrides.Store(geoipOverrides) + hub.setWelcomeMessage(&api.ServerMessage{ Type: "welcome", Welcome: api.NewWelcomeServerMessage(version, api.DefaultWelcomeFeatures...), @@ -588,12 +588,8 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { h.logger.Printf("Error parsing trusted proxies from \"%s\": %s", trustedProxies, err) } - geoipOverrides, _ := LoadGeoIPOverrides(ctx, config, true) - if len(geoipOverrides) > 0 { - h.geoipOverrides.Store(&geoipOverrides) - } else { - h.geoipOverrides.Store(nil) - } + geoipOverrides, _ := geoip.LoadOverrides(ctx, config, true) + h.geoipOverrides.Store(geoipOverrides) if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { if allowed, err := container.ParseIPList(value); err != nil { @@ -1066,8 +1062,8 @@ func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backe } h.mu.Unlock() - if country := client.Country(); IsValidCountry(country) { - statsClientCountries.WithLabelValues(country).Inc() + if country := client.Country(); geoip.IsValidCountry(country) { + statsClientCountries.WithLabelValues(string(country)).Inc() } statsHubSessionsCurrent.WithLabelValues(backend.Id(), string(session.ClientType())).Inc() statsHubSessionsTotal.WithLabelValues(backend.Id(), string(session.ClientType())).Inc() @@ -3157,35 +3153,31 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { client.ReadPump() } -func (h *Hub) OnLookupCountry(client HandlerClient) string { +func (h *Hub) OnLookupCountry(client HandlerClient) geoip.Country { ip := net.ParseIP(client.RemoteAddr()) if ip == nil { - return noCountry + return geoip.NoCountry } - if overrides := h.geoipOverrides.Load(); overrides != nil { - for overrideNet, country := range *overrides { - if overrideNet.Contains(ip) { - return country - } - } + if country, found := h.geoipOverrides.Load().Lookup(ip); found { + return country } if ip.IsLoopback() { - return loopback + return geoip.Loopback } - country := unknownCountry + country := geoip.UnknownCountry if h.geoip != nil { var err error country, err = h.geoip.LookupCountry(ip) if err != nil { h.logger.Printf("Could not lookup country for %s: %s", ip, err) - return unknownCountry + return geoip.UnknownCountry } if country == "" { - country = unknownCountry + country = geoip.UnknownCountry } } return country diff --git a/hub_test.go b/hub_test.go index 79ba5b2..a5929f7 100644 --- a/hub_test.go +++ b/hub_test.go @@ -55,6 +55,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" @@ -5218,11 +5219,11 @@ func TestGeoipOverrides(t *testing.T) { return conf, err }) - assert.Equal(loopback, hub.OnLookupCountry(&Client{addr: "127.0.0.1"})) - assert.Equal(unknownCountry, hub.OnLookupCountry(&Client{addr: "8.8.8.8"})) - assert.Equal(country1, hub.OnLookupCountry(&Client{addr: "10.1.1.2"})) - assert.Equal(country2, hub.OnLookupCountry(&Client{addr: "10.2.1.2"})) - assert.Equal(strings.ToUpper(country3), hub.OnLookupCountry(&Client{addr: "192.168.10.20"})) + assert.Equal(geoip.Loopback, hub.OnLookupCountry(&Client{addr: "127.0.0.1"})) + assert.Equal(geoip.UnknownCountry, hub.OnLookupCountry(&Client{addr: "8.8.8.8"})) + assert.EqualValues(country1, hub.OnLookupCountry(&Client{addr: "10.1.1.2"})) + assert.EqualValues(country2, hub.OnLookupCountry(&Client{addr: "10.2.1.2"})) + assert.EqualValues(strings.ToUpper(country3), hub.OnLookupCountry(&Client{addr: "192.168.10.20"})) } func TestDialoutStatus(t *testing.T) { diff --git a/mcu_common.go b/mcu_common.go index 38d852c..82b5c54 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -30,6 +30,7 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -70,7 +71,7 @@ type McuListener interface { } type McuInitiator interface { - Country() string + Country() geoip.Country } type McuSettings interface { diff --git a/mcu_common_test.go b/mcu_common_test.go index 736b447..bdff6fb 100644 --- a/mcu_common_test.go +++ b/mcu_common_test.go @@ -25,6 +25,7 @@ import ( "testing" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" ) func TestCommonMcuStats(t *testing.T) { @@ -65,9 +66,9 @@ func (m *MockMcuListener) SubscriberClosed(subscriber McuSubscriber) { } type MockMcuInitiator struct { - country string + country geoip.Country } -func (m *MockMcuInitiator) Country() string { +func (m *MockMcuInitiator) Country() geoip.Country { return m.country } diff --git a/mcu_janus_test.go b/mcu_janus_test.go index 1b2eebb..1b1ba15 100644 --- a/mcu_janus_test.go +++ b/mcu_janus_test.go @@ -37,6 +37,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" ) @@ -675,10 +676,10 @@ func (c *TestMcuController) GetStreams(ctx context.Context) ([]PublisherStream, } type TestMcuInitiator struct { - country string + country geoip.Country } -func (i *TestMcuInitiator) Country() string { +func (i *TestMcuInitiator) Country() geoip.Country { return i.country } diff --git a/mcu_proxy.go b/mcu_proxy.go index 930cdfe..e0407ed 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -77,6 +78,8 @@ const ( rttLogDuration = 500 * time.Millisecond ) +type ContinentsMap map[geoip.Continent][]geoip.Continent + type McuProxy interface { AddConnection(ignoreErrors bool, url string, ips ...net.IP) error KeepConnection(url string, ips ...net.IP) @@ -422,7 +425,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str conn.reconnectInterval.Store(int64(initialReconnectInterval)) conn.load.Store(loadNotConnected) conn.bandwidth.Store(nil) - conn.country.Store("") + conn.country.Store(geoip.Country("")) conn.version.Store("") conn.features.Store([]string{}) statsProxyBackendLoadCurrent.WithLabelValues(conn.url.String()).Set(0) @@ -474,7 +477,7 @@ func (c *mcuProxyConnection) IsSameContinent(initiator McuInitiator) bool { return true } - initiatorContinents, found := ContinentMap[initiatorCountry] + initiatorContinents, found := geoip.ContinentMap[initiatorCountry] if found { m := c.proxy.getContinentsMap() // Map continents to other continents (e.g. use Europe for Africa). @@ -485,7 +488,7 @@ func (c *mcuProxyConnection) IsSameContinent(initiator McuInitiator) bool { } } - connContinents := ContinentMap[connCountry] + connContinents := geoip.ContinentMap[connCountry] return ContinentsOverlap(initiatorContinents, connContinents) } @@ -539,8 +542,8 @@ func (c *mcuProxyConnection) Bandwidth() *EventProxyServerBandwidth { return c.bandwidth.Load() } -func (c *mcuProxyConnection) Country() string { - return c.country.Load().(string) +func (c *mcuProxyConnection) Country() geoip.Country { + return c.country.Load().(geoip.Country) } func (c *mcuProxyConnection) Version() string { @@ -733,7 +736,7 @@ func (c *mcuProxyConnection) close() { c.conn = nil c.connectedSince.Store(0) if c.trackClose.CompareAndSwap(true, false) { - statsConnectedProxyBackendsCurrent.WithLabelValues(c.Country()).Dec() + statsConnectedProxyBackendsCurrent.WithLabelValues(string(c.Country())).Dec() } } } @@ -1005,9 +1008,9 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { case "hello": resumed := c.SessionId() == msg.Hello.SessionId c.sessionId.Store(msg.Hello.SessionId) - country := "" + var country geoip.Country if server := msg.Hello.Server; server != nil { - if country = server.Country; country != "" && !IsValidCountry(country) { + if country = server.Country; country != "" && !geoip.IsValidCountry(country) { c.logger.Printf("Proxy %s sent invalid country %s in hello response", c, country) country = "" } @@ -1029,7 +1032,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { c.logger.Printf("Received session %s from %s", c.SessionId(), c) } if c.trackClose.CompareAndSwap(false, true) { - statsConnectedProxyBackendsCurrent.WithLabelValues(c.Country()).Inc() + statsConnectedProxyBackendsCurrent.WithLabelValues(string(c.Country())).Inc() } c.helloProcessed.Store(true) @@ -1603,18 +1606,18 @@ func (m *mcuProxy) loadContinentsMap(cfg *goconf.ConfigFile) error { return nil } - continentsMap := make(map[string][]string) + continentsMap := make(ContinentsMap) for option, value := range options { - option = strings.ToUpper(strings.TrimSpace(option)) - if !IsValidContinent(option) { + option := geoip.Continent(strings.ToUpper(strings.TrimSpace(option))) + if !geoip.IsValidContinent(option) { m.logger.Printf("Ignore unknown continent %s", option) continue } - var values []string + var values []geoip.Continent for v := range internal.SplitEntries(value, ",") { - v = strings.ToUpper(v) - if !IsValidContinent(v) { + v := geoip.Continent(strings.ToUpper(v)) + if !geoip.IsValidContinent(v) { m.logger.Printf("Ignore unknown continent %s for override %s", v, option) continue } @@ -1916,24 +1919,24 @@ func (m *mcuProxy) GetServerInfoSfu() *BackendServerInfoSfu { return sfu } -func (m *mcuProxy) getContinentsMap() map[string][]string { +func (m *mcuProxy) getContinentsMap() ContinentsMap { continentsMap := m.continentsMap.Load() if continentsMap == nil { return nil } - return continentsMap.(map[string][]string) + return continentsMap.(ContinentsMap) } -func (m *mcuProxy) setContinentsMap(continentsMap map[string][]string) { +func (m *mcuProxy) setContinentsMap(continentsMap ContinentsMap) { if continentsMap == nil { - continentsMap = make(map[string][]string) + continentsMap = make(ContinentsMap) } m.continentsMap.Store(continentsMap) } type mcuProxyConnectionsList []*mcuProxyConnection -func ContinentsOverlap(a, b []string) bool { +func ContinentsOverlap(a, b []geoip.Continent) bool { if len(a) == 0 || len(b) == 0 { return false } @@ -1946,7 +1949,7 @@ func ContinentsOverlap(a, b []string) bool { return false } -func sortConnectionsForCountry(connections []*mcuProxyConnection, country string, continentMap map[string][]string) []*mcuProxyConnection { +func sortConnectionsForCountry(connections []*mcuProxyConnection, country geoip.Country, continentMap ContinentsMap) []*mcuProxyConnection { // Move connections in the same country to the start of the list. sorted := make(mcuProxyConnectionsList, 0, len(connections)) unprocessed := make(mcuProxyConnectionsList, 0, len(connections)) @@ -1957,7 +1960,7 @@ func sortConnectionsForCountry(connections []*mcuProxyConnection, country string unprocessed = append(unprocessed, conn) } } - if continents, found := ContinentMap[country]; found && len(unprocessed) > 1 { + if continents, found := geoip.ContinentMap[country]; found && len(unprocessed) > 1 { remaining := make(mcuProxyConnectionsList, 0, len(unprocessed)) // Map continents to other continents (e.g. use Europe for Africa). for _, continent := range continents { @@ -1969,8 +1972,8 @@ func sortConnectionsForCountry(connections []*mcuProxyConnection, country string // Next up are connections on the same or mapped continent. for _, conn := range unprocessed { connCountry := conn.Country() - if IsValidCountry(connCountry) { - connContinents := ContinentMap[connCountry] + if geoip.IsValidCountry(connCountry) { + connContinents := geoip.ContinentMap[connCountry] if ContinentsOverlap(continents, connContinents) { sorted = append(sorted, conn) } else { @@ -2013,7 +2016,7 @@ func (m *mcuProxy) getSortedConnections(initiator McuInitiator) []*mcuProxyConne } if initiator != nil { - if country := initiator.Country(); IsValidCountry(country) { + if country := initiator.Country(); geoip.IsValidCountry(country) { connections = sortConnectionsForCountry(connections, country, m.getContinentsMap()) } } diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 40f5881..219ef34 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -52,6 +52,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -66,7 +67,7 @@ func TestMcuProxyStats(t *testing.T) { collectAndLint(t, proxyMcuStats...) } -func newProxyConnectionWithCountry(country string) *mcuProxyConnection { +func newProxyConnectionWithCountry(country geoip.Country) *mcuProxyConnection { conn := &mcuProxyConnection{} conn.country.Store(country) return conn @@ -79,7 +80,7 @@ func Test_sortConnectionsForCountry(t *testing.T) { conn_jp := newProxyConnectionWithCountry("JP") conn_us := newProxyConnectionWithCountry("US") - testcases := map[string][][]*mcuProxyConnection{ + testcases := map[geoip.Country][][]*mcuProxyConnection{ // Direct country match "DE": { {conn_at, conn_jp, conn_de}, @@ -118,7 +119,7 @@ func Test_sortConnectionsForCountry(t *testing.T) { } for country, test := range testcases { - t.Run(country, func(t *testing.T) { + t.Run(string(country), func(t *testing.T) { t.Parallel() sorted := sortConnectionsForCountry(test[0], country, nil) for idx, conn := range sorted { @@ -135,7 +136,7 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { conn_jp := newProxyConnectionWithCountry("JP") conn_us := newProxyConnectionWithCountry("US") - testcases := map[string][][]*mcuProxyConnection{ + testcases := map[geoip.Country][][]*mcuProxyConnection{ // Direct country match "DE": { {conn_at, conn_jp, conn_de}, @@ -183,14 +184,14 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { }, } - continentMap := map[string][]string{ + continentMap := ContinentsMap{ // Use European connections for Africa. "AF": {"EU"}, // Use Asian and North American connections for Oceania. "OC": {"AS", "NA"}, } for country, test := range testcases { - t.Run(country, func(t *testing.T) { + t.Run(string(country), func(t *testing.T) { t.Parallel() sorted := sortConnectionsForCountry(test[0], country, continentMap) for idx, conn := range sorted { @@ -561,7 +562,7 @@ type TestProxyServerHandler struct { servers []*TestProxyServerHandler tokens map[string]*rsa.PublicKey upgrader *websocket.Upgrader - country string + country geoip.Country mu sync.Mutex load atomic.Uint64 @@ -815,7 +816,7 @@ func (h *TestProxyServerHandler) ClearClients() { clear(h.clients) } -func NewProxyServerForTest(t *testing.T, country string) *TestProxyServerHandler { +func NewProxyServerForTest(t *testing.T, country geoip.Country) *TestProxyServerHandler { t.Helper() upgrader := websocket.Upgrader{} diff --git a/proxy/proxy_remote.go b/proxy/proxy_remote.go index 05c1f4f..3e8712e 100644 --- a/proxy/proxy_remote.go +++ b/proxy/proxy_remote.go @@ -41,6 +41,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -468,9 +469,9 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { resumed := c.sessionId == msg.Hello.SessionId c.sessionId = msg.Hello.SessionId c.helloReceived = true - country := "" + var country geoip.Country if msg.Hello.Server != nil { - if country = msg.Hello.Server.Country; country != "" && !signaling.IsValidCountry(country) { + if country = msg.Hello.Server.Country; country != "" && !geoip.IsValidCountry(country) { c.logger.Printf("Proxy %s sent invalid country %s in hello response", c, country) country = "" } diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index a0e7d4b..12f8fcd 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -54,6 +54,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -108,7 +109,7 @@ var ( type ProxyServer struct { version string - country string + country geoip.Country welcomeMessage string welcomeMsg *api.WelcomeServerMessage config *goconf.ConfigFile @@ -279,9 +280,9 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } - country, _ := config.GetString("app", "country") - country = strings.ToUpper(country) - if signaling.IsValidCountry(country) { + countryString, _ := config.GetString("app", "country") + country := geoip.Country(strings.ToUpper(countryString)) + if geoip.IsValidCountry(country) { logger.Printf("Sending %s as country information", country) } else if country != "" { return nil, fmt.Errorf("invalid country: %s", country) @@ -859,7 +860,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { type emptyInitiator struct{} -func (i *emptyInitiator) Country() string { +func (i *emptyInitiator) Country() geoip.Country { return "" } diff --git a/remotesession.go b/remotesession.go index 94bd4b7..8d71bc3 100644 --- a/remotesession.go +++ b/remotesession.go @@ -29,6 +29,7 @@ import ( "time" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -65,7 +66,7 @@ func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessio return remoteSession, nil } -func (s *RemoteSession) Country() string { +func (s *RemoteSession) Country() geoip.Country { return s.client.Country() } @@ -138,7 +139,7 @@ func (s *RemoteSession) Close() { s.client.Close() } -func (s *RemoteSession) OnLookupCountry(client HandlerClient) string { +func (s *RemoteSession) OnLookupCountry(client HandlerClient) geoip.Country { return s.hub.OnLookupCountry(client) } diff --git a/scripts/get_continent_map.py b/scripts/get_continent_map.py index 61ef9c4..d86cf3d 100755 --- a/scripts/get_continent_map.py +++ b/scripts/get_continent_map.py @@ -87,13 +87,13 @@ def generate_map(filename): continents.setdefault(country, []).append(continent) out = StringIO() - out.write('package signaling\n') + out.write('package geoip\n') out.write('\n') out.write('// This file has been automatically generated, do not modify.\n') out.write('// Source: %s\n' % (URL)) out.write('\n') out.write('var (\n') - out.write('\tContinentMap = map[string][]string{\n') + out.write('\tContinentMap = map[Country][]Continent{\n') for country, continents in sorted(continents.items()): value = [] for continent in continents: From fbf93dca42e981f36477974d985b9f625f2afcc7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 14:14:23 +0100 Subject: [PATCH 424/549] Move Talk-specific API to "talk" package. --- api/signaling.go | 19 +++ api_async.go | 5 +- async_events_nats.go | 8 +- backend_client.go | 2 +- backend_server.go | 56 ++++----- backend_server_test.go | 136 +++++++++++----------- client/main.go | 15 ++- clientsession.go | 66 +++++------ clientsession_test.go | 15 +-- federation.go | 2 +- grpc_client.go | 5 +- grpc_server.go | 5 +- hub.go | 114 ++++++++++-------- hub_test.go | 146 ++++++++++++------------ internal/random_string.go | 34 ++++++ internal/random_string_test.go | 41 +++++++ mcu_common.go | 3 +- mcu_janus.go | 11 +- mcu_proxy.go | 20 +++- mcu_proxy_test.go | 12 +- mcu_test.go | 6 +- proxy/proxy_server_test.go | 3 +- room.go | 49 ++++---- room_ping.go | 28 ++--- room_ping_test.go | 17 +-- room_test.go | 17 +-- roomsessions_test.go | 2 +- session.go | 21 +--- session_test.go | 6 +- api_backend.go => talk/api.go | 44 ++++--- api_backend_test.go => talk/api_test.go | 6 +- testclient_test.go | 3 +- transient_data_test.go | 4 +- virtualsession.go | 10 +- 34 files changed, 529 insertions(+), 402 deletions(-) create mode 100644 internal/random_string.go create mode 100644 internal/random_string_test.go rename api_backend.go => talk/api.go (93%) rename api_backend_test.go => talk/api_test.go (95%) diff --git a/api/signaling.go b/api/signaling.go index 0c7df41..56b4c61 100644 --- a/api/signaling.go +++ b/api/signaling.go @@ -83,6 +83,25 @@ func (s RoomSessionId) WithoutFederation() RoomSessionId { return RoomSessionId(strings.TrimPrefix(string(s), FederatedRoomSessionIdPrefix)) } +type Permission string + +var ( + PERMISSION_MAY_PUBLISH_MEDIA Permission = "publish-media" + PERMISSION_MAY_PUBLISH_AUDIO Permission = "publish-audio" + PERMISSION_MAY_PUBLISH_VIDEO Permission = "publish-video" + PERMISSION_MAY_PUBLISH_SCREEN Permission = "publish-screen" + PERMISSION_MAY_CONTROL Permission = "control" + PERMISSION_TRANSIENT_DATA Permission = "transient-data" + PERMISSION_HIDE_DISPLAYNAMES Permission = "hide-displaynames" + + // DefaultPermissionOverrides contains permission overrides for users where + // no permissions have been set by the server. If a permission is not set in + // this map, it's assumed the user has that permission. + DefaultPermissionOverrides = map[Permission]bool{ // +checklocksignore: Global readonly variable. + PERMISSION_HIDE_DISPLAYNAMES: false, + } +) + // ClientMessage is a message that is sent from a client to the server. type ClientMessage struct { json.Marshaler diff --git a/api_async.go b/api_async.go index 97a92ab..d0a3f81 100644 --- a/api_async.go +++ b/api_async.go @@ -27,6 +27,7 @@ import ( "time" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type AsyncMessage struct { @@ -36,9 +37,9 @@ type AsyncMessage struct { Message *api.ServerMessage `json:"message,omitempty"` - Room *BackendServerRoomRequest `json:"room,omitempty"` + Room *talk.BackendServerRoomRequest `json:"room,omitempty"` - Permissions []Permission `json:"permissions,omitempty"` + Permissions []api.Permission `json:"permissions,omitempty"` AsyncRoom *AsyncRoomMessage `json:"asyncroom,omitempty"` diff --git a/async_events_nats.go b/async_events_nats.go index 4bc5788..565f655 100644 --- a/async_events_nats.go +++ b/async_events_nats.go @@ -91,12 +91,12 @@ func NewAsyncEventsNats(logger log.Logger, client nats.Client) (AsyncEvents, err return events, nil } -func (e *asyncEventsNats) GetServerInfoNats() *BackendServerInfoNats { +func (e *asyncEventsNats) GetServerInfoNats() *talk.BackendServerInfoNats { // TODO: This should call a method on "e.client" directly instead of having a type switch. - var result *BackendServerInfoNats + var result *talk.BackendServerInfoNats switch n := e.client.(type) { case *nats.NativeClient: - result = &BackendServerInfoNats{ + result = &talk.BackendServerInfoNats{ Urls: n.URLs(), } if n.IsConnected() { @@ -107,7 +107,7 @@ func (e *asyncEventsNats) GetServerInfoNats() *BackendServerInfoNats { result.ClusterName = n.ConnectedClusterName() } case *nats.LoopbackClient: - result = &BackendServerInfoNats{ + result = &talk.BackendServerInfoNats{ Urls: []string{nats.LoopbackUrl}, Connected: true, ServerUrl: nats.LoopbackUrl, diff --git a/backend_client.go b/backend_client.go index 54a7def..7c92ad4 100644 --- a/backend_client.go +++ b/backend_client.go @@ -166,7 +166,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } // Add checksum so the backend can validate the request. - AddBackendChecksum(req, data.Bytes(), backend.Secret()) + talk.AddBackendChecksum(req, data.Bytes(), backend.Secret()) start := time.Now() resp, err := c.Do(req) diff --git a/backend_server.go b/backend_server.go index 3b25831..b0e23ce 100644 --- a/backend_server.go +++ b/backend_server.go @@ -264,11 +264,11 @@ func (b *BackendServer) getTurnCredentials(w http.ResponseWriter, r *http.Reques if username == "" { // Make sure to include an actual username in the credentials. - username = newRandomString(randomUsernameLength) + username = internal.RandomString(randomUsernameLength) } username, password := calculateTurnSecret(username, b.turnsecret, b.turnvalid) - result := TurnCredentials{ + result := talk.TurnCredentials{ Username: username, Password: password, TTL: int64(b.turnvalid.Seconds()), @@ -309,8 +309,8 @@ func (b *BackendServer) parseRequestBody(f func(context.Context, http.ResponseWr return } - if r.Header.Get(HeaderBackendSignalingRandom) == "" || - r.Header.Get(HeaderBackendSignalingChecksum) == "" { + if r.Header.Get(talk.HeaderBackendSignalingRandom) == "" || + r.Header.Get(talk.HeaderBackendSignalingChecksum) == "" { http.Error(w, "Authentication check failed", http.StatusForbidden) return } @@ -500,7 +500,7 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container. return result } -func (b *BackendServer) sendRoomIncall(roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomIncall(roomid string, backend *talk.Backend, request *talk.BackendServerRoomRequest) error { if !request.InCall.All { timeout := time.Second @@ -525,7 +525,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *talk.Backend, req return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid string, backend *talk.Backend, request *talk.BackendServerRoomRequest) error { timeout := time.Second // Convert (Nextcloud) session ids to signaling session ids. @@ -558,18 +558,18 @@ loop: b.logger.Printf("Received invalid permissions %+v (%s) for session %s", permissionsInterface, reflect.TypeOf(permissionsInterface), sessionId) continue } - var permissions []Permission + var permissions []api.Permission for idx, ob := range permissionsList { permission, ok := ob.(string) if !ok { b.logger.Printf("Received invalid permission at position %d %+v (%s) for session %s", idx, ob, reflect.TypeOf(ob), sessionId) continue loop } - permissions = append(permissions, Permission(permission)) + permissions = append(permissions, api.Permission(permission)) } wg.Add(1) - go func(sessionId api.PublicSessionId, permissions []Permission) { + go func(sessionId api.PublicSessionId, permissions []api.Permission) { defer wg.Done() message := &AsyncMessage{ Type: "permissions", @@ -589,7 +589,7 @@ loop: return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomMessage(roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomMessage(roomid string, backend *talk.Backend, request *talk.BackendServerRoomRequest) error { message := &AsyncMessage{ Type: "room", Room: request, @@ -597,7 +597,7 @@ func (b *BackendServer) sendRoomMessage(roomid string, backend *talk.Backend, re return b.events.PublishBackendRoomMessage(roomid, backend, message) } -func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, backend *talk.Backend, request *BackendServerRoomRequest) error { +func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, backend *talk.Backend, request *talk.BackendServerRoomRequest) error { timeout := time.Second // Convert (Nextcloud) session ids to signaling session ids. @@ -609,7 +609,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac if len(request.SwitchTo.Sessions) > 0 { // We support both a list of sessions or a map with additional details per session. if request.SwitchTo.Sessions[0] == '[' { - var sessionsList BackendRoomSwitchToSessionsList + var sessionsList talk.BackendRoomSwitchToSessionsList if err := json.Unmarshal(request.SwitchTo.Sessions, &sessionsList); err != nil { return err } @@ -618,7 +618,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac return nil } - var internalSessionsList BackendRoomSwitchToPublicSessionsList + var internalSessionsList talk.BackendRoomSwitchToPublicSessionsList for _, roomSessionId := range sessionsList { if roomSessionId == sessionIdNotInMeeting { continue @@ -647,7 +647,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac request.SwitchTo.SessionsList = internalSessionsList request.SwitchTo.SessionsMap = nil } else { - var sessionsMap BackendRoomSwitchToSessionsMap + var sessionsMap talk.BackendRoomSwitchToSessionsMap if err := json.Unmarshal(request.SwitchTo.Sessions, &sessionsMap); err != nil { return err } @@ -656,7 +656,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac return nil } - internalSessionsMap := make(BackendRoomSwitchToPublicSessionsMap) + internalSessionsMap := make(talk.BackendRoomSwitchToPublicSessionsMap) for roomSessionId, details := range sessionsMap { if roomSessionId == sessionIdNotInMeeting { continue @@ -700,7 +700,7 @@ type BackendResponseWithStatus interface { } type DialoutErrorResponse struct { - BackendServerRoomResponse + talk.BackendServerRoomResponse status int } @@ -711,9 +711,9 @@ func (r *DialoutErrorResponse) Status() int { func returnDialoutError(status int, err *api.Error) (any, error) { response := &DialoutErrorResponse{ - BackendServerRoomResponse: BackendServerRoomResponse{ + BackendServerRoomResponse: talk.BackendServerRoomResponse{ Type: "dialout", - Dialout: &BackendRoomDialoutResponse{ + Dialout: &talk.BackendRoomDialoutResponse{ Error: err, }, }, @@ -729,7 +729,7 @@ func isNumeric(s string) bool { return checkNumeric.MatchString(s) } -func (b *BackendServer) startDialoutInSession(ctx context.Context, session *ClientSession, roomid string, backend *talk.Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { +func (b *BackendServer) startDialoutInSession(ctx context.Context, session *ClientSession, roomid string, backend *talk.Backend, backendUrl string, request *talk.BackendServerRoomRequest) (any, error) { url := backendUrl if url != "" && url[len(url)-1] != '/' { url += "/" @@ -742,7 +742,7 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie url = urls[0] } } - id := newRandomString(32) + id := internal.RandomString(32) msg := &api.ServerMessage{ Id: id, Type: "internal", @@ -799,9 +799,9 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie return nil, api.NewError("unsupported_status", fmt.Sprintf("Unsupported dialout status received: %+v", dialout)) } - return &BackendServerRoomResponse{ + return &talk.BackendServerRoomResponse{ Type: "dialout", - Dialout: &BackendRoomDialoutResponse{ + Dialout: &talk.BackendRoomDialoutResponse{ CallId: dialout.Status.CallId, }, }, nil @@ -810,7 +810,7 @@ func (b *BackendServer) startDialoutInSession(ctx context.Context, session *Clie } } -func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend *talk.Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { +func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend *talk.Backend, backendUrl string, request *talk.BackendServerRoomRequest) (any, error) { if err := request.Dialout.ValidateNumber(); err != nil { return returnDialoutError(http.StatusBadRequest, err) } @@ -862,7 +862,7 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, roomid := v["roomid"] var backend *talk.Backend - backendUrl := r.Header.Get(HeaderBackendServer) + backendUrl := r.Header.Get(talk.HeaderBackendServer) if backendUrl != "" { if u, err := url.Parse(backendUrl); err == nil { backend = b.hub.backend.GetBackend(u) @@ -884,7 +884,7 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, // Old-style Talk, find backend that created the checksum. // TODO(fancycode): Remove once all supported Talk versions send the backend header. for _, b := range b.hub.backend.GetBackends() { - if ValidateBackendChecksum(r, body, b.Secret()) { + if talk.ValidateBackendChecksum(r, body, b.Secret()) { backend = b break } @@ -898,13 +898,13 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, } } - if !ValidateBackendChecksum(r, body, backend.Secret()) { + if !talk.ValidateBackendChecksum(r, body, backend.Secret()) { throttle(ctx) http.Error(w, "Authentication check failed", http.StatusForbidden) return } - var request BackendServerRoomRequest + var request talk.BackendServerRoomRequest if err := json.Unmarshal(body, &request); err != nil { b.logger.Printf("Error decoding body %s: %s", string(body), err) http.Error(w, "Could not read body", http.StatusBadRequest) @@ -1017,7 +1017,7 @@ func (b *BackendServer) statsHandler(w http.ResponseWriter, r *http.Request) { } func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request) { - info := BackendServerInfo{ + info := talk.BackendServerInfo{ Version: b.version, Features: b.hub.info.Features, diff --git a/backend_server_test.go b/backend_server_test.go index 01e1144..7eda72f 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -47,8 +47,10 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "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" ) var ( @@ -229,8 +231,8 @@ func performBackendRequest(requestUrl string, body []byte) (*http.Response, erro return nil, err } request.Header.Set("Content-Type", "application/json") - rnd := newRandomString(32) - check := CalculateBackendChecksum(rnd, body, testBackendSecret) + rnd := internal.RandomString(32) + check := talk.CalculateBackendChecksum(rnd, body, testBackendSecret) request.Header.Set("Spreed-Signaling-Random", rnd) request.Header.Set("Spreed-Signaling-Checksum", check) u, err := url.Parse(requestUrl) @@ -323,9 +325,9 @@ func TestBackendServer_OldCompatAuth(t *testing.T) { roomId := "the-room-id" userid := "the-user-id" roomProperties := json.RawMessage("{\"foo\":\"bar\"}") - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "invite", - Invite: &BackendRoomInviteRequest{ + Invite: &talk.BackendRoomInviteRequest{ UserIds: []string{ userid, }, @@ -342,8 +344,8 @@ func TestBackendServer_OldCompatAuth(t *testing.T) { request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data)) require.NoError(err) request.Header.Set("Content-Type", "application/json") - rnd := newRandomString(32) - check := CalculateBackendChecksum(rnd, data, testBackendSecret) + rnd := internal.RandomString(32) + check := talk.CalculateBackendChecksum(rnd, data, testBackendSecret) request.Header.Set("Spreed-Signaling-Random", rnd) request.Header.Set("Spreed-Signaling-Checksum", check) client := &http.Client{} @@ -378,7 +380,7 @@ func TestBackendServer_UnsupportedRequest(t *testing.T) { assert := assert.New(t) _, _, _, _, _, server := CreateBackendServerForTest(t) - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "lala", } @@ -433,9 +435,9 @@ func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { defer func() { assert.NoError(events.UnregisterUserListener(userid, backend, listener)) }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "invite", - Invite: &BackendRoomInviteRequest{ + Invite: &talk.BackendRoomInviteRequest{ UserIds: []string{ userid, }, @@ -509,9 +511,9 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { defer func() { assert.NoError(events.UnregisterUserListener(testDefaultUserId, backend, listener)) }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "disinvite", - Disinvite: &BackendRoomDisinviteRequest{ + Disinvite: &talk.BackendRoomDisinviteRequest{ UserIds: []string{ testDefaultUserId, }, @@ -569,9 +571,9 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { MustSucceed2(t, client2.JoinRoom, ctx, roomId2) require.True(client2.RunUntilJoined(ctx, hello2.Hello)) - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "disinvite", - Disinvite: &BackendRoomDisinviteRequest{ + Disinvite: &talk.BackendRoomDisinviteRequest{ UserIds: []string{ testDefaultUserId, }, @@ -601,9 +603,9 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { assert.Equal(roomId1, message.RoomId) } - msg = &BackendServerRoomRequest{ + msg = &talk.BackendServerRoomRequest{ Type: "update", - Update: &BackendRoomUpdateRequest{ + Update: &talk.BackendRoomUpdateRequest{ UserIds: []string{ testDefaultUserId, }, @@ -664,9 +666,9 @@ func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { defer func() { assert.NoError(events.UnregisterUserListener(userid, backend, listener)) }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "update", - Update: &BackendRoomUpdateRequest{ + Update: &talk.BackendRoomUpdateRequest{ UserIds: []string{ userid, }, @@ -732,9 +734,9 @@ func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) { defer func() { assert.NoError(events.UnregisterUserListener(userid, backend, listener)) }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "delete", - Delete: &BackendRoomDeleteRequest{ + Delete: &talk.BackendRoomDeleteRequest{ UserIds: []string{ userid, }, @@ -801,10 +803,10 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) // Sessions have all permissions initially (fallback for old-style sessions). - assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_MEDIA) - assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_SCREEN) - assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_MEDIA) - assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_SCREEN) + assertSessionHasPermission(t, session1, api.PERMISSION_MAY_PUBLISH_MEDIA) + assertSessionHasPermission(t, session1, api.PERMISSION_MAY_PUBLISH_SCREEN) + assertSessionHasPermission(t, session2, api.PERMISSION_MAY_PUBLISH_MEDIA) + assertSessionHasPermission(t, session2, api.PERMISSION_MAY_PUBLISH_SCREEN) // Join room by id. roomId := "test-room" @@ -817,27 +819,27 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { assert.NoError(client1.DrainMessages(ctx)) assert.NoError(client2.DrainMessages(ctx)) - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "participants", - Participants: &BackendRoomParticipantsRequest{ + Participants: &talk.BackendRoomParticipantsRequest{ Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA}, }, { "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_SCREEN}, }, }, Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA}, }, { "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_SCREEN}, }, }, }, @@ -856,10 +858,10 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { // TODO: Use event to wait for asynchronous messages. time.Sleep(10 * time.Millisecond) - assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_MEDIA) - assertSessionHasNotPermission(t, session1, PERMISSION_MAY_PUBLISH_SCREEN) - assertSessionHasNotPermission(t, session2, PERMISSION_MAY_PUBLISH_MEDIA) - assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_SCREEN) + assertSessionHasPermission(t, session1, api.PERMISSION_MAY_PUBLISH_MEDIA) + assertSessionHasNotPermission(t, session1, api.PERMISSION_MAY_PUBLISH_SCREEN) + assertSessionHasNotPermission(t, session2, api.PERMISSION_MAY_PUBLISH_MEDIA) + assertSessionHasPermission(t, session2, api.PERMISSION_MAY_PUBLISH_SCREEN) }) } } @@ -882,8 +884,8 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { assert.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Sessions have all permissions initially (fallback for old-style sessions). - assertSessionHasPermission(t, session, PERMISSION_MAY_PUBLISH_MEDIA) - assertSessionHasPermission(t, session, PERMISSION_MAY_PUBLISH_SCREEN) + assertSessionHasPermission(t, session, api.PERMISSION_MAY_PUBLISH_MEDIA) + assertSessionHasPermission(t, session, api.PERMISSION_MAY_PUBLISH_SCREEN) // Join room by id. roomId := "test-room" @@ -895,19 +897,19 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { // Updating with empty permissions upgrades to non-old-style and removes // all previously available permissions. - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "participants", - Participants: &BackendRoomParticipantsRequest{ + Participants: &talk.BackendRoomParticipantsRequest{ Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), - "permissions": []Permission{}, + "permissions": []api.Permission{}, }, }, Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), - "permissions": []Permission{}, + "permissions": []api.Permission{}, }, }, }, @@ -925,8 +927,8 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { // TODO: Use event to wait for asynchronous messages. time.Sleep(10 * time.Millisecond) - assertSessionHasNotPermission(t, session, PERMISSION_MAY_PUBLISH_MEDIA) - assertSessionHasNotPermission(t, session, PERMISSION_MAY_PUBLISH_SCREEN) + assertSessionHasNotPermission(t, session, api.PERMISSION_MAY_PUBLISH_MEDIA) + assertSessionHasNotPermission(t, session, api.PERMISSION_MAY_PUBLISH_SCREEN) } func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { @@ -963,9 +965,9 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ InCall: json.RawMessage("7"), Changed: []api.StringMap{ { @@ -1010,9 +1012,9 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ InCall: json.RawMessage("7"), Changed: []api.StringMap{ { @@ -1150,9 +1152,9 @@ func TestBackendServer_InCallAll(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ InCall: json.RawMessage("7"), All: true, }, @@ -1207,9 +1209,9 @@ func TestBackendServer_InCallAll(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ InCall: json.RawMessage("0"), All: true, }, @@ -1287,9 +1289,9 @@ func TestBackendServer_RoomMessage(t *testing.T) { assert.NoError(client.DrainMessages(ctx)) messageData := json.RawMessage("{\"foo\":\"bar\"}") - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "message", - Message: &BackendRoomMessageRequest{ + Message: &talk.BackendRoomMessageRequest{ Data: messageData, }, } @@ -1328,7 +1330,7 @@ func TestBackendServer_TurnCredentials(t *testing.T) { assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - var cred TurnCredentials + var cred talk.TurnCredentials require.NoError(json.Unmarshal(body, &cred)) m := hmac.New(sha1.New, []byte(turnSecret)) @@ -1452,9 +1454,9 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { MustSucceed1(t, client.RunUntilHello, ctx) roomId := "12345" - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "dialout", - Dialout: &BackendRoomDialoutRequest{ + Dialout: &talk.BackendRoomDialoutRequest{ Number: "+1234567890", }, } @@ -1468,7 +1470,7 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { assert.NoError(err) require.Equal(http.StatusNotFound, res.StatusCode, "Expected error, got %s", string(body)) - var response BackendServerRoomResponse + var response talk.BackendServerRoomResponse if assert.NoError(json.Unmarshal(body, &response)) { assert.Equal("dialout", response.Type) if assert.NotNil(response.Dialout) && @@ -1539,9 +1541,9 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { <-stopped }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "dialout", - Dialout: &BackendRoomDialoutRequest{ + Dialout: &talk.BackendRoomDialoutRequest{ Number: "+1234567890", }, } @@ -1555,7 +1557,7 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { assert.NoError(err) require.Equal(http.StatusOK, res.StatusCode, "Expected success, got %s", string(body)) - var response BackendServerRoomResponse + var response talk.BackendServerRoomResponse if err := json.Unmarshal(body, &response); assert.NoError(err) { assert.Equal("dialout", response.Type) if assert.NotNil(response.Dialout) { @@ -1626,9 +1628,9 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { <-stopped }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "dialout", - Dialout: &BackendRoomDialoutRequest{ + Dialout: &talk.BackendRoomDialoutRequest{ Number: "+1234567890", }, } @@ -1642,7 +1644,7 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { assert.NoError(err) require.Equal(http.StatusOK, res.StatusCode, "Expected success, got %s", string(body)) - var response BackendServerRoomResponse + var response talk.BackendServerRoomResponse if err := json.Unmarshal(body, &response); assert.NoError(err) { assert.Equal("dialout", response.Type) if assert.NotNil(response.Dialout) { @@ -1710,9 +1712,9 @@ func TestBackendServer_DialoutRejected(t *testing.T) { <-stopped }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "dialout", - Dialout: &BackendRoomDialoutRequest{ + Dialout: &talk.BackendRoomDialoutRequest{ Number: "+1234567890", }, } @@ -1726,7 +1728,7 @@ func TestBackendServer_DialoutRejected(t *testing.T) { assert.NoError(err) require.Equal(http.StatusBadGateway, res.StatusCode, "Expected error, got %s", string(body)) - var response BackendServerRoomResponse + var response talk.BackendServerRoomResponse if err := json.Unmarshal(body, &response); assert.NoError(err) { assert.Equal("dialout", response.Type) if assert.NotNil(response.Dialout) && @@ -1824,9 +1826,9 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { wg.Wait() }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "dialout", - Dialout: &BackendRoomDialoutRequest{ + Dialout: &talk.BackendRoomDialoutRequest{ Number: "+1234567890", }, } @@ -1840,7 +1842,7 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { assert.NoError(err) require.Equal(http.StatusOK, res.StatusCode, "Expected success, got %s", string(body)) - var response BackendServerRoomResponse + var response talk.BackendServerRoomResponse if err := json.Unmarshal(body, &response); assert.NoError(err) { assert.Equal("dialout", response.Type) if assert.NotNil(response.Dialout) { diff --git a/client/main.go b/client/main.go index dd38d7f..da1e264 100644 --- a/client/main.go +++ b/client/main.go @@ -47,7 +47,6 @@ import ( "github.com/mailru/easyjson/jlexer" "github.com/mailru/easyjson/jwriter" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" @@ -428,28 +427,28 @@ func registerAuthHandler(router *mux.Router) { return } - rnd := r.Header.Get(signaling.HeaderBackendSignalingRandom) - checksum := r.Header.Get(signaling.HeaderBackendSignalingChecksum) + rnd := r.Header.Get(talk.HeaderBackendSignalingRandom) + checksum := r.Header.Get(talk.HeaderBackendSignalingChecksum) if rnd == "" || checksum == "" { log.Println("No checksum headers found") return } - if verify := signaling.CalculateBackendChecksum(rnd, body, backendSecret); verify != checksum { + if verify := talk.CalculateBackendChecksum(rnd, body, backendSecret); verify != checksum { log.Println("Backend checksum verification failed") return } - var request signaling.BackendClientRequest + var request talk.BackendClientRequest if err := request.UnmarshalJSON(body); err != nil { log.Println(err) return } - response := &signaling.BackendClientResponse{ + response := &talk.BackendClientResponse{ Type: "auth", - Auth: &signaling.BackendClientAuthResponse{ - Version: signaling.BackendVersion, + Auth: &talk.BackendClientAuthResponse{ + Version: talk.BackendVersion, UserId: "sample-user", }, } diff --git a/clientsession.go b/clientsession.go index 7612ac4..e12f08c 100644 --- a/clientsession.go +++ b/clientsession.go @@ -75,7 +75,7 @@ type ClientSession struct { // +checklocks:mu supportsPermissions bool // +checklocks:mu - permissions map[Permission]bool + permissions map[api.Permission]bool backend *talk.Backend backendUrl string @@ -120,7 +120,7 @@ type ClientSession struct { responseHandlers map[string]ResponseHandlerFunc } -func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, backend *talk.Backend, hello *api.HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) { +func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, backend *talk.Backend, hello *api.HelloClientMessage, auth *talk.BackendClientAuthResponse) (*ClientSession, error) { ctx := log.NewLoggerContext(context.Background(), hub.logger) ctx, closeFunc := context.WithCancel(ctx) s := &ClientSession{ @@ -208,18 +208,18 @@ func (s *ClientSession) HasFeature(feature string) bool { } // HasPermission checks if the session has the passed permissions. -func (s *ClientSession) HasPermission(permission Permission) bool { +func (s *ClientSession) HasPermission(permission api.Permission) bool { s.mu.Lock() defer s.mu.Unlock() return s.hasPermissionLocked(permission) } -func (s *ClientSession) GetPermissions() []Permission { +func (s *ClientSession) GetPermissions() []api.Permission { s.mu.Lock() defer s.mu.Unlock() - result := make([]Permission, len(s.permissions)) + result := make([]api.Permission, len(s.permissions)) for p, ok := range s.permissions { if ok { result = append(result, p) @@ -229,7 +229,7 @@ func (s *ClientSession) GetPermissions() []Permission { } // HasAnyPermission checks if the session has one of the passed permissions. -func (s *ClientSession) HasAnyPermission(permission ...Permission) bool { +func (s *ClientSession) HasAnyPermission(permission ...api.Permission) bool { if len(permission) == 0 { return false } @@ -241,7 +241,7 @@ func (s *ClientSession) HasAnyPermission(permission ...Permission) bool { } // +checklocks:s.mu -func (s *ClientSession) hasAnyPermissionLocked(permission ...Permission) bool { +func (s *ClientSession) hasAnyPermissionLocked(permission ...api.Permission) bool { if len(permission) == 0 { return false } @@ -250,10 +250,10 @@ func (s *ClientSession) hasAnyPermissionLocked(permission ...Permission) bool { } // +checklocks:s.mu -func (s *ClientSession) hasPermissionLocked(permission Permission) bool { +func (s *ClientSession) hasPermissionLocked(permission api.Permission) bool { if !s.supportsPermissions { // Old-style session that doesn't receive permissions from Nextcloud. - if result, found := DefaultPermissionOverrides[permission]; found { + if result, found := api.DefaultPermissionOverrides[permission]; found { return result } return true @@ -265,11 +265,11 @@ func (s *ClientSession) hasPermissionLocked(permission Permission) bool { return false } -func (s *ClientSession) SetPermissions(permissions []Permission) { - var p map[Permission]bool +func (s *ClientSession) SetPermissions(permissions []api.Permission) { + var p map[api.Permission]bool for _, permission := range permissions { if p == nil { - p = make(map[Permission]bool) + p = make(map[api.Permission]bool) } p[permission] = true } @@ -584,7 +584,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { // Notify go func(sid api.RoomSessionId) { ctx := log.NewLoggerContext(context.Background(), s.logger) - request := NewBackendClientRoomRequest(room.Id(), s.userId, sid) + request := talk.NewBackendClientRoomRequest(room.Id(), s.userId, sid) request.Room.UpdateFromSession(s) request.Room.Action = "leave" var response api.StringMap @@ -824,10 +824,10 @@ func (s *ClientSession) SubscriberClosed(subscriber McuSubscriber) { } type PermissionError struct { - permission Permission + permission api.Permission } -func (e *PermissionError) Permission() Permission { +func (e *PermissionError) Permission() api.Permission { return e.permission } @@ -843,18 +843,18 @@ func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (M } var mediaTypes MediaType - mayPublishMedia := s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_MEDIA) + mayPublishMedia := s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_MEDIA) for _, md := range sdp.MediaDescriptions { switch md.MediaName.Media { case "audio": - if !mayPublishMedia && !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_AUDIO) { - return 0, &PermissionError{PERMISSION_MAY_PUBLISH_AUDIO} + if !mayPublishMedia && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_AUDIO) { + return 0, &PermissionError{api.PERMISSION_MAY_PUBLISH_AUDIO} } mediaTypes |= MediaTypeAudio case "video": - if !mayPublishMedia && !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_VIDEO) { - return 0, &PermissionError{PERMISSION_MAY_PUBLISH_VIDEO} + if !mayPublishMedia && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_VIDEO) { + return 0, &PermissionError{api.PERMISSION_MAY_PUBLISH_VIDEO} } mediaTypes |= MediaTypeVideo @@ -870,11 +870,11 @@ func (s *ClientSession) IsAllowedToSend(data *api.MessageClientMessageData) erro switch { case data != nil && data.RoomType == "screen": - if s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { + if s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_SCREEN) { return nil } - return &PermissionError{PERMISSION_MAY_PUBLISH_SCREEN} - case s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_MEDIA): + return &PermissionError{api.PERMISSION_MAY_PUBLISH_SCREEN} + case s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_MEDIA): // Client is allowed to publish any media (audio / video). return nil case data != nil && data.Type == "offer": @@ -886,7 +886,7 @@ func (s *ClientSession) IsAllowedToSend(data *api.MessageClientMessageData) erro return nil default: // Candidate or unknown event, check if client is allowed to publish any media. - if s.hasAnyPermissionLocked(PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO) { + if s.hasAnyPermissionLocked(api.PERMISSION_MAY_PUBLISH_AUDIO, api.PERMISSION_MAY_PUBLISH_VIDEO) { return nil } @@ -904,8 +904,8 @@ func (s *ClientSession) CheckOfferType(streamType StreamType, data *api.MessageC // +checklocks:s.mu func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *api.MessageClientMessageData) (MediaType, error) { if streamType == StreamTypeScreen { - if !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { - return 0, &PermissionError{PERMISSION_MAY_PUBLISH_SCREEN} + if !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_SCREEN) { + return 0, &PermissionError{api.PERMISSION_MAY_PUBLISH_SCREEN} } return MediaTypeScreen, nil @@ -1088,10 +1088,10 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { s.mu.Lock() defer s.mu.Unlock() - if !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_MEDIA) { + if !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_MEDIA) { if publisher, found := s.publishers[StreamTypeVideo]; found { - if (publisher.HasMedia(MediaTypeAudio) && !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_AUDIO)) || - (publisher.HasMedia(MediaTypeVideo) && !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_VIDEO)) { + if (publisher.HasMedia(MediaTypeAudio) && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_AUDIO)) || + (publisher.HasMedia(MediaTypeVideo) && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_VIDEO)) { delete(s.publishers, StreamTypeVideo) s.logger.Printf("Session %s is no longer allowed to publish media, closing publisher %s", s.PublicId(), publisher.Id()) go func() { @@ -1101,7 +1101,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { } } } - if !s.hasPermissionLocked(PERMISSION_MAY_PUBLISH_SCREEN) { + if !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_SCREEN) { if publisher, found := s.publishers[StreamTypeScreen]; found { delete(s.publishers, StreamTypeScreen) s.logger.Printf("Session %s is no longer allowed to publish screen, closing publisher %s", s.PublicId(), publisher.Id()) @@ -1317,7 +1317,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes } } - if s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { + if s.HasPermission(api.PERMISSION_HIDE_DISPLAYNAMES) { if copied { message.Event.Join = filterDisplayNames(message.Event.Join) } else { @@ -1361,7 +1361,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes update = true } - if len(data.Chat.Comment) > 0 && s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { + if len(data.Chat.Comment) > 0 && s.HasPermission(api.PERMISSION_HIDE_DISPLAYNAMES) { var comment api.ChatComment if err := json.Unmarshal(data.Chat.Comment, &comment); err != nil { return message @@ -1398,7 +1398,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes } } case "message": - if message.Message != nil && len(message.Message.Data) > 0 && s.HasPermission(PERMISSION_HIDE_DISPLAYNAMES) { + if message.Message != nil && len(message.Message.Data) > 0 && s.HasPermission(api.PERMISSION_HIDE_DISPLAYNAMES) { var data api.MessageServerMessageData if err := json.Unmarshal(message.Message.Data, &data); err != nil { return message diff --git a/clientsession_test.go b/clientsession_test.go index a16f0d6..a308aee 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -34,6 +34,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func TestBandwidth_Client(t *testing.T) { @@ -219,9 +220,9 @@ func TestFeatureChatRelay(t *testing.T) { // Simulate request from the backend. room.processAsyncMessage(&AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "message", - Message: &BackendRoomMessageRequest{ + Message: &talk.BackendRoomMessageRequest{ Data: data, }, }, @@ -416,9 +417,9 @@ func TestFeatureChatRelayFederation(t *testing.T) { // Simulate request from the backend. room.processAsyncMessage(&AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "message", - Message: &BackendRoomMessageRequest{ + Message: &talk.BackendRoomMessageRequest{ Data: data, }, }, @@ -494,7 +495,7 @@ func TestPermissionHideDisplayNames(t *testing.T) { require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client may not receive display names. - session.SetPermissions([]Permission{PERMISSION_HIDE_DISPLAYNAMES}) + session.SetPermissions([]api.Permission{api.PERMISSION_HIDE_DISPLAYNAMES}) } chatComment := api.StringMap{ @@ -516,9 +517,9 @@ func TestPermissionHideDisplayNames(t *testing.T) { // Simulate request from the backend. room.processAsyncMessage(&AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "message", - Message: &BackendRoomMessageRequest{ + Message: &talk.BackendRoomMessageRequest{ Data: data, }, }, diff --git a/federation.go b/federation.go index f936eeb..bdb50c5 100644 --- a/federation.go +++ b/federation.go @@ -482,7 +482,7 @@ func (c *FederationClient) sendHello(auth *api.FederationAuthParams) error { // +checklocks:c.helloMu func (c *FederationClient) sendHelloLocked(auth *api.FederationAuthParams) error { - c.helloMsgId = newRandomString(8) + c.helloMsgId = internal.RandomString(8) authData, err := json.Marshal(auth) if err != nil { diff --git a/grpc_client.go b/grpc_client.go index 07912fb..7e8907b 100644 --- a/grpc_client.go +++ b/grpc_client.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -504,7 +505,7 @@ func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient e return result, nil } -func (c *GrpcClients) GetServerInfoGrpc() (result []BackendServerInfoGrpc) { +func (c *GrpcClients) GetServerInfoGrpc() (result []talk.BackendServerInfoGrpc) { c.mu.RLock() defer c.mu.RUnlock() @@ -513,7 +514,7 @@ func (c *GrpcClients) GetServerInfoGrpc() (result []BackendServerInfoGrpc) { continue } - grpc := BackendServerInfoGrpc{ + grpc := talk.BackendServerInfoGrpc{ Target: client.rawTarget, } if len(client.ip) > 0 { diff --git a/grpc_server.go b/grpc_server.go index b3a887f..4ab15d9 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -40,6 +40,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/config" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -55,10 +56,10 @@ func init() { hostname, err := os.Hostname() if err != nil { - hostname = newRandomString(8) + hostname = internal.RandomString(8) } md := sha256.New() - fmt.Fprintf(md, "%s-%s-%d", newRandomString(32), hostname, os.Getpid()) + fmt.Fprintf(md, "%s-%s-%d", internal.RandomString(32), hostname, os.Getpid()) GrpcServerId = hex.EncodeToString(md.Sum(nil)) } diff --git a/hub.go b/hub.go index f15d9e6..88867a6 100644 --- a/hub.go +++ b/hub.go @@ -160,10 +160,10 @@ type Hub struct { shutdown *internal.Closer shutdownScheduled atomic.Bool - roomUpdated chan *BackendServerRoomRequest - roomDeleted chan *BackendServerRoomRequest - roomInCall chan *BackendServerRoomRequest - roomParticipants chan *BackendServerRoomRequest + roomUpdated chan *talk.BackendServerRoomRequest + roomDeleted chan *talk.BackendServerRoomRequest + roomInCall chan *talk.BackendServerRoomRequest + roomParticipants chan *talk.BackendServerRoomRequest mu sync.RWMutex ru sync.RWMutex @@ -379,10 +379,10 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpc closer: internal.NewCloser(), shutdown: internal.NewCloser(), - roomUpdated: make(chan *BackendServerRoomRequest), - roomDeleted: make(chan *BackendServerRoomRequest), - roomInCall: make(chan *BackendServerRoomRequest), - roomParticipants: make(chan *BackendServerRoomRequest), + roomUpdated: make(chan *talk.BackendServerRoomRequest), + roomDeleted: make(chan *talk.BackendServerRoomRequest), + roomInCall: make(chan *talk.BackendServerRoomRequest), + roomParticipants: make(chan *talk.BackendServerRoomRequest), clients: make(map[uint64]HandlerClient), sessions: make(map[uint64]Session), @@ -952,7 +952,7 @@ func (h *Hub) newSessionIdData(backend *talk.Backend) *SessionIdData { return sessionIdData } -func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backend *talk.Backend, auth *BackendClientResponse) { +func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backend *talk.Backend, auth *talk.BackendClientResponse) { if !c.IsConnected() { // Client disconnected while waiting for "hello" response. return @@ -1362,7 +1362,7 @@ func (h *Hub) processHello(client HandlerClient, message *api.ClientMessage) { } } -func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *BackendClientResponse, error) { +func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) { url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { @@ -1375,8 +1375,8 @@ func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message ctx, cancel := context.WithTimeout(ctx, h.backendTimeout) defer cancel() - var auth BackendClientResponse - request := NewBackendClientAuthRequest(message.Hello.Auth.Params) + var auth talk.BackendClientResponse + request := talk.NewBackendClientAuthRequest(message.Hello.Auth.Params) if err := h.backend.PerformJSONRequest(ctx, url, request, &auth); err != nil { return nil, nil, err } @@ -1386,7 +1386,7 @@ func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message return backend, &auth, nil } -func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *BackendClientResponse, error) { +func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) { url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { @@ -1452,13 +1452,13 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message backendCtx, cancel := context.WithTimeout(ctx, h.backendTimeout) defer cancel() - keyData, cached, found := h.backend.capabilities.GetStringConfig(backendCtx, url, ConfigGroupSignaling, ConfigKeyHelloV2TokenKey) + keyData, cached, found := h.backend.capabilities.GetStringConfig(backendCtx, url, talk.ConfigGroupSignaling, talk.ConfigKeyHelloV2TokenKey) if !found { if cached { // The Nextcloud instance might just have enabled JWT but we probably use // the cached capabilities without the public key. Make sure to re-fetch. h.backend.capabilities.InvalidateCapabilities(url) - keyData, _, found = h.backend.capabilities.GetStringConfig(backendCtx, url, ConfigGroupSignaling, ConfigKeyHelloV2TokenKey) + keyData, _, found = h.backend.capabilities.GetStringConfig(backendCtx, url, talk.ConfigGroupSignaling, talk.ConfigKeyHelloV2TokenKey) } if !found { return nil, errors.New("no key found for issuer") @@ -1528,9 +1528,9 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return nil, nil, InvalidToken } - auth := &BackendClientResponse{ + auth := &talk.BackendClientResponse{ Type: "auth", - Auth: &BackendClientAuthResponse{ + Auth: &talk.BackendClientAuthResponse{ Version: message.Hello.Version, UserId: subject, User: authTokenClaims.GetUserData(), @@ -1543,7 +1543,7 @@ func (h *Hub) processHelloClient(client HandlerClient, message *api.ClientMessag // Make sure the client must send another "hello" in case of errors. defer h.startExpectHello(client) - var authFunc func(context.Context, HandlerClient, *api.ClientMessage) (*talk.Backend, *BackendClientResponse, error) + var authFunc func(context.Context, HandlerClient, *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) switch message.Hello.Version { case api.HelloVersionV1: // Auth information contains a ticket that must be validated against the @@ -1606,9 +1606,9 @@ func (h *Hub) processHelloInternal(client HandlerClient, message *api.ClientMess return } - auth := &BackendClientResponse{ + auth := &talk.BackendClientResponse{ Type: "auth", - Auth: &BackendClientAuthResponse{}, + Auth: &talk.BackendClientAuthResponse{}, } h.processRegister(client, message, backend, auth) } @@ -1846,12 +1846,12 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { return } - var room BackendClientResponse + var room talk.BackendClientResponse if session.ClientType() == api.HelloClientTypeInternal { // Internal clients can join any room. - room = BackendClientResponse{ + room = talk.BackendClientResponse{ Type: "room", - Room: &BackendClientRoomResponse{ + Room: &talk.BackendClientRoomResponse{ RoomId: roomId, }, } @@ -1866,7 +1866,7 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { h.logger.Printf("User did not send a room session id, assuming session %s", session.PublicId()) sessionId = api.RoomSessionId(session.PublicId()) } - request := NewBackendClientRoomRequest(roomId, session.UserId(), sessionId) + request := talk.NewBackendClientRoomRequest(roomId, session.UserId(), sessionId) request.Room.UpdateFromSession(session) if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &room); err != nil { session.SendMessage(message.NewWrappedErrorServerMessage(err)) @@ -1897,7 +1897,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { return 0, &wg } - rooms := make(map[string]map[string][]BackendPingEntry) + rooms := make(map[string]map[string][]talk.BackendPingEntry) urls := make(map[string]*url.URL) for session := range h.federatedSessions { u := session.BackendUrl() @@ -1921,7 +1921,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { roomId := federation.RoomId() entries, found := rooms[roomId] if !found { - entries = make(map[string][]BackendPingEntry) + entries = make(map[string][]talk.BackendPingEntry) rooms[roomId] = entries } @@ -1935,7 +1935,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { urls[u] = p } - entries[u] = append(e, BackendPingEntry{ + entries[u] = append(e, talk.BackendPingEntry{ SessionId: sid, UserId: uid, }) @@ -1950,7 +1950,7 @@ func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { for u, e := range entries { wg.Add(1) count += len(e) - go func(roomId string, url *url.URL, entries []BackendPingEntry) { + go func(roomId string, url *url.URL, entries []talk.BackendPingEntry) { defer wg.Done() sendCtx, cancel := context.WithTimeout(ctx, h.backendTimeout) defer cancel() @@ -2004,7 +2004,7 @@ func (h *Hub) createRoomLocked(id string, properties json.RawMessage, backend *t return room, nil } -func (h *Hub) processJoinRoom(session *ClientSession, message *api.ClientMessage, room *BackendClientResponse) { +func (h *Hub) processJoinRoom(session *ClientSession, message *api.ClientMessage, room *talk.BackendClientResponse) { if room.Type == "error" { session.SendMessage(message.NewErrorServerMessage(room.Error)) return @@ -2327,7 +2327,7 @@ func isAllowedToControl(session Session) bool { return true } - if session.HasPermission(PERMISSION_MAY_CONTROL) { + if session.HasPermission(api.PERMISSION_MAY_CONTROL) { // Moderator clients are allowed to send any control message. return true } @@ -2498,12 +2498,12 @@ func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { } if options := msg.Options; options != nil && options.ActorId != "" && options.ActorType != "" { - request := NewBackendClientRoomRequest(room.Id(), msg.UserId, api.RoomSessionId(publicSessionId)) + request := talk.NewBackendClientRoomRequest(room.Id(), msg.UserId, api.RoomSessionId(publicSessionId)) request.Room.ActorId = options.ActorId request.Room.ActorType = options.ActorType request.Room.InCall = sess.GetInCall() - var response BackendClientResponse + var response talk.BackendClientResponse if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() h.logger.Printf("Could not join virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) @@ -2520,8 +2520,8 @@ func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { return } } else { - request := NewBackendClientSessionRequest(room.Id(), "add", publicSessionId, msg) - var response BackendClientSessionResponse + request := talk.NewBackendClientSessionRequest(room.Id(), "add", publicSessionId, msg) + var response talk.BackendClientSessionResponse if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendOcsUrl(), request, &response); err != nil { sess.Close() h.logger.Printf("Could not add virtual session %s at backend %s: %s", virtualSessionId, session.BackendUrl(), err) @@ -2620,10 +2620,10 @@ func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { if msg.Dialout.Type == "status" { asyncMessage := &AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionSet, + Transient: &talk.BackendRoomTransientRequest{ + Action: talk.TransientActionSet, Key: "callstatus_" + msg.Dialout.Status.CallId, Value: msg.Dialout.Status, }, @@ -2658,7 +2658,7 @@ func isAllowedToUpdateTransientData(session Session) bool { return true } - if session.HasPermission(PERMISSION_TRANSIENT_DATA) { + if session.HasPermission(api.PERMISSION_TRANSIENT_DATA) { return true } @@ -2957,13 +2957,21 @@ func (h *Hub) processByeMsg(client HandlerClient, message *api.ClientMessage) { } } -func (h *Hub) processRoomUpdated(message *BackendServerRoomRequest) { - room := message.room +func (h *Hub) processRoomUpdated(message *talk.BackendServerRoomRequest) { + room := h.GetRoomForBackend(message.RoomId, message.Backend) + if room == nil { + return + } + room.UpdateProperties(message.Update.Properties) } -func (h *Hub) processRoomDeleted(message *BackendServerRoomRequest) { - room := message.room +func (h *Hub) processRoomDeleted(message *talk.BackendServerRoomRequest) { + room := h.GetRoomForBackend(message.RoomId, message.Backend) + if room == nil { + return + } + sessions := room.Close() for _, session := range sessions { // The session is no longer in the room @@ -2977,8 +2985,12 @@ func (h *Hub) processRoomDeleted(message *BackendServerRoomRequest) { } } -func (h *Hub) processRoomInCallChanged(message *BackendServerRoomRequest) { - room := message.room +func (h *Hub) processRoomInCallChanged(message *talk.BackendServerRoomRequest) { + room := h.GetRoomForBackend(message.RoomId, message.Backend) + if room == nil { + return + } + if message.InCall.All { var flags int if err := json.Unmarshal(message.InCall.InCall, &flags); err != nil { @@ -2999,8 +3011,12 @@ func (h *Hub) processRoomInCallChanged(message *BackendServerRoomRequest) { } } -func (h *Hub) processRoomParticipants(message *BackendServerRoomRequest) { - room := message.room +func (h *Hub) processRoomParticipants(message *talk.BackendServerRoomRequest) { + room := h.GetRoomForBackend(message.RoomId, message.Backend) + if room == nil { + return + } + room.PublishUsersChanged(message.Participants.Changed, message.Participants.Users) } @@ -3020,12 +3036,12 @@ func (h *Hub) GetStats() api.StringMap { return result } -func (h *Hub) GetServerInfoDialout() (result []BackendServerInfoDialout) { +func (h *Hub) GetServerInfoDialout() (result []talk.BackendServerInfoDialout) { h.mu.RLock() defer h.mu.RUnlock() for session := range h.dialoutSessions { - dialout := BackendServerInfoDialout{ + dialout := talk.BackendServerInfoDialout{ SessionId: session.PublicId(), } if client := session.GetClient(); client != nil && client.IsConnected() { @@ -3047,7 +3063,7 @@ func (h *Hub) GetServerInfoDialout() (result []BackendServerInfoDialout) { result = append(result, dialout) } - slices.SortFunc(result, func(a, b BackendServerInfoDialout) int { + slices.SortFunc(result, func(a, b talk.BackendServerInfoDialout) int { return strings.Compare(string(a.SessionId), string(b.SessionId)) }) return diff --git a/hub_test.go b/hub_test.go index a5929f7..f420cac 100644 --- a/hub_test.go +++ b/hub_test.go @@ -348,23 +348,23 @@ func WaitForHub(ctx context.Context, t *testing.T, h *Hub) { } } -func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Request, *BackendClientRequest) *BackendClientResponse) func(http.ResponseWriter, *http.Request) { +func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Request, *talk.BackendClientRequest) *talk.BackendClientResponse) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { assert := assert.New(t) body, err := io.ReadAll(r.Body) assert.NoError(err) - rnd := r.Header.Get(HeaderBackendSignalingRandom) - checksum := r.Header.Get(HeaderBackendSignalingChecksum) + rnd := r.Header.Get(talk.HeaderBackendSignalingRandom) + checksum := r.Header.Get(talk.HeaderBackendSignalingChecksum) if rnd == "" || checksum == "" { assert.Fail("No checksum headers found", "request to %s", r.URL) } - if verify := CalculateBackendChecksum(rnd, body, testBackendSecret); verify != checksum { + if verify := talk.CalculateBackendChecksum(rnd, body, testBackendSecret); verify != checksum { assert.Fail("Backend checksum verification failed", "request to %s", r.URL) } - var request BackendClientRequest + var request talk.BackendClientRequest assert.NoError(json.Unmarshal(body, &request)) response := f(w, r, &request) @@ -396,7 +396,7 @@ func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Req } } -func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { +func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *talk.BackendClientRequest) *talk.BackendClientResponse { require := require.New(t) if request.Type != "auth" || request.Auth == nil { require.Fail("Expected an auth backend request", "received %+v", request) @@ -413,10 +413,10 @@ func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re params.UserId = "" } - response := &BackendClientResponse{ + response := &talk.BackendClientResponse{ Type: "auth", - Auth: &BackendClientAuthResponse{ - Version: BackendVersion, + Auth: &talk.BackendClientAuthResponse{ + Version: talk.BackendVersion, UserId: params.UserId, }, } @@ -428,7 +428,7 @@ func processAuthRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re return response } -func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { +func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *talk.BackendClientRequest) *talk.BackendClientResponse { require := require.New(t) assert := assert.New(t) if request.Type != "room" || request.Room == nil { @@ -444,7 +444,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re assert.Fail("Should not receive \"leave\" event for first user", "received %+v", request.Room) } case "test-invalid-room": - response := &BackendClientResponse{ + response := &talk.BackendClientResponse{ Type: "error", Error: &api.Error{ Code: "no_such_room", @@ -471,10 +471,10 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re } // Allow joining any room. - response := &BackendClientResponse{ + response := &talk.BackendClientResponse{ Type: "room", - Room: &BackendClientRoomResponse{ - Version: BackendVersion, + Room: &talk.BackendClientRoomResponse{ + Version: talk.BackendVersion, RoomId: request.Room.RoomId, Properties: testRoomProperties, }, @@ -487,7 +487,7 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re tmp, _ := json.Marshal(data) response.Room.Session = tmp case "test-room-initial-permissions": - permissions := []Permission{PERMISSION_MAY_PUBLISH_AUDIO} + permissions := []api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO} response.Room.Permissions = &permissions } return response @@ -497,15 +497,15 @@ var ( sessionRequestHander struct { sync.Mutex // +checklocks:Mutex - handlers map[*testing.T]func(*BackendClientSessionRequest) + handlers map[*testing.T]func(*talk.BackendClientSessionRequest) } ) -func setSessionRequestHandler(t *testing.T, f func(*BackendClientSessionRequest)) { +func setSessionRequestHandler(t *testing.T, f func(*talk.BackendClientSessionRequest)) { sessionRequestHander.Lock() defer sessionRequestHander.Unlock() if sessionRequestHander.handlers == nil { - sessionRequestHander.handlers = make(map[*testing.T]func(*BackendClientSessionRequest)) + sessionRequestHander.handlers = make(map[*testing.T]func(*talk.BackendClientSessionRequest)) } if _, found := sessionRequestHander.handlers[t]; !found { t.Cleanup(func() { @@ -525,7 +525,7 @@ func clearSessionRequestHandler(t *testing.T) { // nolint delete(sessionRequestHander.handlers, t) } -func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { +func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *talk.BackendClientRequest) *talk.BackendClientResponse { if request.Type != "session" || request.Session == nil { require.Fail(t, "Expected an session backend request", "received %+v", request) } @@ -536,10 +536,10 @@ func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, f(request.Session) } - response := &BackendClientResponse{ + response := &talk.BackendClientResponse{ Type: "session", - Session: &BackendClientSessionResponse{ - Version: BackendVersion, + Session: &talk.BackendClientSessionResponse{ + Version: talk.BackendVersion, RoomId: request.Session.RoomId, }, } @@ -547,10 +547,10 @@ func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, } var ( - pingRequests internal.TestStorage[[]*BackendClientRequest] + pingRequests internal.TestStorage[[]*talk.BackendClientRequest] ) -func getPingRequests(t *testing.T) []*BackendClientRequest { +func getPingRequests(t *testing.T) []*talk.BackendClientRequest { entries, _ := pingRequests.Get(t) return entries } @@ -559,12 +559,12 @@ func clearPingRequests(t *testing.T) { pingRequests.Del(t) } -func storePingRequest(t *testing.T, request *BackendClientRequest) { +func storePingRequest(t *testing.T, request *talk.BackendClientRequest) { entries, _ := pingRequests.Get(t) pingRequests.Set(t, append(entries, request)) } -func processPingRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { +func processPingRequest(t *testing.T, w http.ResponseWriter, r *http.Request, request *talk.BackendClientRequest) *talk.BackendClientResponse { if request.Type != "ping" || request.Ping == nil { require.Fail(t, "Expected an ping backend request", "received %+v", request) } @@ -577,10 +577,10 @@ func processPingRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re storePingRequest(t, request) - response := &BackendClientResponse{ + response := &talk.BackendClientResponse{ Type: "ping", - Ping: &BackendClientRingResponse{ - Version: BackendVersion, + Ping: &talk.BackendClientRingResponse{ + Version: talk.BackendVersion, RoomId: request.Ping.RoomId, }, } @@ -706,7 +706,7 @@ var ( ) func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { - handleFunc := validateBackendChecksum(t, func(w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse { + handleFunc := validateBackendChecksum(t, func(w http.ResponseWriter, r *http.Request, request *talk.BackendClientRequest) *talk.BackendClientResponse { assert.Regexp(t, "/ocs/v2\\.php/apps/spreed/api/v[\\d]/signaling/backend$", r.URL.Path, "invalid url for backend request %+v", request) switch request.Type { @@ -748,7 +748,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { "signaling": signaling, } if strings.Contains(t.Name(), "MultiRoom") { - signaling[ConfigKeySessionPingLimit] = 2 + signaling[talk.ConfigKeySessionPingLimit] = 2 } skipV2, _ := skipV2Capabilities.Get(t) if (strings.Contains(t.Name(), "V2") && !skipV2) || strings.Contains(t.Name(), "Federation") { @@ -771,9 +771,9 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "Ed25519_Nextcloud") { // Simulate Nextcloud which returns the Ed25519 key as base64-encoded data. encoded := base64.StdEncoding.EncodeToString(key.(ed25519.PublicKey)) - signaling[ConfigKeyHelloV2TokenKey] = encoded + signaling[talk.ConfigKeyHelloV2TokenKey] = encoded } else { - signaling[ConfigKeyHelloV2TokenKey] = string(public) + signaling[talk.ConfigKeyHelloV2TokenKey] = string(public) } } spreedCapa, err := json.Marshal(api.StringMap{ @@ -2113,15 +2113,15 @@ func TestClientControlMissingPermissions(t *testing.T) { require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) // Client 1 may not send control messages (will be ignored). - session1.SetPermissions([]Permission{ - PERMISSION_MAY_PUBLISH_AUDIO, - PERMISSION_MAY_PUBLISH_VIDEO, + session1.SetPermissions([]api.Permission{ + api.PERMISSION_MAY_PUBLISH_AUDIO, + api.PERMISSION_MAY_PUBLISH_VIDEO, }) // Client 2 may send control messages. - session2.SetPermissions([]Permission{ - PERMISSION_MAY_PUBLISH_AUDIO, - PERMISSION_MAY_PUBLISH_VIDEO, - PERMISSION_MAY_CONTROL, + session2.SetPermissions([]api.Permission{ + api.PERMISSION_MAY_PUBLISH_AUDIO, + api.PERMISSION_MAY_PUBLISH_VIDEO, + api.PERMISSION_MAY_CONTROL, }) recipient1 := api.MessageClientMessageRecipient{ @@ -2972,7 +2972,7 @@ func TestJoinDisplaynamesPermission(t *testing.T) { require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) // Client 2 may not receive display names. - session2.SetPermissions([]Permission{PERMISSION_HIDE_DISPLAYNAMES}) + session2.SetPermissions([]api.Permission{api.PERMISSION_HIDE_DISPLAYNAMES}) // Join room by id (first client). roomId := "test-room" @@ -3024,8 +3024,8 @@ func TestInitialRoomPermissions(t *testing.T) { session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) - assert.True(session.HasPermission(PERMISSION_MAY_PUBLISH_AUDIO), "Session %s should have %s, got %+v", session.PublicId(), PERMISSION_MAY_PUBLISH_AUDIO, session.GetPermissions()) - assert.False(session.HasPermission(PERMISSION_MAY_PUBLISH_VIDEO), "Session %s should not have %s, got %+v", session.PublicId(), PERMISSION_MAY_PUBLISH_VIDEO, session.GetPermissions()) + assert.True(session.HasPermission(api.PERMISSION_MAY_PUBLISH_AUDIO), "Session %s should have %s, got %+v", session.PublicId(), api.PERMISSION_MAY_PUBLISH_AUDIO, session.GetPermissions()) + assert.False(session.HasPermission(api.PERMISSION_MAY_PUBLISH_VIDEO), "Session %s should not have %s, got %+v", session.PublicId(), api.PERMISSION_MAY_PUBLISH_VIDEO, session.GetPermissions()) } func TestJoinRoomSwitchClient(t *testing.T) { @@ -3382,18 +3382,18 @@ func TestCombineChatRefreshWhileDisconnected(t *testing.T) { // Simulate requests from the backend. room.processAsyncMessage(&AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "message", - Message: &BackendRoomMessageRequest{ + Message: &talk.BackendRoomMessageRequest{ Data: json.RawMessage(chat_refresh), }, }, }) room.processAsyncMessage(&AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "message", - Message: &BackendRoomMessageRequest{ + Message: &talk.BackendRoomMessageRequest{ Data: json.RawMessage(chat_refresh), }, }, @@ -3628,9 +3628,9 @@ func TestClientSendOfferPermissions(t *testing.T) { require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) // Client 1 is the moderator - session1.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_PUBLISH_SCREEN}) + session1.SetPermissions([]api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA, api.PERMISSION_MAY_PUBLISH_SCREEN}) // Client 2 is a guest participant. - session2.SetPermissions([]Permission{}) + session2.SetPermissions([]api.Permission{}) // Client 2 may not send an offer (he doesn't have the necessary permissions). require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ @@ -3706,7 +3706,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client is allowed to send audio only. - session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO}) + session.SetPermissions([]api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO}) // Client may not send an offer with audio and video. require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ @@ -3768,7 +3768,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client is allowed to send audio and video. - session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO}) + session.SetPermissions([]api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO, api.PERMISSION_MAY_PUBLISH_VIDEO}) require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ Type: "session", @@ -3785,19 +3785,19 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) // Client is no longer allowed to send video, this will stop the publisher. - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "participants", - Participants: &BackendRoomParticipantsRequest{ + Participants: &talk.BackendRoomParticipantsRequest{ Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO}, }, }, Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_AUDIO}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO}, }, }, }, @@ -3864,7 +3864,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) // Client is allowed to send audio and video. - session.SetPermissions([]Permission{PERMISSION_MAY_PUBLISH_MEDIA}) + session.SetPermissions([]api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA}) // Client may send an offer (audio and video). require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ @@ -3882,19 +3882,19 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) // Client is no longer allowed to send video, this will stop the publisher. - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "participants", - Participants: &BackendRoomParticipantsRequest{ + Participants: &talk.BackendRoomParticipantsRequest{ Changed: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA, api.PERMISSION_MAY_CONTROL}, }, }, Users: []api.StringMap{ { "sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId), - "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA, PERMISSION_MAY_CONTROL}, + "permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA, api.PERMISSION_MAY_CONTROL}, }, }, }, @@ -4552,7 +4552,7 @@ func TestVirtualClientSessions(t *testing.T) { virtualUserId := "virtual-user-id" generatedSessionId := GetVirtualSessionId(session2, virtualSessionId) - setSessionRequestHandler(t, func(request *BackendClientSessionRequest) { + setSessionRequestHandler(t, func(request *talk.BackendClientSessionRequest) { defer calledCancel() assert.Equal("add", request.Action, "%+v", request) assert.Equal(roomId, request.RoomId, "%+v", request) @@ -4661,7 +4661,7 @@ func TestVirtualClientSessions(t *testing.T) { calledCtx, calledCancel = context.WithTimeout(ctx, time.Second) - setSessionRequestHandler(t, func(request *BackendClientSessionRequest) { + setSessionRequestHandler(t, func(request *talk.BackendClientSessionRequest) { defer calledCancel() assert.Equal("remove", request.Action, "%+v", request) assert.Equal(roomId, request.RoomId, "%+v", request) @@ -4794,7 +4794,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { virtualUserId := "virtual-user-id" generatedSessionId := GetVirtualSessionId(session2, virtualSessionId) - setSessionRequestHandler(t, func(request *BackendClientSessionRequest) { + setSessionRequestHandler(t, func(request *talk.BackendClientSessionRequest) { defer calledCancel() assert.Equal("add", request.Action, "%+v", request) assert.Equal(roomId, request.RoomId, "%+v", request) @@ -4874,9 +4874,9 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ InCall: []byte("0"), Users: []api.StringMap{ { @@ -4981,7 +4981,7 @@ func TestDuplicateVirtualSessions(t *testing.T) { } } - setSessionRequestHandler(t, func(request *BackendClientSessionRequest) { + setSessionRequestHandler(t, func(request *talk.BackendClientSessionRequest) { defer calledCancel() assert.Equal("remove", request.Action, "%+v", request) assert.Equal(roomId, request.RoomId, "%+v", request) @@ -5045,9 +5045,9 @@ func DoTestSwitchToOne(t *testing.T, details api.StringMap) { } // Notify first client to switch to different room. - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "switchto", - SwitchTo: &BackendRoomSwitchToMessageRequest{ + SwitchTo: &talk.BackendRoomSwitchToMessageRequest{ RoomId: roomId2, Sessions: sessions, }, @@ -5146,9 +5146,9 @@ func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.S require.NoError(err) } - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "switchto", - SwitchTo: &BackendRoomSwitchToMessageRequest{ + SwitchTo: &talk.BackendRoomSwitchToMessageRequest{ RoomId: roomId2, Sessions: sessions, }, @@ -5291,9 +5291,9 @@ func TestDialoutStatus(t *testing.T) { <-stopped }() - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "dialout", - Dialout: &BackendRoomDialoutRequest{ + Dialout: &talk.BackendRoomDialoutRequest{ Number: "+1234567890", }, } @@ -5307,7 +5307,7 @@ func TestDialoutStatus(t *testing.T) { assert.NoError(err) require.Equal(http.StatusOK, res.StatusCode, "Expected success, got %s", string(body)) - var response BackendServerRoomResponse + var response talk.BackendServerRoomResponse if assert.NoError(json.Unmarshal(body, &response)) { assert.Equal("dialout", response.Type) if assert.NotNil(response.Dialout) { diff --git a/internal/random_string.go b/internal/random_string.go new file mode 100644 index 0000000..9221823 --- /dev/null +++ b/internal/random_string.go @@ -0,0 +1,34 @@ +/** + * 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 internal + +import ( + "crypto/rand" +) + +func RandomString(size int) string { + s := rand.Text() + for len(s) < size { + s += rand.Text() + } + return s[:size] +} diff --git a/internal/random_string_test.go b/internal/random_string_test.go new file mode 100644 index 0000000..78aca48 --- /dev/null +++ b/internal/random_string_test.go @@ -0,0 +1,41 @@ +/** + * 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 internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRandomString(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + s1 := RandomString(10) + assert.Len(s1, 10) + assert.NotEqual(s1, RandomString(10)) + + s2 := RandomString(123) + assert.Len(s2, 123) + assert.NotEqual(s2, RandomString(123)) +} diff --git a/mcu_common.go b/mcu_common.go index 82b5c54..0f5d6ab 100644 --- a/mcu_common.go +++ b/mcu_common.go @@ -32,6 +32,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -135,7 +136,7 @@ type Mcu interface { SetOnDisconnected(func()) GetStats() any - GetServerInfoSfu() *BackendServerInfoSfu + GetServerInfoSfu() *talk.BackendServerInfoSfu GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) diff --git a/mcu_janus.go b/mcu_janus.go index a58403b..9e9274a 100644 --- a/mcu_janus.go +++ b/mcu_janus.go @@ -39,6 +39,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" ) const ( @@ -581,8 +582,8 @@ func (m *mcuJanus) Info() *InfoMsg { return m.info.Load() } -func (m *mcuJanus) GetServerInfoSfu() *BackendServerInfoSfu { - janus := &BackendServerInfoSfuJanus{ +func (m *mcuJanus) GetServerInfoSfu() *talk.BackendServerInfoSfu { + janus := &talk.BackendServerInfoSfuJanus{ Url: m.url, } if m.IsConnected() { @@ -597,7 +598,7 @@ func (m *mcuJanus) GetServerInfoSfu() *BackendServerInfoSfu { janus.IPv6 = internal.MakePtr(info.IPv6) if plugin, found := info.Plugins[pluginVideoRoom]; found { - janus.VideoRoom = &BackendServerInfoVideoRoom{ + janus.VideoRoom = &talk.BackendServerInfoVideoRoom{ Name: plugin.Name, Version: plugin.VersionString, Author: plugin.Author, @@ -606,8 +607,8 @@ func (m *mcuJanus) GetServerInfoSfu() *BackendServerInfoSfu { } } - sfu := &BackendServerInfoSfu{ - Mode: SfuModeJanus, + sfu := &talk.BackendServerInfoSfu{ + Mode: talk.SfuModeJanus, Janus: janus, } return sfu diff --git a/mcu_proxy.go b/mcu_proxy.go index e0407ed..781f726 100644 --- a/mcu_proxy.go +++ b/mcu_proxy.go @@ -53,6 +53,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -1878,15 +1879,15 @@ func (m *mcuProxy) GetStats() any { return result } -func (m *mcuProxy) GetServerInfoSfu() *BackendServerInfoSfu { +func (m *mcuProxy) GetServerInfoSfu() *talk.BackendServerInfoSfu { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() - sfu := &BackendServerInfoSfu{ - Mode: SfuModeProxy, + sfu := &talk.BackendServerInfoSfu{ + Mode: talk.SfuModeProxy, } for _, c := range m.connections { - proxy := BackendServerInfoSfuProxy{ + proxy := talk.BackendServerInfoSfuProxy{ Url: c.rawUrl, Temporary: c.IsTemporary(), @@ -1905,11 +1906,18 @@ func (m *mcuProxy) GetServerInfoSfu() *BackendServerInfoSfu { proxy.Features = c.Features() proxy.Country = c.Country() proxy.Load = internal.MakePtr(c.Load()) - proxy.Bandwidth = c.Bandwidth() + if bw := c.Bandwidth(); bw != nil { + proxy.Bandwidth = &talk.BackendServerInfoSfuProxyBandwidth{ + Incoming: bw.Incoming, + Outgoing: bw.Outgoing, + Received: bw.Received, + Sent: bw.Sent, + } + } } sfu.Proxies = append(sfu.Proxies, proxy) } - slices.SortFunc(sfu.Proxies, func(a, b BackendServerInfoSfuProxy) int { + slices.SortFunc(sfu.Proxies, func(a, b talk.BackendServerInfoSfuProxy) int { c := strings.Compare(a.Url, b.Url) if c == 0 { c = strings.Compare(a.IP, b.IP) diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 219ef34..71898fa 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -582,7 +582,7 @@ func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { h.mu.Lock() defer h.mu.Unlock() pub := &testProxyServerPublisher{ - id: api.PublicSessionId(newRandomString(32)), + id: api.PublicSessionId(internal.RandomString(32)), } for { @@ -590,7 +590,7 @@ func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { break } - pub.id = api.PublicSessionId(newRandomString(32)) + pub.id = api.PublicSessionId(internal.RandomString(32)) } h.publishers[pub.id] = pub return pub @@ -621,8 +621,8 @@ func (h *TestProxyServerHandler) createSubscriber(pub *testProxyServerPublisher) defer h.mu.Unlock() sub := &testProxyServerSubscriber{ - id: newRandomString(32), - sid: newRandomString(8), + id: internal.RandomString(32), + sid: internal.RandomString(8), pub: pub, } @@ -631,7 +631,7 @@ func (h *TestProxyServerHandler) createSubscriber(pub *testProxyServerPublisher) break } - sub.id = newRandomString(32) + sub.id = internal.RandomString(32) } h.subscribers[sub.id] = sub return sub @@ -753,7 +753,7 @@ func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques t: h.t, server: h, ws: ws, - sessionId: api.PublicSessionId(newRandomString(32)), + sessionId: api.PublicSessionId(internal.RandomString(32)), } h.setClient(client.sessionId, client) diff --git a/mcu_test.go b/mcu_test.go index 8df744b..7f4e9e3 100644 --- a/mcu_test.go +++ b/mcu_test.go @@ -33,8 +33,10 @@ import ( "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -92,7 +94,7 @@ func (m *TestMCU) GetStats() any { return nil } -func (m *TestMCU) GetServerInfoSfu() *BackendServerInfoSfu { +func (m *TestMCU) GetServerInfoSfu() *talk.BackendServerInfoSfu { return nil } @@ -150,7 +152,7 @@ func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publi return nil, errors.New("Waiting for publisher not implemented yet") } - id := newRandomString(8) + id := internal.RandomString(8) sub := &TestMCUSubscriber{ TestMCUClient: TestMCUClient{ t: m.t, diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go index 09a1f23..a5a87f3 100644 --- a/proxy/proxy_server_test.go +++ b/proxy/proxy_server_test.go @@ -48,6 +48,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -387,7 +388,7 @@ func (m *TestMCU) GetStats() any { return nil } -func (m *TestMCU) GetServerInfoSfu() *signaling.BackendServerInfoSfu { +func (m *TestMCU) GetServerInfoSfu() *talk.BackendServerInfoSfu { return nil } diff --git a/room.go b/room.go index dbc9705..7484962 100644 --- a/room.go +++ b/room.go @@ -88,7 +88,7 @@ type Room struct { // +checklocks:mu inCallSessions map[Session]bool // +checklocks:mu - roomSessionData map[api.PublicSessionId]*RoomSessionData + roomSessionData map[api.PublicSessionId]*talk.RoomSessionData // +checklocks:mu statsRoomSessionsCurrent *prometheus.GaugeVec @@ -128,7 +128,7 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEv internalSessions: make(map[*ClientSession]bool), virtualSessions: make(map[*VirtualSession]bool), inCallSessions: make(map[Session]bool), - roomSessionData: make(map[api.PublicSessionId]*RoomSessionData), + roomSessionData: make(map[api.PublicSessionId]*talk.RoomSessionData), statsRoomSessionsCurrent: statsRoomSessionsCurrent.MustCurryWith(prometheus.Labels{ "backend": backend.Id(), @@ -255,7 +255,7 @@ func (r *Room) processAsyncMessage(message *AsyncMessage) { } } -func (r *Room) processBackendRoomRequestRoom(message *BackendServerRoomRequest) { +func (r *Room) processBackendRoomRequestRoom(message *talk.BackendServerRoomRequest) { received := message.ReceivedTime if last, found := r.lastRoomRequests[message.Type]; found && last > received { if msg, err := json.Marshal(message); err == nil { @@ -267,7 +267,8 @@ func (r *Room) processBackendRoomRequestRoom(message *BackendServerRoomRequest) } r.lastRoomRequests[message.Type] = received - message.room = r + message.RoomId = r.Id() + message.Backend = r.Backend() switch message.Type { case "update": r.hub.roomUpdated <- message @@ -284,13 +285,13 @@ func (r *Room) processBackendRoomRequestRoom(message *BackendServerRoomRequest) r.publishSwitchTo(message.SwitchTo) case "transient": switch message.Transient.Action { - case TransientActionSet: + case talk.TransientActionSet: if message.Transient.TTL == 0 { r.doSetTransientData(message.Transient.Key, message.Transient.Value) } else { r.doSetTransientDataTTL(message.Transient.Key, message.Transient.Value, message.Transient.TTL) } - case TransientActionDelete: + case talk.TransientActionDelete: r.doRemoveTransientData(message.Transient.Key) default: r.logger.Printf("Unsupported transient action in room %s: %+v", r.Id(), message.Transient) @@ -313,9 +314,9 @@ func (r *Room) processBackendRoomRequestAsyncRoom(message *AsyncRoomMessage) { } func (r *Room) AddSession(session Session, sessionData json.RawMessage) { - var roomSessionData *RoomSessionData + var roomSessionData *talk.RoomSessionData if len(sessionData) > 0 { - roomSessionData = &RoomSessionData{} + roomSessionData = &talk.RoomSessionData{} if err := json.Unmarshal(sessionData, roomSessionData); err != nil { r.logger.Printf("Error decoding room session data \"%s\": %s", string(sessionData), err) roomSessionData = nil @@ -572,13 +573,13 @@ func (r *Room) UpdateProperties(properties json.RawMessage) { } } -func (r *Room) GetRoomSessionData(session Session) *RoomSessionData { +func (r *Room) GetRoomSessionData(session Session) *talk.RoomSessionData { r.mu.RLock() defer r.mu.RUnlock() return r.roomSessionData[session.PublicId()] } -func (r *Room) PublishSessionJoined(session Session, sessionData *RoomSessionData) { +func (r *Room) PublishSessionJoined(session Session, sessionData *talk.RoomSessionData) { sessionId := session.PublicId() if sessionId == "" { return @@ -1072,7 +1073,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { r.mu.RLock() defer r.mu.RUnlock() - entries := make(map[string][]BackendPingEntry) + entries := make(map[string][]talk.BackendPingEntry) urls := make(map[string]*url.URL) for _, session := range r.sessions { u := session.BackendUrl() @@ -1120,7 +1121,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { urls[u] = parsedBackendUrl } - entries[u] = append(e, BackendPingEntry{ + entries[u] = append(e, talk.BackendPingEntry{ SessionId: sid, UserId: uid, }) @@ -1134,7 +1135,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { for u, e := range entries { wg.Add(1) count += len(e) - go func(url *url.URL, entries []BackendPingEntry) { + go func(url *url.URL, entries []talk.BackendPingEntry) { defer wg.Done() sendCtx, cancel := context.WithTimeout(ctx, r.hub.backendTimeout) defer cancel() @@ -1147,7 +1148,7 @@ func (r *Room) publishActiveSessions() (int, *sync.WaitGroup) { return count, &wg } -func (r *Room) publishRoomMessage(message *BackendRoomMessageRequest) { +func (r *Room) publishRoomMessage(message *talk.BackendRoomMessageRequest) { if message == nil || len(message.Data) == 0 { return } @@ -1168,7 +1169,7 @@ func (r *Room) publishRoomMessage(message *BackendRoomMessageRequest) { } } -func (r *Room) publishSwitchTo(message *BackendRoomSwitchToMessageRequest) { +func (r *Room) publishSwitchTo(message *talk.BackendRoomSwitchToMessageRequest) { var wg sync.WaitGroup if len(message.SessionsList) > 0 { msg := &api.ServerMessage{ @@ -1250,10 +1251,10 @@ func (r *Room) SetTransientData(key string, value any) error { return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionSet, + Transient: &talk.BackendRoomTransientRequest{ + Action: talk.TransientActionSet, Key: key, Value: value, }, @@ -1274,10 +1275,10 @@ func (r *Room) SetTransientDataTTL(key string, value any, ttl time.Duration) err return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionSet, + Transient: &talk.BackendRoomTransientRequest{ + Action: talk.TransientActionSet, Key: key, Value: value, TTL: ttl, @@ -1293,10 +1294,10 @@ func (r *Room) doSetTransientDataTTL(key string, value any, ttl time.Duration) { func (r *Room) RemoveTransientData(key string) error { return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ Type: "room", - Room: &BackendServerRoomRequest{ + Room: &talk.BackendServerRoomRequest{ Type: "transient", - Transient: &BackendRoomTransientRequest{ - Action: TransientActionDelete, + Transient: &talk.BackendRoomTransientRequest{ + Action: talk.TransientActionDelete, Key: key, }, }, diff --git a/room_ping.go b/room_ping.go index 1034c5d..3b94c12 100644 --- a/room_ping.go +++ b/room_ping.go @@ -36,19 +36,19 @@ import ( type pingEntries struct { url *url.URL - entries map[string][]BackendPingEntry + entries map[string][]talk.BackendPingEntry } -func newPingEntries(url *url.URL, roomId string, entries []BackendPingEntry) *pingEntries { +func newPingEntries(url *url.URL, roomId string, entries []talk.BackendPingEntry) *pingEntries { return &pingEntries{ url: url, - entries: map[string][]BackendPingEntry{ + entries: map[string][]talk.BackendPingEntry{ roomId: entries, }, } } -func (e *pingEntries) Add(roomId string, entries []BackendPingEntry) { +func (e *pingEntries) Add(roomId string, entries []talk.BackendPingEntry) { if existing, found := e.entries[roomId]; found { e.entries[roomId] = append(existing, entries...) } else { @@ -121,7 +121,7 @@ func (p *RoomPing) publishEntries(ctx context.Context, entries *pingEntries, tim ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - limit, _, found := p.capabilities.GetIntegerConfig(ctx, entries.url, ConfigGroupSignaling, ConfigKeySessionPingLimit) + limit, _, found := p.capabilities.GetIntegerConfig(ctx, entries.url, talk.ConfigGroupSignaling, talk.ConfigKeySessionPingLimit) if !found || limit <= 0 { // Limit disabled while waiting for the next iteration, fallback to sending // one request per room. @@ -137,7 +137,7 @@ func (p *RoomPing) publishEntries(ctx context.Context, entries *pingEntries, tim return } - var allEntries []BackendPingEntry + var allEntries []talk.BackendPingEntry for _, e := range entries.entries { allEntries = append(allEntries, e...) } @@ -164,28 +164,28 @@ func (p *RoomPing) publishActiveSessions(ctx context.Context) { wg.Wait() } -func (p *RoomPing) sendPingsDirect(ctx context.Context, roomId string, url *url.URL, entries []BackendPingEntry) error { - request := NewBackendClientPingRequest(roomId, entries) - var response BackendClientResponse +func (p *RoomPing) sendPingsDirect(ctx context.Context, roomId string, url *url.URL, entries []talk.BackendPingEntry) error { + request := talk.NewBackendClientPingRequest(roomId, entries) + var response talk.BackendClientResponse return p.backend.PerformJSONRequest(ctx, url, request, &response) } -func (p *RoomPing) sendPingsCombined(ctx context.Context, url *url.URL, entries []BackendPingEntry, limit int, timeout time.Duration) { +func (p *RoomPing) sendPingsCombined(ctx context.Context, url *url.URL, entries []talk.BackendPingEntry, limit int, timeout time.Duration) { logger := log.LoggerFromContext(ctx) for tosend := range slices.Chunk(entries, limit) { subCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - request := NewBackendClientPingRequest("", tosend) - var response BackendClientResponse + request := talk.NewBackendClientPingRequest("", tosend) + var response talk.BackendClientResponse if err := p.backend.PerformJSONRequest(subCtx, url, request, &response); err != nil { logger.Printf("Error sending combined ping session entries %+v to %s: %s", tosend, url, err) } } } -func (p *RoomPing) SendPings(ctx context.Context, roomId string, url *url.URL, entries []BackendPingEntry) error { - limit, _, found := p.capabilities.GetIntegerConfig(ctx, url, ConfigGroupSignaling, ConfigKeySessionPingLimit) +func (p *RoomPing) SendPings(ctx context.Context, roomId string, url *url.URL, entries []talk.BackendPingEntry) error { + limit, _, found := p.capabilities.GetIntegerConfig(ctx, url, talk.ConfigGroupSignaling, talk.ConfigKeySessionPingLimit) if !found || limit <= 0 { // Old-style Nextcloud or session limit not configured. Perform one request // per room. Don't queue to avoid sending all ping requests to old-style diff --git a/room_ping_test.go b/room_ping_test.go index f42693f..e647d21 100644 --- a/room_ping_test.go +++ b/room_ping_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) { @@ -72,7 +73,7 @@ func TestSingleRoomPing(t *testing.T) { room1 := &Room{ id: "sample-room-1", } - entries1 := []BackendPingEntry{ + entries1 := []talk.BackendPingEntry{ { UserId: "foo", SessionId: "123", @@ -87,7 +88,7 @@ func TestSingleRoomPing(t *testing.T) { room2 := &Room{ id: "sample-room-2", } - entries2 := []BackendPingEntry{ + entries2 := []talk.BackendPingEntry{ { UserId: "bar", SessionId: "456", @@ -116,7 +117,7 @@ func TestMultiRoomPing(t *testing.T) { room1 := &Room{ id: "sample-room-1", } - entries1 := []BackendPingEntry{ + entries1 := []talk.BackendPingEntry{ { UserId: "foo", SessionId: "123", @@ -128,7 +129,7 @@ func TestMultiRoomPing(t *testing.T) { room2 := &Room{ id: "sample-room-2", } - entries2 := []BackendPingEntry{ + entries2 := []talk.BackendPingEntry{ { UserId: "bar", SessionId: "456", @@ -156,7 +157,7 @@ func TestMultiRoomPing_Separate(t *testing.T) { room1 := &Room{ id: "sample-room-1", } - entries1 := []BackendPingEntry{ + entries1 := []talk.BackendPingEntry{ { UserId: "foo", SessionId: "123", @@ -164,7 +165,7 @@ func TestMultiRoomPing_Separate(t *testing.T) { } assert.NoError(ping.SendPings(ctx, room1.Id(), u, entries1)) assert.Empty(getPingRequests(t)) - entries2 := []BackendPingEntry{ + entries2 := []talk.BackendPingEntry{ { UserId: "bar", SessionId: "456", @@ -192,7 +193,7 @@ func TestMultiRoomPing_DeleteRoom(t *testing.T) { room1 := &Room{ id: "sample-room-1", } - entries1 := []BackendPingEntry{ + entries1 := []talk.BackendPingEntry{ { UserId: "foo", SessionId: "123", @@ -204,7 +205,7 @@ func TestMultiRoomPing_DeleteRoom(t *testing.T) { room2 := &Room{ id: "sample-room-2", } - entries2 := []BackendPingEntry{ + entries2 := []talk.BackendPingEntry{ { UserId: "bar", SessionId: "456", diff --git a/room_test.go b/room_test.go index 022a545..68a94f9 100644 --- a/room_test.go +++ b/room_test.go @@ -36,6 +36,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func TestRoom_InCall(t *testing.T) { @@ -106,9 +107,9 @@ func TestRoom_Update(t *testing.T) { // Simulate backend request from Nextcloud to update the room. roomProperties := json.RawMessage("{\"foo\":\"bar\"}") - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "update", - Update: &BackendRoomUpdateRequest{ + Update: &talk.BackendRoomUpdateRequest{ UserIds: []string{ testDefaultUserId, }, @@ -200,9 +201,9 @@ func TestRoom_Delete(t *testing.T) { assert.True(client.RunUntilJoined(ctx, hello.Hello)) // Simulate backend request from Nextcloud to update the room. - msg := &BackendServerRoomRequest{ + msg := &talk.BackendServerRoomRequest{ Type: "delete", - Delete: &BackendRoomDeleteRequest{ + Delete: &talk.BackendRoomDeleteRequest{ UserIds: []string{ testDefaultUserId, }, @@ -387,9 +388,9 @@ func TestRoom_InCallAll(t *testing.T) { client1.RunUntilJoined(ctx, hello2.Hello) // Simulate backend request from Nextcloud to update the "inCall" flag of all participants. - msg1 := &BackendServerRoomRequest{ + msg1 := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ All: true, InCall: json.RawMessage(strconv.FormatInt(FlagInCall, 10)), }, @@ -413,9 +414,9 @@ func TestRoom_InCallAll(t *testing.T) { } // Simulate backend request from Nextcloud to update the "inCall" flag of all participants. - msg2 := &BackendServerRoomRequest{ + msg2 := &talk.BackendServerRoomRequest{ Type: "incall", - InCall: &BackendRoomInCallRequest{ + InCall: &talk.BackendRoomInCallRequest{ All: true, InCall: json.RawMessage(strconv.FormatInt(0, 10)), }, diff --git a/roomsessions_test.go b/roomsessions_test.go index 5d33169..81fc57d 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -100,7 +100,7 @@ func (s *DummySession) LeaveRoom(notify bool) *Room { func (s *DummySession) Close() { } -func (s *DummySession) HasPermission(permission Permission) bool { +func (s *DummySession) HasPermission(permission api.Permission) bool { return false } diff --git a/session.go b/session.go index adc93d4..3ef6485 100644 --- a/session.go +++ b/session.go @@ -31,25 +31,6 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/talk" ) -type Permission string - -var ( - PERMISSION_MAY_PUBLISH_MEDIA Permission = "publish-media" - PERMISSION_MAY_PUBLISH_AUDIO Permission = "publish-audio" - PERMISSION_MAY_PUBLISH_VIDEO Permission = "publish-video" - PERMISSION_MAY_PUBLISH_SCREEN Permission = "publish-screen" - PERMISSION_MAY_CONTROL Permission = "control" - PERMISSION_TRANSIENT_DATA Permission = "transient-data" - PERMISSION_HIDE_DISPLAYNAMES Permission = "hide-displaynames" - - // DefaultPermissionOverrides contains permission overrides for users where - // no permissions have been set by the server. If a permission is not set in - // this map, it's assumed the user has that permission. - DefaultPermissionOverrides = map[Permission]bool{ // +checklocksignore: Global readonly variable. - PERMISSION_HIDE_DISPLAYNAMES: false, - } -) - type Session interface { Context() context.Context PrivateId() api.PrivateSessionId @@ -72,7 +53,7 @@ type Session interface { Close() - HasPermission(permission Permission) bool + HasPermission(permission api.Permission) bool SendError(e *api.Error) bool SendMessage(message *api.ServerMessage) bool diff --git a/session_test.go b/session_test.go index 677fd1d..046a86d 100644 --- a/session_test.go +++ b/session_test.go @@ -25,9 +25,11 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/api" ) -func assertSessionHasPermission(t *testing.T, session Session, permission Permission) { +func assertSessionHasPermission(t *testing.T, session Session, permission api.Permission) { t.Helper() assert.True(t, session.HasPermission(permission), "Session %s doesn't have permission %s", session.PublicId(), permission) if cs, ok := session.(*ClientSession); ok { @@ -35,7 +37,7 @@ func assertSessionHasPermission(t *testing.T, session Session, permission Permis } } -func assertSessionHasNotPermission(t *testing.T, session Session, permission Permission) { +func assertSessionHasNotPermission(t *testing.T, session Session, permission api.Permission) { t.Helper() assert.False(t, session.HasPermission(permission), "Session %s has permission %s but shouldn't", session.PublicId(), permission) if cs, ok := session.(*ClientSession); ok { diff --git a/api_backend.go b/talk/api.go similarity index 93% rename from api_backend.go rename to talk/api.go index bb720b8..d54d1b3 100644 --- a/api_backend.go +++ b/talk/api.go @@ -19,11 +19,10 @@ * 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 ( "crypto/hmac" - "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/hex" @@ -36,6 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) const ( @@ -51,14 +51,6 @@ const ( ConfigKeySessionPingLimit = "session-ping-limit" ) -func newRandomString(length int) string { - b := make([]byte, length/2) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return hex.EncodeToString(b) -} - func CalculateBackendChecksum(random string, body []byte, secret []byte) string { mac := hmac.New(sha256.New, secret) mac.Write([]byte(random)) // nolint @@ -68,7 +60,7 @@ func CalculateBackendChecksum(random string, body []byte, secret []byte) string func AddBackendChecksum(r *http.Request, body []byte, secret []byte) { // Add checksum so the backend can validate the request. - rnd := newRandomString(64) + rnd := internal.RandomString(64) checksum := CalculateBackendChecksum(rnd, body, secret) r.Header.Set(HeaderBackendSignalingRandom, rnd) r.Header.Set(HeaderBackendSignalingChecksum, checksum) @@ -88,7 +80,8 @@ func ValidateBackendChecksumValue(checksum string, random string, body []byte, s // Requests from Nextcloud to the signaling server. type BackendServerRoomRequest struct { - room *Room + RoomId string `json:"-"` + Backend *Backend `json:"-"` Type string `json:"type"` @@ -315,7 +308,12 @@ type BackendClientRoomRequest struct { InCall int `json:"incall,omitempty"` } -func (r *BackendClientRoomRequest) UpdateFromSession(s Session) { +type SessionWithUserData interface { + ClientType() api.ClientType + ParsedUserData() (api.StringMap, error) +} + +func (r *BackendClientRoomRequest) UpdateFromSession(s SessionWithUserData) { if s.ClientType() == api.HelloClientTypeFederation { // Need to send additional data for requests of federated users. if u, err := s.ParsedUserData(); err == nil && len(u) > 0 { @@ -351,7 +349,7 @@ type BackendClientRoomResponse struct { // See "RoomSessionData" for a possible content. Session json.RawMessage `json:"session,omitempty"` - Permissions *[]Permission `json:"permissions,omitempty"` + Permissions *[]api.Permission `json:"permissions,omitempty"` } type RoomSessionData struct { @@ -447,6 +445,18 @@ type BackendServerInfoSfuJanus struct { VideoRoom *BackendServerInfoVideoRoom `json:"videoroom,omitempty"` } +type BackendServerInfoSfuProxyBandwidth struct { + // Incoming is the bandwidth utilization for publishers in percent. + Incoming *float64 `json:"incoming,omitempty"` + // Outgoing is the bandwidth utilization for subscribers in percent. + Outgoing *float64 `json:"outgoing,omitempty"` + + // Received is the incoming bandwidth. + Received api.Bandwidth `json:"received,omitempty"` + // Sent is the outgoing bandwidth. + Sent api.Bandwidth `json:"sent,omitempty"` +} + type BackendServerInfoSfuProxy struct { Url string `json:"url"` IP string `json:"ip,omitempty"` @@ -459,9 +469,9 @@ type BackendServerInfoSfuProxy struct { Version string `json:"version,omitempty"` Features []string `json:"features,omitempty"` - Country geoip.Country `json:"country,omitempty"` - Load *uint64 `json:"load,omitempty"` - Bandwidth *EventProxyServerBandwidth `json:"bandwidth,omitempty"` + Country geoip.Country `json:"country,omitempty"` + Load *uint64 `json:"load,omitempty"` + Bandwidth *BackendServerInfoSfuProxyBandwidth `json:"bandwidth,omitempty"` } type SfuMode string diff --git a/api_backend_test.go b/talk/api_test.go similarity index 95% rename from api_backend_test.go rename to talk/api_test.go index 724075d..3bce351 100644 --- a/api_backend_test.go +++ b/talk/api_test.go @@ -19,19 +19,21 @@ * 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 ( "net/http" "testing" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) func TestBackendChecksum(t *testing.T) { t.Parallel() assert := assert.New(t) - rnd := newRandomString(32) + rnd := internal.RandomString(32) body := []byte{1, 2, 3, 4, 5} secret := []byte("shared-secret") diff --git a/testclient_test.go b/testclient_test.go index 2a6b2c9..3b01532 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -43,6 +43,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) var ( @@ -492,7 +493,7 @@ func (c *TestClient) SendHelloInternal() error { } func (c *TestClient) SendHelloInternalWithFeatures(features []string) error { - random := newRandomString(48) + random := internal.RandomString(48) mac := hmac.New(sha256.New, testInternalSecret) mac.Write([]byte(random)) // nolint token := hex.EncodeToString(mac.Sum(nil)) diff --git a/transient_data_test.go b/transient_data_test.go index 950bb5e..e4e9053 100644 --- a/transient_data_test.go +++ b/transient_data_test.go @@ -197,9 +197,9 @@ func Test_TransientMessages(t *testing.T) { require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) // Client 1 may modify transient data. - session1.SetPermissions([]Permission{PERMISSION_TRANSIENT_DATA}) + session1.SetPermissions([]api.Permission{api.PERMISSION_TRANSIENT_DATA}) // Client 2 may not modify transient data. - session2.SetPermissions([]Permission{}) + session2.SetPermissions([]api.Permission{}) require.NoError(client2.SetTransientData("foo", "bar", 0)) if msg, ok := client2.RunUntilMessage(ctx); ok { diff --git a/virtualsession.go b/virtualsession.go index 2c5e11b..59c6e8e 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -240,12 +240,12 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa defer cancel() if options := s.Options(); options != nil && options.ActorId != "" && options.ActorType != "" { - request := NewBackendClientRoomRequest(room.Id(), s.UserId(), api.RoomSessionId(s.PublicId())) + request := talk.NewBackendClientRoomRequest(room.Id(), s.UserId(), api.RoomSessionId(s.PublicId())) request.Room.Action = "leave" request.Room.ActorId = options.ActorId request.Room.ActorType = options.ActorType - var response BackendClientResponse + var response talk.BackendClientResponse if err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response); err != nil { virtualSessionId := GetVirtualSessionId(s.session, s.PublicId()) s.logger.Printf("Could not leave virtual session %s at backend %s: %s", virtualSessionId, s.BackendUrl(), err) @@ -266,11 +266,11 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa return } } else { - request := NewBackendClientSessionRequest(room.Id(), "remove", s.PublicId(), &api.AddSessionInternalClientMessage{ + request := talk.NewBackendClientSessionRequest(room.Id(), "remove", s.PublicId(), &api.AddSessionInternalClientMessage{ UserId: s.userId, User: s.userData, }) - var response BackendClientSessionResponse + var response talk.BackendClientSessionResponse err := s.hub.backend.PerformJSONRequest(ctx, s.ParsedBackendOcsUrl(), request, &response) if err != nil { s.logger.Printf("Could not remove virtual session %s from backend %s: %s", s.PublicId(), s.BackendUrl(), err) @@ -282,7 +282,7 @@ func (s *VirtualSession) notifyBackendRemoved(room *Room, session Session, messa } } -func (s *VirtualSession) HasPermission(permission Permission) bool { +func (s *VirtualSession) HasPermission(permission api.Permission) bool { return true } From 27b46f7f39156ec65aecffc814f393de5741c52e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 14:14:43 +0100 Subject: [PATCH 425/549] Update generated files. --- api_async_easyjson.go | 11 +- .../api_easyjson.go | 571 +++++++++++------- 2 files changed, 359 insertions(+), 223 deletions(-) rename api_backend_easyjson.go => talk/api_easyjson.go (80%) diff --git a/api_async_easyjson.go b/api_async_easyjson.go index 2436e63..45b152b 100644 --- a/api_async_easyjson.go +++ b/api_async_easyjson.go @@ -8,6 +8,7 @@ import ( jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" api "github.com/strukturag/nextcloud-spreed-signaling/api" + talk "github.com/strukturag/nextcloud-spreed-signaling/talk" ) // suppress unused package warning @@ -258,7 +259,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex out.Room = nil } else { if out.Room == nil { - out.Room = new(BackendServerRoomRequest) + out.Room = new(talk.BackendServerRoomRequest) } if in.IsNull() { in.Skip() @@ -274,19 +275,19 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex in.Delim('[') if out.Permissions == nil { if !in.IsDelim(']') { - out.Permissions = make([]Permission, 0, 4) + out.Permissions = make([]api.Permission, 0, 4) } else { - out.Permissions = []Permission{} + out.Permissions = []api.Permission{} } } else { out.Permissions = (out.Permissions)[:0] } for !in.IsDelim(']') { - var v1 Permission + var v1 api.Permission if in.IsNull() { in.Skip() } else { - v1 = Permission(in.String()) + v1 = api.Permission(in.String()) } out.Permissions = append(out.Permissions, v1) in.WantComma() diff --git a/api_backend_easyjson.go b/talk/api_easyjson.go similarity index 80% rename from api_backend_easyjson.go rename to talk/api_easyjson.go index 9e8ef43..0fda7b6 100644 --- a/api_backend_easyjson.go +++ b/talk/api_easyjson.go @@ -1,6 +1,6 @@ // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package signaling +package talk import ( json "encoding/json" @@ -21,7 +21,7 @@ var ( _ easyjson.Marshaler ) -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexer.Lexer, out *TurnCredentials) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(in *jlexer.Lexer, out *TurnCredentials) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -90,7 +90,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwriter.Writer, in TurnCredentials) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(out *jwriter.Writer, in TurnCredentials) { out.RawByte('{') first := true _ = first @@ -131,27 +131,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwri // MarshalJSON supports json.Marshaler interface func (v TurnCredentials) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TurnCredentials) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TurnCredentials) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TurnCredentials) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlexer.Lexer, out *RoomSessionData) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(in *jlexer.Lexer, out *RoomSessionData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -181,7 +181,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwriter.Writer, in RoomSessionData) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(out *jwriter.Writer, in RoomSessionData) { out.RawByte('{') first := true _ = first @@ -197,27 +197,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomSessionData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomSessionData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling1(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomSessionData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomSessionData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling1(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *BackendServerRoomResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(in *jlexer.Lexer, out *BackendServerRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -261,7 +261,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in BackendServerRoomResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(out *jwriter.Writer, in BackendServerRoomResponse) { out.RawByte('{') first := true _ = first @@ -281,27 +281,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlexer.Lexer, out *BackendServerRoomRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(in *jlexer.Lexer, out *BackendServerRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -477,13 +477,18 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwriter.Writer, in BackendServerRoomRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(out *jwriter.Writer, in BackendServerRoomRequest) { out.RawByte('{') first := true _ = first { const prefix string = ",\"type\":" - out.RawString(prefix[1:]) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } out.String(string(in.Type)) } if in.Invite != nil { @@ -547,27 +552,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling3(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling3(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -609,7 +614,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwriter.Writer, in BackendServerInfoVideoRoom) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(out *jwriter.Writer, in BackendServerInfoVideoRoom) { out.RawByte('{') first := true _ = first @@ -645,27 +650,157 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoVideoRoom) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoVideoRoom) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling4(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoVideoRoom) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoVideoRoom) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling4(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(in *jlexer.Lexer, out *BackendServerInfoSfuProxyBandwidth) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "incoming": + if in.IsNull() { + in.Skip() + out.Incoming = nil + } else { + if out.Incoming == nil { + out.Incoming = new(float64) + } + if in.IsNull() { + in.Skip() + } else { + *out.Incoming = float64(in.Float64()) + } + } + case "outgoing": + if in.IsNull() { + in.Skip() + out.Outgoing = nil + } else { + if out.Outgoing == nil { + out.Outgoing = new(float64) + } + if in.IsNull() { + in.Skip() + } else { + *out.Outgoing = float64(in.Float64()) + } + } + case "received": + if in.IsNull() { + in.Skip() + } else { + out.Received = api.Bandwidth(in.Uint64()) + } + case "sent": + if in.IsNull() { + in.Skip() + } else { + out.Sent = api.Bandwidth(in.Uint64()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(out *jwriter.Writer, in BackendServerInfoSfuProxyBandwidth) { + out.RawByte('{') + first := true + _ = first + if in.Incoming != nil { + const prefix string = ",\"incoming\":" + first = false + out.RawString(prefix[1:]) + out.Float64(float64(*in.Incoming)) + } + if in.Outgoing != nil { + const prefix string = ",\"outgoing\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Float64(float64(*in.Outgoing)) + } + if in.Received != 0 { + const prefix string = ",\"received\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Uint64(uint64(in.Received)) + } + if in.Sent != 0 { + const prefix string = ",\"sent\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Uint64(uint64(in.Sent)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BackendServerInfoSfuProxyBandwidth) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BackendServerInfoSfuProxyBandwidth) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BackendServerInfoSfuProxyBandwidth) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BackendServerInfoSfuProxyBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -792,7 +927,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex out.Bandwidth = nil } else { if out.Bandwidth == nil { - out.Bandwidth = new(EventProxyServerBandwidth) + out.Bandwidth = new(BackendServerInfoSfuProxyBandwidth) } if in.IsNull() { in.Skip() @@ -810,7 +945,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwriter.Writer, in BackendServerInfoSfuProxy) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(out *jwriter.Writer, in BackendServerInfoSfuProxy) { out.RawByte('{') first := true _ = first @@ -884,27 +1019,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuProxy) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuProxy) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling5(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuProxy) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuProxy) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling5(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1020,7 +1155,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwriter.Writer, in BackendServerInfoSfuJanus) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(out *jwriter.Writer, in BackendServerInfoSfuJanus) { out.RawByte('{') first := true _ = first @@ -1080,27 +1215,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuJanus) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuJanus) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling6(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuJanus) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuJanus) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *BackendServerInfoSfu) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(in *jlexer.Lexer, out *BackendServerInfoSfu) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1171,7 +1306,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in BackendServerInfoSfu) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(out *jwriter.Writer, in BackendServerInfoSfu) { out.RawByte('{') first := true _ = first @@ -1205,27 +1340,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfu) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfu) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfu) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfu) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *BackendServerInfoNats) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(in *jlexer.Lexer, out *BackendServerInfoNats) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1306,7 +1441,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in BackendServerInfoNats) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(out *jwriter.Writer, in BackendServerInfoNats) { out.RawByte('{') first := true _ = first @@ -1357,27 +1492,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoNats) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoNats) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoNats) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoNats) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *BackendServerInfoGrpc) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(in *jlexer.Lexer, out *BackendServerInfoGrpc) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1425,7 +1560,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in BackendServerInfoGrpc) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(out *jwriter.Writer, in BackendServerInfoGrpc) { out.RawByte('{') first := true _ = first @@ -1455,27 +1590,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoGrpc) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoGrpc) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoGrpc) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoGrpc) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *BackendServerInfoDialout) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(in *jlexer.Lexer, out *BackendServerInfoDialout) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1556,7 +1691,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in BackendServerInfoDialout) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(out *jwriter.Writer, in BackendServerInfoDialout) { out.RawByte('{') first := true _ = first @@ -1605,27 +1740,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoDialout) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoDialout) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *BackendServerInfo) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(in *jlexer.Lexer, out *BackendServerInfo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1778,7 +1913,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in BackendServerInfo) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(out *jwriter.Writer, in BackendServerInfo) { out.RawByte('{') first := true _ = first @@ -1852,27 +1987,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendServerInfo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1931,7 +2066,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in BackendRoomUpdateRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(out *jwriter.Writer, in BackendRoomUpdateRequest) { out.RawByte('{') first := true _ = first @@ -1966,27 +2101,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomUpdateRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomUpdateRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *BackendRoomTransientRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(in *jlexer.Lexer, out *BackendRoomTransientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2036,7 +2171,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in BackendRoomTransientRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(out *jwriter.Writer, in BackendRoomTransientRequest) { out.RawByte('{') first := true _ = first @@ -2072,27 +2207,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomTransientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomTransientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2183,7 +2318,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { out.RawByte('{') first := true _ = first @@ -2236,27 +2371,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2374,7 +2509,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in BackendRoomParticipantsRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(out *jwriter.Writer, in BackendRoomParticipantsRequest) { out.RawByte('{') first := true _ = first @@ -2462,27 +2597,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *BackendRoomMessageRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(in *jlexer.Lexer, out *BackendRoomMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2514,7 +2649,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in BackendRoomMessageRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(out *jwriter.Writer, in BackendRoomMessageRequest) { out.RawByte('{') first := true _ = first @@ -2530,27 +2665,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *BackendRoomInviteRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(in *jlexer.Lexer, out *BackendRoomInviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2636,7 +2771,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in BackendRoomInviteRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(out *jwriter.Writer, in BackendRoomInviteRequest) { out.RawByte('{') first := true _ = first @@ -2690,27 +2825,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *BackendRoomInCallRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(in *jlexer.Lexer, out *BackendRoomInCallRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2842,7 +2977,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in BackendRoomInCallRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(out *jwriter.Writer, in BackendRoomInCallRequest) { out.RawByte('{') first := true _ = first @@ -2950,27 +3085,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomInCallRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInCallRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3083,7 +3218,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in BackendRoomDisinviteRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(out *jwriter.Writer, in BackendRoomDisinviteRequest) { out.RawByte('{') first := true _ = first @@ -3156,27 +3291,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3220,7 +3355,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in BackendRoomDialoutResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(out *jwriter.Writer, in BackendRoomDialoutResponse) { out.RawByte('{') first := true _ = first @@ -3246,27 +3381,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3304,7 +3439,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in BackendRoomDialoutRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(out *jwriter.Writer, in BackendRoomDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3324,27 +3459,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *BackendRoomDialoutError) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(in *jlexer.Lexer, out *BackendRoomDialoutError) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3380,7 +3515,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in BackendRoomDialoutError) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(out *jwriter.Writer, in BackendRoomDialoutError) { out.RawByte('{') first := true _ = first @@ -3400,27 +3535,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutError) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutError) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3471,7 +3606,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in BackendRoomDeleteRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(out *jwriter.Writer, in BackendRoomDeleteRequest) { out.RawByte('{') first := true _ = first @@ -3496,27 +3631,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendRoomDeleteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDeleteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *BackendPingEntry) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(in *jlexer.Lexer, out *BackendPingEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3552,7 +3687,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in BackendPingEntry) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(out *jwriter.Writer, in BackendPingEntry) { out.RawByte('{') first := true _ = first @@ -3578,27 +3713,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendPingEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendPingEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendPingEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendPingEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *BackendClientSessionResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(in *jlexer.Lexer, out *BackendClientSessionResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3634,7 +3769,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in BackendClientSessionResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(out *jwriter.Writer, in BackendClientSessionResponse) { out.RawByte('{') first := true _ = first @@ -3654,27 +3789,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *BackendClientSessionRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(in *jlexer.Lexer, out *BackendClientSessionRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3736,7 +3871,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in BackendClientSessionRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(out *jwriter.Writer, in BackendClientSessionRequest) { out.RawByte('{') first := true _ = first @@ -3776,27 +3911,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *BackendClientRoomResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(in *jlexer.Lexer, out *BackendClientRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3844,7 +3979,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle out.Permissions = nil } else { if out.Permissions == nil { - out.Permissions = new([]Permission) + out.Permissions = new([]api.Permission) } if in.IsNull() { in.Skip() @@ -3853,19 +3988,19 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Delim('[') if *out.Permissions == nil { if !in.IsDelim(']') { - *out.Permissions = make([]Permission, 0, 4) + *out.Permissions = make([]api.Permission, 0, 4) } else { - *out.Permissions = []Permission{} + *out.Permissions = []api.Permission{} } } else { *out.Permissions = (*out.Permissions)[:0] } for !in.IsDelim(']') { - var v71 Permission + var v71 api.Permission if in.IsNull() { in.Skip() } else { - v71 = Permission(in.String()) + v71 = api.Permission(in.String()) } *out.Permissions = append(*out.Permissions, v71) in.WantComma() @@ -3883,7 +4018,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in BackendClientRoomResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(out *jwriter.Writer, in BackendClientRoomResponse) { out.RawByte('{') first := true _ = first @@ -3929,27 +4064,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *BackendClientRoomRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(in *jlexer.Lexer, out *BackendClientRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4021,7 +4156,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in BackendClientRoomRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(out *jwriter.Writer, in BackendClientRoomRequest) { out.RawByte('{') first := true _ = first @@ -4071,27 +4206,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *BackendClientRingResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(in *jlexer.Lexer, out *BackendClientRingResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4127,7 +4262,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in BackendClientRingResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(out *jwriter.Writer, in BackendClientRingResponse) { out.RawByte('{') first := true _ = first @@ -4147,27 +4282,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRingResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRingResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *BackendClientResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(in *jlexer.Lexer, out *BackendClientResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4267,7 +4402,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in BackendClientResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(out *jwriter.Writer, in BackendClientResponse) { out.RawByte('{') first := true _ = first @@ -4307,27 +4442,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *BackendClientRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(in *jlexer.Lexer, out *BackendClientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4413,7 +4548,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in BackendClientRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(out *jwriter.Writer, in BackendClientRequest) { out.RawByte('{') first := true _ = first @@ -4448,27 +4583,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *BackendClientPingRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(in *jlexer.Lexer, out *BackendClientPingRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4531,7 +4666,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in BackendClientPingRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(out *jwriter.Writer, in BackendClientPingRequest) { out.RawByte('{') first := true _ = first @@ -4567,27 +4702,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientPingRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientPingRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *BackendClientAuthResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(in *jlexer.Lexer, out *BackendClientAuthResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4631,7 +4766,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in BackendClientAuthResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(out *jwriter.Writer, in BackendClientAuthResponse) { out.RawByte('{') first := true _ = first @@ -4656,27 +4791,27 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(l, v) } -func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *BackendClientAuthRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(in *jlexer.Lexer, out *BackendClientAuthRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4714,7 +4849,7 @@ func easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in BackendClientAuthRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(out *jwriter.Writer, in BackendClientAuthRequest) { out.RawByte('{') first := true _ = first @@ -4734,23 +4869,23 @@ func easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4354c623EncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4354c623DecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(l, v) } From ecc25c402fd5c3e3f9084930cff7ac4e9ca773aa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 14:56:37 +0100 Subject: [PATCH 426/549] Move async events code to "async/events" package. --- Makefile | 2 +- api_async.go => async/events/api.go | 2 +- .../events/async_events.go | 2 +- .../events/async_events_nats.go | 6 +- .../events/async_events_nats_test.go | 18 +- async/events/async_events_test.go | 168 ++++++++++++++++++ .../eventstest/eventstest.go | 40 +++-- backend_server.go | 29 +-- backend_server_test.go | 66 +++---- clientsession.go | 21 +-- clientsession_test.go | 7 +- hub.go | 37 ++-- hub_test.go | 28 +-- room.go | 47 ++--- server/main.go | 3 +- virtualsession.go | 13 +- 16 files changed, 341 insertions(+), 148 deletions(-) rename api_async.go => async/events/api.go (99%) rename async_events.go => async/events/async_events.go (99%) rename async_events_nats.go => async/events/async_events_nats.go (99%) rename async_events_nats_test.go => async/events/async_events_nats_test.go (71%) create mode 100644 async/events/async_events_test.go rename async_events_test.go => async/eventstest/eventstest.go (69%) diff --git a/Makefile b/Makefile index f9085d9..4fae2e7 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 api/signaling.go */api.go talk/ocs.go)) +EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go api/signaling.go */api.go */*/api.go talk/ocs.go)) EASYJSON_GO_FILES := $(patsubst %.go,%_easyjson.go,$(EASYJSON_FILES)) COMMON_GO_FILES := $(filter-out geoip/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_async.go b/async/events/api.go similarity index 99% rename from api_async.go rename to async/events/api.go index d0a3f81..d91049b 100644 --- a/api_async.go +++ b/async/events/api.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 events import ( "encoding/json" diff --git a/async_events.go b/async/events/async_events.go similarity index 99% rename from async_events.go rename to async/events/async_events.go index d5fb7ab..30efb9c 100644 --- a/async_events.go +++ b/async/events/async_events.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 events import ( "context" diff --git a/async_events_nats.go b/async/events/async_events_nats.go similarity index 99% rename from async_events_nats.go rename to async/events/async_events_nats.go index 565f655..86856c1 100644 --- a/async_events_nats.go +++ b/async/events/async_events_nats.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 events import ( "context" @@ -91,6 +91,10 @@ func NewAsyncEventsNats(logger log.Logger, client nats.Client) (AsyncEvents, err return events, nil } +func (e *asyncEventsNats) GetNatsClient() nats.Client { + return e.client +} + func (e *asyncEventsNats) GetServerInfoNats() *talk.BackendServerInfoNats { // TODO: This should call a method on "e.client" directly instead of having a type switch. var result *talk.BackendServerInfoNats diff --git a/async_events_nats_test.go b/async/events/async_events_nats_test.go similarity index 71% rename from async_events_nats_test.go rename to async/events/async_events_nats_test.go index 24bd50f..82d0dda 100644 --- a/async_events_nats_test.go +++ b/async/events/async_events_nats_test.go @@ -19,29 +19,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package events import ( "testing" - "time" - - "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func Benchmark_GetSubjectForSessionId(b *testing.B) { - require := require.New(b) backend := talk.NewCompatBackend(nil) - data := &SessionIdData{ - Sid: 1, - Created: time.Now().UnixMicro(), - BackendId: backend.Id(), - } - codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) - require.NoError(err) - sid, err := codec.EncodePublic(data) - require.NoError(err, "could not create session id") + sid := api.PublicSessionId(internal.RandomString(256)) for b.Loop() { GetSubjectForSessionId(sid, backend) } diff --git a/async/events/async_events_test.go b/async/events/async_events_test.go new file mode 100644 index 0000000..23beb5b --- /dev/null +++ b/async/events/async_events_test.go @@ -0,0 +1,168 @@ +/** + * 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 events + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" +) + +type TestBackendRoomListener struct { + events AsyncChannel +} + +func (l *TestBackendRoomListener) AsyncChannel() AsyncChannel { + return l.events +} + +func testAsyncEvents(t *testing.T, events AsyncEvents) { + require := require.New(t) + assert := assert.New(t) + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + assert.NoError(events.Close(ctx)) + }) + + listener := &TestBackendRoomListener{ + events: make(AsyncChannel, 1), + } + + roomId := "1234" + backend := talk.NewCompatBackend(nil) + require.NoError(events.RegisterBackendRoomListener(roomId, backend, listener)) + defer func() { + assert.NoError(events.UnregisterBackendRoomListener(roomId, backend, listener)) + }() + + msg := &AsyncMessage{ + Type: "room", + Room: &talk.BackendServerRoomRequest{ + Type: "test", + }, + } + if assert.NoError(events.PublishBackendRoomMessage(roomId, backend, msg)) { + received := <-listener.events + var receivedMsg AsyncMessage + if assert.NoError(nats.Decode(received, &receivedMsg)) { + assert.True(msg.SendTime.Equal(receivedMsg.SendTime), "send times don't match, expected %s, got %s", msg.SendTime, receivedMsg.SendTime) + receivedMsg.SendTime = msg.SendTime + assert.Equal(msg, &receivedMsg) + } + } + + require.NoError(events.RegisterRoomListener(roomId, backend, listener)) + defer func() { + assert.NoError(events.UnregisterRoomListener(roomId, backend, listener)) + }() + + roomMessage := &AsyncMessage{ + Type: "room", + Room: &talk.BackendServerRoomRequest{ + Type: "other-test", + }, + } + if assert.NoError(events.PublishRoomMessage(roomId, backend, roomMessage)) { + received := <-listener.events + var receivedMsg AsyncMessage + if assert.NoError(nats.Decode(received, &receivedMsg)) { + assert.True(roomMessage.SendTime.Equal(receivedMsg.SendTime), "send times don't match, expected %s, got %s", roomMessage.SendTime, receivedMsg.SendTime) + receivedMsg.SendTime = roomMessage.SendTime + assert.Equal(roomMessage, &receivedMsg) + } + } + + userId := "the-user" + require.NoError(events.RegisterUserListener(userId, backend, listener)) + defer func() { + assert.NoError(events.UnregisterUserListener(userId, backend, listener)) + }() + + userMessage := &AsyncMessage{ + Type: "room", + Room: &talk.BackendServerRoomRequest{ + Type: "user-test", + }, + } + if assert.NoError(events.PublishUserMessage(userId, backend, userMessage)) { + received := <-listener.events + var receivedMsg AsyncMessage + if assert.NoError(nats.Decode(received, &receivedMsg)) { + assert.True(userMessage.SendTime.Equal(receivedMsg.SendTime), "send times don't match, expected %s, got %s", userMessage.SendTime, receivedMsg.SendTime) + receivedMsg.SendTime = userMessage.SendTime + assert.Equal(userMessage, &receivedMsg) + } + } + + sessionId := api.PublicSessionId("the-session") + require.NoError(events.RegisterSessionListener(sessionId, backend, listener)) + defer func() { + assert.NoError(events.UnregisterSessionListener(sessionId, backend, listener)) + }() + + sessionMessage := &AsyncMessage{ + Type: "room", + Room: &talk.BackendServerRoomRequest{ + Type: "session-test", + }, + } + if assert.NoError(events.PublishSessionMessage(sessionId, backend, sessionMessage)) { + received := <-listener.events + var receivedMsg AsyncMessage + if assert.NoError(nats.Decode(received, &receivedMsg)) { + assert.True(sessionMessage.SendTime.Equal(receivedMsg.SendTime), "send times don't match, expected %s, got %s", sessionMessage.SendTime, receivedMsg.SendTime) + receivedMsg.SendTime = sessionMessage.SendTime + assert.Equal(sessionMessage, &receivedMsg) + } + } +} + +func TestAsyncEvents_Loopback(t *testing.T) { + t.Parallel() + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + events, err := NewAsyncEvents(ctx, nats.LoopbackUrl) + require.NoError(t, err) + testAsyncEvents(t, events) +} + +func TestAsyncEvents_NATS(t *testing.T) { + t.Parallel() + + server, _ := nats.StartLocalServer(t) + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + events, err := NewAsyncEvents(ctx, server.ClientURL()) + require.NoError(t, err) + testAsyncEvents(t, events) +} diff --git a/async_events_test.go b/async/eventstest/eventstest.go similarity index 69% rename from async_events_test.go rename to async/eventstest/eventstest.go index dead7c0..5f551cd 100644 --- a/async_events_test.go +++ b/async/eventstest/eventstest.go @@ -1,6 +1,6 @@ /** * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2022 struktur AG + * Copyright (C) 2025 struktur AG * * @author Joachim Bauch * @@ -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 eventstest import ( "context" @@ -30,19 +30,22 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" ) var ( - eventBackendsForTest = []string{ + testTimeout = 10 * time.Second + + EventBackendsForTest = []string{ "loopback", "nats", } ) -func getAsyncEventsForTest(t *testing.T) AsyncEvents { - var events AsyncEvents +func GetAsyncEventsForTest(t *testing.T) events.AsyncEvents { + var events events.AsyncEvents if strings.HasSuffix(t.Name(), "/nats") { events = getRealAsyncEventsForTest(t) } else { @@ -56,21 +59,25 @@ func getAsyncEventsForTest(t *testing.T) AsyncEvents { return events } -func getRealAsyncEventsForTest(t *testing.T) AsyncEvents { +func getRealAsyncEventsForTest(t *testing.T) events.AsyncEvents { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) server, _ := nats.StartLocalServer(t) - events, err := NewAsyncEvents(ctx, server.ClientURL()) + events, err := events.NewAsyncEvents(ctx, server.ClientURL()) if err != nil { require.NoError(t, err) } return events } -func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { +type natsEvents interface { + GetNatsClient() nats.Client +} + +func getLoopbackAsyncEventsForTest(t *testing.T) events.AsyncEvents { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - events, err := NewAsyncEvents(ctx, nats.LoopbackUrl) + events, err := events.NewAsyncEvents(ctx, nats.LoopbackUrl) if err != nil { require.NoError(t, err) } @@ -79,22 +86,27 @@ func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client := (events.(*asyncEventsNats)).client - nats.WaitForSubscriptionsEmpty(ctx, t, client) + e, ok := (events.(natsEvents)) + if !ok { + // Only can wait for NATS events. + return + } + + nats.WaitForSubscriptionsEmpty(ctx, t, e.GetNatsClient()) }) return events } -func waitForAsyncEventsFlushed(ctx context.Context, t *testing.T, events AsyncEvents) { +func WaitForAsyncEventsFlushed(ctx context.Context, t *testing.T, events events.AsyncEvents) { t.Helper() - e, ok := (events.(*asyncEventsNats)) + e, ok := (events.(natsEvents)) if !ok { // Only can wait for NATS events. return } - client, ok := e.client.(*nats.NativeClient) + client, ok := e.GetNatsClient().(*nats.NativeClient) if !ok { // The loopback NATS clients is executing all events synchronously. return diff --git a/backend_server.go b/backend_server.go index b0e23ce..c550231 100644 --- a/backend_server.go +++ b/backend_server.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" @@ -71,7 +72,7 @@ const ( type BackendServer struct { logger log.Logger hub *Hub - events AsyncEvents + events events.AsyncEvents roomSessions RoomSessions version string @@ -329,7 +330,7 @@ func (b *BackendServer) parseRequestBody(f func(context.Context, http.ResponseWr } func (b *BackendServer) sendRoomInvite(roomid string, backend *talk.Backend, userids []string, properties json.RawMessage) { - msg := &AsyncMessage{ + msg := &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Type: "event", @@ -351,7 +352,7 @@ func (b *BackendServer) sendRoomInvite(roomid string, backend *talk.Backend, use } func (b *BackendServer) sendRoomDisinvite(roomid string, backend *talk.Backend, reason string, userids []string, sessionids []api.RoomSessionId) { - msg := &AsyncMessage{ + msg := &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Type: "event", @@ -400,7 +401,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *talk.Backend, } func (b *BackendServer) sendRoomUpdate(roomid string, backend *talk.Backend, notified_userids []string, all_userids []string, properties json.RawMessage) { - msg := &AsyncMessage{ + msg := &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Type: "event", @@ -518,7 +519,7 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *talk.Backend, req } } - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "room", Room: request, } @@ -571,7 +572,7 @@ loop: go func(sessionId api.PublicSessionId, permissions []api.Permission) { defer wg.Done() - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "permissions", Permissions: permissions, } @@ -582,7 +583,7 @@ loop: } wg.Wait() - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "room", Room: request, } @@ -590,7 +591,7 @@ loop: } func (b *BackendServer) sendRoomMessage(roomid string, backend *talk.Backend, request *talk.BackendServerRoomRequest) error { - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "room", Room: request, } @@ -688,7 +689,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac } request.SwitchTo.Sessions = nil - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "room", Room: request, } @@ -922,14 +923,14 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter, b.sendRoomDisinvite(roomid, backend, api.DisinviteReasonDisinvited, request.Disinvite.UserIds, request.Disinvite.SessionIds) b.sendRoomUpdate(roomid, backend, request.Disinvite.UserIds, request.Disinvite.AllUserIds, request.Disinvite.Properties) case "update": - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "room", Room: &request, } err = b.events.PublishBackendRoomMessage(roomid, backend, message) b.sendRoomUpdate(roomid, backend, nil, request.Update.UserIds, request.Update.Properties) case "delete": - message := &AsyncMessage{ + message := &events.AsyncMessage{ Type: "room", Room: &request, } @@ -1016,6 +1017,10 @@ func (b *BackendServer) statsHandler(w http.ResponseWriter, r *http.Request) { w.Write(statsData) // nolint } +type withServerInfoNats interface { + GetServerInfoNats() *talk.BackendServerInfoNats +} + func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request) { info := talk.BackendServerInfo{ Version: b.version, @@ -1026,7 +1031,7 @@ func (b *BackendServer) serverinfoHandler(w http.ResponseWriter, r *http.Request if mcu := b.hub.mcu; mcu != nil { info.Sfu = mcu.GetServerInfoSfu() } - if e, ok := b.events.(*asyncEventsNats); ok { + if e, ok := b.events.(withServerInfoNats); ok { info.Nats = e.GetServerInfoNats() } if rpcClients := b.hub.rpcClients; rpcClients != nil { diff --git a/backend_server_test.go b/backend_server_test.go index 7eda72f..59b5fc4 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -47,6 +47,8 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/async/eventstest" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" @@ -60,11 +62,11 @@ var ( turnServers = strings.Split(turnServersString, ",") ) -func CreateBackendServerForTest(t *testing.T) (*goconf.ConfigFile, *BackendServer, AsyncEvents, *Hub, *mux.Router, *httptest.Server) { +func CreateBackendServerForTest(t *testing.T) (*goconf.ConfigFile, *BackendServer, events.AsyncEvents, *Hub, *mux.Router, *httptest.Server) { return CreateBackendServerForTestFromConfig(t, nil) } -func CreateBackendServerForTestWithTurn(t *testing.T) (*goconf.ConfigFile, *BackendServer, AsyncEvents, *Hub, *mux.Router, *httptest.Server) { +func CreateBackendServerForTestWithTurn(t *testing.T) (*goconf.ConfigFile, *BackendServer, events.AsyncEvents, *Hub, *mux.Router, *httptest.Server) { config := goconf.NewConfigFile() config.AddOption("turn", "apikey", turnApiKey) config.AddOption("turn", "secret", turnSecret) @@ -72,7 +74,7 @@ func CreateBackendServerForTestWithTurn(t *testing.T) (*goconf.ConfigFile, *Back return CreateBackendServerForTestFromConfig(t, config) } -func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFile) (*goconf.ConfigFile, *BackendServer, AsyncEvents, *Hub, *mux.Router, *httptest.Server) { +func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFile) (*goconf.ConfigFile, *BackendServer, events.AsyncEvents, *Hub, *mux.Router, *httptest.Server) { require := require.New(t) r := mux.NewRouter() registerBackendHandler(t, r) @@ -102,7 +104,7 @@ func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFil config.AddOption("sessions", "blockkey", "09876543210987654321098765432109") config.AddOption("clients", "internalsecret", string(testInternalSecret)) config.AddOption("geoip", "url", "none") - events := getAsyncEventsForTest(t) + events := eventstest.GetAsyncEventsForTest(t) logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) hub, err := NewHub(ctx, config, events, nil, nil, nil, r, "no-version") @@ -168,7 +170,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - events1, err := NewAsyncEvents(ctx, nats.ClientURL()) + events1, err := events.NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -193,7 +195,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config2.AddOption("sessions", "blockkey", "09876543210987654321098765432109") config2.AddOption("clients", "internalsecret", string(testInternalSecret)) config2.AddOption("geoip", "url", "none") - events2, err := NewAsyncEvents(ctx, nats.ClientURL()) + events2, err := events.NewAsyncEvents(ctx, nats.ClientURL()) require.NoError(err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -244,13 +246,13 @@ func performBackendRequest(requestUrl string, body []byte) (*http.Response, erro return client.Do(request) } -func expectRoomlistEvent(t *testing.T, ch AsyncChannel, msgType string) (*api.EventServerMessage, bool) { +func expectRoomlistEvent(t *testing.T, ch events.AsyncChannel, msgType string) (*api.EventServerMessage, bool) { assert := assert.New(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() select { case natsMsg := <-ch: - var message AsyncMessage + var message events.AsyncMessage if !assert.NoError(nats.Decode(natsMsg, &message)) || !assert.Equal("message", message.Type, "invalid message type, got %+v", message) || !assert.NotNil(message.Message, "message missing, got %+v", message) { @@ -397,7 +399,7 @@ func TestBackendServer_UnsupportedRequest(t *testing.T) { func TestBackendServer_RoomInvite(t *testing.T) { t.Parallel() - for _, backend := range eventBackendsForTest { + for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) @@ -408,17 +410,17 @@ func TestBackendServer_RoomInvite(t *testing.T) { } type channelEventListener struct { - ch AsyncChannel + ch events.AsyncChannel } -func (l *channelEventListener) AsyncChannel() AsyncChannel { +func (l *channelEventListener) AsyncChannel() events.AsyncChannel { return l.ch } func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) - _, _, events, hub, _, server := CreateBackendServerForTest(t) + _, _, asyncEvents, hub, _, server := CreateBackendServerForTest(t) u, err := url.Parse(server.URL) require.NoError(err) @@ -427,14 +429,15 @@ func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { roomProperties := json.RawMessage("{\"foo\":\"bar\"}") backend := hub.backend.GetBackend(u) - eventsChan := make(AsyncChannel, 1) + eventsChan := make(events.AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } - require.NoError(events.RegisterUserListener(userid, backend, listener)) + require.NoError(asyncEvents.RegisterUserListener(userid, backend, listener)) defer func() { - assert.NoError(events.UnregisterUserListener(userid, backend, listener)) + assert.NoError(asyncEvents.UnregisterUserListener(userid, backend, listener)) }() + msg := &talk.BackendServerRoomRequest{ Type: "invite", Invite: &talk.BackendRoomInviteRequest{ @@ -466,7 +469,7 @@ func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) { func TestBackendServer_RoomDisinvite(t *testing.T) { t.Parallel() - for _, backend := range eventBackendsForTest { + for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) @@ -479,7 +482,7 @@ func TestBackendServer_RoomDisinvite(t *testing.T) { func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) - _, _, events, hub, _, server := CreateBackendServerForTest(t) + _, _, asyncEvents, hub, _, server := CreateBackendServerForTest(t) u, err := url.Parse(server.URL) require.NoError(err) @@ -503,14 +506,15 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { roomProperties := json.RawMessage("{\"foo\":\"bar\"}") - eventsChan := make(AsyncChannel, 1) + eventsChan := make(events.AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } - require.NoError(events.RegisterUserListener(testDefaultUserId, backend, listener)) + require.NoError(asyncEvents.RegisterUserListener(testDefaultUserId, backend, listener)) defer func() { - assert.NoError(events.UnregisterUserListener(testDefaultUserId, backend, listener)) + assert.NoError(asyncEvents.UnregisterUserListener(testDefaultUserId, backend, listener)) }() + msg := &talk.BackendServerRoomRequest{ Type: "disinvite", Disinvite: &talk.BackendRoomDisinviteRequest{ @@ -629,7 +633,7 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { func TestBackendServer_RoomUpdate(t *testing.T) { t.Parallel() - for _, backend := range eventBackendsForTest { + for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) @@ -642,7 +646,7 @@ func TestBackendServer_RoomUpdate(t *testing.T) { func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) - _, _, events, hub, _, server := CreateBackendServerForTest(t) + _, _, asyncEvents, hub, _, server := CreateBackendServerForTest(t) u, err := url.Parse(server.URL) require.NoError(err) @@ -658,14 +662,15 @@ func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { userid := "test-userid" roomProperties := json.RawMessage("{\"foo\":\"bar\"}") - eventsChan := make(AsyncChannel, 1) + eventsChan := make(events.AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } - require.NoError(events.RegisterUserListener(userid, backend, listener)) + require.NoError(asyncEvents.RegisterUserListener(userid, backend, listener)) defer func() { - assert.NoError(events.UnregisterUserListener(userid, backend, listener)) + assert.NoError(asyncEvents.UnregisterUserListener(userid, backend, listener)) }() + msg := &talk.BackendServerRoomRequest{ Type: "update", Update: &talk.BackendRoomUpdateRequest{ @@ -700,7 +705,7 @@ func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) { func TestBackendServer_RoomDelete(t *testing.T) { t.Parallel() - for _, backend := range eventBackendsForTest { + for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() logger := log.NewLoggerForTest(t) @@ -713,7 +718,7 @@ func TestBackendServer_RoomDelete(t *testing.T) { func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) { require := require.New(t) assert := assert.New(t) - _, _, events, hub, _, server := CreateBackendServerForTest(t) + _, _, asyncEvents, hub, _, server := CreateBackendServerForTest(t) u, err := url.Parse(server.URL) require.NoError(err) @@ -726,14 +731,15 @@ func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) { require.NoError(err) userid := "test-userid" - eventsChan := make(AsyncChannel, 1) + eventsChan := make(events.AsyncChannel, 1) listener := &channelEventListener{ ch: eventsChan, } - require.NoError(events.RegisterUserListener(userid, backend, listener)) + require.NoError(asyncEvents.RegisterUserListener(userid, backend, listener)) defer func() { - assert.NoError(events.UnregisterUserListener(userid, backend, listener)) + assert.NoError(asyncEvents.UnregisterUserListener(userid, backend, listener)) }() + msg := &talk.BackendServerRoomRequest{ Type: "delete", Delete: &talk.BackendRoomDeleteRequest{ diff --git a/clientsession.go b/clientsession.go index e12f08c..68a60e4 100644 --- a/clientsession.go +++ b/clientsession.go @@ -37,6 +37,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -57,7 +58,7 @@ type ResponseHandlerFunc func(message *api.ClientMessage) bool type ClientSession struct { logger log.Logger hub *Hub - events AsyncEvents + events events.AsyncEvents privateId api.PrivateSessionId publicId api.PublicSessionId data *SessionIdData @@ -82,7 +83,7 @@ type ClientSession struct { parsedBackendUrl *url.URL mu sync.Mutex - asyncCh AsyncChannel + asyncCh events.AsyncChannel // +checklocks:mu client HandlerClient @@ -140,7 +141,7 @@ func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.Pub parseUserData: parseUserData(auth.User), backend: backend, - asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), + asyncCh: make(events.AsyncChannel, events.DefaultAsyncChannelSize), } if s.clientType == api.HelloClientTypeInternal { s.backendUrl = hello.Auth.InternalParams.Backend @@ -398,7 +399,7 @@ func (s *ClientSession) releaseMcuObjects() { } } -func (s *ClientSession) AsyncChannel() AsyncChannel { +func (s *ClientSession) AsyncChannel() events.AsyncChannel { return s.asyncCh } @@ -1071,7 +1072,7 @@ func (s *ClientSession) GetSubscriber(id api.PublicSessionId, streamType StreamT } func (s *ClientSession) processAsyncNatsMessage(msg *nats.Msg) { - var message AsyncMessage + var message events.AsyncMessage if err := nats.Decode(msg, &message); err != nil { s.logger.Printf("Could not decode NATS message %+v: %s", msg, err) return @@ -1080,7 +1081,7 @@ func (s *ClientSession) processAsyncNatsMessage(msg *nats.Msg) { s.processAsyncMessage(&message) } -func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { +func (s *ClientSession) processAsyncMessage(message *events.AsyncMessage) { switch message.Type { case "permissions": s.SetPermissions(message.Permissions) @@ -1128,7 +1129,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { mc, err := s.GetOrCreateSubscriber(ctx, s.hub.mcu, message.SendOffer.SessionId, StreamType(message.SendOffer.Data.RoomType)) if err != nil { s.logger.Printf("Could not create MCU subscriber for session %s to process sendoffer in %s: %s", message.SendOffer.SessionId, s.PublicId(), err) - if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ + if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Id: message.SendOffer.MessageId, @@ -1141,7 +1142,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { return } else if mc == nil { s.logger.Printf("No MCU subscriber found for session %s to process sendoffer in %s", message.SendOffer.SessionId, s.PublicId()) - if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ + if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Id: message.SendOffer.MessageId, @@ -1157,7 +1158,7 @@ func (s *ClientSession) processAsyncMessage(message *AsyncMessage) { mc.SendMessage(s.Context(), nil, message.SendOffer.Data, func(err error, response api.StringMap) { if err != nil { s.logger.Printf("Could not send MCU message %+v for session %s to %s: %s", message.SendOffer.Data, message.SendOffer.SessionId, s.PublicId(), err) - if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &AsyncMessage{ + if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Id: message.SendOffer.MessageId, @@ -1413,7 +1414,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes return message } -func (s *ClientSession) filterAsyncMessage(msg *AsyncMessage) *api.ServerMessage { +func (s *ClientSession) filterAsyncMessage(msg *events.AsyncMessage) *api.ServerMessage { switch msg.Type { case "message": if msg.Message == nil { diff --git a/clientsession_test.go b/clientsession_test.go index a308aee..f59b45d 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -218,7 +219,7 @@ func TestFeatureChatRelay(t *testing.T) { require.NoError(err) // Simulate request from the backend. - room.processAsyncMessage(&AsyncMessage{ + room.processAsyncMessage(&events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "message", @@ -415,7 +416,7 @@ func TestFeatureChatRelayFederation(t *testing.T) { require.NoError(err) // Simulate request from the backend. - room.processAsyncMessage(&AsyncMessage{ + room.processAsyncMessage(&events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "message", @@ -515,7 +516,7 @@ func TestPermissionHideDisplayNames(t *testing.T) { require.NoError(err) // Simulate request from the backend. - room.processAsyncMessage(&AsyncMessage{ + room.processAsyncMessage(&events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "message", diff --git a/hub.go b/hub.go index 88867a6..96b8802 100644 --- a/hub.go +++ b/hub.go @@ -53,6 +53,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/etcd" @@ -146,7 +147,7 @@ func init() { type Hub struct { version string logger log.Logger - events AsyncEvents + events events.AsyncEvents upgrader websocket.Upgrader sessionIds *SessionIdCodec info *api.WelcomeServerMessage @@ -225,7 +226,7 @@ type Hub struct { blockedCandidates atomic.Pointer[container.IPList] } -func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { +func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { logger := log.LoggerFromContext(ctx) hashKey, _ := config.GetStringOptionWithEnv(cfg, "sessions", "hashkey") switch len(hashKey) { @@ -1626,7 +1627,7 @@ func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId api.R if session == nil { // Session is located on a different server. Should already have been closed // but send "bye" again as additional safeguard. - msg := &AsyncMessage{ + msg := &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Type: "bye", @@ -2150,7 +2151,7 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { return } - subject = GetSubjectForSessionId(msg.Recipient.SessionId, sess.Backend()) + subject = events.GetSubjectForSessionId(msg.Recipient.SessionId, sess.Backend()) recipientSessionId = msg.Recipient.SessionId if sess, ok := sess.(*ClientSession); ok { recipient = sess @@ -2160,7 +2161,7 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { if sess.ClientType() == api.HelloClientTypeVirtual { virtualSession := sess.(*VirtualSession) clientSession := virtualSession.Session() - subject = GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) + subject = events.GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) recipientSessionId = clientSession.PublicId() recipient = clientSession // The client should see his session id as recipient. @@ -2170,7 +2171,7 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { } } } else { - subject = GetSubjectForSessionId(msg.Recipient.SessionId, nil) + subject = events.GetSubjectForSessionId(msg.Recipient.SessionId, nil) recipientSessionId = msg.Recipient.SessionId serverRecipient = &msg.Recipient } @@ -2183,14 +2184,14 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { return } - subject = GetSubjectForUserId(msg.Recipient.UserId, session.Backend()) + subject = events.GetSubjectForUserId(msg.Recipient.UserId, session.Backend()) } case api.RecipientTypeRoom: fallthrough case api.RecipientTypeCall: if session != nil { if room = session.GetRoom(); room != nil { - subject = GetSubjectForRoomId(room.Id(), room.Backend()) + subject = events.GetSubjectForRoomId(room.Id(), room.Backend()) if h.mcu != nil { var data api.MessageClientMessageData @@ -2283,9 +2284,9 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { return } - async := &AsyncMessage{ + async := &events.AsyncMessage{ Type: "sendoffer", - SendOffer: &SendOfferMessage{ + SendOffer: &events.SendOfferMessage{ MessageId: message.Id, SessionId: session.PublicId(), Data: clientData, @@ -2297,7 +2298,7 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { return } - async := &AsyncMessage{ + async := &events.AsyncMessage{ Type: "message", Message: response, } @@ -2356,7 +2357,7 @@ func (h *Hub) processControlMsg(session Session, message *api.ClientMessage) { return } - subject = GetSubjectForSessionId(msg.Recipient.SessionId, nil) + subject = events.GetSubjectForSessionId(msg.Recipient.SessionId, nil) recipientSessionId = msg.Recipient.SessionId h.mu.RLock() sess, found := h.sessions[data.Sid] @@ -2369,7 +2370,7 @@ func (h *Hub) processControlMsg(session Session, message *api.ClientMessage) { if sess.ClientType() == api.HelloClientTypeVirtual { virtualSession := sess.(*VirtualSession) clientSession := virtualSession.Session() - subject = GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) + subject = events.GetSubjectForSessionId(clientSession.PublicId(), sess.Backend()) recipientSessionId = clientSession.PublicId() recipient = clientSession // The client should see his session id as recipient. @@ -2394,14 +2395,14 @@ func (h *Hub) processControlMsg(session Session, message *api.ClientMessage) { return } - subject = GetSubjectForUserId(msg.Recipient.UserId, session.Backend()) + subject = events.GetSubjectForUserId(msg.Recipient.UserId, session.Backend()) } case api.RecipientTypeRoom: fallthrough case api.RecipientTypeCall: if session != nil { if room = session.GetRoom(); room != nil { - subject = GetSubjectForRoomId(room.Id(), room.Backend()) + subject = events.GetSubjectForRoomId(room.Id(), room.Backend()) } } } @@ -2425,7 +2426,7 @@ func (h *Hub) processControlMsg(session Session, message *api.ClientMessage) { if recipient != nil { recipient.SendMessage(response) } else { - async := &AsyncMessage{ + async := &events.AsyncMessage{ Type: "message", Message: response, } @@ -2618,7 +2619,7 @@ func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { roomId := msg.Dialout.RoomId msg.Dialout.RoomId = "" // Don't send room id to recipients. if msg.Dialout.Type == "status" { - asyncMessage := &AsyncMessage{ + asyncMessage := &events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "transient", @@ -2636,7 +2637,7 @@ func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { h.logger.Printf("Error publishing dialout message %+v to room %s", msg.Dialout, roomId) } } else { - if err := h.events.PublishRoomMessage(roomId, session.Backend(), &AsyncMessage{ + if err := h.events.PublishRoomMessage(roomId, session.Backend(), &events.AsyncMessage{ Type: "message", Message: &api.ServerMessage{ Type: "dialout", diff --git a/hub_test.go b/hub_test.go index f420cac..9ee4129 100644 --- a/hub_test.go +++ b/hub_test.go @@ -54,6 +54,8 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/async/eventstest" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" @@ -158,7 +160,7 @@ func getTestConfigWithMultipleUrls(server *httptest.Server) (*goconf.ConfigFile, return config, nil } -func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { +func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, events.AsyncEvents, *mux.Router, *httptest.Server) { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) @@ -170,7 +172,7 @@ func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Serve server.Close() }) - events := getAsyncEventsForTest(t) + events := eventstest.GetAsyncEventsForTest(t) config, err := getConfigFunc(server) require.NoError(err) h, err := NewHub(ctx, config, events, nil, nil, nil, r, "no-version") @@ -191,18 +193,18 @@ func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Serve return h, events, r, server } -func CreateHubForTest(t *testing.T) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { +func CreateHubForTest(t *testing.T) (*Hub, events.AsyncEvents, *mux.Router, *httptest.Server) { return CreateHubForTestWithConfig(t, getTestConfig) } -func CreateHubWithMultipleBackendsForTest(t *testing.T) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { +func CreateHubWithMultipleBackendsForTest(t *testing.T) (*Hub, events.AsyncEvents, *mux.Router, *httptest.Server) { h, events, r, server := CreateHubForTestWithConfig(t, getTestConfigWithMultipleBackends) registerBackendHandlerUrl(t, r, "/one") registerBackendHandlerUrl(t, r, "/two") return h, events, r, server } -func CreateHubWithMultipleUrlsForTest(t *testing.T) (*Hub, AsyncEvents, *mux.Router, *httptest.Server) { +func CreateHubWithMultipleUrlsForTest(t *testing.T) (*Hub, events.AsyncEvents, *mux.Router, *httptest.Server) { h, events, r, server := CreateHubForTestWithConfig(t, getTestConfigWithMultipleUrls) registerBackendHandlerUrl(t, r, "/one") registerBackendHandlerUrl(t, r, "/two") @@ -245,7 +247,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http addr1, addr2 = addr2, addr1 } - events1, err := NewAsyncEvents(ctx, nats1.ClientURL()) + events1, err := events.NewAsyncEvents(ctx, nats1.ClientURL()) require.NoError(err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -259,7 +261,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http require.NoError(err) b1, err := NewBackendServer(ctx, config1, h1, "no-version") require.NoError(err) - events2, err := NewAsyncEvents(ctx, nats2.ClientURL()) + events2, err := events.NewAsyncEvents(ctx, nats2.ClientURL()) require.NoError(err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -2005,8 +2007,8 @@ func TestClientMessageToSessionId(t *testing.T) { require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) // Make sure the session subscription events are processed. - waitForAsyncEventsFlushed(ctx, t, hub1.events) - waitForAsyncEventsFlushed(ctx, t, hub2.events) + eventstest.WaitForAsyncEventsFlushed(ctx, t, hub1.events) + eventstest.WaitForAsyncEventsFlushed(ctx, t, hub2.events) recipient1 := api.MessageClientMessageRecipient{ Type: "session", @@ -2066,8 +2068,8 @@ func TestClientControlToSessionId(t *testing.T) { require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) // Make sure the session subscription events are processed. - waitForAsyncEventsFlushed(ctx, t, hub1.events) - waitForAsyncEventsFlushed(ctx, t, hub2.events) + eventstest.WaitForAsyncEventsFlushed(ctx, t, hub1.events) + eventstest.WaitForAsyncEventsFlushed(ctx, t, hub2.events) recipient1 := api.MessageClientMessageRecipient{ Type: "session", @@ -3380,7 +3382,7 @@ func TestCombineChatRefreshWhileDisconnected(t *testing.T) { require.NoError(json.Unmarshal([]byte(chat_refresh), &data)) // Simulate requests from the backend. - room.processAsyncMessage(&AsyncMessage{ + room.processAsyncMessage(&events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "message", @@ -3389,7 +3391,7 @@ func TestCombineChatRefreshWhileDisconnected(t *testing.T) { }, }, }) - room.processAsyncMessage(&AsyncMessage{ + room.processAsyncMessage(&events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "message", diff --git a/room.go b/room.go index 7484962..775d29e 100644 --- a/room.go +++ b/room.go @@ -36,6 +36,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" @@ -70,7 +71,7 @@ type Room struct { id string logger log.Logger hub *Hub - events AsyncEvents + events events.AsyncEvents backend *talk.Backend // +checklocks:mu @@ -78,7 +79,7 @@ type Room struct { closer *internal.Closer mu *sync.RWMutex - asyncCh AsyncChannel + asyncCh events.AsyncChannel // +checklocks:mu sessions map[api.PublicSessionId]Session // +checklocks:mu @@ -110,19 +111,19 @@ func getRoomIdForBackend(id string, backend *talk.Backend) string { return backend.Id() + "|" + id } -func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEvents, backend *talk.Backend) (*Room, error) { +func NewRoom(roomId string, properties json.RawMessage, hub *Hub, asyncEvents events.AsyncEvents, backend *talk.Backend) (*Room, error) { room := &Room{ id: roomId, logger: hub.logger, hub: hub, - events: events, + events: asyncEvents, backend: backend, properties: properties, closer: internal.NewCloser(), mu: &sync.RWMutex{}, - asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), + asyncCh: make(events.AsyncChannel, events.DefaultAsyncChannelSize), sessions: make(map[api.PublicSessionId]Session), internalSessions: make(map[*ClientSession]bool), @@ -140,7 +141,7 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, events AsyncEv transientData: NewTransientData(), } - if err := events.RegisterBackendRoomListener(roomId, backend, room); err != nil { + if err := asyncEvents.RegisterBackendRoomListener(roomId, backend, room); err != nil { return nil, err } @@ -185,7 +186,7 @@ func (r *Room) IsEqual(other *Room) bool { return b1.Id() == b2.Id() } -func (r *Room) AsyncChannel() AsyncChannel { +func (r *Room) AsyncChannel() events.AsyncChannel { return r.asyncCh } @@ -235,7 +236,7 @@ func (r *Room) Close() []Session { } func (r *Room) processAsyncNatsMessage(msg *nats.Msg) { - var message AsyncMessage + var message events.AsyncMessage if err := nats.Decode(msg, &message); err != nil { r.logger.Printf("Could not decode NATS message %+v: %s", msg, err) return @@ -244,7 +245,7 @@ func (r *Room) processAsyncNatsMessage(msg *nats.Msg) { r.processAsyncMessage(&message) } -func (r *Room) processAsyncMessage(message *AsyncMessage) { +func (r *Room) processAsyncMessage(message *events.AsyncMessage) { switch message.Type { case "room": r.processBackendRoomRequestRoom(message.Room) @@ -301,7 +302,7 @@ func (r *Room) processBackendRoomRequestRoom(message *talk.BackendServerRoomRequ } } -func (r *Room) processBackendRoomRequestAsyncRoom(message *AsyncRoomMessage) { +func (r *Room) processBackendRoomRequestAsyncRoom(message *events.AsyncRoomMessage) { switch message.Type { case "sessionjoined": r.notifySessionJoined(message.SessionId) @@ -373,9 +374,9 @@ func (r *Room) AddSession(session Session, sessionData json.RawMessage) { } // Trigger notifications that the session joined. - if err := r.events.PublishBackendRoomMessage(r.id, r.backend, &AsyncMessage{ + if err := r.events.PublishBackendRoomMessage(r.id, r.backend, &events.AsyncMessage{ Type: "asyncroom", - AsyncRoom: &AsyncRoomMessage{ + AsyncRoom: &events.AsyncRoomMessage{ Type: "sessionjoined", SessionId: sid, ClientType: session.ClientType(), @@ -411,7 +412,7 @@ func (r *Room) notifySessionJoined(sessionId api.PublicSessionId) { session = nil } - events := make([]api.EventServerMessageSessionEntry, 0, len(sessions)) + joinEvents := make([]api.EventServerMessageSessionEntry, 0, len(sessions)) for _, s := range sessions { entry := api.EventServerMessageSessionEntry{ SessionId: s.PublicId(), @@ -423,7 +424,7 @@ func (r *Room) notifySessionJoined(sessionId api.PublicSessionId) { entry.RoomSessionId = s.RoomSessionId() entry.Federated = s.ClientType() == api.HelloClientTypeFederation } - events = append(events, entry) + joinEvents = append(joinEvents, entry) } msg := &api.ServerMessage{ @@ -431,11 +432,11 @@ func (r *Room) notifySessionJoined(sessionId api.PublicSessionId) { Event: &api.EventServerMessage{ Target: "room", Type: "join", - Join: events, + Join: joinEvents, }, } - if err := r.events.PublishSessionMessage(sessionId, r.backend, &AsyncMessage{ + if err := r.events.PublishSessionMessage(sessionId, r.backend, &events.AsyncMessage{ Type: "message", Message: msg, }); err != nil { @@ -467,7 +468,7 @@ func (r *Room) notifySessionJoined(sessionId api.PublicSessionId) { }, } - if err := r.events.PublishSessionMessage(sessionId, r.backend, &AsyncMessage{ + if err := r.events.PublishSessionMessage(sessionId, r.backend, &events.AsyncMessage{ Type: "message", Message: msg, }); err != nil { @@ -545,7 +546,7 @@ func (r *Room) RemoveSession(session Session) bool { } func (r *Room) publish(message *api.ServerMessage) error { - return r.events.PublishRoomMessage(r.id, r.backend, &AsyncMessage{ + return r.events.PublishRoomMessage(r.id, r.backend, &events.AsyncMessage{ Type: "message", Message: message, }) @@ -1188,7 +1189,7 @@ func (r *Room) publishSwitchTo(message *talk.BackendRoomSwitchToMessageRequest) go func(sessionId api.PublicSessionId) { defer wg.Done() - if err := r.events.PublishSessionMessage(sessionId, r.backend, &AsyncMessage{ + if err := r.events.PublishSessionMessage(sessionId, r.backend, &events.AsyncMessage{ Type: "message", Message: msg, }); err != nil { @@ -1216,7 +1217,7 @@ func (r *Room) publishSwitchTo(message *talk.BackendRoomSwitchToMessageRequest) }, } - if err := r.events.PublishSessionMessage(sessionId, r.backend, &AsyncMessage{ + if err := r.events.PublishSessionMessage(sessionId, r.backend, &events.AsyncMessage{ Type: "message", Message: msg, }); err != nil { @@ -1249,7 +1250,7 @@ func (r *Room) SetTransientData(key string, value any) error { return r.RemoveTransientData(key) } - return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ + return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "transient", @@ -1273,7 +1274,7 @@ func (r *Room) SetTransientDataTTL(key string, value any, ttl time.Duration) err return r.SetTransientData(key, value) } - return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ + return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "transient", @@ -1292,7 +1293,7 @@ func (r *Room) doSetTransientDataTTL(key string, value any, ttl time.Duration) { } func (r *Room) RemoveTransientData(key string) error { - return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &AsyncMessage{ + return r.events.PublishBackendRoomMessage(r.Id(), r.Backend(), &events.AsyncMessage{ Type: "room", Room: &talk.BackendServerRoomRequest{ Type: "transient", diff --git a/server/main.go b/server/main.go index 341f395..7320b77 100644 --- a/server/main.go +++ b/server/main.go @@ -42,6 +42,7 @@ import ( "github.com/gorilla/mux" signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" @@ -185,7 +186,7 @@ func main() { natsUrl = nats.DefaultURL } - events, err := signaling.NewAsyncEvents(stopCtx, natsUrl) + events, err := events.NewAsyncEvents(stopCtx, natsUrl) if err != nil { logger.Fatal("Could not create async events client: ", err) } diff --git a/virtualsession.go b/virtualsession.go index 59c6e8e..bfb85bb 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -29,6 +29,7 @@ import ( "sync/atomic" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -60,7 +61,7 @@ type VirtualSession struct { parseUserData func() (api.StringMap, error) - asyncCh AsyncChannel + asyncCh events.AsyncChannel } func GetVirtualSessionId(session Session, sessionId api.PublicSessionId) api.PublicSessionId { @@ -87,7 +88,7 @@ func NewVirtualSession(session *ClientSession, privateId api.PrivateSessionId, p parseUserData: parseUserData(msg.User), options: msg.Options, - asyncCh: make(AsyncChannel, DefaultAsyncChannelSize), + asyncCh: make(events.AsyncChannel, events.DefaultAsyncChannelSize), } if err := session.events.RegisterSessionListener(publicId, session.Backend(), result); err != nil { @@ -198,7 +199,7 @@ func (s *VirtualSession) LeaveRoom(notify bool) *Room { return room } -func (s *VirtualSession) AsyncChannel() AsyncChannel { +func (s *VirtualSession) AsyncChannel() events.AsyncChannel { return s.asyncCh } @@ -315,7 +316,7 @@ func (s *VirtualSession) Options() *api.AddSessionOptions { } func (s *VirtualSession) processAsyncNatsMessage(msg *nats.Msg) { - var message AsyncMessage + var message events.AsyncMessage if err := nats.Decode(msg, &message); err != nil { s.logger.Printf("Could not decode NATS message %+v: %s", msg, err) return @@ -324,7 +325,7 @@ func (s *VirtualSession) processAsyncNatsMessage(msg *nats.Msg) { s.processAsyncMessage(&message) } -func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { +func (s *VirtualSession) processAsyncMessage(message *events.AsyncMessage) { if message.Type == "message" && message.Message != nil { switch message.Message.Type { case "message": @@ -359,7 +360,7 @@ func (s *VirtualSession) processAsyncMessage(message *AsyncMessage) { return } - s.session.processAsyncMessage(&AsyncMessage{ + s.session.processAsyncMessage(&events.AsyncMessage{ Type: "message", SendTime: message.SendTime, Message: &api.ServerMessage{ From 75f6579efa0e9b5f6eb6c706c862e8b51b8dfab9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 14:56:59 +0100 Subject: [PATCH 427/549] Update generated files. --- .../events/api_easyjson.go | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) rename api_async_easyjson.go => async/events/api_easyjson.go (82%) diff --git a/api_async_easyjson.go b/async/events/api_easyjson.go similarity index 82% rename from api_async_easyjson.go rename to async/events/api_easyjson.go index 45b152b..42552c5 100644 --- a/api_async_easyjson.go +++ b/async/events/api_easyjson.go @@ -1,6 +1,6 @@ // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package signaling +package events import ( json "encoding/json" @@ -19,7 +19,7 @@ var ( _ easyjson.Marshaler ) -func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexer.Lexer, out *SendOfferMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(in *jlexer.Lexer, out *SendOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -69,7 +69,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe in.Consumed() } } -func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwriter.Writer, in SendOfferMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(out *jwriter.Writer, in SendOfferMessage) { out.RawByte('{') first := true _ = first @@ -104,27 +104,27 @@ func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwri // MarshalJSON supports json.Marshaler interface func (v SendOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v SendOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *SendOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *SendOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(l, v) } -func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlexer.Lexer, out *AsyncRoomMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(in *jlexer.Lexer, out *AsyncRoomMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -166,7 +166,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex in.Consumed() } } -func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwriter.Writer, in AsyncRoomMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(out *jwriter.Writer, in AsyncRoomMessage) { out.RawByte('{') first := true _ = first @@ -191,27 +191,27 @@ func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwr // MarshalJSON supports json.Marshaler interface func (v AsyncRoomMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AsyncRoomMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AsyncRoomMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AsyncRoomMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(l, v) } -func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *AsyncMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(in *jlexer.Lexer, out *AsyncMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -338,7 +338,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex in.Consumed() } } -func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in AsyncMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(out *jwriter.Writer, in AsyncMessage) { out.RawByte('{') first := true _ = first @@ -397,23 +397,23 @@ func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwr // MarshalJSON supports json.Marshaler interface func (v AsyncMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AsyncMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AsyncMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AsyncMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(l, v) } From 827de250eae6a6faeb6a61ea62e4943b114a0af8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 15:03:48 +0100 Subject: [PATCH 428/549] Move flags to "internal" package. --- clientsession.go | 3 ++- flags.go => internal/flags.go | 2 +- flags_test.go => internal/flags_test.go | 2 +- mcu_janus_publisher.go | 2 +- virtualsession.go | 5 +++-- 5 files changed, 8 insertions(+), 6 deletions(-) rename flags.go => internal/flags.go (98%) rename flags_test.go => internal/flags_test.go (99%) diff --git a/clientsession.go b/clientsession.go index 68a60e4..1042981 100644 --- a/clientsession.go +++ b/clientsession.go @@ -38,6 +38,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "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" @@ -72,7 +73,7 @@ type ClientSession struct { parseUserData func() (api.StringMap, error) - inCall Flags + inCall internal.Flags // +checklocks:mu supportsPermissions bool // +checklocks:mu diff --git a/flags.go b/internal/flags.go similarity index 98% rename from flags.go rename to internal/flags.go index e089e1e..289bdea 100644 --- a/flags.go +++ b/internal/flags.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 internal import ( "sync/atomic" diff --git a/flags_test.go b/internal/flags_test.go similarity index 99% rename from flags_test.go rename to internal/flags_test.go index de162de..9664955 100644 --- a/flags_test.go +++ b/internal/flags_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 internal import ( "sync" diff --git a/mcu_janus_publisher.go b/mcu_janus_publisher.go index fcf7801..cd642da 100644 --- a/mcu_janus_publisher.go +++ b/mcu_janus_publisher.go @@ -52,7 +52,7 @@ type mcuJanusPublisher struct { id api.PublicSessionId settings NewPublisherSettings stats publisherStatsCounter - sdpFlags Flags + sdpFlags internal.Flags sdpReady *internal.Closer offerSdp atomic.Pointer[sdp.SessionDescription] answerSdp atomic.Pointer[sdp.SessionDescription] diff --git a/virtualsession.go b/virtualsession.go index bfb85bb..b8e6cfa 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -30,6 +30,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "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" @@ -55,8 +56,8 @@ type VirtualSession struct { sessionId api.PublicSessionId userId string userData json.RawMessage - inCall Flags - flags Flags + inCall internal.Flags + flags internal.Flags options *api.AddSessionOptions parseUserData func() (api.StringMap, error) From 221b6adb8e6d16092597924c2dc3422e119b6703 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 16 Dec 2025 15:41:02 +0100 Subject: [PATCH 429/549] codecov: Ignore easyjson stubs in any folders. --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index d790054..c479bb2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -10,6 +10,7 @@ comment: ignore: - "*_easyjson.go" + - "**/*_easyjson.go" - "*.pb.go" component_management: From f8da2cb0e50304473def431eeb1ace87868bd4c3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 09:16:30 +0100 Subject: [PATCH 430/549] Fix flaky "TestVirtualSessionCustomInCall" for cases where update is sent after joined. --- virtualsession_test.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/virtualsession_test.go b/virtualsession_test.go index b69a8cf..06c1ef1 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -459,7 +459,17 @@ func TestVirtualSessionCustomInCall(t *testing.T) { roomMsg = MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) + // In some cases, the participants update event is triggered a bit after the joined + // event. If this happens, the "client" will also receive an additional update + // event after the joined of the internal client. + var expectUpdate bool if _, additional, ok := clientInternal.RunUntilJoinedAndReturn(ctx, helloInternal.Hello, hello.Hello); ok { + if len(additional) == 0 { + if msg, ok := clientInternal.RunUntilMessage(ctx); ok { + additional = append(additional, msg) + } + expectUpdate = true + } if assert.Len(additional, 1) && assert.Equal("event", additional[0].Type) { assert.Equal("participants", additional[0].Event.Target) assert.Equal("update", additional[0].Event.Type) @@ -467,7 +477,22 @@ func TestVirtualSessionCustomInCall(t *testing.T) { assert.EqualValues(0, additional[0].Event.Update.Users[0]["inCall"]) } } - client.RunUntilJoined(ctx, helloInternal.Hello, hello.Hello) + if _, additional, ok := client.RunUntilJoinedAndReturn(ctx, helloInternal.Hello, hello.Hello); ok { + if expectUpdate { + if len(additional) == 0 { + if msg, ok := client.RunUntilMessage(ctx); ok { + additional = append(additional, msg) + } + } + + if assert.Len(additional, 1) && assert.Equal("event", additional[0].Type) { + assert.Equal("participants", additional[0].Event.Target) + assert.Equal("update", additional[0].Event.Type) + assert.EqualValues(helloInternal.Hello.SessionId, additional[0].Event.Update.Users[0]["sessionId"]) + assert.EqualValues(0, additional[0].Event.Update.Users[0]["inCall"]) + } + } + } internalSessionId := api.PublicSessionId("session1") userId := "user1" From 64152f804b652336475c3264e3f4c336b90493fc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 09:34:55 +0100 Subject: [PATCH 431/549] Wait for initialization code to complete before stopping. --- backend_storage_etcd.go | 23 ++++++++++++++++++++++- proxy_config_etcd.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/backend_storage_etcd.go b/backend_storage_etcd.go index 03a04ac..de57479 100644 --- a/backend_storage_etcd.go +++ b/backend_storage_etcd.go @@ -27,6 +27,8 @@ import ( "errors" "net/url" "slices" + "sync" + "sync/atomic" "time" "github.com/dlintw/goconf" @@ -46,9 +48,11 @@ type backendStorageEtcd struct { keyPrefix string keyInfos map[string]*etcd.BackendInformationEtcd + initializing atomic.Bool initializedCtx context.Context initializedFunc context.CancelFunc wakeupChanForTesting chan struct{} + runningDone sync.WaitGroup closeCtx context.Context closeFunc context.CancelFunc @@ -107,7 +111,17 @@ func (s *backendStorageEtcd) wakeupForTesting() { } func (s *backendStorageEtcd) EtcdClientCreated(client etcd.Client) { + s.initializing.Store(true) + if s.closeCtx.Err() != nil { + // Stopped before etcd client was connected. + s.initializedFunc() + return + } + + s.runningDone.Add(1) go func() { + defer s.runningDone.Done() + defer s.initializedFunc() if err := client.WaitForConnection(s.closeCtx); err != nil { if errors.Is(err, context.Canceled) { return @@ -284,8 +298,15 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client etcd.Client, key string, prev } func (s *backendStorageEtcd) Close() { - s.etcdClient.RemoveListener(s) + firstStop := s.closeCtx.Err() == nil s.closeFunc() + s.etcdClient.RemoveListener(s) + if firstStop { + if s.initializing.Load() { + <-s.initializedCtx.Done() + } + s.runningDone.Wait() + } } func (s *backendStorageEtcd) Reload(config *goconf.ConfigFile) { diff --git a/proxy_config_etcd.go b/proxy_config_etcd.go index 69e7b43..87c4930 100644 --- a/proxy_config_etcd.go +++ b/proxy_config_etcd.go @@ -26,6 +26,7 @@ import ( "encoding/json" "errors" "sync" + "sync/atomic" "time" "github.com/dlintw/goconf" @@ -50,6 +51,11 @@ type proxyConfigEtcd struct { closeCtx context.Context closeFunc context.CancelFunc + + initializing atomic.Bool + initializedCtx context.Context + initializedFunc context.CancelFunc + runningDone sync.WaitGroup } func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, proxy McuProxy) (ProxyConfig, error) { @@ -57,6 +63,7 @@ func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient return nil, errors.New("no etcd endpoints configured") } + initializedCtx, initializedFunc := context.WithCancel(context.Background()) closeCtx, closeFunc := context.WithCancel(context.Background()) result := &proxyConfigEtcd{ @@ -69,6 +76,9 @@ func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient closeCtx: closeCtx, closeFunc: closeFunc, + + initializedCtx: initializedCtx, + initializedFunc: initializedFunc, } if err := result.configure(config, false); err != nil { return nil, err @@ -97,12 +107,29 @@ func (p *proxyConfigEtcd) Reload(config *goconf.ConfigFile) error { } func (p *proxyConfigEtcd) Stop() { - p.client.RemoveListener(p) + firstStop := p.closeCtx.Err() == nil p.closeFunc() + p.client.RemoveListener(p) + if firstStop { + if p.initializing.Load() { + <-p.initializedCtx.Done() + } + p.runningDone.Wait() + } } func (p *proxyConfigEtcd) EtcdClientCreated(client etcd.Client) { + p.initializing.Store(true) + if p.closeCtx.Err() != nil { + // Stopped before etcd client was connected. + p.initializedFunc() + return + } + + p.runningDone.Add(1) go func() { + defer p.runningDone.Done() + defer p.initializedFunc() if err := client.WaitForConnection(p.closeCtx); err != nil { if errors.Is(err, context.Canceled) { return @@ -138,6 +165,7 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client etcd.Client) { nextRevision = response.Header.Revision + 1 break } + p.initializedFunc() prevRevision := nextRevision backoff.Reset() From 80bdeb79fcd5db0c1424bbb2ef211aa2428893f0 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 10:05:24 +0100 Subject: [PATCH 432/549] Fix flaky "TestDuplicateVirtualSessions" for mixed ordering of joined/update. --- hub_test.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/hub_test.go b/hub_test.go index 9ee4129..6f1ff9d 100644 --- a/hub_test.go +++ b/hub_test.go @@ -4761,14 +4761,21 @@ func TestDuplicateVirtualSessions(t *testing.T) { MustSucceed2(t, client2.JoinRoom, ctx, roomId) - client1.RunUntilJoined(ctx, hello2.Hello) + if _, additional, ok := client1.RunUntilJoinedAndReturn(ctx, hello2.Hello); ok { + // TODO: Should not receive participants update before joined event. + if len(additional) == 0 { + if msg, ok := client1.RunUntilMessage(ctx); ok { + additional = append(additional, msg) + } + } - if msg, ok := client1.RunUntilMessage(ctx); ok { - if msg, ok := checkMessageParticipantsInCall(t, msg); ok { - if assert.Len(msg.Users, 1) { - assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) - assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) - assert.EqualValues(3, msg.Users[0]["inCall"], "%+v", msg) + if assert.Len(additional, 1) { + if msg, ok := checkMessageParticipantsInCall(t, additional[0]); ok { + if assert.Len(msg.Users, 1) { + assert.Equal(true, msg.Users[0]["internal"], "%+v", msg) + assert.EqualValues(hello2.Hello.SessionId, msg.Users[0]["sessionId"], "%+v", msg) + assert.EqualValues(3, msg.Users[0]["inCall"], "%+v", msg) + } } } } From f517e554fea5c82c97ce4f455a0c47cd9ec42862 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 11:22:41 +0100 Subject: [PATCH 433/549] Send updated load synchronously, assert on errors while waiting. --- mcu_proxy_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go index 71898fa..a5ef519 100644 --- a/mcu_proxy_test.go +++ b/mcu_proxy_test.go @@ -717,7 +717,7 @@ func (h *TestProxyServerHandler) updateLoad(delta int64) { msg := h.getLoadMessage(load) for _, c := range h.clients { - go c.sendMessage(msg) + c.sendMessage(msg) } } @@ -1292,7 +1292,7 @@ func Test_ProxyPublisherBandwidth(t *testing.T) { } // Wait until proxy has been updated - for ctx.Err() == nil { + for assert.NoError(t, ctx.Err()) { mcu.connectionsMu.RLock() connections := mcu.connections mcu.connectionsMu.RUnlock() @@ -1364,7 +1364,7 @@ func Test_ProxyPublisherBandwidthOverload(t *testing.T) { } // Wait until proxy has been updated - for ctx.Err() == nil { + for assert.NoError(t, ctx.Err()) { mcu.connectionsMu.RLock() connections := mcu.connections mcu.connectionsMu.RUnlock() @@ -1660,7 +1660,7 @@ func Test_ProxySubscriberBandwidth(t *testing.T) { serverDE.UpdateBandwidth(0, 100) // Wait until proxy has been updated - for ctx.Err() == nil { + for assert.NoError(t, ctx.Err()) { mcu.connectionsMu.RLock() connections := mcu.connections mcu.connectionsMu.RUnlock() @@ -1724,7 +1724,7 @@ func Test_ProxySubscriberBandwidthOverload(t *testing.T) { serverUS.UpdateBandwidth(0, 102) // Wait until proxy has been updated - for ctx.Err() == nil { + for assert.NoError(t, ctx.Err()) { mcu.connectionsMu.RLock() connections := mcu.connections mcu.connectionsMu.RUnlock() From e3e096332717766525ae383c47827c6dbf8d60be Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 17:34:37 +0100 Subject: [PATCH 434/549] Filter duplicate "flags" events. --- clientsession.go | 50 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/clientsession.go b/clientsession.go index 1042981..aaa65d7 100644 --- a/clientsession.go +++ b/clientsession.go @@ -113,9 +113,11 @@ type ClientSession struct { // +checklocks:mu virtualSessions map[*VirtualSession]bool - seenJoinedLock sync.Mutex - // +checklocks:seenJoinedLock + filterDuplicateLock sync.Mutex + // +checklocks:filterDuplicateLock seenJoinedEvents map[api.PublicSessionId]bool + // +checklocks:filterDuplicateLock + seenFlags map[api.PublicSessionId]uint32 responseHandlersLock sync.Mutex // +checklocks:responseHandlersLock @@ -339,9 +341,10 @@ func (s *ClientSession) onRoomSet(hasRoom bool) { s.roomJoinTime.Store(0) } - s.seenJoinedLock.Lock() - defer s.seenJoinedLock.Unlock() + s.filterDuplicateLock.Lock() + defer s.filterDuplicateLock.Unlock() s.seenJoinedEvents = nil + s.seenFlags = nil } func (s *ClientSession) IsInRoom(id string) bool { @@ -1254,8 +1257,8 @@ func filterDisplayNames(events []api.EventServerMessageSessionEntry) []api.Event } func (s *ClientSession) filterDuplicateJoin(entries []api.EventServerMessageSessionEntry) []api.EventServerMessageSessionEntry { - s.seenJoinedLock.Lock() - defer s.seenJoinedLock.Unlock() + s.filterDuplicateLock.Lock() + defer s.filterDuplicateLock.Unlock() // Due to the asynchronous events, a session might received a "Joined" event // for the same (other) session twice, so filter these out on a per-session @@ -1276,12 +1279,36 @@ func (s *ClientSession) filterDuplicateJoin(entries []api.EventServerMessageSess return result } +func (s *ClientSession) filterDuplicateFlags(message *api.RoomFlagsServerMessage) bool { + if message == nil { + return true + } + + s.filterDuplicateLock.Lock() + defer s.filterDuplicateLock.Unlock() + + // Due to the asynchronous events, a session might received a "flags" event + // for the same (other) session twice, so filter these out on a per-session + // level. + if prev, found := s.seenFlags[message.SessionId]; found && prev == message.Flags { + s.logger.Printf("Session %s got duplicate flags event for %s, ignoring", s.publicId, message.SessionId) + return true + } + + if s.seenFlags == nil { + s.seenFlags = make(map[api.PublicSessionId]uint32) + } + s.seenFlags[message.SessionId] = message.Flags + return false +} + func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMessage { switch message.Type { case "event": switch message.Event.Target { case "participants": - if message.Event.Type == "update" { + switch message.Event.Type { + case "update": m := message.Event.Update users := make(map[any]bool) for _, entry := range m.Users { @@ -1296,6 +1323,10 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes // TODO(jojo): Only send all users if current session id has // changed its "inCall" flag to true. m.Changed = nil + case "flags": + if s.filterDuplicateFlags(message.Event.Flags) { + return nil + } } case "room": switch message.Event.Type { @@ -1335,11 +1366,12 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes } } case "leave": - s.seenJoinedLock.Lock() - defer s.seenJoinedLock.Unlock() + s.filterDuplicateLock.Lock() + defer s.filterDuplicateLock.Unlock() for _, e := range message.Event.Leave { delete(s.seenJoinedEvents, e) + delete(s.seenFlags, e) } case "message": if message.Event.Message == nil || len(message.Event.Message.Data) == 0 { From df678831d8c821dcbc308b6b19b47575c6ce10d2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 17:35:07 +0100 Subject: [PATCH 435/549] Process only single "joined" event instead of discarding any received. --- virtualsession_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/virtualsession_test.go b/virtualsession_test.go index 06c1ef1..6708943 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -63,9 +63,7 @@ func TestVirtualSession(t *testing.T) { roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) - - // Ignore "join" events. - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello.Hello) internalSessionId := api.PublicSessionId("session1") userId := "user1" From b82a26dadbdc6930b8d93afbf1d88ceb8633e1b3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 20:07:33 +0100 Subject: [PATCH 436/549] Don't log initial empty transient data. --- room.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/room.go b/room.go index 775d29e..da84c91 100644 --- a/room.go +++ b/room.go @@ -1331,6 +1331,8 @@ func (r *Room) fetchInitialTransientData() { if err != nil { r.logger.Printf("Received error while getting transient data for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) return + } else if len(data) == 0 { + return } r.logger.Printf("Received initial transient data %+v from %s", data, c.Target()) From 85dc4146270c164675af700a503d83184304db7f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 17 Dec 2025 20:53:57 +0100 Subject: [PATCH 437/549] Fix race in flaky "DoTestSwitchToOne" / "DoTestSwitchToMultiple". --- hub_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hub_test.go b/hub_test.go index 6f1ff9d..706a871 100644 --- a/hub_test.go +++ b/hub_test.go @@ -5032,11 +5032,17 @@ func DoTestSwitchToOne(t *testing.T, details api.StringMap) { roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) + // TODO: If we join both clients immediately and then afterwards wait for both with + // "WaitForUsersJoined", the clustered test sometimes fails under load because the + // second session receives the remote "joined" event before joining the room itself. + client1.RunUntilJoined(ctx, hello1.Hello) + roomSessionId2 := api.RoomSessionId("roomsession2") roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + client1.RunUntilJoined(ctx, hello2.Hello) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) roomId2 := "test-room-2" var sessions json.RawMessage @@ -5132,11 +5138,17 @@ func DoTestSwitchToMultiple(t *testing.T, details1 api.StringMap, details2 api.S roomMsg := MustSucceed3(t, client1.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId1) require.Equal(roomId1, roomMsg.Room.RoomId) + // TODO: If we join both clients immediately and then afterwards wait for both with + // "WaitForUsersJoined", the clustered test sometimes fails under load because the + // second session receives the remote "joined" event before joining the room itself. + client1.RunUntilJoined(ctx, hello1.Hello) + roomSessionId2 := api.RoomSessionId("roomsession2") roomMsg = MustSucceed3(t, client2.JoinRoomWithRoomSession, ctx, roomId1, roomSessionId2) require.Equal(roomId1, roomMsg.Room.RoomId) - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + client1.RunUntilJoined(ctx, hello2.Hello) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) roomId2 := "test-room-2" var sessions json.RawMessage From 88bb94bd2ae8cccdc8cfa3002ba3e752932b53e6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 08:50:13 +0100 Subject: [PATCH 438/549] Allow transient data with both "initial" and "set" in TestDialoutStatus. --- hub_test.go | 4 ++-- testclient_test.go | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/hub_test.go b/hub_test.go index 706a871..4c71101 100644 --- a/hub_test.go +++ b/hub_test.go @@ -5339,10 +5339,10 @@ func TestDialoutStatus(t *testing.T) { key := "callstatus_" + callId if msg, ok := client.RunUntilMessage(ctx); ok { - checkMessageTransientSet(t, msg, key, api.StringMap{ + checkMessageTransientInitialOrSet(t, msg, key, api.StringMap{ "callid": callId, "status": "accepted", - }, nil) + }) } require.NoError(internalClient.SendInternalDialout(&api.DialoutInternalClientMessage{ diff --git a/testclient_test.go b/testclient_test.go index 3b01532..dbf60a5 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -1101,6 +1101,15 @@ func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string return true } +func checkMessageTransientInitialOrSet(t *testing.T, message *api.ServerMessage, key string, value any) bool { + assert := assert.New(t) + return checkMessageType(t, message, "transient") && + assert.True(message.TransientData.Type == "initial" || message.TransientData.Type == "set", "invalid message type in %+v", message) && + assert.Equal(key, message.TransientData.Key, "invalid key in %+v", message) && + assert.EqualValues(value, message.TransientData.Value, "invalid value in %+v", message) && + assert.Nil(message.TransientData.OldValue, "invalid old value in %+v", message) +} + func checkMessageTransientSet(t *testing.T, message *api.ServerMessage, key string, value any, oldValue any) bool { assert := assert.New(t) return checkMessageType(t, message, "transient") && From b2934836a9f1fbf4b44a6f2a067bdf35e6fa79fc Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 10:32:36 +0100 Subject: [PATCH 439/549] Move backend configuration code to "talk" package. --- backend_client.go | 4 +- grpc_client_test.go | 32 ++++++--------- .../backend_configuration.go | 27 +++++++------ .../backend_configuration_stats_prometheus.go | 2 +- .../backend_configuration_test.go | 30 +++++++------- .../backend_storage_etcd.go | 26 +++++++------ .../backend_storage_etcd_test.go | 2 +- .../backend_storage_static.go | 33 ++++++++-------- test/wakeup_channel.go | 32 +++++++++++++++ test/wakeup_channel_test.go | 39 +++++++++++++++++++ 10 files changed, 147 insertions(+), 80 deletions(-) rename backend_configuration.go => talk/backend_configuration.go (86%) rename backend_configuration_stats_prometheus.go => talk/backend_configuration_stats_prometheus.go (98%) rename backend_configuration_test.go => talk/backend_configuration_test.go (98%) rename backend_storage_etcd.go => talk/backend_storage_etcd.go (93%) rename backend_storage_etcd_test.go => talk/backend_storage_etcd_test.go (99%) rename backend_storage_static.go => talk/backend_storage_static.go (92%) create mode 100644 test/wakeup_channel.go create mode 100644 test/wakeup_channel_test.go diff --git a/backend_client.go b/backend_client.go index 7c92ad4..8f33d3f 100644 --- a/backend_client.go +++ b/backend_client.go @@ -53,7 +53,7 @@ func init() { type BackendClient struct { hub *Hub version string - backends *BackendConfiguration + backends *talk.BackendConfiguration pool *pool.HttpClientPool capabilities *talk.Capabilities @@ -62,7 +62,7 @@ type BackendClient struct { func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient etcd.Client) (*BackendClient, error) { logger := log.LoggerFromContext(ctx) - backends, err := NewBackendConfiguration(logger, config, etcdClient) + backends, err := talk.NewBackendConfiguration(logger, config, etcdClient) if err != nil { return nil, err } diff --git a/grpc_client_test.go b/grpc_client_test.go index 2a2a3ee..f315327 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -94,16 +94,6 @@ func NewGrpcClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, loo return NewGrpcClientsForTestWithConfig(t, config, etcdClient, lookup) } -func drainWakeupChannel(ch <-chan struct{}) { - for { - select { - case <-ch: - default: - return - } - } -} - func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { t.Helper() @@ -151,7 +141,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { assert.Empty(client.GetClients()) - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) _, addr1 := NewGrpcServerForTest(t) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) waitForEvent(ctx, t, ch) @@ -159,7 +149,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { assert.Equal(addr1, clients[0].Target()) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) _, addr2 := NewGrpcServerForTest(t) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) waitForEvent(ctx, t, ch) @@ -168,14 +158,14 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { assert.Equal(addr2, clients[1].Target()) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.DeleteValue("/grpctargets/one") waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr2, clients[0].Target()) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) _, addr3 := NewGrpcServerForTest(t) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr3+"\"}")) waitForEvent(ctx, t, ch) @@ -198,7 +188,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { assert.Empty(client.GetClients()) - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) _, addr1 := NewGrpcServerForTest(t) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) waitForEvent(ctx, t, ch) @@ -206,7 +196,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { assert.Equal(addr1, clients[0].Target()) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) server2, addr2 := NewGrpcServerForTest(t) server2.serverId = GrpcServerId embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) @@ -216,7 +206,7 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { assert.Equal(addr1, clients[0].Target()) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.DeleteValue("/grpctargets/two") waitForEvent(ctx, t, ch) if clients := client.GetClients(); assert.Len(clients, 1) { @@ -248,7 +238,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest require.NoError(err) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) dnsMonitor.CheckHostnames() if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(targetWithIp1, clients[0].Target()) @@ -256,7 +246,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest } lookup.Set("testgrpc", []net.IP{ip1, ip2}) - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) dnsMonitor.CheckHostnames() waitForEvent(ctx, t, ch) @@ -268,7 +258,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest } lookup.Set("testgrpc", []net.IP{ip2}) - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) dnsMonitor.CheckHostnames() waitForEvent(ctx, t, ch) @@ -299,7 +289,7 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { assert.Empty(client.GetClients()) lookup.Set("testgrpc", []net.IP{ip1}) - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) dnsMonitor.CheckHostnames() waitForEvent(testCtx, t, ch) diff --git a/backend_configuration.go b/talk/backend_configuration.go similarity index 86% rename from backend_configuration.go rename to talk/backend_configuration.go index 6329930..4234a3e 100644 --- a/backend_configuration.go +++ b/talk/backend_configuration.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 ( "fmt" @@ -33,7 +33,6 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( @@ -47,9 +46,9 @@ type BackendStorage interface { Close() Reload(cfg *goconf.ConfigFile) - GetCompatBackend() *talk.Backend - GetBackend(u *url.URL) *talk.Backend - GetBackends() []*talk.Backend + GetCompatBackend() *Backend + GetBackend(u *url.URL) *Backend + GetBackends() []*Backend } type BackendStorageStats interface { @@ -62,29 +61,29 @@ type BackendStorageStats interface { type backendStorageCommon struct { mu sync.RWMutex // +checklocks:mu - backends map[string][]*talk.Backend + backends map[string][]*Backend stats BackendStorageStats // +checklocksignore: Only written to from constructor } -func (s *backendStorageCommon) GetBackends() []*talk.Backend { +func (s *backendStorageCommon) GetBackends() []*Backend { s.mu.RLock() defer s.mu.RUnlock() - var result []*talk.Backend + var result []*Backend for _, entries := range s.backends { result = append(result, entries...) } - slices.SortFunc(result, func(a, b *talk.Backend) int { + slices.SortFunc(result, func(a, b *Backend) int { return strings.Compare(a.Id(), b.Id()) }) - result = slices.CompactFunc(result, func(a, b *talk.Backend) bool { + result = slices.CompactFunc(result, func(a, b *Backend) bool { return a.Id() == b.Id() }) return result } -func (s *backendStorageCommon) getBackendLocked(u *url.URL) *talk.Backend { +func (s *backendStorageCommon) getBackendLocked(u *url.URL) *Backend { s.mu.RLock() defer s.mu.RUnlock() @@ -178,16 +177,16 @@ func (b *BackendConfiguration) Reload(config *goconf.ConfigFile) { b.storage.Reload(config) } -func (b *BackendConfiguration) GetCompatBackend() *talk.Backend { +func (b *BackendConfiguration) GetCompatBackend() *Backend { return b.storage.GetCompatBackend() } -func (b *BackendConfiguration) GetBackend(u *url.URL) *talk.Backend { +func (b *BackendConfiguration) GetBackend(u *url.URL) *Backend { u, _ = internal.CanonicalizeUrl(u) return b.storage.GetBackend(u) } -func (b *BackendConfiguration) GetBackends() []*talk.Backend { +func (b *BackendConfiguration) GetBackends() []*Backend { return b.storage.GetBackends() } diff --git a/backend_configuration_stats_prometheus.go b/talk/backend_configuration_stats_prometheus.go similarity index 98% rename from backend_configuration_stats_prometheus.go rename to talk/backend_configuration_stats_prometheus.go index b1e8bf7..57bc049 100644 --- a/backend_configuration_stats_prometheus.go +++ b/talk/backend_configuration_stats_prometheus.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 ( "github.com/prometheus/client_golang/prometheus" diff --git a/backend_configuration_test.go b/talk/backend_configuration_test.go similarity index 98% rename from backend_configuration_test.go rename to talk/backend_configuration_test.go index a452df4..47b8fb9 100644 --- a/backend_configuration_test.go +++ b/talk/backend_configuration_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" @@ -35,7 +35,11 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/test" +) + +var ( + testBackendSecret = []byte("secret") ) func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) { @@ -488,9 +492,9 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { } } -func sortBackends(backends []*talk.Backend) []*talk.Backend { +func sortBackends(backends []*Backend) []*Backend { result := slices.Clone(backends) - slices.SortFunc(result, func(a, b *talk.Backend) int { + slices.SortFunc(result, func(a, b *Backend) int { return strings.Compare(a.Id(), b.Id()) }) return result @@ -543,7 +547,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { } } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.SetValue("/backends/1_one", []byte("{\"url\":\""+url1+"\",\"secret\":\""+secret1+"\"}")) <-ch assert.Equal(1, stats.value) @@ -558,7 +562,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { url2 := "https://domain1.invalid/bar" secret2 := string(testBackendSecret) + "-backend2" - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.SetValue("/backends/2_two", []byte("{\"url\":\""+url2+"\",\"secret\":\""+secret2+"\"}")) <-ch assert.Equal(2, stats.value) @@ -577,7 +581,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { url3 := "https://domain2.invalid/foo" secret3 := string(testBackendSecret) + "-backend3" - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.SetValue("/backends/3_three", []byte("{\"url\":\""+url3+"\",\"secret\":\""+secret3+"\"}")) <-ch assert.Equal(3, stats.value) @@ -597,7 +601,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { } } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.DeleteValue("/backends/1_one") <-ch assert.Equal(2, stats.value) @@ -608,7 +612,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { assert.Equal(secret3, string(backends[1].Secret())) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.DeleteValue("/backends/2_two") <-ch assert.Equal(1, stats.value) @@ -796,7 +800,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { url2 := "https://domain1.invalid/bar" - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.SetValue("/backends/1_one", []byte("{\"urls\":[\""+url1+"\",\""+url2+"\"],\"secret\":\""+secret1+"\"}")) <-ch assert.Equal(1, stats.value) @@ -816,7 +820,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { url4 := "https://domain3.invalid/foo" - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.SetValue("/backends/3_three", []byte("{\"urls\":[\""+url3+"\",\""+url4+"\"],\"secret\":\""+secret3+"\"}")) <-ch assert.Equal(2, stats.value) @@ -836,7 +840,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { } } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.DeleteValue("/backends/1_one") <-ch assert.Equal(1, stats.value) @@ -845,7 +849,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { assert.Equal(secret3, string(backends[0].Secret())) } - drainWakeupChannel(ch) + test.DrainWakeupChannel(ch) embedEtcd.DeleteValue("/backends/3_three") <-ch diff --git a/backend_storage_etcd.go b/talk/backend_storage_etcd.go similarity index 93% rename from backend_storage_etcd.go rename to talk/backend_storage_etcd.go index de57479..f17f7f5 100644 --- a/backend_storage_etcd.go +++ b/talk/backend_storage_etcd.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" @@ -37,7 +37,11 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/talk" +) + +const ( + initialWaitDelay = time.Second + maxWaitDelay = 8 * time.Second ) type backendStorageEtcd struct { @@ -72,7 +76,7 @@ func NewBackendStorageEtcd(logger log.Logger, config *goconf.ConfigFile, etcdCli closeCtx, closeFunc := context.WithCancel(context.Background()) result := &backendStorageEtcd{ backendStorageCommon: backendStorageCommon{ - backends: make(map[string][]*talk.Backend), + backends: make(map[string][]*Backend), stats: stats, }, logger: logger, @@ -199,7 +203,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client etcd.Client, key string, data return } - backend := talk.NewBackendFromEtcd(key, &info) + backend := NewBackendFromEtcd(key, &info) s.mu.Lock() defer s.mu.Unlock() @@ -212,7 +216,7 @@ func (s *backendStorageEtcd) EtcdKeyUpdated(client etcd.Client, key string, data if !found { // Simple case, first backend for this host s.logger.Printf("Added backend %s (from %s)", info.Urls[idx], key) - s.backends[host] = []*talk.Backend{backend} + s.backends[host] = []*Backend{backend} added = true continue } @@ -252,14 +256,14 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client etcd.Client, key string, prev } delete(s.keyInfos, key) - var deleted map[string][]*talk.Backend + var deleted map[string][]*Backend seen := make(map[string]bool) for idx, u := range info.ParsedUrls { host := u.Host entries, found := s.backends[host] if !found { if d, ok := deleted[host]; ok { - if slices.ContainsFunc(d, func(b *talk.Backend) bool { + if slices.ContainsFunc(d, func(b *Backend) bool { return slices.Contains(b.Urls(), u.String()) }) { s.logger.Printf("Removing backend %s (from %s)", info.Urls[idx], key) @@ -269,12 +273,12 @@ func (s *backendStorageEtcd) EtcdKeyDeleted(client etcd.Client, key string, prev } s.logger.Printf("Removing backend %s (from %s)", info.Urls[idx], key) - newEntries := make([]*talk.Backend, 0, len(entries)-1) + newEntries := make([]*Backend, 0, len(entries)-1) for _, entry := range entries { if entry.Id() == key { if len(info.ParsedUrls) > 1 { if deleted == nil { - deleted = make(map[string][]*talk.Backend) + deleted = make(map[string][]*Backend) } deleted[host] = append(deleted[host], entry) } @@ -313,11 +317,11 @@ func (s *backendStorageEtcd) Reload(config *goconf.ConfigFile) { // Backend updates are processed through etcd. } -func (s *backendStorageEtcd) GetCompatBackend() *talk.Backend { +func (s *backendStorageEtcd) GetCompatBackend() *Backend { return nil } -func (s *backendStorageEtcd) GetBackend(u *url.URL) *talk.Backend { +func (s *backendStorageEtcd) GetBackend(u *url.URL) *Backend { s.mu.RLock() defer s.mu.RUnlock() diff --git a/backend_storage_etcd_test.go b/talk/backend_storage_etcd_test.go similarity index 99% rename from backend_storage_etcd_test.go rename to talk/backend_storage_etcd_test.go index 0ff8efe..de8f310 100644 --- a/backend_storage_etcd_test.go +++ b/talk/backend_storage_etcd_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 ( "testing" diff --git a/backend_storage_static.go b/talk/backend_storage_static.go similarity index 92% rename from backend_storage_static.go rename to talk/backend_storage_static.go index ea2e89a..033faf1 100644 --- a/backend_storage_static.go +++ b/talk/backend_storage_static.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 ( "net/url" @@ -31,31 +31,30 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type backendStorageStatic struct { backendStorageCommon logger log.Logger - backendsById map[string]*talk.Backend + backendsById map[string]*Backend // Deprecated allowAll bool commonSecret []byte - compatBackend *talk.Backend + compatBackend *Backend } func NewBackendStorageStatic(logger log.Logger, cfg *goconf.ConfigFile, stats BackendStorageStats) (BackendStorage, error) { allowAll, _ := cfg.GetBool("backend", "allowall") commonSecret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret") - backends := make(map[string][]*talk.Backend) - backendsById := make(map[string]*talk.Backend) - var compatBackend *talk.Backend + backends := make(map[string][]*Backend) + backendsById := make(map[string]*Backend) + var compatBackend *Backend numBackends := 0 if allowAll { logger.Println("WARNING: All backend hostnames are allowed, only use for development!") - compatBackend = talk.NewCompatBackend(cfg) + compatBackend = NewCompatBackend(cfg) if sessionLimit := compatBackend.Limit(); sessionLimit > 0 { logger.Printf("Allow a maximum of %d sessions", sessionLimit) } @@ -63,7 +62,7 @@ func NewBackendStorageStatic(logger log.Logger, cfg *goconf.ConfigFile, stats Ba backendsById[compatBackend.Id()] = compatBackend numBackends++ } else if backendIds, _ := cfg.GetString("backend", "backends"); backendIds != "" { - added := make(map[string]*talk.Backend) + added := make(map[string]*Backend) for host, configuredBackends := range getConfiguredHosts(logger, backendIds, cfg, commonSecret) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { @@ -94,11 +93,11 @@ func NewBackendStorageStatic(logger log.Logger, cfg *goconf.ConfigFile, stats Ba if len(allowMap) == 0 { logger.Println("WARNING: No backend hostnames are allowed, check your configuration!") } else { - compatBackend = talk.NewCompatBackend(cfg) + compatBackend = NewCompatBackend(cfg) hosts := make([]string, 0, len(allowMap)) for host := range allowMap { hosts = append(hosts, host) - backends[host] = []*talk.Backend{compatBackend} + backends[host] = []*Backend{compatBackend} } if len(hosts) > 1 { logger.Println("WARNING: Using deprecated backend configuration. Please migrate the \"allowed\" setting to the new \"backends\" configuration.") @@ -171,7 +170,7 @@ const ( ) // +checklocks:s.mu -func (s *backendStorageStatic) UpsertHost(host string, backends []*talk.Backend, seen map[string]seenState) { +func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen map[string]seenState) { for existingIndex, existingBackend := range s.backends[host] { found := false index := 0 @@ -252,8 +251,8 @@ func getConfiguredBackendIDs(backendIds string) (ids []string) { return ids } -func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.ConfigFile, commonSecret string) (hosts map[string][]*talk.Backend) { - hosts = make(map[string][]*talk.Backend) +func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.ConfigFile, commonSecret string) (hosts map[string][]*Backend) { + hosts = make(map[string][]*Backend) seenUrls := make(map[string]string) for _, id := range getConfiguredBackendIDs(backendIds) { var urls []string @@ -271,7 +270,7 @@ func getConfiguredHosts(logger log.Logger, backendIds string, cfg *goconf.Config continue } - backend, err := talk.NewBackendFromConfig(logger, id, cfg, commonSecret) + backend, err := NewBackendFromConfig(logger, id, cfg, commonSecret) if err != nil { logger.Printf("%s", err) continue @@ -351,14 +350,14 @@ func (s *backendStorageStatic) Reload(cfg *goconf.ConfigFile) { } } -func (s *backendStorageStatic) GetCompatBackend() *talk.Backend { +func (s *backendStorageStatic) GetCompatBackend() *Backend { s.mu.RLock() defer s.mu.RUnlock() return s.compatBackend } -func (s *backendStorageStatic) GetBackend(u *url.URL) *talk.Backend { +func (s *backendStorageStatic) GetBackend(u *url.URL) *Backend { s.mu.RLock() defer s.mu.RUnlock() diff --git a/test/wakeup_channel.go b/test/wakeup_channel.go new file mode 100644 index 0000000..39476be --- /dev/null +++ b/test/wakeup_channel.go @@ -0,0 +1,32 @@ +/** + * 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 test + +func DrainWakeupChannel(ch <-chan struct{}) { + for { + select { + case <-ch: + default: + return + } + } +} diff --git a/test/wakeup_channel_test.go b/test/wakeup_channel_test.go new file mode 100644 index 0000000..12dbc6a --- /dev/null +++ b/test/wakeup_channel_test.go @@ -0,0 +1,39 @@ +/** + * 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 test + +import ( + "testing" +) + +func TestDrainWakeupChannel(t *testing.T) { + t.Parallel() + + ch := make(chan struct{}, 2) + ch <- struct{}{} + ch <- struct{}{} + + DrainWakeupChannel(ch) + + ch <- struct{}{} + ch <- struct{}{} +} From 348e7b3360db321134fc5e0765eed767cd0c55fe Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 10:52:05 +0100 Subject: [PATCH 440/549] Store time when join room was successfull in session, not when it ended. --- clientsession.go | 12 ++++++------ hub.go | 11 +++++++---- roomsessions_test.go | 3 ++- session.go | 3 ++- virtualsession.go | 5 +++-- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/clientsession.go b/clientsession.go index aaa65d7..b55d7ac 100644 --- a/clientsession.go +++ b/clientsession.go @@ -329,14 +329,14 @@ func (s *ClientSession) ParsedUserData() (api.StringMap, error) { return s.parseUserData() } -func (s *ClientSession) SetRoom(room *Room) { +func (s *ClientSession) SetRoom(room *Room, joinTime time.Time) { s.room.Store(room) - s.onRoomSet(room != nil) + s.onRoomSet(room != nil, joinTime) } -func (s *ClientSession) onRoomSet(hasRoom bool) { +func (s *ClientSession) onRoomSet(hasRoom bool, joinTime time.Time) { if hasRoom { - s.roomJoinTime.Store(time.Now().UnixNano()) + s.roomJoinTime.Store(joinTime.UnixNano()) } else { s.roomJoinTime.Store(0) } @@ -361,7 +361,7 @@ func (s *ClientSession) SetFederationClient(federation *FederationClient) { defer s.mu.Unlock() s.doLeaveRoom(true) - s.onRoomSet(federation != nil) + s.onRoomSet(federation != nil, time.Now()) if prev := s.federation.Swap(federation); prev != nil && prev != federation { prev.Close() @@ -561,7 +561,7 @@ func (s *ClientSession) doLeaveRoom(notify bool) *Room { } s.doUnsubscribeRoomEvents(notify) - s.SetRoom(nil) + s.SetRoom(nil, time.Time{}) s.releaseMcuObjects() room.RemoveSession(s) return room diff --git a/hub.go b/hub.go index 96b8802..e6eb2fa 100644 --- a/hub.go +++ b/hub.go @@ -1848,8 +1848,10 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { } var room talk.BackendClientResponse + var joinRoomTime time.Time if session.ClientType() == api.HelloClientTypeInternal { // Internal clients can join any room. + joinRoomTime = time.Now() room = talk.BackendClientResponse{ Type: "room", Room: &talk.BackendClientRoomResponse{ @@ -1876,6 +1878,7 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { // TODO(jojo): Validate response + joinRoomTime = time.Now() if message.Room.SessionId != "" { // There can only be one connection per Nextcloud Talk session, // disconnect any other connections without sending a "leave" event. @@ -1886,7 +1889,7 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { } } - h.processJoinRoom(session, message, &room) + h.processJoinRoom(session, message, &room, joinRoomTime) } func (h *Hub) publishFederatedSessions() (int, *sync.WaitGroup) { @@ -2005,7 +2008,7 @@ func (h *Hub) createRoomLocked(id string, properties json.RawMessage, backend *t return room, nil } -func (h *Hub) processJoinRoom(session *ClientSession, message *api.ClientMessage, room *talk.BackendClientResponse) { +func (h *Hub) processJoinRoom(session *ClientSession, message *api.ClientMessage, room *talk.BackendClientResponse, joinTime time.Time) { if room.Type == "error" { session.SendMessage(message.NewErrorServerMessage(room.Error)) return @@ -2051,7 +2054,7 @@ func (h *Hub) processJoinRoom(session *ClientSession, message *api.ClientMessage delete(h.dialoutSessions, session) } h.mu.Unlock() - session.SetRoom(r) + session.SetRoom(r, joinTime) if room.Room.Permissions != nil { session.SetPermissions(*room.Room.Permissions) } @@ -2540,7 +2543,7 @@ func (h *Hub) processInternalMsg(sess Session, message *api.ClientMessage) { statsHubSessionsTotal.WithLabelValues(session.Backend().Id(), string(sess.ClientType())).Inc() h.logger.Printf("Session %s added virtual session %s with initial flags %d", session.PublicId(), sess.PublicId(), sess.Flags()) session.AddVirtualSession(sess) - sess.SetRoom(room) + sess.SetRoom(room, time.Now()) room.AddSession(sess, nil) case "updatesession": msg := msg.UpdateSession diff --git a/roomsessions_test.go b/roomsessions_test.go index 81fc57d..0a8d5b5 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -26,6 +26,7 @@ import ( "encoding/json" "net/url" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -82,7 +83,7 @@ func (s *DummySession) ParsedBackendUrl() *url.URL { return nil } -func (s *DummySession) SetRoom(room *Room) { +func (s *DummySession) SetRoom(room *Room, joinTime time.Time) { } func (s *DummySession) GetRoom() *Room { diff --git a/session.go b/session.go index 3ef6485..606cccf 100644 --- a/session.go +++ b/session.go @@ -26,6 +26,7 @@ import ( "encoding/json" "net/url" "sync" + "time" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -46,7 +47,7 @@ type Session interface { BackendUrl() string ParsedBackendUrl() *url.URL - SetRoom(room *Room) + SetRoom(room *Room, joinTime time.Time) GetRoom() *Room LeaveRoom(notify bool) *Room IsInRoom(id string) bool diff --git a/virtualsession.go b/virtualsession.go index b8e6cfa..4a34810 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -27,6 +27,7 @@ import ( "errors" "net/url" "sync/atomic" + "time" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async/events" @@ -169,7 +170,7 @@ func (s *VirtualSession) ParsedUserData() (api.StringMap, error) { return s.parseUserData() } -func (s *VirtualSession) SetRoom(room *Room) { +func (s *VirtualSession) SetRoom(room *Room, joinTime time.Time) { s.room.Store(room) if room != nil { if err := s.hub.roomSessions.SetRoomSession(s, api.RoomSessionId(s.PublicId())); err != nil { @@ -195,7 +196,7 @@ func (s *VirtualSession) LeaveRoom(notify bool) *Room { return nil } - s.SetRoom(nil) + s.SetRoom(nil, time.Time{}) room.RemoveSession(s) return room } From b8b94dc802395d426c80b7f75fe38e763a8a19c2 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 11:13:16 +0100 Subject: [PATCH 441/549] Include session id in log message on ignored room event. --- clientsession.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clientsession.go b/clientsession.go index b55d7ac..7af19bd 100644 --- a/clientsession.go +++ b/clientsession.go @@ -1491,7 +1491,7 @@ func (s *ClientSession) filterAsyncMessage(msg *events.AsyncMessage) *api.Server // Can happen mostly during tests where an older room async message // could be received by a subscriber that joined after it was sent. if joined := s.getRoomJoinTime(); joined.IsZero() || msg.SendTime.Before(joined) { - s.logger.Printf("Message %+v was sent on %s before room was joined on %s, ignoring", msg.Message, msg.SendTime, joined) + s.logger.Printf("Message %+v was sent on %s before session %s join room on %s, ignoring", msg.Message, msg.SendTime, s.publicId, joined) return nil } } From 13313c5d96098934ffc3d422948a81fab97f4fd3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 13:34:42 +0100 Subject: [PATCH 442/549] Stop using "DrainMessages" in tests which might process too many messages. --- backend_server_test.go | 13 +++++++------ testclient_test.go | 15 --------------- virtualsession_test.go | 23 +++++++++++++++-------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/backend_server_test.go b/backend_server_test.go index 59b5fc4..42f8b05 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -502,7 +502,7 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { } // Ignore "join" events. - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello.Hello) roomProperties := json.RawMessage("{\"foo\":\"bar\"}") @@ -818,12 +818,13 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { roomId := "test-room" roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) + client1.RunUntilJoined(ctx, hello1.Hello) roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. - assert.NoError(client1.DrainMessages(ctx)) - assert.NoError(client2.DrainMessages(ctx)) + client1.RunUntilJoined(ctx, hello2.Hello) + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) msg := &talk.BackendServerRoomRequest{ Type: "participants", @@ -899,7 +900,7 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello.Hello) // Updating with empty permissions upgrades to non-old-style and removes // all previously available permissions. @@ -1283,7 +1284,7 @@ func TestBackendServer_RoomMessage(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() - client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") defer client.CloseWithBye() // Join room by id. @@ -1292,7 +1293,7 @@ func TestBackendServer_RoomMessage(t *testing.T) { require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello.Hello) messageData := json.RawMessage("{\"foo\":\"bar\"}") msg := &talk.BackendServerRoomRequest{ diff --git a/testclient_test.go b/testclient_test.go index dbf60a5..c6fffd2 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -645,21 +645,6 @@ func (c *TestClient) RemoveTransientData(key string) error { return c.WriteJSON(message) } -func (c *TestClient) DrainMessages(ctx context.Context) error { - select { - case err := <-c.readErrorChan: - return err - case <-c.messageChan: - n := len(c.messageChan) - for range n { - <-c.messageChan - } - case <-ctx.Done(): - return ctx.Err() - } - return nil -} - func (c *TestClient) GetPendingMessages(ctx context.Context) ([]*api.ServerMessage, error) { var result []*api.ServerMessage select { diff --git a/virtualsession_test.go b/virtualsession_test.go index 6708943..de2c7a1 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -96,6 +96,14 @@ func TestVirtualSession(t *testing.T) { // Also a participants update event will be triggered for the virtual user. msg2 := MustSucceed1(t, client.RunUntilMessage, ctx) + msg3 := MustSucceed1(t, client.RunUntilMessage, ctx) + if msg2.Type == "event" && msg3.Type == "event" && msg2.Event.Type == "flags" { + // The order is not specified, could be "participants" before "flags" or vice versa. + // Ensure consistent order for checks below ("participants", "flags"). + t.Logf("Switching messages order") + msg2, msg3 = msg3, msg2 + } + if updateMsg, ok := checkMessageParticipantsInCall(t, msg2); ok { assert.Equal(roomId, updateMsg.RoomId) if assert.Len(updateMsg.Users, 1) { @@ -105,7 +113,6 @@ func TestVirtualSession(t *testing.T) { } } - msg3 := MustSucceed1(t, client.RunUntilMessage, ctx) if flagsMsg, ok := checkMessageParticipantFlags(t, msg3); ok { assert.Equal(roomId, flagsMsg.RoomId) assert.Equal(sessionId, flagsMsg.SessionId) @@ -136,7 +143,7 @@ func TestVirtualSession(t *testing.T) { } // A new client will receive the initial flags of the virtual session. - client2, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) @@ -168,7 +175,7 @@ func TestVirtualSession(t *testing.T) { assert.True(gotFlags, "Didn't receive initial flags in %+v", receivedMessages) // Ignore "join" messages from second client - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello2.Hello) // When sending to a virtual session, the message is sent to the actual // client and contains a "Recipient" block with the internal session id. @@ -244,7 +251,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello.Hello) internalSessionId := api.PublicSessionId("session1") userId := "user1" @@ -321,7 +328,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { } // A new client will receive the initial flags of the virtual session. - client2, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) @@ -353,7 +360,7 @@ func TestVirtualSessionActorInformation(t *testing.T) { assert.True(gotFlags, "Didn't receive initial flags in %+v", receivedMessages) // Ignore "join" messages from second client - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello2.Hello) // When sending to a virtual session, the message is sent to the actual // client and contains a "Recipient" block with the internal session id. @@ -609,13 +616,13 @@ func TestVirtualSessionCleanup(t *testing.T) { assert.NotEmpty(hello.Hello.SessionId) assert.NotEmpty(hello.Hello.ResumeId) } - client, _ := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) require.Equal(roomId, roomMsg.Room.RoomId) // Ignore "join" events. - assert.NoError(client.DrainMessages(ctx)) + client.RunUntilJoined(ctx, hello.Hello) internalSessionId := api.PublicSessionId("session1") userId := "user1" From 8c12403c4f04d90b80245459da5930ee2afee026 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 14:41:56 +0100 Subject: [PATCH 443/549] Filter "leave" events for which no "join" was sent before. --- clientsession.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/clientsession.go b/clientsession.go index 7af19bd..f24d44b 100644 --- a/clientsession.go +++ b/clientsession.go @@ -1256,6 +1256,35 @@ func filterDisplayNames(events []api.EventServerMessageSessionEntry) []api.Event return result } +// +checklocks:s.filterDuplicateLock +func (s *ClientSession) filterUnknownLeave(entries []api.PublicSessionId) []api.PublicSessionId { + idx := slices.IndexFunc(entries, func(e api.PublicSessionId) bool { + return !s.seenJoinedEvents[e] // +checklocksignore + }) + if idx == -1 { + return entries + } else if idx+1 == len(entries) { + // Simple case: all entries filtered. + s.logger.Printf("Session %s got unknown leave events for %+v", s.publicId, entries) + return nil + } + + // Filter remaining entries. + filtered := []api.PublicSessionId{ + entries[idx], + } + result := append([]api.PublicSessionId{}, entries[:idx]...) + for _, e := range entries[idx+1:] { + if s.seenJoinedEvents[e] { + result = append(result, e) + } else { + filtered = append(filtered, e) + } + } + s.logger.Printf("Session %s got unknown leave events for %+v", s.publicId, filtered) + return result +} + func (s *ClientSession) filterDuplicateJoin(entries []api.EventServerMessageSessionEntry) []api.EventServerMessageSessionEntry { s.filterDuplicateLock.Lock() defer s.filterDuplicateLock.Unlock() @@ -1369,10 +1398,27 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes s.filterDuplicateLock.Lock() defer s.filterDuplicateLock.Unlock() + leave := s.filterUnknownLeave(message.Event.Leave) + if len(leave) == 0 { + return nil + } + for _, e := range message.Event.Leave { delete(s.seenJoinedEvents, e) delete(s.seenFlags, e) } + + if len(leave) != len(message.Event.Leave) { + message = &api.ServerMessage{ + Id: message.Id, + Type: message.Type, + Event: &api.EventServerMessage{ + Type: message.Event.Type, + Target: message.Event.Target, + Leave: leave, + }, + } + } case "message": if message.Event.Message == nil || len(message.Event.Message.Data) == 0 { return message From 315b2583e18b18a1ce15a16f0f6a12a64e650e31 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 14:43:02 +0100 Subject: [PATCH 444/549] Relax order when checking for join/leave in RunTestClientTakeoverRoomSession. --- hub_test.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/hub_test.go b/hub_test.go index 4c71101..f1ad48f 100644 --- a/hub_test.go +++ b/hub_test.go @@ -3588,10 +3588,22 @@ func RunTestClientTakeoverRoomSession(t *testing.T) { client2.RunUntilErrorIs(ctx2, ErrNoMessageReceived, context.DeadlineExceeded) // The permanently connected client will receive a "left" event from the - // overridden session and a "joined" for the new session. In that order as - // both were on the same server. - client3.RunUntilLeft(ctx, hello1.Hello) - client3.RunUntilJoined(ctx, hello2.Hello) + // overridden session and a "joined" for the new session. + msg1 := MustSucceed1(t, client3.RunUntilMessage, ctx) + msg2 := MustSucceed1(t, client3.RunUntilMessage, ctx) + if msg1.Type == "event" && msg2.Type == "event" && msg1.Event.Type == "join" { + t.Logf("Switching messages order") + msg1, msg2 = msg2, msg1 + } + + client3.checkMessageRoomLeave(msg1, hello1.Hello) + if checkMessageType(t, msg2, "event") && + assert.Equal("room", msg2.Event.Target, "invalid target in %+v", msg2) && + assert.Equal("join", msg2.Event.Type, "invalid event type in %+v", msg2) && + assert.Len(msg2.Event.Join, 1, "invalid number of join event entries: %+v", msg2.Event) { + assert.Equal(hello2.Hello.SessionId, msg2.Event.Join[0].SessionId) + assert.Equal(hello2.Hello.UserId, msg2.Event.Join[0].UserId) + } } func TestClientSendOfferPermissions(t *testing.T) { From 9bbc0588e33a9c521eb82d87db1ee8a940d3fc51 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 18 Dec 2025 15:03:32 +0100 Subject: [PATCH 445/549] Move code for commandline applications to "cmd" folder. --- .codecov.yml | 22 ++++++++--------- Makefile | 24 +++++++++---------- {client => cmd/client}/main.go | 0 {client => cmd/client}/stats.go | 0 {client => cmd/client}/stats_test.go | 0 {proxy => cmd/proxy}/main.go | 0 {proxy => cmd/proxy}/proxy_client.go | 0 {proxy => cmd/proxy}/proxy_remote.go | 0 {proxy => cmd/proxy}/proxy_remote_test.go | 0 {proxy => cmd/proxy}/proxy_server.go | 0 {proxy => cmd/proxy}/proxy_server_test.go | 0 {proxy => cmd/proxy}/proxy_session.go | 0 .../proxy}/proxy_stats_prometheus.go | 0 {proxy => cmd/proxy}/proxy_testclient_test.go | 0 {proxy => cmd/proxy}/proxy_tokens.go | 0 {proxy => cmd/proxy}/proxy_tokens_etcd.go | 0 .../proxy}/proxy_tokens_etcd_test.go | 0 {proxy => cmd/proxy}/proxy_tokens_static.go | 0 {server => cmd/server}/main.go | 0 19 files changed, 23 insertions(+), 23 deletions(-) rename {client => cmd/client}/main.go (100%) rename {client => cmd/client}/stats.go (100%) rename {client => cmd/client}/stats_test.go (100%) rename {proxy => cmd/proxy}/main.go (100%) rename {proxy => cmd/proxy}/proxy_client.go (100%) rename {proxy => cmd/proxy}/proxy_remote.go (100%) rename {proxy => cmd/proxy}/proxy_remote_test.go (100%) rename {proxy => cmd/proxy}/proxy_server.go (100%) rename {proxy => cmd/proxy}/proxy_server_test.go (100%) rename {proxy => cmd/proxy}/proxy_session.go (100%) rename {proxy => cmd/proxy}/proxy_stats_prometheus.go (100%) rename {proxy => cmd/proxy}/proxy_testclient_test.go (100%) rename {proxy => cmd/proxy}/proxy_tokens.go (100%) rename {proxy => cmd/proxy}/proxy_tokens_etcd.go (100%) rename {proxy => cmd/proxy}/proxy_tokens_etcd_test.go (100%) rename {proxy => cmd/proxy}/proxy_tokens_static.go (100%) rename {server => cmd/server}/main.go (100%) diff --git a/.codecov.yml b/.codecov.yml index c479bb2..183a4d3 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -27,10 +27,18 @@ component_management: name: async paths: - async/** - - component_id: module_client - name: client + - component_id: module_cmd_client + name: cmd/client paths: - - client/** + - cmd/client/** + - component_id: module_cmd_proxy + name: cmd/proxy + paths: + - cmd/proxy/** + - component_id: module_cmd_server + name: cmd/server + paths: + - cmd/server/** - component_id: module_config name: config paths: @@ -75,18 +83,10 @@ component_management: name: pool paths: - pool/** - - component_id: module_proxy - name: proxy - paths: - - proxy/** - component_id: module_security name: security paths: - security/** - - component_id: module_server - name: server - paths: - - server/** - component_id: module_talk name: talk paths: diff --git a/Makefile b/Makefile index 4fae2e7..26a0059 100644 --- a/Makefile +++ b/Makefile @@ -18,16 +18,16 @@ PROTOBUF_VERSION := $(shell grep google.golang.org/protobuf go.mod | xargs | cut 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)) +TEST_GO_FILES := $(wildcard *_test.go */*_test.go */*/*_test.go) EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go api/signaling.go */api.go */*/api.go talk/ocs.go)) EASYJSON_GO_FILES := $(patsubst %.go,%_easyjson.go,$(EASYJSON_FILES)) -COMMON_GO_FILES := $(filter-out geoip/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)) -CLIENT_GO_FILES := $(filter-out $(CLIENT_TEST_GO_FILES),$(wildcard client/*.go)) -SERVER_TEST_GO_FILES := $(wildcard server/*_test.go)) -SERVER_GO_FILES := $(filter-out $(SERVER_TEST_GO_FILES),$(wildcard server/*.go)) -PROXY_TEST_GO_FILES := $(wildcard proxy/*_test.go)) -PROXY_GO_FILES := $(filter-out $(PROXY_TEST_GO_FILES),$(wildcard proxy/*.go)) +COMMON_GO_FILES := $(filter-out geoip/continentmap.go $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES) $(EASYJSON_GO_FILES) $(TEST_GO_FILES),$(wildcard *.go */*.go */*/*.go)) +CLIENT_TEST_GO_FILES := $(wildcard cmd/client/*_test.go)) +CLIENT_GO_FILES := $(filter-out $(CLIENT_TEST_GO_FILES),$(wildcard cmd/client/*.go)) +SERVER_TEST_GO_FILES := $(wildcard cmd/server/*_test.go)) +SERVER_GO_FILES := $(filter-out $(SERVER_TEST_GO_FILES),$(wildcard cmd/server/*.go)) +PROXY_TEST_GO_FILES := $(wildcard cmd/proxy/*_test.go)) +PROXY_GO_FILES := $(filter-out $(PROXY_TEST_GO_FILES),$(wildcard cmd/proxy/*.go)) ifneq ($(VERSION),) INTERNALLDFLAGS := -X main.version=$(VERSION) @@ -107,7 +107,7 @@ get: $(GO) get $(PACKAGE) fmt: hook | $(PROTO_GO_FILES) - $(GOFMT) -s -w *.go client proxy server + $(GOFMT) -s -w *.go cmd/client cmd/proxy cmd/server vet: GOEXPERIMENT=synctest $(GO) vet ./... @@ -165,17 +165,17 @@ $(TMPDIR): client: $(BINDIR)/client $(BINDIR)/client: go.mod go.sum $(CLIENT_GO_FILES) $(COMMON_GO_FILES) | $(BINDIR) - $(GO) build $(BUILDARGS) -ldflags '$(INTERNALLDFLAGS)' -o $@ ./client/... + $(GO) build $(BUILDARGS) -ldflags '$(INTERNALLDFLAGS)' -o $@ ./cmd/client/... server: $(BINDIR)/signaling $(BINDIR)/signaling: go.mod go.sum $(SERVER_GO_FILES) $(COMMON_GO_FILES) | $(BINDIR) - $(GO) build $(BUILDARGS) -ldflags '$(INTERNALLDFLAGS)' -o $@ ./server/... + $(GO) build $(BUILDARGS) -ldflags '$(INTERNALLDFLAGS)' -o $@ ./cmd/server/... proxy: $(BINDIR)/proxy $(BINDIR)/proxy: go.mod go.sum $(PROXY_GO_FILES) $(COMMON_GO_FILES) | $(BINDIR) - $(GO) build $(BUILDARGS) -ldflags '$(INTERNALLDFLAGS)' -o $@ ./proxy/... + $(GO) build $(BUILDARGS) -ldflags '$(INTERNALLDFLAGS)' -o $@ ./cmd/proxy/... clean: rm -f easyjson-bootstrap*.go diff --git a/client/main.go b/cmd/client/main.go similarity index 100% rename from client/main.go rename to cmd/client/main.go diff --git a/client/stats.go b/cmd/client/stats.go similarity index 100% rename from client/stats.go rename to cmd/client/stats.go diff --git a/client/stats_test.go b/cmd/client/stats_test.go similarity index 100% rename from client/stats_test.go rename to cmd/client/stats_test.go diff --git a/proxy/main.go b/cmd/proxy/main.go similarity index 100% rename from proxy/main.go rename to cmd/proxy/main.go diff --git a/proxy/proxy_client.go b/cmd/proxy/proxy_client.go similarity index 100% rename from proxy/proxy_client.go rename to cmd/proxy/proxy_client.go diff --git a/proxy/proxy_remote.go b/cmd/proxy/proxy_remote.go similarity index 100% rename from proxy/proxy_remote.go rename to cmd/proxy/proxy_remote.go diff --git a/proxy/proxy_remote_test.go b/cmd/proxy/proxy_remote_test.go similarity index 100% rename from proxy/proxy_remote_test.go rename to cmd/proxy/proxy_remote_test.go diff --git a/proxy/proxy_server.go b/cmd/proxy/proxy_server.go similarity index 100% rename from proxy/proxy_server.go rename to cmd/proxy/proxy_server.go diff --git a/proxy/proxy_server_test.go b/cmd/proxy/proxy_server_test.go similarity index 100% rename from proxy/proxy_server_test.go rename to cmd/proxy/proxy_server_test.go diff --git a/proxy/proxy_session.go b/cmd/proxy/proxy_session.go similarity index 100% rename from proxy/proxy_session.go rename to cmd/proxy/proxy_session.go diff --git a/proxy/proxy_stats_prometheus.go b/cmd/proxy/proxy_stats_prometheus.go similarity index 100% rename from proxy/proxy_stats_prometheus.go rename to cmd/proxy/proxy_stats_prometheus.go diff --git a/proxy/proxy_testclient_test.go b/cmd/proxy/proxy_testclient_test.go similarity index 100% rename from proxy/proxy_testclient_test.go rename to cmd/proxy/proxy_testclient_test.go diff --git a/proxy/proxy_tokens.go b/cmd/proxy/proxy_tokens.go similarity index 100% rename from proxy/proxy_tokens.go rename to cmd/proxy/proxy_tokens.go diff --git a/proxy/proxy_tokens_etcd.go b/cmd/proxy/proxy_tokens_etcd.go similarity index 100% rename from proxy/proxy_tokens_etcd.go rename to cmd/proxy/proxy_tokens_etcd.go diff --git a/proxy/proxy_tokens_etcd_test.go b/cmd/proxy/proxy_tokens_etcd_test.go similarity index 100% rename from proxy/proxy_tokens_etcd_test.go rename to cmd/proxy/proxy_tokens_etcd_test.go diff --git a/proxy/proxy_tokens_static.go b/cmd/proxy/proxy_tokens_static.go similarity index 100% rename from proxy/proxy_tokens_static.go rename to cmd/proxy/proxy_tokens_static.go diff --git a/server/main.go b/cmd/server/main.go similarity index 100% rename from server/main.go rename to cmd/server/main.go From 124c37108b3ea3a960c46f97b27e8b141343e888 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 09:55:57 +0100 Subject: [PATCH 446/549] Move SFU related code to separate packages. With that move GRPC client code to "grpc" package to break dependencies. Also introduce new "proxy" package with common proxy-related code. --- .codecov.yml | 13 + Makefile | 4 +- transient_data.go => api/transient_data.go | 32 +- api/transient_data_test.go | 143 + backend_server_test.go | 5 +- clientsession.go | 87 +- clientsession_test.go | 18 +- cmd/proxy/proxy_remote.go | 38 +- cmd/proxy/proxy_remote_test.go | 6 +- cmd/proxy/proxy_server.go | 177 +- cmd/proxy/proxy_server_test.go | 285 +- cmd/proxy/proxy_session.go | 103 +- cmd/proxy/proxy_testclient_test.go | 28 +- cmd/server/main.go | 36 +- federation.go | 9 +- federation_test.go | 5 +- api_grpc.go => grpc/api.go | 6 +- api_grpc_easyjson.go => grpc/api_easyjson.go | 22 +- grpc_backend.pb.go => grpc/backend.pb.go | 20 +- grpc_backend.proto => grpc/backend.proto | 24 +- .../backend_grpc.pb.go | 10 +- grpc_client.go => grpc/client.go | 182 +- .../client_stats_prometheus.go | 52 +- grpc/client_test.go | 174 ++ grpc_common.go => grpc/common.go | 32 +- grpc/common_test.go | 30 + grpc_internal.pb.go => grpc/internal.pb.go | 46 +- grpc_internal.proto => grpc/internal.proto | 4 +- .../internal_grpc.pb.go | 12 +- grpc/server_id.go | 45 + grpc_sessions.pb.go => grpc/sessions.pb.go | 80 +- grpc_sessions.proto => grpc/sessions.proto | 4 +- .../sessions_grpc.pb.go | 18 +- grpc_mcu.pb.go => grpc/sfu.pb.go | 74 +- grpc_mcu.proto => grpc/sfu.proto | 4 +- grpc_mcu_grpc.pb.go => grpc/sfu_grpc.pb.go | 10 +- syscallconn.go => grpc/syscallconn.go | 2 +- grpc/test/client.go | 75 + grpc_client_test.go | 211 +- grpc_remote_client.go | 7 +- grpc_server.go | 101 +- grpc_server_test.go | 68 +- grpc_stats_prometheus.go | 22 - hub.go | 55 +- hub_sfu_janus_test.go | 757 +++++ hub_sfu_proxy_test.go | 610 ++++ hub_test.go | 36 +- ...data_test.go => hub_transient_data_test.go | 113 - mcu_janus_test.go | 2106 ------------- mcu_proxy_test.go | 2606 ----------------- .../test/metrics.go | 8 +- api_proxy.go => proxy/api.go | 99 +- .../api_easyjson.go | 889 +++--- remotesession.go | 11 +- room.go | 25 +- roomsessions_builtin.go | 7 +- mcu_common.go => sfu/common.go | 157 +- sfu/internal/settings.go | 81 + sfu/internal/stats_prometheus.go | 83 + sfu/internal/stats_prometheus_test.go | 33 + mcu_janus_client.go => sfu/janus/client.go | 68 +- .../janus/events_handler.go | 342 ++- .../janus/events_handler_test.go | 181 +- mcu_janus.go => sfu/janus/janus.go | 316 +- janus_client.go => sfu/janus/janus/janus.go | 165 +- sfu/janus/janus_test.go | 881 ++++++ mcu_test.go => sfu/janus/mcu_test.go | 21 +- .../janus/publisher.go | 64 +- .../janus/publisher_stats_counter.go | 34 +- .../janus/publisher_stats_counter_test.go | 39 +- .../janus/publisher_test.go | 20 +- .../janus/remote_publisher.go | 38 +- .../janus/remote_subscriber.go | 28 +- .../janus/stats_prometheus.go | 110 +- .../janus/stream_selection.go | 2 +- .../janus/subscriber.go | 71 +- sfu/janus/test/janus.go | 588 ++++ mcu_common_test.go => sfu/mock/mock.go | 42 +- proxy_config.go => sfu/proxy/config.go | 4 +- .../proxy/config_etcd.go | 42 +- .../proxy/config_etcd_test.go | 6 +- .../proxy/config_static.go | 18 +- .../proxy/config_static_test.go | 8 +- .../proxy/config_test.go | 2 +- mcu_proxy.go => sfu/proxy/proxy.go | 544 ++-- sfu/proxy/proxy_test.go | 1239 ++++++++ sfu/proxy/stats_prometheus.go | 81 + sfu/proxy/test/proxy.go | 125 + sfu/proxy/testserver/server.go | 753 +++++ sfu/test/sfu.go | 312 ++ 90 files changed, 8627 insertions(+), 7517 deletions(-) rename transient_data.go => api/transient_data.go (92%) create mode 100644 api/transient_data_test.go rename api_grpc.go => grpc/api.go (90%) rename api_grpc_easyjson.go => grpc/api_easyjson.go (60%) rename grpc_backend.pb.go => grpc/backend.pb.go (89%) rename grpc_backend.proto => grpc/backend.proto (72%) rename grpc_backend_grpc.pb.go => grpc/backend_grpc.pb.go (96%) rename grpc_client.go => grpc/client.go (81%) rename grpc_common_test.go => grpc/client_stats_prometheus.go (54%) create mode 100644 grpc/client_test.go rename grpc_common.go => grpc/common.go (88%) create mode 100644 grpc/common_test.go rename grpc_internal.pb.go => grpc/internal.pb.go (85%) rename grpc_internal.proto => grpc/internal.proto (97%) rename grpc_internal_grpc.pb.go => grpc/internal_grpc.pb.go (95%) create mode 100644 grpc/server_id.go rename grpc_sessions.pb.go => grpc/sessions.pb.go (86%) rename grpc_sessions.proto => grpc/sessions.proto (98%) rename grpc_sessions_grpc.pb.go => grpc/sessions_grpc.pb.go (95%) rename grpc_mcu.pb.go => grpc/sfu.pb.go (73%) rename grpc_mcu.proto => grpc/sfu.proto (97%) rename grpc_mcu_grpc.pb.go => grpc/sfu_grpc.pb.go (96%) rename syscallconn.go => grpc/syscallconn.go (99%) create mode 100644 grpc/test/client.go create mode 100644 hub_sfu_janus_test.go create mode 100644 hub_sfu_proxy_test.go rename transient_data_test.go => hub_transient_data_test.go (80%) delete mode 100644 mcu_janus_test.go delete mode 100644 mcu_proxy_test.go rename stats_prometheus_test.go => metrics/test/metrics.go (92%) rename api_proxy.go => proxy/api.go (72%) rename api_proxy_easyjson.go => proxy/api_easyjson.go (73%) rename mcu_common.go => sfu/common.go (54%) create mode 100644 sfu/internal/settings.go create mode 100644 sfu/internal/stats_prometheus.go create mode 100644 sfu/internal/stats_prometheus_test.go rename mcu_janus_client.go => sfu/janus/client.go (68%) rename mcu_janus_events_handler.go => sfu/janus/events_handler.go (72%) rename mcu_janus_events_handler_test.go => sfu/janus/events_handler_test.go (78%) rename mcu_janus.go => sfu/janus/janus.go (70%) rename janus_client.go => sfu/janus/janus/janus.go (84%) create mode 100644 sfu/janus/janus_test.go rename mcu_test.go => sfu/janus/mcu_test.go (88%) rename mcu_janus_publisher.go => sfu/janus/publisher.go (85%) rename publisher_stats_counter.go => sfu/janus/publisher_stats_counter.go (84%) rename publisher_stats_counter_test.go => sfu/janus/publisher_stats_counter_test.go (80%) rename mcu_janus_publisher_test.go => sfu/janus/publisher_test.go (85%) rename mcu_janus_remote_publisher.go => sfu/janus/remote_publisher.go (79%) rename mcu_janus_remote_subscriber.go => sfu/janus/remote_subscriber.go (82%) rename mcu_stats_prometheus.go => sfu/janus/stats_prometheus.go (60%) rename mcu_janus_stream_selection.go => sfu/janus/stream_selection.go (99%) rename mcu_janus_subscriber.go => sfu/janus/subscriber.go (83%) create mode 100644 sfu/janus/test/janus.go rename mcu_common_test.go => sfu/mock/mock.go (58%) rename proxy_config.go => sfu/proxy/config.go (95%) rename proxy_config_etcd.go => sfu/proxy/config_etcd.go (83%) rename proxy_config_etcd_test.go => sfu/proxy/config_etcd_test.go (96%) rename proxy_config_static.go => sfu/proxy/config_static.go (88%) rename proxy_config_static_test.go => sfu/proxy/config_static_test.go (92%) rename proxy_config_test.go => sfu/proxy/config_test.go (99%) rename mcu_proxy.go => sfu/proxy/proxy.go (74%) create mode 100644 sfu/proxy/proxy_test.go create mode 100644 sfu/proxy/stats_prometheus.go create mode 100644 sfu/proxy/test/proxy.go create mode 100644 sfu/proxy/testserver/server.go create mode 100644 sfu/test/sfu.go diff --git a/.codecov.yml b/.codecov.yml index 183a4d3..e3aba2f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -12,6 +12,7 @@ ignore: - "*_easyjson.go" - "**/*_easyjson.go" - "*.pb.go" + - "**/*.pb.go" component_management: individual_components: @@ -59,6 +60,10 @@ component_management: name: geoip paths: - geoip/** + - component_id: module_grpc + name: grpc + paths: + - grpc/** - component_id: module_internal name: internal paths: @@ -83,10 +88,18 @@ component_management: name: pool paths: - pool/** + - component_id: module_proxy + name: proxy + paths: + - proxy/** - component_id: module_security name: security paths: - security/** + - component_id: module_sfu + name: sfu + paths: + - sfu/** - component_id: module_talk name: talk paths: diff --git a/Makefile b/Makefile index 26a0059..7f29cb7 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ VENDORDIR := "$(CURDIR)/vendor" VERSION := $(shell "$(CURDIR)/scripts/get-version.sh") TARVERSION := $(shell "$(CURDIR)/scripts/get-version.sh" --tar) PACKAGENAME := github.com/strukturag/nextcloud-spreed-signaling -GRPC_PROTO_FILES := $(basename $(wildcard grpc_*.proto)) +GRPC_PROTO_FILES := $(basename $(wildcard grpc/*.proto)) PROTOBUF_VERSION := $(shell grep google.golang.org/protobuf go.mod | xargs | cut -d ' ' -f 2) -PROTO_FILES := $(filter-out $(GRPC_PROTO_FILES),$(basename $(wildcard *.proto))) +PROTO_FILES := $(filter-out $(GRPC_PROTO_FILES),$(basename $(wildcard *.proto */*.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 */*_test.go */*/*_test.go) diff --git a/transient_data.go b/api/transient_data.go similarity index 92% rename from transient_data.go rename to api/transient_data.go index 1ec8e1f..520e86e 100644 --- a/transient_data.go +++ b/api/transient_data.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 api import ( "encoding/json" @@ -27,8 +27,6 @@ import ( "reflect" "sync" "time" - - "github.com/strukturag/nextcloud-spreed-signaling/api" ) const ( @@ -36,7 +34,7 @@ const ( ) type TransientListener interface { - SendMessage(message *api.ServerMessage) bool + SendMessage(message *ServerMessage) bool } type TransientDataEntry struct { @@ -103,7 +101,7 @@ func NewTransientData() *TransientData { } // +checklocks:t.mu -func (t *TransientData) sendMessageToListener(listener TransientListener, message *api.ServerMessage) { +func (t *TransientData) sendMessageToListener(listener TransientListener, message *ServerMessage) { t.mu.Unlock() defer t.mu.Lock() @@ -112,9 +110,9 @@ func (t *TransientData) sendMessageToListener(listener TransientListener, messag // +checklocks:t.mu func (t *TransientData) notifySet(key string, prev, value any) { - msg := &api.ServerMessage{ + msg := &ServerMessage{ Type: "transient", - TransientData: &api.TransientDataServerMessage{ + TransientData: &TransientDataServerMessage{ Type: "set", Key: key, Value: value, @@ -128,9 +126,9 @@ func (t *TransientData) notifySet(key string, prev, value any) { // +checklocks:t.mu func (t *TransientData) notifyDeleted(key string, prev *TransientDataEntry) { - msg := &api.ServerMessage{ + msg := &ServerMessage{ Type: "transient", - TransientData: &api.TransientDataServerMessage{ + TransientData: &TransientDataServerMessage{ Type: "remove", Key: key, }, @@ -153,13 +151,13 @@ func (t *TransientData) AddListener(listener TransientListener) { } t.listeners[listener] = true if len(t.data) > 0 { - data := make(api.StringMap, len(t.data)) + data := make(StringMap, len(t.data)) for k, v := range t.data { data[k] = v.Value } - msg := &api.ServerMessage{ + msg := &ServerMessage{ Type: "transient", - TransientData: &api.TransientDataServerMessage{ + TransientData: &TransientDataServerMessage{ Type: "initial", Data: data, }, @@ -328,7 +326,7 @@ func (t *TransientData) compareAndRemove(key string, old any) bool { } // GetData returns a copy of the internal data. -func (t *TransientData) GetData() api.StringMap { +func (t *TransientData) GetData() StringMap { t.mu.Lock() defer t.mu.Unlock() @@ -336,7 +334,7 @@ func (t *TransientData) GetData() api.StringMap { return nil } - result := make(api.StringMap, len(t.data)) + result := make(StringMap, len(t.data)) for k, entry := range t.data { result[k] = entry.Value } @@ -372,7 +370,7 @@ func (t *TransientData) SetInitial(data TransientDataEntries) { t.data = make(TransientDataEntries) } - msgData := make(api.StringMap, len(data)) + msgData := make(StringMap, len(data)) for k, v := range data { if _, found := t.data[k]; found { // Entry already present (i.e. was set by regular event). @@ -385,9 +383,9 @@ func (t *TransientData) SetInitial(data TransientDataEntries) { if len(msgData) == 0 { return } - msg := &api.ServerMessage{ + msg := &ServerMessage{ Type: "transient", - TransientData: &api.TransientDataServerMessage{ + TransientData: &TransientDataServerMessage{ Type: "initial", Data: msgData, }, diff --git a/api/transient_data_test.go b/api/transient_data_test.go new file mode 100644 index 0000000..a584eab --- /dev/null +++ b/api/transient_data_test.go @@ -0,0 +1,143 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 api + +import ( + "sync" + "testing" + "testing/synctest" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/test" +) + +func Test_TransientData(t *testing.T) { + t.Parallel() + test.SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + data := NewTransientData() + assert.False(data.Set("foo", nil)) + assert.True(data.Set("foo", "bar")) + assert.False(data.Set("foo", "bar")) + assert.True(data.Set("foo", "baz")) + assert.False(data.CompareAndSet("foo", "bar", "lala")) + assert.True(data.CompareAndSet("foo", "baz", "lala")) + assert.False(data.CompareAndSet("test", nil, nil)) + assert.True(data.CompareAndSet("test", nil, "123")) + assert.False(data.CompareAndSet("test", nil, "456")) + assert.False(data.CompareAndRemove("test", "1234")) + assert.True(data.CompareAndRemove("test", "123")) + assert.False(data.Remove("lala")) + assert.True(data.Remove("foo")) + + assert.True(data.SetTTL("test", "1234", time.Millisecond)) + assert.Equal("1234", data.GetData()["test"]) + // Data is removed after the TTL + start := time.Now() + time.Sleep(time.Millisecond) + synctest.Wait() + assert.Equal(time.Millisecond, time.Since(start)) + assert.Nil(data.GetData()["test"]) + + assert.True(data.SetTTL("test", "1234", time.Millisecond)) + assert.Equal("1234", data.GetData()["test"]) + assert.True(data.SetTTL("test", "2345", 3*time.Millisecond)) + assert.Equal("2345", data.GetData()["test"]) + start = time.Now() + // Data is removed after the TTL only if the value still matches + time.Sleep(2 * time.Millisecond) + synctest.Wait() + assert.Equal("2345", data.GetData()["test"]) + // Data is removed after the (second) TTL + time.Sleep(time.Millisecond) + synctest.Wait() + assert.Equal(3*time.Millisecond, time.Since(start)) + assert.Nil(data.GetData()["test"]) + + // Setting existing key will update the TTL + assert.True(data.SetTTL("test", "1234", time.Millisecond)) + assert.False(data.SetTTL("test", "1234", 3*time.Millisecond)) + start = time.Now() + // Data still exists after the first TTL + time.Sleep(2 * time.Millisecond) + synctest.Wait() + assert.Equal("1234", data.GetData()["test"]) + // Data is removed after the (updated) TTL + time.Sleep(time.Millisecond) + synctest.Wait() + assert.Equal(3*time.Millisecond, time.Since(start)) + assert.Nil(data.GetData()["test"]) + }) +} + +type MockTransientListener struct { + mu sync.Mutex + sending chan struct{} + done chan struct{} + + // +checklocks:mu + data *TransientData +} + +func (l *MockTransientListener) SendMessage(message *ServerMessage) bool { + close(l.sending) + + time.Sleep(10 * time.Millisecond) + + l.mu.Lock() + defer l.mu.Unlock() + defer close(l.done) + + time.Sleep(10 * time.Millisecond) + + return true +} + +func (l *MockTransientListener) Close() { + l.mu.Lock() + defer l.mu.Unlock() + + l.data.RemoveListener(l) +} + +func Test_TransientDataDeadlock(t *testing.T) { + t.Parallel() + data := NewTransientData() + + listener := &MockTransientListener{ + sending: make(chan struct{}), + done: make(chan struct{}), + + data: data, + } + data.AddListener(listener) + + go func() { + <-listener.sending + listener.Close() + }() + + data.Set("foo", "bar") + <-listener.done +} diff --git a/backend_server_test.go b/backend_server_test.go index 42f8b05..c15c239 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -49,6 +49,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/async/eventstest" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" @@ -177,7 +178,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g defer cancel() assert.NoError(events1.Close(ctx)) }) - client1, _ := NewGrpcClientsForTest(t, addr2, nil) + client1, _ := grpctest.NewClientsForTest(t, addr2, nil) hub1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") require.NoError(err) @@ -202,7 +203,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g defer cancel() assert.NoError(events2.Close(ctx)) }) - client2, _ := NewGrpcClientsForTest(t, addr1, nil) + client2, _ := grpctest.NewClientsForTest(t, addr1, nil) hub2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") require.NoError(err) diff --git a/clientsession.go b/clientsession.go index f24d44b..e19b4b5 100644 --- a/clientsession.go +++ b/clientsession.go @@ -41,6 +41,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/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -99,9 +100,9 @@ type ClientSession struct { publisherWaiters async.ChannelWaiters // +checklocksignore // +checklocks:mu - publishers map[StreamType]McuPublisher + publishers map[sfu.StreamType]sfu.Publisher // +checklocks:mu - subscribers map[StreamId]McuSubscriber + subscribers map[sfu.StreamId]sfu.Subscriber // +checklocks:mu pendingClientMessages []*api.ServerMessage @@ -384,7 +385,7 @@ func (s *ClientSession) getRoomJoinTime() time.Time { // +checklocks:s.mu func (s *ClientSession) releaseMcuObjects() { if len(s.publishers) > 0 { - go func(publishers map[StreamType]McuPublisher) { + go func(publishers map[sfu.StreamType]sfu.Publisher) { ctx := context.Background() for _, publisher := range publishers { publisher.Close(ctx) @@ -393,7 +394,7 @@ func (s *ClientSession) releaseMcuObjects() { s.publishers = nil } if len(s.subscribers) > 0 { - go func(subscribers map[StreamId]McuSubscriber) { + go func(subscribers map[sfu.StreamId]sfu.Subscriber) { ctx := context.Background() for _, subscriber := range subscribers { subscriber.Close(ctx) @@ -659,7 +660,7 @@ func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { } // +checklocks:s.mu -func (s *ClientSession) sendOffer(client McuClient, sender api.PublicSessionId, streamType StreamType, offer api.StringMap) { +func (s *ClientSession) sendOffer(client sfu.Client, sender api.PublicSessionId, streamType sfu.StreamType, offer api.StringMap) { offer_message := &api.AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -688,7 +689,7 @@ func (s *ClientSession) sendOffer(client McuClient, sender api.PublicSessionId, } // +checklocks:s.mu -func (s *ClientSession) sendCandidate(client McuClient, sender api.PublicSessionId, streamType StreamType, candidate any) { +func (s *ClientSession) sendCandidate(client sfu.Client, sender api.PublicSessionId, streamType sfu.StreamType, candidate any) { candidate_message := &api.AnswerOfferMessage{ To: s.PublicId(), From: sender, @@ -760,7 +761,7 @@ func (s *ClientSession) SendMessages(messages []*api.ServerMessage) bool { return true } -func (s *ClientSession) OnUpdateOffer(client McuClient, offer api.StringMap) { +func (s *ClientSession) OnUpdateOffer(client sfu.Client, offer api.StringMap) { s.mu.Lock() defer s.mu.Unlock() @@ -772,7 +773,7 @@ func (s *ClientSession) OnUpdateOffer(client McuClient, offer api.StringMap) { } } -func (s *ClientSession) OnIceCandidate(client McuClient, candidate any) { +func (s *ClientSession) OnIceCandidate(client sfu.Client, candidate any) { s.mu.Lock() defer s.mu.Unlock() @@ -793,7 +794,7 @@ func (s *ClientSession) OnIceCandidate(client McuClient, candidate any) { s.logger.Printf("Session %s received candidate %+v for unknown client %s", s.PublicId(), candidate, client.Id()) } -func (s *ClientSession) OnIceCompleted(client McuClient) { +func (s *ClientSession) OnIceCompleted(client sfu.Client) { // TODO(jojo): This causes a JavaScript error when creating a candidate from "null". // Figure out a better way to signal this. @@ -801,10 +802,10 @@ func (s *ClientSession) OnIceCompleted(client McuClient) { // s.OnIceCandidate(client, nil) } -func (s *ClientSession) SubscriberSidUpdated(subscriber McuSubscriber) { +func (s *ClientSession) SubscriberSidUpdated(subscriber sfu.Subscriber) { } -func (s *ClientSession) PublisherClosed(publisher McuPublisher) { +func (s *ClientSession) PublisherClosed(publisher sfu.Publisher) { s.mu.Lock() defer s.mu.Unlock() @@ -816,7 +817,7 @@ func (s *ClientSession) PublisherClosed(publisher McuPublisher) { } } -func (s *ClientSession) SubscriberClosed(subscriber McuSubscriber) { +func (s *ClientSession) SubscriberClosed(subscriber sfu.Subscriber) { s.mu.Lock() defer s.mu.Unlock() @@ -841,13 +842,13 @@ func (e *PermissionError) Error() string { } // +checklocks:s.mu -func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (MediaType, error) { +func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (sfu.MediaType, error) { if sdp == nil { // Should have already been checked when data was validated. return 0, api.ErrNoSdp } - var mediaTypes MediaType + var mediaTypes sfu.MediaType mayPublishMedia := s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_MEDIA) for _, md := range sdp.MediaDescriptions { switch md.MediaName.Media { @@ -856,13 +857,13 @@ func (s *ClientSession) isSdpAllowedToSendLocked(sdp *sdp.SessionDescription) (M return 0, &PermissionError{api.PERMISSION_MAY_PUBLISH_AUDIO} } - mediaTypes |= MediaTypeAudio + mediaTypes |= sfu.MediaTypeAudio case "video": if !mayPublishMedia && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_VIDEO) { return 0, &PermissionError{api.PERMISSION_MAY_PUBLISH_VIDEO} } - mediaTypes |= MediaTypeVideo + mediaTypes |= sfu.MediaTypeVideo } } @@ -899,7 +900,7 @@ func (s *ClientSession) IsAllowedToSend(data *api.MessageClientMessageData) erro } } -func (s *ClientSession) CheckOfferType(streamType StreamType, data *api.MessageClientMessageData) (MediaType, error) { +func (s *ClientSession) CheckOfferType(streamType sfu.StreamType, data *api.MessageClientMessageData) (sfu.MediaType, error) { s.mu.Lock() defer s.mu.Unlock() @@ -907,13 +908,13 @@ func (s *ClientSession) CheckOfferType(streamType StreamType, data *api.MessageC } // +checklocks:s.mu -func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *api.MessageClientMessageData) (MediaType, error) { - if streamType == StreamTypeScreen { +func (s *ClientSession) checkOfferTypeLocked(streamType sfu.StreamType, data *api.MessageClientMessageData) (sfu.MediaType, error) { + if streamType == sfu.StreamTypeScreen { if !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_SCREEN) { return 0, &PermissionError{api.PERMISSION_MAY_PUBLISH_SCREEN} } - return MediaTypeScreen, nil + return sfu.MediaTypeScreen, nil } else if data != nil && data.Type == "offer" { mediaTypes, err := s.isSdpAllowedToSendLocked(data.OfferSdp) if err != nil { @@ -926,7 +927,7 @@ func (s *ClientSession) checkOfferTypeLocked(streamType StreamType, data *api.Me return 0, nil } -func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, streamType StreamType, data *api.MessageClientMessageData) (McuPublisher, error) { +func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu sfu.SFU, streamType sfu.StreamType, data *api.MessageClientMessageData) (sfu.Publisher, error) { s.mu.Lock() defer s.mu.Unlock() @@ -941,7 +942,7 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea s.mu.Unlock() defer s.mu.Lock() - settings := NewPublisherSettings{ + settings := sfu.NewPublisherSettings{ Bitrate: data.Bitrate, MediaTypes: mediaTypes, @@ -952,7 +953,7 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea } if backend := s.Backend(); backend != nil { var maxBitrate api.Bandwidth - if streamType == StreamTypeScreen { + if streamType == sfu.StreamTypeScreen { maxBitrate = backend.MaxScreenBitrate() } else { maxBitrate = backend.MaxStreamBitrate() @@ -971,11 +972,11 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea s.mu.Lock() defer s.mu.Unlock() if s.publishers == nil { - s.publishers = make(map[StreamType]McuPublisher) + s.publishers = make(map[sfu.StreamType]sfu.Publisher) } if prev, found := s.publishers[streamType]; found { // Another thread created the publisher while we were waiting. - go func(pub McuPublisher) { + go func(pub sfu.Publisher) { closeCtx := context.Background() pub.Close(closeCtx) }(publisher) @@ -993,18 +994,18 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu Mcu, strea } // +checklocks:s.mu -func (s *ClientSession) getPublisherLocked(streamType StreamType) McuPublisher { +func (s *ClientSession) getPublisherLocked(streamType sfu.StreamType) sfu.Publisher { return s.publishers[streamType] } -func (s *ClientSession) GetPublisher(streamType StreamType) McuPublisher { +func (s *ClientSession) GetPublisher(streamType sfu.StreamType) sfu.Publisher { s.mu.Lock() defer s.mu.Unlock() return s.getPublisherLocked(streamType) } -func (s *ClientSession) GetOrWaitForPublisher(ctx context.Context, streamType StreamType) McuPublisher { +func (s *ClientSession) GetOrWaitForPublisher(ctx context.Context, streamType sfu.StreamType) sfu.Publisher { s.mu.Lock() defer s.mu.Unlock() @@ -1033,13 +1034,13 @@ func (s *ClientSession) GetOrWaitForPublisher(ctx context.Context, streamType St } } -func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id api.PublicSessionId, streamType StreamType) (McuSubscriber, error) { +func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu sfu.SFU, id api.PublicSessionId, streamType sfu.StreamType) (sfu.Subscriber, error) { s.mu.Lock() defer s.mu.Unlock() // TODO(jojo): Add method to remove subscribers. - subscriber, found := s.subscribers[getStreamId(id, streamType)] + subscriber, found := s.subscribers[sfu.GetStreamId(id, streamType)] if !found { client := s.getClientUnlocked() s.mu.Unlock() @@ -1050,17 +1051,17 @@ func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id a return nil, err } if s.subscribers == nil { - s.subscribers = make(map[StreamId]McuSubscriber) + s.subscribers = make(map[sfu.StreamId]sfu.Subscriber) } - if prev, found := s.subscribers[getStreamId(id, streamType)]; found { + if prev, found := s.subscribers[sfu.GetStreamId(id, streamType)]; found { // Another thread created the subscriber while we were waiting. - go func(sub McuSubscriber) { + go func(sub sfu.Subscriber) { closeCtx := context.Background() sub.Close(closeCtx) }(subscriber) subscriber = prev } else { - s.subscribers[getStreamId(id, streamType)] = subscriber + s.subscribers[sfu.GetStreamId(id, streamType)] = subscriber } s.logger.Printf("Subscribing %s from %s as %s in session %s", streamType, id, subscriber.Id(), s.PublicId()) } @@ -1068,11 +1069,11 @@ func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu Mcu, id a return subscriber, nil } -func (s *ClientSession) GetSubscriber(id api.PublicSessionId, streamType StreamType) McuSubscriber { +func (s *ClientSession) GetSubscriber(id api.PublicSessionId, streamType sfu.StreamType) sfu.Subscriber { s.mu.Lock() defer s.mu.Unlock() - return s.subscribers[getStreamId(id, streamType)] + return s.subscribers[sfu.GetStreamId(id, streamType)] } func (s *ClientSession) processAsyncNatsMessage(msg *nats.Msg) { @@ -1094,10 +1095,10 @@ func (s *ClientSession) processAsyncMessage(message *events.AsyncMessage) { defer s.mu.Unlock() if !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_MEDIA) { - if publisher, found := s.publishers[StreamTypeVideo]; found { - if (publisher.HasMedia(MediaTypeAudio) && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_AUDIO)) || - (publisher.HasMedia(MediaTypeVideo) && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_VIDEO)) { - delete(s.publishers, StreamTypeVideo) + if publisher, found := s.publishers[sfu.StreamTypeVideo]; found { + if (publisher.HasMedia(sfu.MediaTypeAudio) && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_AUDIO)) || + (publisher.HasMedia(sfu.MediaTypeVideo) && !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_VIDEO)) { + delete(s.publishers, sfu.StreamTypeVideo) s.logger.Printf("Session %s is no longer allowed to publish media, closing publisher %s", s.PublicId(), publisher.Id()) go func() { publisher.Close(context.Background()) @@ -1107,8 +1108,8 @@ func (s *ClientSession) processAsyncMessage(message *events.AsyncMessage) { } } if !s.hasPermissionLocked(api.PERMISSION_MAY_PUBLISH_SCREEN) { - if publisher, found := s.publishers[StreamTypeScreen]; found { - delete(s.publishers, StreamTypeScreen) + if publisher, found := s.publishers[sfu.StreamTypeScreen]; found { + delete(s.publishers, sfu.StreamTypeScreen) s.logger.Printf("Session %s is no longer allowed to publish screen, closing publisher %s", s.PublicId(), publisher.Id()) go func() { publisher.Close(context.Background()) @@ -1130,7 +1131,7 @@ func (s *ClientSession) processAsyncMessage(message *events.AsyncMessage) { ctx, cancel := context.WithTimeout(s.Context(), s.hub.mcuTimeout) defer cancel() - mc, err := s.GetOrCreateSubscriber(ctx, s.hub.mcu, message.SendOffer.SessionId, StreamType(message.SendOffer.Data.RoomType)) + mc, err := s.GetOrCreateSubscriber(ctx, s.hub.mcu, message.SendOffer.SessionId, sfu.StreamType(message.SendOffer.Data.RoomType)) if err != nil { s.logger.Printf("Could not create MCU subscriber for session %s to process sendoffer in %s: %s", message.SendOffer.SessionId, s.PublicId(), err) if err := s.events.PublishSessionMessage(message.SendOffer.SessionId, s.backend, &events.AsyncMessage{ diff --git a/clientsession_test.go b/clientsession_test.go index f59b45d..49554b7 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -35,6 +35,8 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -47,7 +49,7 @@ func TestBandwidth_Client(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := test.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -83,15 +85,15 @@ func TestBandwidth_Client(t *testing.T) { pub := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(pub) - assert.Equal(bitrate, pub.settings.Bitrate) + assert.Equal(bitrate, pub.Settings().Bitrate) } func TestBandwidth_Backend(t *testing.T) { t.Parallel() - streamTypes := []StreamType{ - StreamTypeVideo, - StreamTypeScreen, + streamTypes := []sfu.StreamType{ + sfu.StreamTypeVideo, + sfu.StreamTypeScreen, } for _, streamType := range streamTypes { @@ -113,7 +115,7 @@ func TestBandwidth_Backend(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := test.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -158,12 +160,12 @@ func TestBandwidth_Backend(t *testing.T) { require.NotNil(pub, "Could not find publisher") var expectBitrate api.Bandwidth - if streamType == StreamTypeVideo { + if streamType == sfu.StreamTypeVideo { expectBitrate = backend.MaxStreamBitrate() } else { expectBitrate = backend.MaxScreenBitrate() } - assert.Equal(expectBitrate, pub.settings.Bitrate) + assert.Equal(expectBitrate, pub.Settings().Bitrate) }) } } diff --git a/cmd/proxy/proxy_remote.go b/cmd/proxy/proxy_remote.go index 3e8712e..3e326c3 100644 --- a/cmd/proxy/proxy_remote.go +++ b/cmd/proxy/proxy_remote.go @@ -39,10 +39,10 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/gorilla/websocket" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" ) const ( @@ -91,9 +91,9 @@ type RemoteConnection struct { helloReceived bool // +checklocks:mu - pendingMessages []*signaling.ProxyClientMessage + pendingMessages []*proxy.ClientMessage // +checklocks:mu - messageCallbacks map[string]chan *signaling.ProxyServerMessage + messageCallbacks map[string]chan *proxy.ServerMessage } func NewRemoteConnection(p *ProxyServer, proxyUrl string, tokenId string, tokenKey *rsa.PrivateKey, tlsConfig *tls.Config) (*RemoteConnection, error) { @@ -117,7 +117,7 @@ func NewRemoteConnection(p *ProxyServer, proxyUrl string, tokenId string, tokenK reconnectTimer: time.NewTimer(0), - messageCallbacks: make(map[string]chan *signaling.ProxyServerMessage), + messageCallbacks: make(map[string]chan *proxy.ServerMessage), } result.reconnectInterval.Store(int64(initialReconnectInterval)) @@ -226,10 +226,10 @@ func (c *RemoteConnection) scheduleReconnectLocked() { // +checklocks:c.mu func (c *RemoteConnection) sendHello(ctx context.Context) error { c.helloMsgId = strconv.FormatInt(c.msgId.Add(1), 10) - msg := &signaling.ProxyClientMessage{ + msg := &proxy.ClientMessage{ Id: c.helloMsgId, Type: "hello", - Hello: &signaling.HelloProxyClientMessage{ + Hello: &proxy.HelloClientMessage{ Version: "1.0", }, } @@ -301,7 +301,7 @@ func (c *RemoteConnection) Close() error { } func (c *RemoteConnection) createToken(subject string) (string, error) { - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now()), Issuer: c.tokenId, @@ -317,7 +317,7 @@ func (c *RemoteConnection) createToken(subject string) (string, error) { return tokenString, nil } -func (c *RemoteConnection) SendMessage(msg *signaling.ProxyClientMessage) error { +func (c *RemoteConnection) SendMessage(msg *proxy.ClientMessage) error { c.mu.Lock() defer c.mu.Unlock() @@ -325,7 +325,7 @@ func (c *RemoteConnection) SendMessage(msg *signaling.ProxyClientMessage) error } // +checklocks:c.mu -func (c *RemoteConnection) deferMessage(ctx context.Context, msg *signaling.ProxyClientMessage) { +func (c *RemoteConnection) deferMessage(ctx context.Context, msg *proxy.ClientMessage) { c.pendingMessages = append(c.pendingMessages, msg) if ctx.Done() != nil { go func() { @@ -344,7 +344,7 @@ func (c *RemoteConnection) deferMessage(ctx context.Context, msg *signaling.Prox } // +checklocks:c.mu -func (c *RemoteConnection) sendMessageLocked(ctx context.Context, msg *signaling.ProxyClientMessage) error { +func (c *RemoteConnection) sendMessageLocked(ctx context.Context, msg *proxy.ClientMessage) error { if c.conn == nil { // Defer until connected. c.deferMessage(ctx, msg) @@ -390,7 +390,7 @@ func (c *RemoteConnection) readPump(conn *websocket.Conn) { continue } - var message signaling.ProxyServerMessage + var message proxy.ServerMessage if err := json.Unmarshal(msg, &message); err != nil { c.logger.Printf("could not decode message %s: %s", string(msg), err) continue @@ -446,7 +446,7 @@ func (c *RemoteConnection) writePump() { } } -func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { +func (c *RemoteConnection) processHello(msg *proxy.ServerMessage) { c.mu.Lock() defer c.mu.Unlock() @@ -501,7 +501,7 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) { } } -func (c *RemoteConnection) handleCallback(msg *signaling.ProxyServerMessage) bool { +func (c *RemoteConnection) handleCallback(msg *proxy.ServerMessage) bool { if msg.Id == "" { return false } @@ -520,7 +520,7 @@ func (c *RemoteConnection) handleCallback(msg *signaling.ProxyServerMessage) boo return true } -func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { +func (c *RemoteConnection) processMessage(msg *proxy.ServerMessage) { if c.handleCallback(msg) { return } @@ -542,7 +542,7 @@ func (c *RemoteConnection) processMessage(msg *signaling.ProxyServerMessage) { } } -func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { +func (c *RemoteConnection) processEvent(msg *proxy.ServerMessage) { switch msg.Event.Type { case "update-load": // Ignore @@ -554,7 +554,7 @@ func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) { } } -func (c *RemoteConnection) sendMessageWithCallbackLocked(ctx context.Context, msg *signaling.ProxyClientMessage) (string, <-chan *signaling.ProxyServerMessage, error) { +func (c *RemoteConnection) sendMessageWithCallbackLocked(ctx context.Context, msg *proxy.ClientMessage) (string, <-chan *proxy.ServerMessage, error) { msg.Id = strconv.FormatInt(c.msgId.Add(1), 10) c.mu.Lock() @@ -564,12 +564,12 @@ func (c *RemoteConnection) sendMessageWithCallbackLocked(ctx context.Context, ms return "", nil, err } - ch := make(chan *signaling.ProxyServerMessage, 1) + ch := make(chan *proxy.ServerMessage, 1) c.messageCallbacks[msg.Id] = ch return msg.Id, ch, nil } -func (c *RemoteConnection) RequestMessage(ctx context.Context, msg *signaling.ProxyClientMessage) (*signaling.ProxyServerMessage, error) { +func (c *RemoteConnection) RequestMessage(ctx context.Context, msg *proxy.ClientMessage) (*proxy.ServerMessage, error) { id, ch, err := c.sendMessageWithCallbackLocked(ctx, msg) if err != nil { return nil, err @@ -600,7 +600,7 @@ func (c *RemoteConnection) SendBye() error { return nil } - return c.sendMessageLocked(c.closeCtx, &signaling.ProxyClientMessage{ + return c.sendMessageLocked(c.closeCtx, &proxy.ClientMessage{ Type: "bye", }) } diff --git a/cmd/proxy/proxy_remote_test.go b/cmd/proxy/proxy_remote_test.go index 0c98002..8b4dd47 100644 --- a/cmd/proxy/proxy_remote_test.go +++ b/cmd/proxy/proxy_remote_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - signaling "github.com/strukturag/nextcloud-spreed-signaling" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" ) func (c *RemoteConnection) WaitForConnection(ctx context.Context) error { @@ -202,9 +202,9 @@ func Test_ProxyRemoteConnectionCreatePublisher(t *testing.T) { port := 1234 rtcpPort := 2345 - _, err = conn.RequestMessage(ctx, &signaling.ProxyClientMessage{ + _, err = conn.RequestMessage(ctx, &proxy.ClientMessage{ Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "publish-remote", ClientId: publisherId, Hostname: hostname, diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index 12f8fcd..6817cf7 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -46,7 +46,6 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" "github.com/gorilla/websocket" - "github.com/notedit/janus-go" "github.com/prometheus/client_golang/prometheus/promhttp" signaling "github.com/strukturag/nextcloud-spreed-signaling" @@ -56,6 +55,10 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" + janusapi "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) const ( @@ -117,7 +120,7 @@ type ProxyServer struct { logger log.Logger url string - mcu signaling.Mcu + mcu sfu.SFU stopped atomic.Bool load atomic.Uint64 @@ -143,7 +146,7 @@ type ProxyServer struct { clientsLock sync.RWMutex // +checklocks:clientsLock - clients map[string]signaling.McuClient + clients map[string]sfu.Client // +checklocks:clientsLock clientIds map[string]string @@ -366,7 +369,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * ReadBufferSize: websocketReadBufferSize, WriteBufferSize: websocketWriteBufferSize, Subprotocols: []string{ - signaling.JanusEventsSubprotocol, + janus.EventsSubprotocol, }, }, @@ -375,7 +378,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * cookie: sessionIds, sessions: make(map[uint64]*ProxySession), - clients: make(map[string]signaling.McuClient), + clients: make(map[string]sfu.Client), clientIds: make(map[string]string), tokenId: tokenId, @@ -434,7 +437,7 @@ func (s *ProxyServer) Start(cfg *goconf.ConfigFile) error { mcuType, _ := cfg.GetString("mcu", "type") if mcuType == "" { - mcuType = signaling.McuTypeDefault + mcuType = sfu.TypeDefault } backoff, err := async.NewExponentialBackoff(initialMcuRetry, maxMcuRetry) @@ -445,13 +448,13 @@ func (s *ProxyServer) Start(cfg *goconf.ConfigFile) error { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() - var mcu signaling.Mcu + var mcu sfu.SFU for { switch mcuType { - case signaling.McuTypeJanus: - mcu, err = signaling.NewMcuJanus(ctx, s.url, cfg) + case sfu.TypeJanus: + mcu, err = janus.NewJanusSFU(ctx, s.url, cfg) if err == nil { - signaling.RegisterJanusMcuStats() + janus.RegisterStats() } default: return fmt.Errorf("unsupported MCU type: %s", mcuType) @@ -502,10 +505,10 @@ loop: } } -func (s *ProxyServer) newLoadEvent(load uint64, incoming api.Bandwidth, outgoing api.Bandwidth) *signaling.ProxyServerMessage { - msg := &signaling.ProxyServerMessage{ +func (s *ProxyServer) newLoadEvent(load uint64, incoming api.Bandwidth, outgoing api.Bandwidth) *proxy.ServerMessage { + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "update-load", Load: load, }, @@ -513,7 +516,7 @@ func (s *ProxyServer) newLoadEvent(load uint64, incoming api.Bandwidth, outgoing maxIncoming := s.maxIncoming.Load() maxOutgoing := s.maxOutgoing.Load() if maxIncoming > 0 || maxOutgoing > 0 || incoming != 0 || outgoing != 0 { - msg.Event.Bandwidth = &signaling.EventProxyServerBandwidth{ + msg.Event.Bandwidth = &proxy.EventServerBandwidth{ Received: incoming, Sent: outgoing, } @@ -603,9 +606,9 @@ func (s *ProxyServer) ScheduleShutdown() { return } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "shutdown-scheduled", }, } @@ -683,9 +686,9 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { } ctx := log.NewLoggerContext(r.Context(), s.logger) - if conn.Subprotocol() == signaling.JanusEventsSubprotocol { + if conn.Subprotocol() == janus.EventsSubprotocol { agent := r.Header.Get("User-Agent") - signaling.RunJanusEventsHandler(ctx, s.mcu, conn, addr, agent) + janus.RunEventsHandler(ctx, s.mcu, conn, addr, agent) return } @@ -705,9 +708,9 @@ func (s *ProxyServer) clientClosed(client *signaling.Client) { func (s *ProxyServer) onMcuConnected() { s.logger.Printf("Connection to %s established", s.url) - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "backend-connected", }, } @@ -724,9 +727,9 @@ func (s *ProxyServer) onMcuDisconnected() { } s.logger.Printf("Connection to %s lost", s.url) - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "backend-disconnected", }, } @@ -743,9 +746,9 @@ func (s *ProxyServer) sendCurrentLoad(session *ProxySession) { } func (s *ProxyServer) sendShutdownScheduled(session *ProxySession) { - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "shutdown-scheduled", }, } @@ -756,7 +759,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { if proxyDebugMessages { s.logger.Printf("Message: %s", string(data)) } - var message signaling.ProxyClientMessage + var message proxy.ClientMessage if err := message.UnmarshalJSON(data); err != nil { if session := client.GetSession(); session != nil { s.logger.Printf("Error decoding message from client %s: %v", session.PublicId(), err) @@ -817,18 +820,18 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { prev := session.SetClient(client) if prev != nil { - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "bye", - Bye: &signaling.ByeProxyServerMessage{ + Bye: &proxy.ByeServerMessage{ Reason: "session_resumed", }, } prev.SendMessage(msg) } - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "hello", - Hello: &signaling.HelloProxyServerMessage{ + Hello: &proxy.HelloServerMessage{ Version: api.HelloVersionV1, SessionId: session.PublicId(), Server: s.welcomeMsg, @@ -875,15 +878,15 @@ func (p *proxyRemotePublisher) PublisherId() api.PublicSessionId { return p.publisherId } -func (p *proxyRemotePublisher) StartPublishing(ctx context.Context, publisher signaling.McuRemotePublisherProperties) error { +func (p *proxyRemotePublisher) StartPublishing(ctx context.Context, publisher sfu.RemotePublisherProperties) error { conn, err := p.proxy.getRemoteConnection(p.remoteUrl) if err != nil { return err } - if _, err := conn.RequestMessage(ctx, &signaling.ProxyClientMessage{ + if _, err := conn.RequestMessage(ctx, &proxy.ClientMessage{ Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "publish-remote", ClientId: string(p.publisherId), Hostname: p.proxy.remoteHostname, @@ -897,7 +900,7 @@ func (p *proxyRemotePublisher) StartPublishing(ctx context.Context, publisher si return nil } -func (p *proxyRemotePublisher) StopPublishing(ctx context.Context, publisher signaling.McuRemotePublisherProperties) error { +func (p *proxyRemotePublisher) StopPublishing(ctx context.Context, publisher sfu.RemotePublisherProperties) error { defer p.proxy.removeRemotePublisher(p) conn, err := p.proxy.getRemoteConnection(p.remoteUrl) @@ -905,9 +908,9 @@ func (p *proxyRemotePublisher) StopPublishing(ctx context.Context, publisher sig return err } - if _, err := conn.RequestMessage(ctx, &signaling.ProxyClientMessage{ + if _, err := conn.RequestMessage(ctx, &proxy.ClientMessage{ Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "unpublish-remote", ClientId: string(p.publisherId), Hostname: p.proxy.remoteHostname, @@ -921,15 +924,15 @@ func (p *proxyRemotePublisher) StopPublishing(ctx context.Context, publisher sig return nil } -func (p *proxyRemotePublisher) GetStreams(ctx context.Context) ([]signaling.PublisherStream, error) { +func (p *proxyRemotePublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { conn, err := p.proxy.getRemoteConnection(p.remoteUrl) if err != nil { return nil, err } - response, err := conn.RequestMessage(ctx, &signaling.ProxyClientMessage{ + response, err := conn.RequestMessage(ctx, &proxy.ClientMessage{ Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "get-publisher-streams", ClientId: string(p.publisherId), }, @@ -988,7 +991,7 @@ func (s *ProxyServer) removeRemotePublisher(publisher *proxyRemotePublisher) { } } -func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, session *ProxySession, message *signaling.ProxyClientMessage) { +func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, session *ProxySession, message *proxy.ClientMessage) { cmd := message.Command statsCommandMessagesTotal.WithLabelValues(cmd.Type).Inc() @@ -1006,7 +1009,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s id := uuid.New().String() settings := cmd.PublisherSettings if settings == nil { - settings = &signaling.NewPublisherSettings{ + settings = &sfu.NewPublisherSettings{ Bitrate: cmd.Bitrate, // nolint MediaTypes: cmd.MediaTypes, // nolint } @@ -1026,10 +1029,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s session.StorePublisher(ctx, id, publisher) s.StoreClient(id, publisher) - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: id, Bitrate: publisher.MaxBitrate(), }, @@ -1040,7 +1043,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s case "create-subscriber": id := uuid.New().String() publisherId := cmd.PublisherId - var subscriber signaling.McuSubscriber + var subscriber sfu.Subscriber var err error handleCreateError := func(err error) { @@ -1048,7 +1051,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s s.logger.Printf("Timeout while creating %s subscriber on %s for %s", cmd.StreamType, publisherId, session.PublicId()) session.sendMessage(message.NewErrorServerMessage(TimeoutCreatingSubscriber)) return - } else if errors.Is(err, signaling.ErrRemoteStreamsNotSupported) { + } else if errors.Is(err, janus.ErrRemoteStreamsNotSupported) { session.sendMessage(message.NewErrorServerMessage(RemoteSubscribersNotSupported)) return } @@ -1063,7 +1066,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - remoteMcu, ok := s.mcu.(signaling.RemoteMcu) + remoteMcu, ok := s.mcu.(sfu.RemoteSfu) if !ok { session.sendMessage(message.NewErrorServerMessage(RemoteSubscribersNotSupported)) return @@ -1095,7 +1098,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s publisherId: publisherId, } - var publisher signaling.McuRemotePublisher + var publisher sfu.RemotePublisher publisher, err = remoteMcu.NewRemotePublisher(subCtx, session, controller, cmd.StreamType) if err != nil { handleCreateError(err) @@ -1131,10 +1134,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s session.StoreSubscriber(ctx, id, subscriber) s.StoreClient(id, subscriber) - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: id, Sid: subscriber.Sid(), }, @@ -1149,7 +1152,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuPublisher) + publisher, ok := client.(sfu.Publisher) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1169,10 +1172,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s client.Close(context.Background()) }() - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: cmd.ClientId, }, } @@ -1184,7 +1187,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - subscriber, ok := client.(signaling.McuSubscriber) + subscriber, ok := client.(sfu.Subscriber) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1204,10 +1207,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s client.Close(context.Background()) }() - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: cmd.ClientId, }, } @@ -1219,7 +1222,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuRemoteAwarePublisher) + publisher, ok := client.(sfu.RemoteAwarePublisher) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1229,8 +1232,8 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s defer cancel() if err := publisher.PublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { - var je *janus.ErrorMsg - if !errors.As(err, &je) || je.Err.Code != signaling.JANUS_VIDEOROOM_ERROR_ID_EXISTS { + var je *janusapi.ErrorMsg + if !errors.As(err, &je) || je.Err.Code != janusapi.JANUS_VIDEOROOM_ERROR_ID_EXISTS { s.logger.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return @@ -1256,10 +1259,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s } session.AddRemotePublisher(publisher, cmd.Hostname, cmd.Port, cmd.RtcpPort) - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: cmd.ClientId, }, } @@ -1271,7 +1274,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuRemoteAwarePublisher) + publisher, ok := client.(sfu.RemoteAwarePublisher) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1288,10 +1291,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s session.RemoveRemotePublisher(publisher, cmd.Hostname, cmd.Port, cmd.RtcpPort) - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: cmd.ClientId, }, } @@ -1303,7 +1306,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - publisher, ok := client.(signaling.McuPublisherWithStreams) + publisher, ok := client.(sfu.PublisherWithStreams) if !ok { session.sendMessage(message.NewErrorServerMessage(UnknownClient)) return @@ -1316,10 +1319,10 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s return } - response := &signaling.ProxyServerMessage{ + response := &proxy.ServerMessage{ Id: message.Id, Type: "command", - Command: &signaling.CommandProxyServerMessage{ + Command: &proxy.CommandServerMessage{ Id: cmd.ClientId, Streams: streams, }, @@ -1331,7 +1334,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s } } -func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, session *ProxySession, message *signaling.ProxyClientMessage) { +func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, session *ProxySession, message *proxy.ClientMessage) { payload := message.Payload mcuClient := s.GetClient(payload.ClientId) if mcuClient == nil { @@ -1358,10 +1361,10 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s } case "endOfCandidates": // Ignore but confirm, not passed along to Janus anyway. - session.sendMessage(&signaling.ProxyServerMessage{ + session.sendMessage(&proxy.ServerMessage{ Id: message.Id, Type: "payload", - Payload: &signaling.PayloadProxyServerMessage{ + Payload: &proxy.PayloadServerMessage{ Type: payload.Type, ClientId: payload.ClientId, }, @@ -1390,7 +1393,7 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s defer cancel() mcuClient.SendMessage(ctx2, nil, mcuData, func(err error, response api.StringMap) { - var responseMsg *signaling.ProxyServerMessage + var responseMsg *proxy.ServerMessage if errors.Is(err, api.ErrCandidateFiltered) { // Silently ignore filtered candidates. err = nil @@ -1400,10 +1403,10 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s s.logger.Printf("Error sending %+v to %s client %s: %s", mcuData, mcuClient.StreamType(), payload.ClientId, err) responseMsg = message.NewWrappedErrorServerMessage(err) } else { - responseMsg = &signaling.ProxyServerMessage{ + responseMsg = &proxy.ServerMessage{ Id: message.Id, Type: "payload", - Payload: &signaling.PayloadProxyServerMessage{ + Payload: &proxy.PayloadServerMessage{ Type: payload.Type, ClientId: payload.ClientId, Payload: response, @@ -1415,14 +1418,14 @@ func (s *ProxyServer) processPayload(ctx context.Context, client *ProxyClient, s }) } -func (s *ProxyServer) processBye(ctx context.Context, client *ProxyClient, session *ProxySession, message *signaling.ProxyClientMessage) { +func (s *ProxyServer) processBye(ctx context.Context, client *ProxyClient, session *ProxySession, message *proxy.ClientMessage) { s.logger.Printf("Closing session %s", session.PublicId()) s.DeleteSession(session.Sid()) } -func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, string, error) { +func (s *ProxyServer) parseToken(tokenValue string) (*proxy.TokenClaims, string, error) { reason := "auth-failed" - token, err := jwt.ParseWithClaims(tokenValue, &signaling.TokenClaims{}, func(token *jwt.Token) (any, error) { + token, err := jwt.ParseWithClaims(tokenValue, &proxy.TokenClaims{}, func(token *jwt.Token) (any, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { s.logger.Printf("Unexpected signing method: %v", token.Header["alg"]) @@ -1430,7 +1433,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } - claims, ok := token.Claims.(*signaling.TokenClaims) + claims, ok := token.Claims.(*proxy.TokenClaims) if !ok { s.logger.Printf("Unsupported claims type: %+v", token.Claims) reason = "unsupported-claims" @@ -1466,7 +1469,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str return nil, reason, TokenAuthFailed } - claims, ok := token.Claims.(*signaling.TokenClaims) + claims, ok := token.Claims.(*proxy.TokenClaims) if !ok || !token.Valid { return nil, "auth-failed", TokenAuthFailed } @@ -1480,7 +1483,7 @@ func (s *ProxyServer) parseToken(tokenValue string) (*signaling.TokenClaims, str return claims, "", nil } -func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*ProxySession, error) { +func (s *ProxyServer) NewSession(hello *proxy.HelloClientMessage) (*ProxySession, error) { if proxyDebugMessages { s.logger.Printf("Hello: %+v", hello) } @@ -1558,14 +1561,14 @@ func (s *ProxyServer) deleteSessionLocked(id uint64) { } } -func (s *ProxyServer) StoreClient(id string, client signaling.McuClient) { +func (s *ProxyServer) StoreClient(id string, client sfu.Client) { s.clientsLock.Lock() defer s.clientsLock.Unlock() s.clients[id] = client s.clientIds[client.Id()] = id } -func (s *ProxyServer) DeleteClient(id string, client signaling.McuClient) bool { +func (s *ProxyServer) DeleteClient(id string, client sfu.Client) bool { s.clientsLock.Lock() defer s.clientsLock.Unlock() if _, found := s.clients[id]; !found { @@ -1593,7 +1596,7 @@ func (s *ProxyServer) GetClientsLoad() (load uint64, incoming api.Bandwidth, out for _, c := range s.clients { // Use "current" bandwidth usage if supported. - if bw, ok := c.(signaling.McuClientWithBandwidth); ok { + if bw, ok := c.(sfu.ClientWithBandwidth); ok { if bandwidth := bw.Bandwidth(); bandwidth != nil { incoming += bandwidth.Received outgoing += bandwidth.Sent @@ -1602,9 +1605,9 @@ func (s *ProxyServer) GetClientsLoad() (load uint64, incoming api.Bandwidth, out } bitrate := c.MaxBitrate() - if _, ok := c.(signaling.McuPublisher); ok { + if _, ok := c.(sfu.Publisher); ok { incoming += bitrate - } else if _, ok := c.(signaling.McuSubscriber); ok { + } else if _, ok := c.(sfu.Subscriber); ok { outgoing += bitrate } } @@ -1613,17 +1616,17 @@ func (s *ProxyServer) GetClientsLoad() (load uint64, incoming api.Bandwidth, out return } -func (s *ProxyServer) GetClient(id string) signaling.McuClient { +func (s *ProxyServer) GetClient(id string) sfu.Client { s.clientsLock.RLock() defer s.clientsLock.RUnlock() return s.clients[id] } -func (s *ProxyServer) GetPublisher(publisherId string) signaling.McuPublisher { +func (s *ProxyServer) GetPublisher(publisherId string) sfu.Publisher { s.clientsLock.RLock() defer s.clientsLock.RUnlock() for _, c := range s.clients { - pub, ok := c.(signaling.McuPublisher) + pub, ok := c.(sfu.Publisher) if !ok { continue } @@ -1635,7 +1638,7 @@ func (s *ProxyServer) GetPublisher(publisherId string) signaling.McuPublisher { return nil } -func (s *ProxyServer) GetClientId(client signaling.McuClient) string { +func (s *ProxyServer) GetClientId(client sfu.Client) string { s.clientsLock.RLock() defer s.clientsLock.RUnlock() return s.clientIds[client.Id()] @@ -1710,7 +1713,7 @@ func (s *ProxyServer) getRemoteConnection(url string) (*RemoteConnection, error) return conn, nil } -func (s *ProxyServer) PublisherDeleted(publisher signaling.McuPublisher) { +func (s *ProxyServer) PublisherDeleted(publisher sfu.Publisher) { s.sessionsLock.RLock() defer s.sessionsLock.RUnlock() diff --git a/cmd/proxy/proxy_server_test.go b/cmd/proxy/proxy_server_test.go index a5a87f3..ed71a54 100644 --- a/cmd/proxy/proxy_server_test.go +++ b/cmd/proxy/proxy_server_test.go @@ -44,10 +44,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -156,9 +157,9 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httpte func TestTokenValid(t *testing.T) { t.Parallel() - proxy, key, _ := newProxyServerForTest(t) + proxyServer, key, _ := newProxyServerForTest(t) - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, @@ -168,20 +169,20 @@ func TestTokenValid(t *testing.T) { tokenString, err := token.SignedString(key) require.NoError(t, err) - hello := &signaling.HelloProxyClientMessage{ + hello := &proxy.HelloClientMessage{ Version: "1.0", Token: tokenString, } - if session, err := proxy.NewSession(hello); assert.NoError(t, err) { - defer proxy.DeleteSession(session.Sid()) + if session, err := proxyServer.NewSession(hello); assert.NoError(t, err) { + defer proxyServer.DeleteSession(session.Sid()) } } func TestTokenNotSigned(t *testing.T) { t.Parallel() - proxy, _, _ := newProxyServerForTest(t) + proxyServer, _, _ := newProxyServerForTest(t) - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, @@ -191,22 +192,22 @@ func TestTokenNotSigned(t *testing.T) { tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType) require.NoError(t, err) - hello := &signaling.HelloProxyClientMessage{ + hello := &proxy.HelloClientMessage{ Version: "1.0", Token: tokenString, } - if session, err := proxy.NewSession(hello); !assert.ErrorIs(t, err, TokenAuthFailed) { + if session, err := proxyServer.NewSession(hello); !assert.ErrorIs(t, err, TokenAuthFailed) { if session != nil { - defer proxy.DeleteSession(session.Sid()) + defer proxyServer.DeleteSession(session.Sid()) } } } func TestTokenUnknown(t *testing.T) { t.Parallel() - proxy, key, _ := newProxyServerForTest(t) + proxyServer, key, _ := newProxyServerForTest(t) - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest + "2", @@ -216,22 +217,22 @@ func TestTokenUnknown(t *testing.T) { tokenString, err := token.SignedString(key) require.NoError(t, err) - hello := &signaling.HelloProxyClientMessage{ + hello := &proxy.HelloClientMessage{ Version: "1.0", Token: tokenString, } - if session, err := proxy.NewSession(hello); !assert.ErrorIs(t, err, TokenAuthFailed) { + if session, err := proxyServer.NewSession(hello); !assert.ErrorIs(t, err, TokenAuthFailed) { if session != nil { - defer proxy.DeleteSession(session.Sid()) + defer proxyServer.DeleteSession(session.Sid()) } } } func TestTokenInFuture(t *testing.T) { t.Parallel() - proxy, key, _ := newProxyServerForTest(t) + proxyServer, key, _ := newProxyServerForTest(t) - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), Issuer: TokenIdForTest, @@ -241,22 +242,22 @@ func TestTokenInFuture(t *testing.T) { tokenString, err := token.SignedString(key) require.NoError(t, err) - hello := &signaling.HelloProxyClientMessage{ + hello := &proxy.HelloClientMessage{ Version: "1.0", Token: tokenString, } - if session, err := proxy.NewSession(hello); !assert.ErrorIs(t, err, TokenNotValidYet) { + if session, err := proxyServer.NewSession(hello); !assert.ErrorIs(t, err, TokenNotValidYet) { if session != nil { - defer proxy.DeleteSession(session.Sid()) + defer proxyServer.DeleteSession(session.Sid()) } } } func TestTokenExpired(t *testing.T) { t.Parallel() - proxy, key, _ := newProxyServerForTest(t) + proxyServer, key, _ := newProxyServerForTest(t) - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge * 2)), Issuer: TokenIdForTest, @@ -266,13 +267,13 @@ func TestTokenExpired(t *testing.T) { tokenString, err := token.SignedString(key) require.NoError(t, err) - hello := &signaling.HelloProxyClientMessage{ + hello := &proxy.HelloClientMessage{ Version: "1.0", Token: tokenString, } - if session, err := proxy.NewSession(hello); !assert.ErrorIs(t, err, TokenExpired) { + if session, err := proxyServer.NewSession(hello); !assert.ErrorIs(t, err, TokenExpired) { if session != nil { - defer proxy.DeleteSession(session.Sid()) + defer proxyServer.DeleteSession(session.Sid()) } } } @@ -392,18 +393,18 @@ func (m *TestMCU) GetServerInfoSfu() *talk.BackendServerInfoSfu { return nil } -func (m *TestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *TestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { return nil, errors.New("not implemented") } -func (m *TestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher api.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { +func (m *TestMCU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { return nil, errors.New("not implemented") } type TestMCUPublisher struct { id api.PublicSessionId sid string - streamType signaling.StreamType + streamType sfu.StreamType } func (p *TestMCUPublisher) Id() string { @@ -418,7 +419,7 @@ func (p *TestMCUPublisher) Sid() string { return p.sid } -func (p *TestMCUPublisher) StreamType() signaling.StreamType { +func (p *TestMCUPublisher) StreamType() sfu.StreamType { return p.streamType } @@ -433,14 +434,14 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *api.Message callback(errors.New("not implemented"), nil) } -func (p *TestMCUPublisher) HasMedia(signaling.MediaType) bool { +func (p *TestMCUPublisher) HasMedia(sfu.MediaType) bool { return false } -func (p *TestMCUPublisher) SetMedia(mediaTypes signaling.MediaType) { +func (p *TestMCUPublisher) SetMedia(mediaTypes sfu.MediaType) { } -func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]signaling.PublisherStream, error) { +func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { return nil, errors.New("not implemented") } @@ -459,14 +460,14 @@ type PublisherTestMCU struct { type TestPublisherWithBandwidth struct { TestMCUPublisher - bandwidth *signaling.McuClientBandwidthInfo + bandwidth *sfu.ClientBandwidthInfo } -func (p *TestPublisherWithBandwidth) Bandwidth() *signaling.McuClientBandwidthInfo { +func (p *TestPublisherWithBandwidth) Bandwidth() *sfu.ClientBandwidthInfo { return p.bandwidth } -func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { publisher := &TestPublisherWithBandwidth{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -474,7 +475,7 @@ func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener signaling. streamType: streamType, }, - bandwidth: &signaling.McuClientBandwidthInfo{ + bandwidth: &sfu.ClientBandwidthInfo{ Sent: api.BandwidthFromBytes(1000), Received: api.BandwidthFromBytes(2000), }, @@ -494,13 +495,13 @@ func TestProxyPublisherBandwidth(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) - proxy.maxIncoming.Store(api.BandwidthFromBytes(10000)) - proxy.maxOutgoing.Store(api.BandwidthFromBytes(10000)) + proxyServer.maxIncoming.Store(api.BandwidthFromBytes(10000)) + proxyServer.maxOutgoing.Store(api.BandwidthFromBytes(10000)) mcu := NewPublisherTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -517,12 +518,12 @@ func TestProxyPublisherBandwidth(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, }, })) @@ -533,7 +534,7 @@ func TestProxyPublisherBandwidth(t *testing.T) { } } - proxy.updateLoad() + proxyServer.updateLoad() if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { if err := checkMessageType(message, "event"); assert.NoError(err) && assert.Equal("update-load", message.Event.Type) { @@ -574,7 +575,7 @@ func NewHangingTestMCU(t *testing.T) *HangingTestMCU { } } -func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { ctx2, cancel := context.WithTimeout(m.ctx, testTimeout*2) defer cancel() @@ -592,7 +593,7 @@ func (m *HangingTestMCU) NewPublisher(ctx context.Context, listener signaling.Mc } } -func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener signaling.McuListener, publisher api.PublicSessionId, streamType signaling.StreamType, initiator signaling.McuInitiator) (signaling.McuSubscriber, error) { +func (m *HangingTestMCU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { ctx2, cancel := context.WithTimeout(m.ctx, testTimeout*2) defer cancel() @@ -614,10 +615,10 @@ func TestProxyCancelOnClose(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewHangingTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -634,19 +635,19 @@ func TestProxyCancelOnClose(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, }, })) // Simulate expired session while request is still being processed. go func() { <-mcu.creating - if session := proxy.GetSession(1); assert.NotNil(session) { + if session := proxyServer.GetSession(1); assert.NotNil(session) { session.Close() } }() @@ -679,7 +680,7 @@ func NewCodecsTestMCU(t *testing.T) *CodecsTestMCU { } } -func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *CodecsTestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { assert.Equal(m.t, "opus,g722", settings.AudioCodec) assert.Equal(m.t, "vp9,vp8,av1", settings.VideoCodec) return &TestMCUPublisher{ @@ -693,10 +694,10 @@ func TestProxyCodecs(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewCodecsTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -713,13 +714,13 @@ func TestProxyCodecs(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", - StreamType: signaling.StreamTypeVideo, - PublisherSettings: &signaling.NewPublisherSettings{ + StreamType: sfu.StreamTypeVideo, + PublisherSettings: &sfu.NewPublisherSettings{ AudioCodec: "opus,g722", VideoCodec: "vp9,vp8,av1", }, @@ -737,16 +738,16 @@ func TestProxyCodecs(t *testing.T) { type StreamTestMCU struct { TestMCU - streams []signaling.PublisherStream + streams []sfu.PublisherStream } type StreamsTestPublisher struct { TestMCUPublisher - streams []signaling.PublisherStream + streams []sfu.PublisherStream } -func (m *StreamTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *StreamTestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { return &StreamsTestPublisher{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -758,11 +759,11 @@ func (m *StreamTestMCU) NewPublisher(ctx context.Context, listener signaling.Mcu }, nil } -func (p *StreamsTestPublisher) GetStreams(ctx context.Context) ([]signaling.PublisherStream, error) { +func (p *StreamsTestPublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { return p.streams, nil } -func NewStreamTestMCU(t *testing.T, streams []signaling.PublisherStream) *StreamTestMCU { +func NewStreamTestMCU(t *testing.T, streams []sfu.PublisherStream) *StreamTestMCU { return &StreamTestMCU{ TestMCU: TestMCU{ t: t, @@ -776,9 +777,9 @@ func TestProxyStreams(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) - streams := []signaling.PublisherStream{ + streams := []sfu.PublisherStream{ { Mid: "0", Mindex: 0, @@ -794,7 +795,7 @@ func TestProxyStreams(t *testing.T) { } mcu := NewStreamTestMCU(t, streams) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -811,12 +812,12 @@ func TestProxyStreams(t *testing.T) { _, err := client.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, }, })) @@ -832,10 +833,10 @@ func TestProxyStreams(t *testing.T) { require.NotEmpty(clientId, "should have received publisher id") - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "3456", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "get-publisher-streams", ClientId: clientId, }, @@ -867,12 +868,12 @@ func NewRemoteSubscriberTestMCU(t *testing.T) *RemoteSubscriberTestMCU { type TestRemotePublisher struct { t *testing.T - streamType signaling.StreamType + streamType sfu.StreamType refcnt atomic.Int32 closed context.Context closeFunc context.CancelFunc - listener signaling.McuListener - controller signaling.RemotePublisherController + listener sfu.Listener + controller sfu.RemotePublisherController } func (p *TestRemotePublisher) Id() string { @@ -887,7 +888,7 @@ func (p *TestRemotePublisher) Sid() string { return "sid" } -func (p *TestRemotePublisher) StreamType() signaling.StreamType { +func (p *TestRemotePublisher) StreamType() sfu.StreamType { return p.streamType } @@ -920,14 +921,14 @@ func (p *TestRemotePublisher) RtcpPort() int { return 2 } -func (p *TestRemotePublisher) SetMedia(mediaType signaling.MediaType) { +func (p *TestRemotePublisher) SetMedia(mediaType sfu.MediaType) { } -func (p *TestRemotePublisher) HasMedia(mediaType signaling.MediaType) bool { +func (p *TestRemotePublisher) HasMedia(mediaType sfu.MediaType) bool { return false } -func (m *RemoteSubscriberTestMCU) NewRemotePublisher(ctx context.Context, listener signaling.McuListener, controller signaling.RemotePublisherController, streamType signaling.StreamType) (signaling.McuRemotePublisher, error) { +func (m *RemoteSubscriberTestMCU) NewRemotePublisher(ctx context.Context, listener sfu.Listener, controller sfu.RemotePublisherController, streamType sfu.StreamType) (sfu.RemotePublisher, error) { require.Nil(m.t, m.publisher) assert.EqualValues(m.t, "video", streamType) closeCtx, closeFunc := context.WithCancel(context.Background()) @@ -960,7 +961,7 @@ func (s *TestRemoteSubscriber) Sid() string { return "sid" } -func (s *TestRemoteSubscriber) StreamType() signaling.StreamType { +func (s *TestRemoteSubscriber) StreamType() sfu.StreamType { return s.publisher.StreamType() } @@ -981,7 +982,7 @@ func (s *TestRemoteSubscriber) Publisher() api.PublicSessionId { return api.PublicSessionId(s.publisher.Id()) } -func (m *RemoteSubscriberTestMCU) NewRemoteSubscriber(ctx context.Context, listener signaling.McuListener, publisher signaling.McuRemotePublisher) (signaling.McuRemoteSubscriber, error) { +func (m *RemoteSubscriberTestMCU) NewRemoteSubscriber(ctx context.Context, listener sfu.Listener, publisher sfu.RemotePublisher) (sfu.RemoteSubscriber, error) { require.Nil(m.t, m.subscriber) pub, ok := publisher.(*TestRemotePublisher) require.True(m.t, ok) @@ -1001,14 +1002,14 @@ func TestProxyRemoteSubscriber(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewRemoteSubscriberTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu // Unused but must be set so remote subscribing works - proxy.tokenId = "token" - proxy.tokenKey = key - proxy.remoteHostname = "test-hostname" + proxyServer.tokenId = "token" + proxyServer.tokenKey = key + proxyServer.remoteHostname = "test-hostname" ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1026,7 +1027,7 @@ func TestProxyRemoteSubscriber(t *testing.T) { assert.NoError(err) publisherId := api.PublicSessionId("the-publisher-id") - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, @@ -1037,12 +1038,12 @@ func TestProxyRemoteSubscriber(t *testing.T) { tokenString, err := token.SignedString(key) require.NoError(err) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-subscriber", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, PublisherId: publisherId, RemoteUrl: "https://remote-hostname", RemoteToken: tokenString, @@ -1058,12 +1059,12 @@ func TestProxyRemoteSubscriber(t *testing.T) { } } - assert.True(proxy.hasRemotePublishers()) + assert.True(proxyServer.hasRemotePublishers()) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "3456", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "delete-subscriber", ClientId: clientId, }, @@ -1089,21 +1090,21 @@ func TestProxyRemoteSubscriber(t *testing.T) { } } - assert.False(proxy.hasRemotePublishers()) + assert.False(proxyServer.hasRemotePublishers()) } func TestProxyCloseRemoteOnSessionClose(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewRemoteSubscriberTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu // Unused but must be set so remote subscribing works - proxy.tokenId = "token" - proxy.tokenKey = key - proxy.remoteHostname = "test-hostname" + proxyServer.tokenId = "token" + proxyServer.tokenKey = key + proxyServer.remoteHostname = "test-hostname" ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1121,7 +1122,7 @@ func TestProxyCloseRemoteOnSessionClose(t *testing.T) { assert.NoError(err) publisherId := api.PublicSessionId("the-publisher-id") - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, @@ -1132,12 +1133,12 @@ func TestProxyCloseRemoteOnSessionClose(t *testing.T) { tokenString, err := token.SignedString(key) require.NoError(err) - require.NoError(client.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-subscriber", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, PublisherId: publisherId, RemoteUrl: "https://remote-hostname", RemoteToken: tokenString, @@ -1194,7 +1195,7 @@ type UnpublishRemoteTestPublisher struct { remoteData *remotePublisherData } -func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener signaling.McuListener, id api.PublicSessionId, sid string, streamType signaling.StreamType, settings signaling.NewPublisherSettings, initiator signaling.McuInitiator) (signaling.McuPublisher, error) { +func (m *UnpublishRemoteTestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { publisher := &UnpublishRemoteTestPublisher{ TestMCUPublisher: TestMCUPublisher{ id: id, @@ -1259,10 +1260,10 @@ func TestProxyUnpublishRemote(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewUnpublishRemoteTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1280,17 +1281,17 @@ func TestProxyUnpublishRemote(t *testing.T) { assert.NoError(err) publisherId := api.PublicSessionId("the-publisher-id") - require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client1.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", PublisherId: publisherId, Sid: "1234-abcd", - StreamType: signaling.StreamTypeVideo, - PublisherSettings: &signaling.NewPublisherSettings{ + StreamType: sfu.StreamTypeVideo, + PublisherSettings: &sfu.NewPublisherSettings{ Bitrate: 1234567, - MediaTypes: signaling.MediaTypeAudio | signaling.MediaTypeVideo, + MediaTypes: sfu.MediaTypeAudio | sfu.MediaTypeVideo, }, }, })) @@ -1317,12 +1318,12 @@ func TestProxyUnpublishRemote(t *testing.T) { _, err = client2.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client2.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client2.WriteJSON(&proxy.ClientMessage{ Id: "3456", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "publish-remote", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, ClientId: clientId, Hostname: "remote-host", Port: 10001, @@ -1346,12 +1347,12 @@ func TestProxyUnpublishRemote(t *testing.T) { } } - require.NoError(client2.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client2.WriteJSON(&proxy.ClientMessage{ Id: "4567", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "unpublish-remote", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, ClientId: clientId, Hostname: "remote-host", Port: 10001, @@ -1376,10 +1377,10 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewUnpublishRemoteTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1397,17 +1398,17 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { assert.NoError(err) publisherId := api.PublicSessionId("the-publisher-id") - require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client1.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", PublisherId: publisherId, Sid: "1234-abcd", - StreamType: signaling.StreamTypeVideo, - PublisherSettings: &signaling.NewPublisherSettings{ + StreamType: sfu.StreamTypeVideo, + PublisherSettings: &sfu.NewPublisherSettings{ Bitrate: 1234567, - MediaTypes: signaling.MediaTypeAudio | signaling.MediaTypeVideo, + MediaTypes: sfu.MediaTypeAudio | sfu.MediaTypeVideo, }, }, })) @@ -1434,12 +1435,12 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { _, err = client2.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client2.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client2.WriteJSON(&proxy.ClientMessage{ Id: "3456", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "publish-remote", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, ClientId: clientId, Hostname: "remote-host", Port: 10001, @@ -1463,10 +1464,10 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { } } - require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client1.WriteJSON(&proxy.ClientMessage{ Id: "4567", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "delete-publisher", ClientId: clientId, }, @@ -1490,8 +1491,8 @@ func TestProxyUnpublishRemotePublisherClosed(t *testing.T) { } // ...but the session no longer contains information on the remote publisher. - if data, err := proxy.cookie.DecodePublic(hello2.Hello.SessionId); assert.NoError(err) { - session := proxy.GetSession(data.Sid) + if data, err := proxyServer.cookie.DecodePublic(hello2.Hello.SessionId); assert.NoError(err) { + session := proxyServer.GetSession(data.Sid) if assert.NotNil(session) { session.remotePublishersLock.Lock() defer session.remotePublishersLock.Unlock() @@ -1508,10 +1509,10 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { t.Parallel() assert := assert.New(t) require := require.New(t) - proxy, key, server := newProxyServerForTest(t) + proxyServer, key, server := newProxyServerForTest(t) mcu := NewUnpublishRemoteTestMCU(t) - proxy.mcu = mcu + proxyServer.mcu = mcu ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1529,17 +1530,17 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { assert.NoError(err) publisherId := api.PublicSessionId("the-publisher-id") - require.NoError(client1.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client1.WriteJSON(&proxy.ClientMessage{ Id: "2345", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", PublisherId: publisherId, Sid: "1234-abcd", - StreamType: signaling.StreamTypeVideo, - PublisherSettings: &signaling.NewPublisherSettings{ + StreamType: sfu.StreamTypeVideo, + PublisherSettings: &sfu.NewPublisherSettings{ Bitrate: 1234567, - MediaTypes: signaling.MediaTypeAudio | signaling.MediaTypeVideo, + MediaTypes: sfu.MediaTypeAudio | sfu.MediaTypeVideo, }, }, })) @@ -1566,12 +1567,12 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { _, err = client2.RunUntilLoad(ctx, 0) assert.NoError(err) - require.NoError(client2.WriteJSON(&signaling.ProxyClientMessage{ + require.NoError(client2.WriteJSON(&proxy.ClientMessage{ Id: "3456", Type: "command", - Command: &signaling.CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "publish-remote", - StreamType: signaling.StreamTypeVideo, + StreamType: sfu.StreamTypeVideo, ClientId: clientId, Hostname: "remote-host", Port: 10001, diff --git a/cmd/proxy/proxy_session.go b/cmd/proxy/proxy_session.go index 482e75f..0c12f23 100644 --- a/cmd/proxy/proxy_session.go +++ b/cmd/proxy/proxy_session.go @@ -28,9 +28,10 @@ import ( "sync/atomic" "time" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) const ( @@ -58,23 +59,23 @@ type ProxySession struct { // +checklocks:clientLock client *ProxyClient // +checklocks:clientLock - pendingMessages []*signaling.ProxyServerMessage + pendingMessages []*proxy.ServerMessage publishersLock sync.Mutex // +checklocks:publishersLock - publishers map[string]signaling.McuPublisher + publishers map[string]sfu.Publisher // +checklocks:publishersLock - publisherIds map[signaling.McuPublisher]string + publisherIds map[sfu.Publisher]string subscribersLock sync.Mutex // +checklocks:subscribersLock - subscribers map[string]signaling.McuSubscriber + subscribers map[string]sfu.Subscriber // +checklocks:subscribersLock - subscriberIds map[signaling.McuSubscriber]string + subscriberIds map[sfu.Subscriber]string remotePublishersLock sync.Mutex // +checklocks:remotePublishersLock - remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData + remotePublishers map[sfu.RemoteAwarePublisher]map[string]*remotePublisherData } func NewProxySession(proxy *ProxyServer, sid uint64, id api.PublicSessionId) *ProxySession { @@ -87,11 +88,11 @@ func NewProxySession(proxy *ProxyServer, sid uint64, id api.PublicSessionId) *Pr ctx: ctx, closeFunc: closeFunc, - publishers: make(map[string]signaling.McuPublisher), - publisherIds: make(map[signaling.McuPublisher]string), + publishers: make(map[string]sfu.Publisher), + publisherIds: make(map[sfu.Publisher]string), - subscribers: make(map[string]signaling.McuSubscriber), - subscriberIds: make(map[signaling.McuSubscriber]string), + subscribers: make(map[string]sfu.Subscriber), + subscriberIds: make(map[sfu.Subscriber]string), } result.MarkUsed() return result @@ -131,9 +132,9 @@ func (s *ProxySession) Close() { if s.IsExpired() { reason = "session_expired" } - prev.SendMessage(&signaling.ProxyServerMessage{ + prev.SendMessage(&proxy.ServerMessage{ Type: "bye", - Bye: &signaling.ByeProxyServerMessage{ + Bye: &proxy.ByeServerMessage{ Reason: reason, }, }) @@ -150,7 +151,7 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient { s.clientLock.Lock() prev := s.client s.client = client - var messages []*signaling.ProxyServerMessage + var messages []*proxy.ServerMessage if client != nil { messages, s.pendingMessages = s.pendingMessages, nil } @@ -168,16 +169,16 @@ func (s *ProxySession) SetClient(client *ProxyClient) *ProxyClient { return prev } -func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer api.StringMap) { +func (s *ProxySession) OnUpdateOffer(client sfu.Client, offer api.StringMap) { id := s.proxy.GetClientId(client) if id == "" { s.logger.Printf("Received offer %+v from unknown %s client %s (%+v)", offer, client.StreamType(), client.Id(), client) return } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "payload", - Payload: &signaling.PayloadProxyServerMessage{ + Payload: &proxy.PayloadServerMessage{ Type: "offer", ClientId: id, Payload: api.StringMap{ @@ -188,16 +189,16 @@ func (s *ProxySession) OnUpdateOffer(client signaling.McuClient, offer api.Strin s.sendMessage(msg) } -func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate any) { +func (s *ProxySession) OnIceCandidate(client sfu.Client, candidate any) { id := s.proxy.GetClientId(client) if id == "" { s.logger.Printf("Received candidate %+v from unknown %s client %s (%+v)", candidate, client.StreamType(), client.Id(), client) return } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "payload", - Payload: &signaling.PayloadProxyServerMessage{ + Payload: &proxy.PayloadServerMessage{ Type: "candidate", ClientId: id, Payload: api.StringMap{ @@ -208,7 +209,7 @@ func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate any) s.sendMessage(msg) } -func (s *ProxySession) sendMessage(message *signaling.ProxyServerMessage) { +func (s *ProxySession) sendMessage(message *proxy.ServerMessage) { var client *ProxyClient s.clientLock.Lock() client = s.client @@ -221,16 +222,16 @@ func (s *ProxySession) sendMessage(message *signaling.ProxyServerMessage) { } } -func (s *ProxySession) OnIceCompleted(client signaling.McuClient) { +func (s *ProxySession) OnIceCompleted(client sfu.Client) { id := s.proxy.GetClientId(client) if id == "" { s.logger.Printf("Received ice completed event from unknown %s client %s (%+v)", client.StreamType(), client.Id(), client) return } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "ice-completed", ClientId: id, }, @@ -238,16 +239,16 @@ func (s *ProxySession) OnIceCompleted(client signaling.McuClient) { s.sendMessage(msg) } -func (s *ProxySession) SubscriberSidUpdated(subscriber signaling.McuSubscriber) { +func (s *ProxySession) SubscriberSidUpdated(subscriber sfu.Subscriber) { id := s.proxy.GetClientId(subscriber) if id == "" { s.logger.Printf("Received subscriber sid updated event from unknown %s subscriber %s (%+v)", subscriber.StreamType(), subscriber.Id(), subscriber) return } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "subscriber-sid-updated", ClientId: id, Sid: subscriber.Sid(), @@ -256,15 +257,15 @@ func (s *ProxySession) SubscriberSidUpdated(subscriber signaling.McuSubscriber) s.sendMessage(msg) } -func (s *ProxySession) PublisherClosed(publisher signaling.McuPublisher) { +func (s *ProxySession) PublisherClosed(publisher sfu.Publisher) { if id := s.DeletePublisher(publisher); id != "" { if s.proxy.DeleteClient(id, publisher) { statsPublishersCurrent.WithLabelValues(string(publisher.StreamType())).Dec() } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "publisher-closed", ClientId: id, }, @@ -273,15 +274,15 @@ func (s *ProxySession) PublisherClosed(publisher signaling.McuPublisher) { } } -func (s *ProxySession) SubscriberClosed(subscriber signaling.McuSubscriber) { +func (s *ProxySession) SubscriberClosed(subscriber sfu.Subscriber) { if id := s.DeleteSubscriber(subscriber); id != "" { if s.proxy.DeleteClient(id, subscriber) { statsSubscribersCurrent.WithLabelValues(string(subscriber.StreamType())).Dec() } - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "subscriber-closed", ClientId: id, }, @@ -290,7 +291,7 @@ func (s *ProxySession) SubscriberClosed(subscriber signaling.McuSubscriber) { } } -func (s *ProxySession) StorePublisher(ctx context.Context, id string, publisher signaling.McuPublisher) { +func (s *ProxySession) StorePublisher(ctx context.Context, id string, publisher sfu.Publisher) { s.publishersLock.Lock() defer s.publishersLock.Unlock() @@ -298,7 +299,7 @@ func (s *ProxySession) StorePublisher(ctx context.Context, id string, publisher s.publisherIds[publisher] = id } -func (s *ProxySession) DeletePublisher(publisher signaling.McuPublisher) string { +func (s *ProxySession) DeletePublisher(publisher sfu.Publisher) string { s.publishersLock.Lock() defer s.publishersLock.Unlock() @@ -309,7 +310,7 @@ func (s *ProxySession) DeletePublisher(publisher signaling.McuPublisher) string delete(s.publishers, id) delete(s.publisherIds, publisher) - if rp, ok := publisher.(signaling.McuRemoteAwarePublisher); ok { + if rp, ok := publisher.(sfu.RemoteAwarePublisher); ok { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() delete(s.remotePublishers, rp) @@ -318,7 +319,7 @@ func (s *ProxySession) DeletePublisher(publisher signaling.McuPublisher) string return id } -func (s *ProxySession) StoreSubscriber(ctx context.Context, id string, subscriber signaling.McuSubscriber) { +func (s *ProxySession) StoreSubscriber(ctx context.Context, id string, subscriber sfu.Subscriber) { s.subscribersLock.Lock() defer s.subscribersLock.Unlock() @@ -326,7 +327,7 @@ func (s *ProxySession) StoreSubscriber(ctx context.Context, id string, subscribe s.subscriberIds[subscriber] = id } -func (s *ProxySession) DeleteSubscriber(subscriber signaling.McuSubscriber) string { +func (s *ProxySession) DeleteSubscriber(subscriber sfu.Subscriber) string { s.subscribersLock.Lock() defer s.subscribersLock.Unlock() @@ -344,7 +345,7 @@ func (s *ProxySession) clearPublishers() { s.publishersLock.Lock() defer s.publishersLock.Unlock() - go func(publishers map[string]signaling.McuPublisher) { + go func(publishers map[string]sfu.Publisher) { for id, publisher := range publishers { if s.proxy.DeleteClient(id, publisher) { statsPublishersCurrent.WithLabelValues(string(publisher.StreamType())).Dec() @@ -353,7 +354,7 @@ func (s *ProxySession) clearPublishers() { } }(s.publishers) // Can't use clear(...) here as the map is processed by the goroutine above. - s.publishers = make(map[string]signaling.McuPublisher) + s.publishers = make(map[string]sfu.Publisher) clear(s.publisherIds) } @@ -361,7 +362,7 @@ func (s *ProxySession) clearRemotePublishers() { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() - go func(remotePublishers map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData) { + go func(remotePublishers map[sfu.RemoteAwarePublisher]map[string]*remotePublisherData) { for publisher, entries := range remotePublishers { for _, data := range entries { if err := publisher.UnpublishRemote(context.Background(), s.PublicId(), data.hostname, data.port, data.rtcpPort); err != nil { @@ -377,7 +378,7 @@ func (s *ProxySession) clearSubscribers() { s.subscribersLock.Lock() defer s.subscribersLock.Unlock() - go func(subscribers map[string]signaling.McuSubscriber) { + go func(subscribers map[string]sfu.Subscriber) { for id, subscriber := range subscribers { if s.proxy.DeleteClient(id, subscriber) { statsSubscribersCurrent.WithLabelValues(string(subscriber.StreamType())).Dec() @@ -386,7 +387,7 @@ func (s *ProxySession) clearSubscribers() { } }(s.subscribers) // Can't use clear(...) here as the map is processed by the goroutine above. - s.subscribers = make(map[string]signaling.McuSubscriber) + s.subscribers = make(map[string]sfu.Subscriber) clear(s.subscriberIds) } @@ -396,7 +397,7 @@ func (s *ProxySession) NotifyDisconnected() { s.clearRemotePublishers() } -func (s *ProxySession) AddRemotePublisher(publisher signaling.McuRemoteAwarePublisher, hostname string, port int, rtcpPort int) bool { +func (s *ProxySession) AddRemotePublisher(publisher sfu.RemoteAwarePublisher, hostname string, port int, rtcpPort int) bool { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() @@ -404,7 +405,7 @@ func (s *ProxySession) AddRemotePublisher(publisher signaling.McuRemoteAwarePubl if !found { remote = make(map[string]*remotePublisherData) if s.remotePublishers == nil { - s.remotePublishers = make(map[signaling.McuRemoteAwarePublisher]map[string]*remotePublisherData) + s.remotePublishers = make(map[sfu.RemoteAwarePublisher]map[string]*remotePublisherData) } s.remotePublishers[publisher] = remote } @@ -424,7 +425,7 @@ func (s *ProxySession) AddRemotePublisher(publisher signaling.McuRemoteAwarePubl return true } -func (s *ProxySession) RemoveRemotePublisher(publisher signaling.McuRemoteAwarePublisher, hostname string, port int, rtcpPort int) { +func (s *ProxySession) RemoveRemotePublisher(publisher sfu.RemoteAwarePublisher, hostname string, port int, rtcpPort int) { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() @@ -443,13 +444,13 @@ func (s *ProxySession) RemoveRemotePublisher(publisher signaling.McuRemoteAwareP } } -func (s *ProxySession) OnPublisherDeleted(publisher signaling.McuPublisher) { - if publisher, ok := publisher.(signaling.McuRemoteAwarePublisher); ok { +func (s *ProxySession) OnPublisherDeleted(publisher sfu.Publisher) { + if publisher, ok := publisher.(sfu.RemoteAwarePublisher); ok { s.OnRemoteAwarePublisherDeleted(publisher) } } -func (s *ProxySession) OnRemoteAwarePublisherDeleted(publisher signaling.McuRemoteAwarePublisher) { +func (s *ProxySession) OnRemoteAwarePublisherDeleted(publisher sfu.RemoteAwarePublisher) { s.remotePublishersLock.Lock() defer s.remotePublishersLock.Unlock() @@ -457,9 +458,9 @@ func (s *ProxySession) OnRemoteAwarePublisherDeleted(publisher signaling.McuRemo delete(s.remotePublishers, publisher) for _, entry := range entries { - msg := &signaling.ProxyServerMessage{ + msg := &proxy.ServerMessage{ Type: "event", - Event: &signaling.EventProxyServerMessage{ + Event: &proxy.EventServerMessage{ Type: "publisher-closed", ClientId: string(entry.id), }, diff --git a/cmd/proxy/proxy_testclient_test.go b/cmd/proxy/proxy_testclient_test.go index 7cd42a8..6304abc 100644 --- a/cmd/proxy/proxy_testclient_test.go +++ b/cmd/proxy/proxy_testclient_test.go @@ -35,8 +35,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" ) var ( @@ -120,16 +120,16 @@ loop: } func (c *ProxyTestClient) SendBye() error { - hello := &signaling.ProxyClientMessage{ + hello := &proxy.ClientMessage{ Id: "9876", Type: "bye", - Bye: &signaling.ByeProxyClientMessage{}, + Bye: &proxy.ByeClientMessage{}, } return c.WriteJSON(hello) } func (c *ProxyTestClient) WriteJSON(data any) error { - if msg, ok := data.(*signaling.ProxyClientMessage); ok { + if msg, ok := data.(*proxy.ClientMessage); ok { if err := msg.CheckValid(); err != nil { return err } @@ -140,11 +140,11 @@ func (c *ProxyTestClient) WriteJSON(data any) error { return c.conn.WriteJSON(data) } -func (c *ProxyTestClient) RunUntilMessage(ctx context.Context) (message *signaling.ProxyServerMessage, err error) { +func (c *ProxyTestClient) RunUntilMessage(ctx context.Context) (message *proxy.ServerMessage, err error) { select { case err = <-c.readErrorChan: case msg := <-c.messageChan: - var m signaling.ProxyServerMessage + var m proxy.ServerMessage if err = json.Unmarshal(msg, &m); err == nil { message = &m } @@ -165,7 +165,7 @@ func checkUnexpectedClose(err error) error { return nil } -func checkMessageType(message *signaling.ProxyServerMessage, expectedType string) error { +func checkMessageType(message *proxy.ServerMessage, expectedType string) error { if message == nil { return ErrNoMessageReceived } @@ -192,7 +192,7 @@ func checkMessageType(message *signaling.ProxyServerMessage, expectedType string } func (c *ProxyTestClient) SendHello(key any) error { - claims := &signaling.TokenClaims{ + claims := &proxy.TokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), Issuer: TokenIdForTest, @@ -202,10 +202,10 @@ func (c *ProxyTestClient) SendHello(key any) error { tokenString, err := token.SignedString(key) c.require.NoError(err) - hello := &signaling.ProxyClientMessage{ + hello := &proxy.ClientMessage{ Id: "1234", Type: "hello", - Hello: &signaling.HelloProxyClientMessage{ + Hello: &proxy.HelloClientMessage{ Version: "1.0", Features: []string{}, Token: tokenString, @@ -214,7 +214,7 @@ func (c *ProxyTestClient) SendHello(key any) error { return c.WriteJSON(hello) } -func (c *ProxyTestClient) RunUntilHello(ctx context.Context) (message *signaling.ProxyServerMessage, err error) { +func (c *ProxyTestClient) RunUntilHello(ctx context.Context) (message *proxy.ServerMessage, err error) { if message, err = c.RunUntilMessage(ctx); err != nil { return nil, err } @@ -228,7 +228,7 @@ func (c *ProxyTestClient) RunUntilHello(ctx context.Context) (message *signaling return message, nil } -func (c *ProxyTestClient) RunUntilLoad(ctx context.Context, load uint64) (message *signaling.ProxyServerMessage, err error) { +func (c *ProxyTestClient) RunUntilLoad(ctx context.Context, load uint64) (message *proxy.ServerMessage, err error) { if message, err = c.RunUntilMessage(ctx); err != nil { return nil, err } @@ -247,8 +247,8 @@ func (c *ProxyTestClient) RunUntilLoad(ctx context.Context, load uint64) (messag return message, nil } -func (c *ProxyTestClient) SendCommand(command *signaling.CommandProxyClientMessage) error { - message := &signaling.ProxyClientMessage{ +func (c *ProxyTestClient) SendCommand(command *proxy.CommandClientMessage) error { + message := &proxy.ClientMessage{ Id: "2345", Type: "command", Command: command, diff --git a/cmd/server/main.go b/cmd/server/main.go index 7320b77..70cafcb 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -46,9 +46,13 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy" ) var ( @@ -228,7 +232,7 @@ func main() { }() defer rpcServer.Close() - rpcClients, err := signaling.NewGrpcClients(stopCtx, cfg, etcdClient, dnsMonitor, version) + rpcClients, err := grpc.NewClients(stopCtx, cfg, etcdClient, dnsMonitor, version) if err != nil { logger.Fatalf("Could not create RPC clients: %s", err) } @@ -243,15 +247,15 @@ func main() { mcuUrl, _ := config.GetStringOptionWithEnv(cfg, "mcu", "url") mcuType, _ := cfg.GetString("mcu", "type") if mcuType == "" && mcuUrl != "" { - logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) - mcuType = signaling.McuTypeJanus - } else if mcuType == signaling.McuTypeJanus && mcuUrl == "" { + logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", sfu.TypeJanus) + mcuType = sfu.TypeJanus + } else if mcuType == sfu.TypeJanus && mcuUrl == "" { logger.Printf("WARNING: Old-style MCU configuration detected with type but no url, disabling") mcuType = "" } if mcuType != "" { - var mcu signaling.Mcu + var mcu sfu.SFU mcuRetry := initialMcuRetry mcuRetryTimer := time.NewTimer(mcuRetry) mcuTypeLoop: @@ -259,14 +263,14 @@ func main() { // Context should be cancelled on signals but need a way to differentiate later. ctx := context.TODO() switch mcuType { - case signaling.McuTypeJanus: - mcu, err = signaling.NewMcuJanus(ctx, mcuUrl, cfg) - signaling.UnregisterProxyMcuStats() - signaling.RegisterJanusMcuStats() - case signaling.McuTypeProxy: - mcu, err = signaling.NewMcuProxy(ctx, cfg, etcdClient, rpcClients, dnsMonitor) - signaling.UnregisterJanusMcuStats() - signaling.RegisterProxyMcuStats() + case sfu.TypeJanus: + mcu, err = janus.NewJanusSFU(ctx, mcuUrl, cfg) + proxy.UnregisterStats() + janus.RegisterStats() + case sfu.TypeProxy: + mcu, err = proxy.NewProxySFU(ctx, cfg, etcdClient, rpcClients, dnsMonitor) + janus.UnregisterStats() + proxy.RegisterStats() default: logger.Fatal("Unsupported MCU type: ", mcuType) } @@ -295,9 +299,9 @@ func main() { mcuUrl, _ = config.GetStringOptionWithEnv(cfg, "mcu", "url") mcuType, _ = cfg.GetString("mcu", "type") if mcuType == "" && mcuUrl != "" { - logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", signaling.McuTypeJanus) - mcuType = signaling.McuTypeJanus - } else if mcuType == signaling.McuTypeJanus && mcuUrl == "" { + logger.Printf("WARNING: Old-style MCU configuration detected with url but no type, defaulting to type %s", sfu.TypeJanus) + mcuType = sfu.TypeJanus + } else if mcuType == sfu.TypeJanus && mcuUrl == "" { logger.Printf("WARNING: Old-style MCU configuration detected with type but no url, disabling") mcuType = "" break mcuTypeLoop diff --git a/federation.go b/federation.go index bdb50c5..a0f6296 100644 --- a/federation.go +++ b/federation.go @@ -48,6 +48,7 @@ const ( ) var ( + ErrNotConnected = errors.New("not connected") ErrFederationNotSupported = api.NewError("federation_unsupported", "The target server does not support federation.") federationWriteBufferPool = &sync.Pool{} @@ -936,8 +937,8 @@ func (c *FederationClient) processMessage(msg *api.ServerMessage) { } } case "transient": - if remoteSessionId != "" && msg.TransientData != nil && msg.TransientData.Key == TransientSessionDataPrefix+string(remoteSessionId) { - msg.TransientData.Key = TransientSessionDataPrefix + string(localSessionId) + if remoteSessionId != "" && msg.TransientData != nil && msg.TransientData.Key == api.TransientSessionDataPrefix+string(remoteSessionId) { + msg.TransientData.Key = api.TransientSessionDataPrefix + string(localSessionId) } } c.session.SendMessage(msg) @@ -955,8 +956,8 @@ func (c *FederationClient) ProxyMessage(message *api.ClientMessage) error { } case "transient": if hello := c.hello.Load(); hello != nil { - if message.TransientData != nil && message.TransientData.Key == TransientSessionDataPrefix+string(c.session.PublicId()) { - message.TransientData.Key = TransientSessionDataPrefix + string(hello.SessionId) + if message.TransientData != nil && message.TransientData.Key == api.TransientSessionDataPrefix+string(c.session.PublicId()) { + message.TransientData.Key = api.TransientSessionDataPrefix + string(hello.SessionId) } } } diff --git a/federation_test.go b/federation_test.go index 2965bf7..515f49a 100644 --- a/federation_test.go +++ b/federation_test.go @@ -34,6 +34,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" ) func Test_FederationInvalidToken(t *testing.T) { @@ -714,13 +715,13 @@ func Test_FederationMedia(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu1 := NewTestMCU(t) + mcu1 := test.NewSFU(t) require.NoError(mcu1.Start(ctx)) defer mcu1.Stop() hub1.SetMcu(mcu1) - mcu2 := NewTestMCU(t) + mcu2 := test.NewSFU(t) require.NoError(mcu2.Start(ctx)) defer mcu2.Stop() diff --git a/api_grpc.go b/grpc/api.go similarity index 90% rename from api_grpc.go rename to grpc/api.go index b67fe87..e29df35 100644 --- a/api_grpc.go +++ b/grpc/api.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 grpc import ( "errors" @@ -27,11 +27,11 @@ import ( // Information on a GRPC target in the etcd cluster. -type GrpcTargetInformationEtcd struct { +type TargetInformationEtcd struct { Address string `json:"address"` } -func (p *GrpcTargetInformationEtcd) CheckValid() error { +func (p *TargetInformationEtcd) CheckValid() error { if l := len(p.Address); l == 0 { return errors.New("address missing") } else if p.Address[l-1] == '/' { diff --git a/api_grpc_easyjson.go b/grpc/api_easyjson.go similarity index 60% rename from api_grpc_easyjson.go rename to grpc/api_easyjson.go index fd3e48c..055f6ad 100644 --- a/api_grpc_easyjson.go +++ b/grpc/api_easyjson.go @@ -1,6 +1,6 @@ // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package signaling +package grpc import ( json "encoding/json" @@ -17,7 +17,7 @@ var ( _ easyjson.Marshaler ) -func easyjson5dc3c167DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexer.Lexer, out *GrpcTargetInformationEtcd) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(in *jlexer.Lexer, out *TargetInformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -47,7 +47,7 @@ func easyjson5dc3c167DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe in.Consumed() } } -func easyjson5dc3c167EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwriter.Writer, in GrpcTargetInformationEtcd) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(out *jwriter.Writer, in TargetInformationEtcd) { out.RawByte('{') first := true _ = first @@ -60,25 +60,25 @@ func easyjson5dc3c167EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwri } // MarshalJSON supports json.Marshaler interface -func (v GrpcTargetInformationEtcd) MarshalJSON() ([]byte, error) { +func (v TargetInformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson5dc3c167EncodeGithubComStrukturagNextcloudSpreedSignaling(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v GrpcTargetInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson5dc3c167EncodeGithubComStrukturagNextcloudSpreedSignaling(w, v) +func (v TargetInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *GrpcTargetInformationEtcd) UnmarshalJSON(data []byte) error { +func (v *TargetInformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson5dc3c167DecodeGithubComStrukturagNextcloudSpreedSignaling(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *GrpcTargetInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson5dc3c167DecodeGithubComStrukturagNextcloudSpreedSignaling(l, v) +func (v *TargetInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(l, v) } diff --git a/grpc_backend.pb.go b/grpc/backend.pb.go similarity index 89% rename from grpc_backend.pb.go rename to grpc/backend.pb.go index 35d30d2..d977af1 100644 --- a/grpc_backend.pb.go +++ b/grpc/backend.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go. DO NOT EDIT. -// source: grpc_backend.proto +// source: grpc/backend.proto -package signaling +package grpc import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -131,14 +131,14 @@ var File_grpc_backend_proto protoreflect.FileDescriptor const file_grpc_backend_proto_rawDesc = "" + "\n" + - "\x12grpc_backend.proto\x12\tsignaling\"*\n" + + "\x12grpc/backend.proto\x12\x04grpc\"*\n" + "\x16GetSessionCountRequest\x12\x10\n" + "\x03url\x18\x01 \x01(\tR\x03url\",\n" + "\x14GetSessionCountReply\x12\x14\n" + - "\x05count\x18\x01 \x01(\rR\x05count2e\n" + + "\x05count\x18\x01 \x01(\rR\x05count2[\n" + "\n" + - "RpcBackend\x12W\n" + - "\x0fGetSessionCount\x12!.signaling.GetSessionCountRequest\x1a\x1f.signaling.GetSessionCountReply\"\x00B signaling.GetSessionCountRequest - 1, // 1: signaling.RpcBackend.GetSessionCount:output_type -> signaling.GetSessionCountReply + 0, // 0: grpc.RpcBackend.GetSessionCount:input_type -> grpc.GetSessionCountRequest + 1, // 1: grpc.RpcBackend.GetSessionCount:output_type -> grpc.GetSessionCountReply 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name diff --git a/grpc_backend.proto b/grpc/backend.proto similarity index 72% rename from grpc_backend.proto rename to grpc/backend.proto index f667f12..28fe818 100644 --- a/grpc_backend.proto +++ b/grpc/backend.proto @@ -19,20 +19,20 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - syntax = "proto3"; +syntax = "proto3"; - option go_package = "github.com/strukturag/nextcloud-spreed-signaling;signaling"; +option go_package = "github.com/strukturag/nextcloud-spreed-signaling/grpc"; - package signaling; +package grpc; - service RpcBackend { - rpc GetSessionCount(GetSessionCountRequest) returns (GetSessionCountReply) {} - } +service RpcBackend { + rpc GetSessionCount(GetSessionCountRequest) returns (GetSessionCountReply) {} +} - message GetSessionCountRequest { - string url = 1; - } +message GetSessionCountRequest { +string url = 1; +} - message GetSessionCountReply { - uint32 count = 1; - } +message GetSessionCountReply { + uint32 count = 1; +} diff --git a/grpc_backend_grpc.pb.go b/grpc/backend_grpc.pb.go similarity index 96% rename from grpc_backend_grpc.pb.go rename to grpc/backend_grpc.pb.go index 6f8c1c9..3d84ced 100644 --- a/grpc_backend_grpc.pb.go +++ b/grpc/backend_grpc.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// source: grpc_backend.proto +// source: grpc/backend.proto -package signaling +package grpc import ( context "context" @@ -37,7 +37,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - RpcBackend_GetSessionCount_FullMethodName = "/signaling.RpcBackend/GetSessionCount" + RpcBackend_GetSessionCount_FullMethodName = "/grpc.RpcBackend/GetSessionCount" ) // RpcBackendClient is the client API for RpcBackend service. @@ -126,7 +126,7 @@ func _RpcBackend_GetSessionCount_Handler(srv interface{}, ctx context.Context, d // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RpcBackend_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "signaling.RpcBackend", + ServiceName: "grpc.RpcBackend", HandlerType: (*RpcBackendServer)(nil), Methods: []grpc.MethodDesc{ { @@ -135,5 +135,5 @@ var RpcBackend_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "grpc_backend.proto", + Metadata: "grpc/backend.proto", } diff --git a/grpc_client.go b/grpc/client.go similarity index 81% rename from grpc_client.go rename to grpc/client.go index 7e8907b..d913afe 100644 --- a/grpc_client.go +++ b/grpc/client.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 grpc import ( "context" @@ -50,18 +50,23 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( - GrpcTargetTypeStatic = "static" - GrpcTargetTypeEtcd = "etcd" + TargetTypeStatic = "static" + TargetTypeEtcd = "etcd" - DefaultGrpcTargetType = GrpcTargetTypeStatic + DefaultTargetType = TargetTypeStatic + + initialWaitDelay = time.Second + maxWaitDelay = 8 * time.Second ) var ( - ErrNoSuchResumeId = errors.New("unknown resume id") + ErrNoSuchResumeId = errors.New("unknown resume id") + ErrNoSuchRoomSession = errors.New("unknown room session id") customResolverPrefix atomic.Uint64 ) @@ -77,7 +82,7 @@ type grpcClientImpl struct { RpcSessionsClient } -func newGrpcClientImpl(conn grpc.ClientConnInterface) *grpcClientImpl { +func newClientImpl(conn grpc.ClientConnInterface) *grpcClientImpl { return &grpcClientImpl{ RpcBackendClient: NewRpcBackendClient(conn), RpcInternalClient: NewRpcInternalClient(conn), @@ -86,7 +91,7 @@ func newGrpcClientImpl(conn grpc.ClientConnInterface) *grpcClientImpl { } } -type GrpcClient struct { +type Client struct { logger log.Logger ip net.IP rawTarget string @@ -136,7 +141,7 @@ func (r *customIpResolver) Close() { // Noop } -func NewGrpcClient(logger log.Logger, target string, ip net.IP, opts ...grpc.DialOption) (*GrpcClient, error) { +func NewClient(logger log.Logger, target string, ip net.IP, opts ...grpc.DialOption) (*Client, error) { var conn *grpc.ClientConn var err error if ip != nil { @@ -161,13 +166,13 @@ func NewGrpcClient(logger log.Logger, target string, ip net.IP, opts ...grpc.Dia return nil, err } - result := &GrpcClient{ + result := &Client{ logger: logger, ip: ip, rawTarget: target, target: target, conn: conn, - impl: newGrpcClientImpl(conn), + impl: newClientImpl(conn), } if ip != nil { @@ -177,27 +182,27 @@ func NewGrpcClient(logger log.Logger, target string, ip net.IP, opts ...grpc.Dia return result, nil } -func (c *GrpcClient) Target() string { +func (c *Client) Target() string { return c.target } -func (c *GrpcClient) Version() string { +func (c *Client) Version() string { return c.version.Load().(string) } -func (c *GrpcClient) Close() error { +func (c *Client) Close() error { return c.conn.Close() } -func (c *GrpcClient) IsSelf() bool { +func (c *Client) IsSelf() bool { return c.isSelf.Load() } -func (c *GrpcClient) SetSelf(self bool) { +func (c *Client) SetSelf(self bool) { c.isSelf.Store(self) } -func (c *GrpcClient) GetServerId(ctx context.Context) (string, string, error) { +func (c *Client) GetServerId(ctx context.Context) (string, string, error) { statsGrpcClientCalls.WithLabelValues("GetServerId").Inc() response, err := c.impl.GetServerId(ctx, &GetServerIdRequest{}, grpc.WaitForReady(true)) if err != nil { @@ -207,7 +212,7 @@ func (c *GrpcClient) GetServerId(ctx context.Context) (string, string, error) { return response.GetServerId(), response.GetVersion(), nil } -func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId api.PrivateSessionId) (*LookupResumeIdReply, error) { +func (c *Client) LookupResumeId(ctx context.Context, resumeId api.PrivateSessionId) (*LookupResumeIdReply, error) { statsGrpcClientCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging c.logger.Printf("Lookup resume id %s on %s", resumeId, c.Target()) @@ -227,7 +232,7 @@ func (c *GrpcClient) LookupResumeId(ctx context.Context, resumeId api.PrivateSes return response, nil } -func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId api.RoomSessionId, disconnectReason string) (api.PublicSessionId, error) { +func (c *Client) LookupSessionId(ctx context.Context, roomSessionId api.RoomSessionId, disconnectReason string) (api.PublicSessionId, error) { statsGrpcClientCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging c.logger.Printf("Lookup room session %s on %s", roomSessionId, c.Target()) @@ -249,13 +254,13 @@ func (c *GrpcClient) LookupSessionId(ctx context.Context, roomSessionId api.Room return api.PublicSessionId(sessionId), nil } -func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId api.PublicSessionId, room *Room, backendUrl string) (bool, error) { +func (c *Client) IsSessionInCall(ctx context.Context, sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, error) { statsGrpcClientCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging - c.logger.Printf("Check if session %s is in call %s on %s", sessionId, room.Id(), c.Target()) + c.logger.Printf("Check if session %s is in call %s on %s", sessionId, roomId, c.Target()) response, err := c.impl.IsSessionInCall(ctx, &IsSessionInCallRequest{ SessionId: string(sessionId), - RoomId: room.Id(), + RoomId: roomId, BackendUrl: backendUrl, }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { @@ -267,7 +272,7 @@ func (c *GrpcClient) IsSessionInCall(ctx context.Context, sessionId api.PublicSe return response.GetInCall(), nil } -func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[api.PublicSessionId]*InternalSessionData, virtual map[api.PublicSessionId]*VirtualSessionData, err error) { +func (c *Client) GetInternalSessions(ctx context.Context, roomId string, backendUrls []string) (internal map[api.PublicSessionId]*InternalSessionData, virtual map[api.PublicSessionId]*VirtualSessionData, err error) { statsGrpcClientCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging c.logger.Printf("Get internal sessions for %s on %s", roomId, c.Target()) @@ -302,7 +307,7 @@ func (c *GrpcClient) GetInternalSessions(ctx context.Context, roomId string, bac return } -func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId api.PublicSessionId, streamType StreamType) (api.PublicSessionId, string, net.IP, string, string, error) { +func (c *Client) GetPublisherId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (api.PublicSessionId, string, net.IP, string, string, error) { statsGrpcClientCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging c.logger.Printf("Get %s publisher id %s on %s", streamType, sessionId, c.Target()) @@ -319,7 +324,7 @@ func (c *GrpcClient) GetPublisherId(ctx context.Context, sessionId api.PublicSes return api.PublicSessionId(response.GetPublisherId()), response.GetProxyUrl(), net.ParseIP(response.GetIp()), response.GetConnectToken(), response.GetPublisherToken(), nil } -func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, error) { +func (c *Client) GetSessionCount(ctx context.Context, url string) (uint32, error) { statsGrpcClientCalls.WithLabelValues("GetSessionCount").Inc() // TODO: Remove debug logging c.logger.Printf("Get session count for %s on %s", url, c.Target()) @@ -335,13 +340,13 @@ func (c *GrpcClient) GetSessionCount(ctx context.Context, url string) (uint32, e return response.GetCount(), nil } -func (c *GrpcClient) GetTransientData(ctx context.Context, room *Room) (TransientDataEntries, error) { +func (c *Client) GetTransientData(ctx context.Context, roomId string, backend *talk.Backend) (api.TransientDataEntries, error) { statsGrpcClientCalls.WithLabelValues("GetTransientData").Inc() // TODO: Remove debug logging - c.logger.Printf("Get transient data for %s@%s on %s", room.Id(), room.Backend().Id(), c.Target()) + c.logger.Printf("Get transient data for %s@%s on %s", roomId, backend.Id(), c.Target()) response, err := c.impl.GetTransientData(ctx, &GetTransientDataRequest{ - RoomId: room.Id(), - BackendUrls: room.Backend().Urls(), + RoomId: roomId, + BackendUrls: backend.Urls(), }, grpc.WaitForReady(true)) if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { return nil, nil @@ -354,16 +359,16 @@ func (c *GrpcClient) GetTransientData(ctx context.Context, room *Room) (Transien return nil, nil } - result := make(TransientDataEntries, len(entries)) + result := make(api.TransientDataEntries, len(entries)) for k, v := range entries { var value any if err := json.Unmarshal(v.Value, &value); err != nil { return nil, err } if v.Expires > 0 { - result[k] = NewTransientDataEntryWithExpires(value, time.UnixMicro(v.Expires)) + result[k] = api.NewTransientDataEntryWithExpires(value, time.UnixMicro(v.Expires)) } else { - result[k] = NewTransientDataEntry(value, 0) + result[k] = api.NewTransientDataEntry(value, 0) } } return result, nil @@ -426,7 +431,7 @@ func (p *SessionProxy) Close() error { return p.client.CloseSend() } -func (c *GrpcClient) ProxySession(ctx context.Context, sessionId api.PublicSessionId, receiver ProxySessionReceiver) (*SessionProxy, error) { +func (c *Client) ProxySession(ctx context.Context, sessionId api.PublicSessionId, receiver ProxySessionReceiver) (*SessionProxy, error) { statsGrpcClientCalls.WithLabelValues("ProxySession").Inc() md := metadata.Pairs( "sessionId", string(sessionId), @@ -451,20 +456,20 @@ func (c *GrpcClient) ProxySession(ctx context.Context, sessionId api.PublicSessi return proxy, nil } -type grpcClientsList struct { - clients []*GrpcClient +type clientsList struct { + clients []*Client entry *dns.MonitorEntry } -type GrpcClients struct { +type Clients struct { mu sync.RWMutex version string logger log.Logger // +checklocks:mu - clientsMap map[string]*grpcClientsList + clientsMap map[string]*clientsList // +checklocks:mu - clients []*GrpcClient + clients []*Client dnsMonitor *dns.Monitor // +checklocks:mu @@ -473,7 +478,7 @@ type GrpcClients struct { etcdClient etcd.Client // +checklocksignore: Only written to from constructor. targetPrefix string // +checklocks:mu - targetInformation map[string]*GrpcTargetInformationEtcd + targetInformation map[string]*TargetInformationEtcd dialOptions atomic.Value // []grpc.DialOption creds credentials.TransportCredentials @@ -486,10 +491,10 @@ type GrpcClients struct { closeFunc context.CancelFunc // +checklocksignore: No locking necessary. } -func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, dnsMonitor *dns.Monitor, version string) (*GrpcClients, error) { +func NewClients(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, dnsMonitor *dns.Monitor, version string) (*Clients, error) { initializedCtx, initializedFunc := context.WithCancel(context.Background()) closeCtx, closeFunc := context.WithCancel(context.Background()) - result := &GrpcClients{ + result := &Clients{ version: version, logger: log.LoggerFromContext(ctx), dnsMonitor: dnsMonitor, @@ -505,7 +510,7 @@ func NewGrpcClients(ctx context.Context, config *goconf.ConfigFile, etcdClient e return result, nil } -func (c *GrpcClients) GetServerInfoGrpc() (result []talk.BackendServerInfoGrpc) { +func (c *Clients) GetServerInfoGrpc() (result []talk.BackendServerInfoGrpc) { c.mu.RLock() defer c.mu.RUnlock() @@ -531,7 +536,7 @@ func (c *GrpcClients) GetServerInfoGrpc() (result []talk.BackendServerInfoGrpc) return } -func (c *GrpcClients) load(config *goconf.ConfigFile, fromReload bool) error { +func (c *Clients) load(config *goconf.ConfigFile, fromReload bool) error { creds, err := NewReloadableCredentials(c.logger, config, false) if err != nil { return err @@ -549,13 +554,13 @@ func (c *GrpcClients) load(config *goconf.ConfigFile, fromReload bool) error { targetType, _ := config.GetString("grpc", "targettype") if targetType == "" { - targetType = DefaultGrpcTargetType + targetType = DefaultTargetType } switch targetType { - case GrpcTargetTypeStatic: + case TargetTypeStatic: err = c.loadTargetsStatic(config, fromReload, opts...) - case GrpcTargetTypeEtcd: + case TargetTypeEtcd: err = c.loadTargetsEtcd(config, fromReload, opts...) default: err = fmt.Errorf("unknown GRPC target type: %s", targetType) @@ -563,7 +568,7 @@ func (c *GrpcClients) load(config *goconf.ConfigFile, fromReload bool) error { return err } -func (c *GrpcClients) closeClient(client *GrpcClient) { +func (c *Clients) closeClient(client *Client) { if client.IsSelf() { // Already closed. return @@ -574,7 +579,7 @@ func (c *GrpcClients) closeClient(client *GrpcClient) { } } -func (c *GrpcClients) isClientAvailable(target string, client *GrpcClient) bool { +func (c *Clients) isClientAvailable(target string, client *Client) bool { c.mu.RLock() defer c.mu.RUnlock() @@ -586,7 +591,7 @@ func (c *GrpcClients) isClientAvailable(target string, client *GrpcClient) bool return slices.Contains(entries.clients, client) } -func (c *GrpcClients) getServerIdWithTimeout(ctx context.Context, client *GrpcClient) (string, string, error) { +func (c *Clients) getServerIdWithTimeout(ctx context.Context, client *Client) (string, string, error) { ctx2, cancel := context.WithTimeout(ctx, time.Second) defer cancel() @@ -594,7 +599,11 @@ func (c *GrpcClients) getServerIdWithTimeout(ctx context.Context, client *GrpcCl return id, version, err } -func (c *GrpcClients) checkIsSelf(ctx context.Context, target string, client *GrpcClient) { +func (c *Clients) WaitForSelfCheck() { + c.selfCheckWaitGroup.Wait() +} + +func (c *Clients) checkIsSelf(ctx context.Context, target string, client *Client) { backoff, _ := async.NewExponentialBackoff(initialWaitDelay, maxWaitDelay) defer c.selfCheckWaitGroup.Done() @@ -623,7 +632,7 @@ loop: } client.version.Store(version) - if id == GrpcServerId { + if id == ServerId { c.logger.Printf("GRPC target %s is this server, removing", client.Target()) c.closeClient(client) client.SetSelf(true) @@ -637,7 +646,7 @@ loop: } } -func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bool, opts ...grpc.DialOption) error { +func (c *Clients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bool, opts ...grpc.DialOption) error { c.mu.Lock() defer c.mu.Unlock() @@ -654,8 +663,8 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo c.dnsDiscovery = dnsDiscovery } - clientsMap := make(map[string]*grpcClientsList) - var clients []*GrpcClient + clientsMap := make(map[string]*clientsList) + var clients []*Client removeTargets := make(map[string]bool, len(c.clientsMap)) for target, entries := range c.clientsMap { removeTargets[target] = true @@ -690,13 +699,13 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo return err } - clientsMap[target] = &grpcClientsList{ + clientsMap[target] = &clientsList{ entry: entry, } continue } - client, err := NewGrpcClient(c.logger, target, nil, opts...) + client, err := NewClient(c.logger, target, nil, opts...) if err != nil { for _, entry := range clientsMap { for _, client := range entry.clients { @@ -717,7 +726,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo c.logger.Printf("Adding %s as GRPC target", client.Target()) entry, found := clientsMap[target] if !found { - entry = &grpcClientsList{} + entry = &clientsList{} clientsMap[target] = entry } entry.clients = append(entry.clients, client) @@ -746,7 +755,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo return nil } -func (c *GrpcClients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { +func (c *Clients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { c.mu.Lock() defer c.mu.Unlock() @@ -759,7 +768,7 @@ func (c *GrpcClients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []ne opts := c.dialOptions.Load().([]grpc.DialOption) mapModified := false - var newClients []*GrpcClient + var newClients []*Client for _, ip := range removed { for _, client := range e.clients { if ip.Equal(client.ip) { @@ -780,7 +789,7 @@ func (c *GrpcClients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []ne } for _, ip := range added { - client, err := NewGrpcClient(c.logger, target, ip, opts...) + client, err := NewClient(c.logger, target, ip, opts...) if err != nil { c.logger.Printf("Error creating client to %s with IP %s: %s", target, ip.String(), err) continue @@ -798,7 +807,7 @@ func (c *GrpcClients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []ne if mapModified { c.clientsMap[target].clients = newClients - c.clients = make([]*GrpcClient, 0, len(c.clientsMap)) + c.clients = make([]*Client, 0, len(c.clientsMap)) for _, entry := range c.clientsMap { c.clients = append(c.clients, entry.clients...) } @@ -806,7 +815,7 @@ func (c *GrpcClients) onLookup(entry *dns.MonitorEntry, all []net.IP, added []ne } } -func (c *GrpcClients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool, opts ...grpc.DialOption) error { +func (c *Clients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool, opts ...grpc.DialOption) error { c.mu.Lock() defer c.mu.Unlock() @@ -820,14 +829,14 @@ func (c *GrpcClients) loadTargetsEtcd(config *goconf.ConfigFile, fromReload bool } c.targetPrefix = targetPrefix if c.targetInformation == nil { - c.targetInformation = make(map[string]*GrpcTargetInformationEtcd) + c.targetInformation = make(map[string]*TargetInformationEtcd) } c.etcdClient.AddListener(c) return nil } -func (c *GrpcClients) EtcdClientCreated(client etcd.Client) { +func (c *Clients) EtcdClientCreated(client etcd.Client) { go func() { if err := client.WaitForConnection(c.closeCtx); err != nil { if errors.Is(err, context.Canceled) { @@ -883,18 +892,18 @@ func (c *GrpcClients) EtcdClientCreated(client etcd.Client) { }() } -func (c *GrpcClients) EtcdWatchCreated(client etcd.Client, key string) { +func (c *Clients) EtcdWatchCreated(client etcd.Client, key string) { } -func (c *GrpcClients) getGrpcTargets(ctx context.Context, client etcd.Client, targetPrefix string) (*clientv3.GetResponse, error) { +func (c *Clients) getGrpcTargets(ctx context.Context, client etcd.Client, targetPrefix string) (*clientv3.GetResponse, error) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() return client.Get(ctx, targetPrefix, clientv3.WithPrefix()) } -func (c *GrpcClients) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { - var info GrpcTargetInformationEtcd +func (c *Clients) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { + var info TargetInformationEtcd if err := json.Unmarshal(data, &info); err != nil { c.logger.Printf("Could not decode GRPC target %s=%s: %s", key, string(data), err) return @@ -919,7 +928,7 @@ func (c *GrpcClients) EtcdKeyUpdated(client etcd.Client, key string, data []byte } opts := c.dialOptions.Load().([]grpc.DialOption) - cl, err := NewGrpcClient(c.logger, info.Address, nil, opts...) + cl, err := NewClient(c.logger, info.Address, nil, opts...) if err != nil { c.logger.Printf("Could not create GRPC client for target %s: %s", info.Address, err) return @@ -931,10 +940,10 @@ func (c *GrpcClients) EtcdKeyUpdated(client etcd.Client, key string, data []byte c.logger.Printf("Adding %s as GRPC target", cl.Target()) if c.clientsMap == nil { - c.clientsMap = make(map[string]*grpcClientsList) + c.clientsMap = make(map[string]*clientsList) } - c.clientsMap[info.Address] = &grpcClientsList{ - clients: []*GrpcClient{cl}, + c.clientsMap[info.Address] = &clientsList{ + clients: []*Client{cl}, } c.clients = append(c.clients, cl) c.targetInformation[key] = &info @@ -942,7 +951,7 @@ func (c *GrpcClients) EtcdKeyUpdated(client etcd.Client, key string, data []byte c.wakeupForTesting() } -func (c *GrpcClients) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { +func (c *Clients) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { c.mu.Lock() defer c.mu.Unlock() @@ -950,7 +959,7 @@ func (c *GrpcClients) EtcdKeyDeleted(client etcd.Client, key string, prevValue [ } // +checklocks:c.mu -func (c *GrpcClients) removeEtcdClientLocked(key string) { +func (c *Clients) removeEtcdClientLocked(key string) { info, found := c.targetInformation[key] if !found { c.logger.Printf("No connection found for %s, ignoring", key) @@ -969,7 +978,7 @@ func (c *GrpcClients) removeEtcdClientLocked(key string) { c.closeClient(client) } delete(c.clientsMap, info.Address) - c.clients = make([]*GrpcClient, 0, len(c.clientsMap)) + c.clients = make([]*Client, 0, len(c.clientsMap)) for _, entry := range c.clientsMap { c.clients = append(c.clients, entry.clients...) } @@ -977,7 +986,7 @@ func (c *GrpcClients) removeEtcdClientLocked(key string) { c.wakeupForTesting() } -func (c *GrpcClients) WaitForInitialized(ctx context.Context) error { +func (c *Clients) WaitForInitialized(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() @@ -986,7 +995,20 @@ func (c *GrpcClients) WaitForInitialized(ctx context.Context) error { } } -func (c *GrpcClients) wakeupForTesting() { +func (c *Clients) GetWakeupChannelForTesting() <-chan struct{} { + c.mu.Lock() + defer c.mu.Unlock() + + if c.wakeupChanForTesting != nil { + return c.wakeupChanForTesting + } + + ch := make(chan struct{}, 1) + c.wakeupChanForTesting = ch + return ch +} + +func (c *Clients) wakeupForTesting() { if c.wakeupChanForTesting == nil { return } @@ -997,13 +1019,13 @@ func (c *GrpcClients) wakeupForTesting() { } } -func (c *GrpcClients) Reload(config *goconf.ConfigFile) { +func (c *Clients) Reload(config *goconf.ConfigFile) { if err := c.load(config, true); err != nil { c.logger.Printf("Could not reload RPC clients: %s", err) } } -func (c *GrpcClients) Close() { +func (c *Clients) Close() { c.mu.Lock() defer c.mu.Unlock() @@ -1035,7 +1057,7 @@ func (c *GrpcClients) Close() { c.closeFunc() } -func (c *GrpcClients) GetClients() []*GrpcClient { +func (c *Clients) GetClients() []*Client { c.mu.RLock() defer c.mu.RUnlock() @@ -1043,7 +1065,7 @@ func (c *GrpcClients) GetClients() []*GrpcClient { return c.clients } - result := make([]*GrpcClient, 0, len(c.clients)-1) + result := make([]*Client, 0, len(c.clients)-1) for _, client := range c.clients { if client.IsSelf() { continue diff --git a/grpc_common_test.go b/grpc/client_stats_prometheus.go similarity index 54% rename from grpc_common_test.go rename to grpc/client_stats_prometheus.go index 27d8d23..02f791f 100644 --- a/grpc_common_test.go +++ b/grpc/client_stats_prometheus.go @@ -19,38 +19,34 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package grpc import ( - "context" - "errors" - "time" + "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) -func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context, counter uint64) error { - if c.loader == nil { - return errors.New("no certificate loaded") - } +var ( + statsGrpcClients = prometheus.NewGauge(prometheus.GaugeOpts{ // +checklocksignore: Global readonly variable. + Namespace: "signaling", + Subsystem: "grpc", + Name: "clients", + Help: "The current number of GRPC clients", + }) + statsGrpcClientCalls = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "grpc", + Name: "client_calls_total", + Help: "The total number of GRPC client calls", + }, []string{"method"}) - for counter == c.loader.GetReloadCounter() { - if err := ctx.Err(); err != nil { - return err - } - time.Sleep(time.Millisecond) + grpcClientStats = []prometheus.Collector{ + statsGrpcClients, + statsGrpcClientCalls, } - return nil -} - -func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context, counter uint64) error { - if c.pool == nil { - return errors.New("no certificate pool loaded") - } - - for counter == c.pool.GetReloadCounter() { - if err := ctx.Err(); err != nil { - return err - } - time.Sleep(time.Millisecond) - } - return nil +) + +func RegisterGrpcClientStats() { + metrics.RegisterAll(grpcClientStats...) } diff --git a/grpc/client_test.go b/grpc/client_test.go new file mode 100644 index 0000000..331a2af --- /dev/null +++ b/grpc/client_test.go @@ -0,0 +1,174 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2022 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 grpc + +import ( + "context" + "fmt" + "net" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/dns" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" +) + +func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { + dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + client, err := NewClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") + require.NoError(t, err) + t.Cleanup(func() { + client.Close() + }) + + return client, dnsMonitor +} + +func NewClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { + config := goconf.NewConfigFile() + config.AddOption("grpc", "targets", addr) + config.AddOption("grpc", "dnsdiscovery", "true") + + return NewClientsForTestWithConfig(t, config, nil, lookup) +} + +func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { + config := goconf.NewConfigFile() + config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) + + config.AddOption("grpc", "targettype", "etcd") + config.AddOption("grpc", "targetprefix", "/grpctargets") + + logger := log.NewLoggerForTest(t) + etcdClient, err := etcd.NewClient(logger, config, "") + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, etcdClient.Close()) + }) + + return NewClientsForTestWithConfig(t, config, etcdClient, lookup) +} + +func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { + t.Helper() + + select { + case <-ch: + return + case <-ctx.Done(): + assert.Fail(t, "timeout waiting for event") + } +} + +func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + lookup := dns.NewMockLookupForTest(t) + target := "testgrpc:12345" + ip1 := net.ParseIP("192.168.0.1") + ip2 := net.ParseIP("192.168.0.2") + targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) + targetWithIp2 := fmt.Sprintf("%s (%s)", target, ip2) + lookup.Set("testgrpc", []net.IP{ip1}) + client, dnsMonitor := NewClientsForTest(t, target, lookup) + ch := client.GetWakeupChannelForTesting() + + ctx, cancel := context.WithTimeout(ctx, testTimeout) + defer cancel() + + // Wait for initial check to be done to make sure internal dnsmonitor goroutine is waiting. + if err := dnsMonitor.WaitForTicker(ctx); err != nil { + require.NoError(err) + } + + test.DrainWakeupChannel(ch) + dnsMonitor.CheckHostnames() + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(targetWithIp1, clients[0].Target()) + assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) + } + + lookup.Set("testgrpc", []net.IP{ip1, ip2}) + test.DrainWakeupChannel(ch) + dnsMonitor.CheckHostnames() + waitForEvent(ctx, t, ch) + + if clients := client.GetClients(); assert.Len(clients, 2) { + assert.Equal(targetWithIp1, clients[0].Target()) + assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) + assert.Equal(targetWithIp2, clients[1].Target()) + assert.True(clients[1].ip.Equal(ip2), "Expected IP %s, got %s", ip2, clients[1].ip) + } + + lookup.Set("testgrpc", []net.IP{ip2}) + test.DrainWakeupChannel(ch) + dnsMonitor.CheckHostnames() + waitForEvent(ctx, t, ch) + + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(targetWithIp2, clients[0].Target()) + assert.True(clients[0].ip.Equal(ip2), "Expected IP %s, got %s", ip2, clients[0].ip) + } + }) +} + +func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { + t.Parallel() + assert := assert.New(t) + lookup := dns.NewMockLookupForTest(t) + target := "testgrpc:12345" + ip1 := net.ParseIP("192.168.0.1") + targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) + client, dnsMonitor := NewClientsForTest(t, target, lookup) + ch := client.GetWakeupChannelForTesting() + + testCtx, testCtxCancel := context.WithTimeout(context.Background(), testTimeout) + defer testCtxCancel() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + require.NoError(t, client.WaitForInitialized(ctx)) + + assert.Empty(client.GetClients()) + + lookup.Set("testgrpc", []net.IP{ip1}) + test.DrainWakeupChannel(ch) + dnsMonitor.CheckHostnames() + waitForEvent(testCtx, t, ch) + + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(targetWithIp1, clients[0].Target()) + assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) + } +} diff --git a/grpc_common.go b/grpc/common.go similarity index 88% rename from grpc_common.go rename to grpc/common.go index c6ec5c4..1464932 100644 --- a/grpc_common.go +++ b/grpc/common.go @@ -19,13 +19,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package grpc import ( "context" "crypto/tls" + "errors" "fmt" "net" + "time" "github.com/dlintw/goconf" "google.golang.org/grpc/credentials" @@ -136,6 +138,34 @@ func (c *reloadableCredentials) Close() { } } +func (c *reloadableCredentials) WaitForCertificateReload(ctx context.Context, counter uint64) error { + if c.loader == nil { + return errors.New("no certificate loaded") + } + + for counter == c.loader.GetReloadCounter() { + if err := ctx.Err(); err != nil { + return err + } + time.Sleep(time.Millisecond) + } + return nil +} + +func (c *reloadableCredentials) WaitForCertPoolReload(ctx context.Context, counter uint64) error { + if c.pool == nil { + return errors.New("no certificate pool loaded") + } + + for counter == c.pool.GetReloadCounter() { + if err := ctx.Err(); err != nil { + return err + } + time.Sleep(time.Millisecond) + } + return nil +} + func NewReloadableCredentials(logger log.Logger, config *goconf.ConfigFile, server bool) (credentials.TransportCredentials, error) { var prefix string var caPrefix string diff --git a/grpc/common_test.go b/grpc/common_test.go new file mode 100644 index 0000000..2b3d1ed --- /dev/null +++ b/grpc/common_test.go @@ -0,0 +1,30 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2022 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 grpc + +import ( + "time" +) + +const ( + testTimeout = 10 * time.Second +) diff --git a/grpc_internal.pb.go b/grpc/internal.pb.go similarity index 85% rename from grpc_internal.pb.go rename to grpc/internal.pb.go index b0e4219..243f459 100644 --- a/grpc_internal.pb.go +++ b/grpc/internal.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go. DO NOT EDIT. -// source: grpc_internal.proto +// source: grpc/internal.proto -package signaling +package grpc import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -279,7 +279,7 @@ var File_grpc_internal_proto protoreflect.FileDescriptor const file_grpc_internal_proto_rawDesc = "" + "\n" + - "\x13grpc_internal.proto\x12\tsignaling\"\x14\n" + + "\x13grpc/internal.proto\x12\x04grpc\"\x14\n" + "\x12GetServerIdRequest\"H\n" + "\x10GetServerIdReply\x12\x1a\n" + "\bserverId\x18\x01 \x01(\tR\bserverId\x12\x18\n" + @@ -289,15 +289,15 @@ const file_grpc_internal_proto_rawDesc = "" + "\vbackendUrls\x18\x02 \x03(\tR\vbackendUrls\"H\n" + "\x16GrpcTransientDataEntry\x12\x14\n" + "\x05value\x18\x01 \x01(\fR\x05value\x12\x18\n" + - "\aexpires\x18\x02 \x01(\x03R\aexpires\"\xbf\x01\n" + - "\x15GetTransientDataReply\x12G\n" + - "\aentries\x18\x01 \x03(\v2-.signaling.GetTransientDataReply.EntriesEntryR\aentries\x1a]\n" + + "\aexpires\x18\x02 \x01(\x03R\aexpires\"\xb5\x01\n" + + "\x15GetTransientDataReply\x12B\n" + + "\aentries\x18\x01 \x03(\v2(.grpc.GetTransientDataReply.EntriesEntryR\aentries\x1aX\n" + "\fEntriesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x127\n" + - "\x05value\x18\x02 \x01(\v2!.signaling.GrpcTransientDataEntryR\x05value:\x028\x012\xb6\x01\n" + - "\vRpcInternal\x12K\n" + - "\vGetServerId\x12\x1d.signaling.GetServerIdRequest\x1a\x1b.signaling.GetServerIdReply\"\x00\x12Z\n" + - "\x10GetTransientData\x12\".signaling.GetTransientDataRequest\x1a .signaling.GetTransientDataReply\"\x00B signaling.GetTransientDataReply.EntriesEntry - 3, // 1: signaling.GetTransientDataReply.EntriesEntry.value:type_name -> signaling.GrpcTransientDataEntry - 0, // 2: signaling.RpcInternal.GetServerId:input_type -> signaling.GetServerIdRequest - 2, // 3: signaling.RpcInternal.GetTransientData:input_type -> signaling.GetTransientDataRequest - 1, // 4: signaling.RpcInternal.GetServerId:output_type -> signaling.GetServerIdReply - 4, // 5: signaling.RpcInternal.GetTransientData:output_type -> signaling.GetTransientDataReply + 5, // 0: grpc.GetTransientDataReply.entries:type_name -> grpc.GetTransientDataReply.EntriesEntry + 3, // 1: grpc.GetTransientDataReply.EntriesEntry.value:type_name -> grpc.GrpcTransientDataEntry + 0, // 2: grpc.RpcInternal.GetServerId:input_type -> grpc.GetServerIdRequest + 2, // 3: grpc.RpcInternal.GetTransientData:input_type -> grpc.GetTransientDataRequest + 1, // 4: grpc.RpcInternal.GetServerId:output_type -> grpc.GetServerIdReply + 4, // 5: grpc.RpcInternal.GetTransientData:output_type -> grpc.GetTransientDataReply 4, // [4:6] is the sub-list for method output_type 2, // [2:4] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name diff --git a/grpc_internal.proto b/grpc/internal.proto similarity index 97% rename from grpc_internal.proto rename to grpc/internal.proto index 22c873f..3a72483 100644 --- a/grpc_internal.proto +++ b/grpc/internal.proto @@ -21,9 +21,9 @@ */ syntax = "proto3"; -option go_package = "github.com/strukturag/nextcloud-spreed-signaling;signaling"; +option go_package = "github.com/strukturag/nextcloud-spreed-signaling/grpc"; -package signaling; +package grpc; service RpcInternal { rpc GetServerId(GetServerIdRequest) returns (GetServerIdReply) {} diff --git a/grpc_internal_grpc.pb.go b/grpc/internal_grpc.pb.go similarity index 95% rename from grpc_internal_grpc.pb.go rename to grpc/internal_grpc.pb.go index 9899a97..23d7f42 100644 --- a/grpc_internal_grpc.pb.go +++ b/grpc/internal_grpc.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// source: grpc_internal.proto +// source: grpc/internal.proto -package signaling +package grpc import ( context "context" @@ -37,8 +37,8 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - RpcInternal_GetServerId_FullMethodName = "/signaling.RpcInternal/GetServerId" - RpcInternal_GetTransientData_FullMethodName = "/signaling.RpcInternal/GetTransientData" + RpcInternal_GetServerId_FullMethodName = "/grpc.RpcInternal/GetServerId" + RpcInternal_GetTransientData_FullMethodName = "/grpc.RpcInternal/GetTransientData" ) // RpcInternalClient is the client API for RpcInternal service. @@ -160,7 +160,7 @@ func _RpcInternal_GetTransientData_Handler(srv interface{}, ctx context.Context, // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RpcInternal_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "signaling.RpcInternal", + ServiceName: "grpc.RpcInternal", HandlerType: (*RpcInternalServer)(nil), Methods: []grpc.MethodDesc{ { @@ -173,5 +173,5 @@ var RpcInternal_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "grpc_internal.proto", + Metadata: "grpc/internal.proto", } diff --git a/grpc/server_id.go b/grpc/server_id.go new file mode 100644 index 0000000..8198f52 --- /dev/null +++ b/grpc/server_id.go @@ -0,0 +1,45 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 grpc + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "os" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" +) + +var ( + ServerId string +) + +func init() { + hostname, err := os.Hostname() + if err != nil { + hostname = internal.RandomString(8) + } + md := sha256.New() + fmt.Fprintf(md, "%s-%s-%d", internal.RandomString(32), hostname, os.Getpid()) + ServerId = hex.EncodeToString(md.Sum(nil)) +} diff --git a/grpc_sessions.pb.go b/grpc/sessions.pb.go similarity index 86% rename from grpc_sessions.pb.go rename to grpc/sessions.pb.go index 3d9fbc8..783d885 100644 --- a/grpc_sessions.pb.go +++ b/grpc/sessions.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go. DO NOT EDIT. -// source: grpc_sessions.proto +// source: grpc/sessions.proto -package signaling +package grpc import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -331,7 +331,7 @@ func (x *IsSessionInCallReply) GetInCall() bool { type GetInternalSessionsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RoomId string `protobuf:"bytes,1,opt,name=roomId,proto3" json:"roomId,omitempty"` - // Deprecated: Marked as deprecated in grpc_sessions.proto. + // Deprecated: Marked as deprecated in grpc/sessions.proto. BackendUrl string `protobuf:"bytes,2,opt,name=backendUrl,proto3" json:"backendUrl,omitempty"` BackendUrls []string `protobuf:"bytes,3,rep,name=backendUrls,proto3" json:"backendUrls,omitempty"` unknownFields protoimpl.UnknownFields @@ -375,7 +375,7 @@ func (x *GetInternalSessionsRequest) GetRoomId() string { return "" } -// Deprecated: Marked as deprecated in grpc_sessions.proto. +// Deprecated: Marked as deprecated in grpc/sessions.proto. func (x *GetInternalSessionsRequest) GetBackendUrl() string { if x != nil { return x.BackendUrl @@ -646,7 +646,7 @@ var File_grpc_sessions_proto protoreflect.FileDescriptor const file_grpc_sessions_proto_rawDesc = "" + "\n" + - "\x13grpc_sessions.proto\x12\tsignaling\"3\n" + + "\x13grpc/sessions.proto\x12\x04grpc\"3\n" + "\x15LookupResumeIdRequest\x12\x1a\n" + "\bresumeId\x18\x01 \x01(\tR\bresumeId\"3\n" + "\x13LookupResumeIdReply\x12\x1c\n" + @@ -676,20 +676,20 @@ const file_grpc_sessions_proto_rawDesc = "" + "\bfeatures\x18\x03 \x03(\tR\bfeatures\"J\n" + "\x12VirtualSessionData\x12\x1c\n" + "\tsessionId\x18\x01 \x01(\tR\tsessionId\x12\x16\n" + - "\x06inCall\x18\x02 \x01(\rR\x06inCall\"\xaf\x01\n" + - "\x18GetInternalSessionsReply\x12J\n" + - "\x10internalSessions\x18\x01 \x03(\v2\x1e.signaling.InternalSessionDataR\x10internalSessions\x12G\n" + - "\x0fvirtualSessions\x18\x02 \x03(\v2\x1d.signaling.VirtualSessionDataR\x0fvirtualSessions\"0\n" + + "\x06inCall\x18\x02 \x01(\rR\x06inCall\"\xa5\x01\n" + + "\x18GetInternalSessionsReply\x12E\n" + + "\x10internalSessions\x18\x01 \x03(\v2\x19.grpc.InternalSessionDataR\x10internalSessions\x12B\n" + + "\x0fvirtualSessions\x18\x02 \x03(\v2\x18.grpc.VirtualSessionDataR\x0fvirtualSessions\"0\n" + "\x14ClientSessionMessage\x12\x18\n" + "\amessage\x18\x01 \x01(\fR\amessage\"0\n" + "\x14ServerSessionMessage\x12\x18\n" + - "\amessage\x18\x01 \x01(\fR\amessage2\xd2\x03\n" + - "\vRpcSessions\x12T\n" + - "\x0eLookupResumeId\x12 .signaling.LookupResumeIdRequest\x1a\x1e.signaling.LookupResumeIdReply\"\x00\x12W\n" + - "\x0fLookupSessionId\x12!.signaling.LookupSessionIdRequest\x1a\x1f.signaling.LookupSessionIdReply\"\x00\x12W\n" + - "\x0fIsSessionInCall\x12!.signaling.IsSessionInCallRequest\x1a\x1f.signaling.IsSessionInCallReply\"\x00\x12c\n" + - "\x13GetInternalSessions\x12%.signaling.GetInternalSessionsRequest\x1a#.signaling.GetInternalSessionsReply\"\x00\x12V\n" + - "\fProxySession\x12\x1f.signaling.ClientSessionMessage\x1a\x1f.signaling.ServerSessionMessage\"\x00(\x010\x01B signaling.InternalSessionData - 8, // 1: signaling.GetInternalSessionsReply.virtualSessions:type_name -> signaling.VirtualSessionData - 0, // 2: signaling.RpcSessions.LookupResumeId:input_type -> signaling.LookupResumeIdRequest - 2, // 3: signaling.RpcSessions.LookupSessionId:input_type -> signaling.LookupSessionIdRequest - 4, // 4: signaling.RpcSessions.IsSessionInCall:input_type -> signaling.IsSessionInCallRequest - 6, // 5: signaling.RpcSessions.GetInternalSessions:input_type -> signaling.GetInternalSessionsRequest - 10, // 6: signaling.RpcSessions.ProxySession:input_type -> signaling.ClientSessionMessage - 1, // 7: signaling.RpcSessions.LookupResumeId:output_type -> signaling.LookupResumeIdReply - 3, // 8: signaling.RpcSessions.LookupSessionId:output_type -> signaling.LookupSessionIdReply - 5, // 9: signaling.RpcSessions.IsSessionInCall:output_type -> signaling.IsSessionInCallReply - 9, // 10: signaling.RpcSessions.GetInternalSessions:output_type -> signaling.GetInternalSessionsReply - 11, // 11: signaling.RpcSessions.ProxySession:output_type -> signaling.ServerSessionMessage + 7, // 0: grpc.GetInternalSessionsReply.internalSessions:type_name -> grpc.InternalSessionData + 8, // 1: grpc.GetInternalSessionsReply.virtualSessions:type_name -> grpc.VirtualSessionData + 0, // 2: grpc.RpcSessions.LookupResumeId:input_type -> grpc.LookupResumeIdRequest + 2, // 3: grpc.RpcSessions.LookupSessionId:input_type -> grpc.LookupSessionIdRequest + 4, // 4: grpc.RpcSessions.IsSessionInCall:input_type -> grpc.IsSessionInCallRequest + 6, // 5: grpc.RpcSessions.GetInternalSessions:input_type -> grpc.GetInternalSessionsRequest + 10, // 6: grpc.RpcSessions.ProxySession:input_type -> grpc.ClientSessionMessage + 1, // 7: grpc.RpcSessions.LookupResumeId:output_type -> grpc.LookupResumeIdReply + 3, // 8: grpc.RpcSessions.LookupSessionId:output_type -> grpc.LookupSessionIdReply + 5, // 9: grpc.RpcSessions.IsSessionInCall:output_type -> grpc.IsSessionInCallReply + 9, // 10: grpc.RpcSessions.GetInternalSessions:output_type -> grpc.GetInternalSessionsReply + 11, // 11: grpc.RpcSessions.ProxySession:output_type -> grpc.ServerSessionMessage 7, // [7:12] is the sub-list for method output_type 2, // [2:7] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name diff --git a/grpc_sessions.proto b/grpc/sessions.proto similarity index 98% rename from grpc_sessions.proto rename to grpc/sessions.proto index 0503553..858eca0 100644 --- a/grpc_sessions.proto +++ b/grpc/sessions.proto @@ -21,9 +21,9 @@ */ syntax = "proto3"; -option go_package = "github.com/strukturag/nextcloud-spreed-signaling;signaling"; +option go_package = "github.com/strukturag/nextcloud-spreed-signaling/grpc"; -package signaling; +package grpc; service RpcSessions { rpc LookupResumeId(LookupResumeIdRequest) returns (LookupResumeIdReply) {} diff --git a/grpc_sessions_grpc.pb.go b/grpc/sessions_grpc.pb.go similarity index 95% rename from grpc_sessions_grpc.pb.go rename to grpc/sessions_grpc.pb.go index dc2173a..c10dcb7 100644 --- a/grpc_sessions_grpc.pb.go +++ b/grpc/sessions_grpc.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// source: grpc_sessions.proto +// source: grpc/sessions.proto -package signaling +package grpc import ( context "context" @@ -37,11 +37,11 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - RpcSessions_LookupResumeId_FullMethodName = "/signaling.RpcSessions/LookupResumeId" - RpcSessions_LookupSessionId_FullMethodName = "/signaling.RpcSessions/LookupSessionId" - RpcSessions_IsSessionInCall_FullMethodName = "/signaling.RpcSessions/IsSessionInCall" - RpcSessions_GetInternalSessions_FullMethodName = "/signaling.RpcSessions/GetInternalSessions" - RpcSessions_ProxySession_FullMethodName = "/signaling.RpcSessions/ProxySession" + RpcSessions_LookupResumeId_FullMethodName = "/grpc.RpcSessions/LookupResumeId" + RpcSessions_LookupSessionId_FullMethodName = "/grpc.RpcSessions/LookupSessionId" + RpcSessions_IsSessionInCall_FullMethodName = "/grpc.RpcSessions/IsSessionInCall" + RpcSessions_GetInternalSessions_FullMethodName = "/grpc.RpcSessions/GetInternalSessions" + RpcSessions_ProxySession_FullMethodName = "/grpc.RpcSessions/ProxySession" ) // RpcSessionsClient is the client API for RpcSessions service. @@ -254,7 +254,7 @@ type RpcSessions_ProxySessionServer = grpc.BidiStreamingServer[ClientSessionMess // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RpcSessions_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "signaling.RpcSessions", + ServiceName: "grpc.RpcSessions", HandlerType: (*RpcSessionsServer)(nil), Methods: []grpc.MethodDesc{ { @@ -282,5 +282,5 @@ var RpcSessions_ServiceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, - Metadata: "grpc_sessions.proto", + Metadata: "grpc/sessions.proto", } diff --git a/grpc_mcu.pb.go b/grpc/sfu.pb.go similarity index 73% rename from grpc_mcu.pb.go rename to grpc/sfu.pb.go index add128c..4c4cabf 100644 --- a/grpc_mcu.pb.go +++ b/grpc/sfu.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go. DO NOT EDIT. -// source: grpc_mcu.proto +// source: grpc/sfu.proto -package signaling +package grpc import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -49,7 +49,7 @@ type GetPublisherIdRequest struct { func (x *GetPublisherIdRequest) Reset() { *x = GetPublisherIdRequest{} - mi := &file_grpc_mcu_proto_msgTypes[0] + mi := &file_grpc_sfu_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -61,7 +61,7 @@ func (x *GetPublisherIdRequest) String() string { func (*GetPublisherIdRequest) ProtoMessage() {} func (x *GetPublisherIdRequest) ProtoReflect() protoreflect.Message { - mi := &file_grpc_mcu_proto_msgTypes[0] + mi := &file_grpc_sfu_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -74,7 +74,7 @@ func (x *GetPublisherIdRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetPublisherIdRequest.ProtoReflect.Descriptor instead. func (*GetPublisherIdRequest) Descriptor() ([]byte, []int) { - return file_grpc_mcu_proto_rawDescGZIP(), []int{0} + return file_grpc_sfu_proto_rawDescGZIP(), []int{0} } func (x *GetPublisherIdRequest) GetSessionId() string { @@ -104,7 +104,7 @@ type GetPublisherIdReply struct { func (x *GetPublisherIdReply) Reset() { *x = GetPublisherIdReply{} - mi := &file_grpc_mcu_proto_msgTypes[1] + mi := &file_grpc_sfu_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -116,7 +116,7 @@ func (x *GetPublisherIdReply) String() string { func (*GetPublisherIdReply) ProtoMessage() {} func (x *GetPublisherIdReply) ProtoReflect() protoreflect.Message { - mi := &file_grpc_mcu_proto_msgTypes[1] + mi := &file_grpc_sfu_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -129,7 +129,7 @@ func (x *GetPublisherIdReply) ProtoReflect() protoreflect.Message { // Deprecated: Use GetPublisherIdReply.ProtoReflect.Descriptor instead. func (*GetPublisherIdReply) Descriptor() ([]byte, []int) { - return file_grpc_mcu_proto_rawDescGZIP(), []int{1} + return file_grpc_sfu_proto_rawDescGZIP(), []int{1} } func (x *GetPublisherIdReply) GetPublisherId() string { @@ -167,11 +167,11 @@ func (x *GetPublisherIdReply) GetPublisherToken() string { return "" } -var File_grpc_mcu_proto protoreflect.FileDescriptor +var File_grpc_sfu_proto protoreflect.FileDescriptor -const file_grpc_mcu_proto_rawDesc = "" + +const file_grpc_sfu_proto_rawDesc = "" + "\n" + - "\x0egrpc_mcu.proto\x12\tsignaling\"U\n" + + "\x0egrpc/sfu.proto\x12\x04grpc\"U\n" + "\x15GetPublisherIdRequest\x12\x1c\n" + "\tsessionId\x18\x01 \x01(\tR\tsessionId\x12\x1e\n" + "\n" + @@ -182,30 +182,30 @@ const file_grpc_mcu_proto_rawDesc = "" + "\bproxyUrl\x18\x02 \x01(\tR\bproxyUrl\x12\x0e\n" + "\x02ip\x18\x03 \x01(\tR\x02ip\x12\"\n" + "\fconnectToken\x18\x04 \x01(\tR\fconnectToken\x12&\n" + - "\x0epublisherToken\x18\x05 \x01(\tR\x0epublisherToken2^\n" + - "\x06RpcMcu\x12T\n" + - "\x0eGetPublisherId\x12 .signaling.GetPublisherIdRequest\x1a\x1e.signaling.GetPublisherIdReply\"\x00B signaling.GetPublisherIdRequest - 1, // 1: signaling.RpcMcu.GetPublisherId:output_type -> signaling.GetPublisherIdReply +var file_grpc_sfu_proto_depIdxs = []int32{ + 0, // 0: grpc.RpcMcu.GetPublisherId:input_type -> grpc.GetPublisherIdRequest + 1, // 1: grpc.RpcMcu.GetPublisherId:output_type -> grpc.GetPublisherIdReply 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name @@ -213,26 +213,26 @@ var file_grpc_mcu_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for field type_name } -func init() { file_grpc_mcu_proto_init() } -func file_grpc_mcu_proto_init() { - if File_grpc_mcu_proto != nil { +func init() { file_grpc_sfu_proto_init() } +func file_grpc_sfu_proto_init() { + if File_grpc_sfu_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_mcu_proto_rawDesc), len(file_grpc_mcu_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_sfu_proto_rawDesc), len(file_grpc_sfu_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_grpc_mcu_proto_goTypes, - DependencyIndexes: file_grpc_mcu_proto_depIdxs, - MessageInfos: file_grpc_mcu_proto_msgTypes, + GoTypes: file_grpc_sfu_proto_goTypes, + DependencyIndexes: file_grpc_sfu_proto_depIdxs, + MessageInfos: file_grpc_sfu_proto_msgTypes, }.Build() - File_grpc_mcu_proto = out.File - file_grpc_mcu_proto_goTypes = nil - file_grpc_mcu_proto_depIdxs = nil + File_grpc_sfu_proto = out.File + file_grpc_sfu_proto_goTypes = nil + file_grpc_sfu_proto_depIdxs = nil } diff --git a/grpc_mcu.proto b/grpc/sfu.proto similarity index 97% rename from grpc_mcu.proto rename to grpc/sfu.proto index c766ddc..e0906c1 100644 --- a/grpc_mcu.proto +++ b/grpc/sfu.proto @@ -21,9 +21,9 @@ */ syntax = "proto3"; -option go_package = "github.com/strukturag/nextcloud-spreed-signaling;signaling"; +option go_package = "github.com/strukturag/nextcloud-spreed-signaling/grpc"; -package signaling; +package grpc; service RpcMcu { rpc GetPublisherId(GetPublisherIdRequest) returns (GetPublisherIdReply) {} diff --git a/grpc_mcu_grpc.pb.go b/grpc/sfu_grpc.pb.go similarity index 96% rename from grpc_mcu_grpc.pb.go rename to grpc/sfu_grpc.pb.go index af6565c..a1c38dc 100644 --- a/grpc_mcu_grpc.pb.go +++ b/grpc/sfu_grpc.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// source: grpc_mcu.proto +// source: grpc/sfu.proto -package signaling +package grpc import ( context "context" @@ -37,7 +37,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - RpcMcu_GetPublisherId_FullMethodName = "/signaling.RpcMcu/GetPublisherId" + RpcMcu_GetPublisherId_FullMethodName = "/grpc.RpcMcu/GetPublisherId" ) // RpcMcuClient is the client API for RpcMcu service. @@ -126,7 +126,7 @@ func _RpcMcu_GetPublisherId_Handler(srv interface{}, ctx context.Context, dec fu // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RpcMcu_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "signaling.RpcMcu", + ServiceName: "grpc.RpcMcu", HandlerType: (*RpcMcuServer)(nil), Methods: []grpc.MethodDesc{ { @@ -135,5 +135,5 @@ var RpcMcu_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "grpc_mcu.proto", + Metadata: "grpc/sfu.proto", } diff --git a/syscallconn.go b/grpc/syscallconn.go similarity index 99% rename from syscallconn.go rename to grpc/syscallconn.go index db33e69..6f7a13d 100644 --- a/syscallconn.go +++ b/grpc/syscallconn.go @@ -16,7 +16,7 @@ * */ -package signaling +package grpc import ( "net" diff --git a/grpc/test/client.go b/grpc/test/client.go new file mode 100644 index 0000000..52a8f86 --- /dev/null +++ b/grpc/test/client.go @@ -0,0 +1,75 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/dns" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { + dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + client, err := grpc.NewClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") + require.NoError(t, err) + t.Cleanup(func() { + client.Close() + }) + + return client, dnsMonitor +} + +func NewClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { + config := goconf.NewConfigFile() + config.AddOption("grpc", "targets", addr) + config.AddOption("grpc", "dnsdiscovery", "true") + + return NewClientsForTestWithConfig(t, config, nil, lookup) +} + +func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { + config := goconf.NewConfigFile() + config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) + + config.AddOption("grpc", "targettype", "etcd") + config.AddOption("grpc", "targetprefix", "/grpctargets") + + logger := log.NewLoggerForTest(t) + etcdClient, err := etcd.NewClient(logger, config, "") + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, etcdClient.Close()) + }) + + return NewClientsForTestWithConfig(t, config, etcdClient, lookup) +} diff --git a/grpc_client_test.go b/grpc_client_test.go index f315327..34a3146 100644 --- a/grpc_client_test.go +++ b/grpc_client_test.go @@ -23,77 +23,19 @@ package signaling import ( "context" - "crypto/rand" - "crypto/rsa" - "fmt" - "net" - "path" "testing" "time" - "github.com/dlintw/goconf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) -func (c *GrpcClients) getWakeupChannelForTesting() <-chan struct{} { - c.mu.Lock() - defer c.mu.Unlock() - - if c.wakeupChanForTesting != nil { - return c.wakeupChanForTesting - } - - ch := make(chan struct{}, 1) - c.wakeupChanForTesting = ch - return ch -} - -func NewGrpcClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*GrpcClients, *dns.Monitor) { - dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - client, err := NewGrpcClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") - require.NoError(t, err) - t.Cleanup(func() { - client.Close() - }) - - return client, dnsMonitor -} - -func NewGrpcClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*GrpcClients, *dns.Monitor) { - config := goconf.NewConfigFile() - config.AddOption("grpc", "targets", addr) - config.AddOption("grpc", "dnsdiscovery", "true") - - return NewGrpcClientsForTestWithConfig(t, config, nil, lookup) -} - -func NewGrpcClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dns.MockLookup) (*GrpcClients, *dns.Monitor) { - config := goconf.NewConfigFile() - config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) - - config.AddOption("grpc", "targettype", "etcd") - config.AddOption("grpc", "targetprefix", "/grpctargets") - - logger := log.NewLoggerForTest(t) - etcdClient, err := etcd.NewClient(logger, config, "") - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, etcdClient.Close()) - }) - - return NewGrpcClientsForTestWithConfig(t, config, etcdClient, lookup) -} - func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { t.Helper() @@ -117,7 +59,7 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - client, _ := NewGrpcClientsWithEtcdForTest(t, embedEtcd, nil) + client, _ := grpctest.NewClientsWithEtcdForTest(t, embedEtcd, nil) ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() require.NoError(t, client.WaitForInitialized(ctx)) @@ -133,8 +75,8 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) embedEtcd := etcdtest.NewServerForTest(t) - client, _ := NewGrpcClientsWithEtcdForTest(t, embedEtcd, nil) - ch := client.getWakeupChannelForTesting() + client, _ := grpctest.NewClientsWithEtcdForTest(t, embedEtcd, nil) + ch := client.GetWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() @@ -180,8 +122,8 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) embedEtcd := etcdtest.NewServerForTest(t) - client, _ := NewGrpcClientsWithEtcdForTest(t, embedEtcd, nil) - ch := client.getWakeupChannelForTesting() + client, _ := grpctest.NewClientsWithEtcdForTest(t, embedEtcd, nil) + ch := client.GetWakeupChannelForTesting() ctx, cancel := context.WithTimeout(ctx, testTimeout) defer cancel() @@ -198,10 +140,10 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { test.DrainWakeupChannel(ch) server2, addr2 := NewGrpcServerForTest(t) - server2.serverId = GrpcServerId + server2.serverId = grpc.ServerId embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) waitForEvent(ctx, t, ch) - client.selfCheckWaitGroup.Wait() + client.WaitForSelfCheck() if clients := client.GetClients(); assert.Len(clients, 1) { assert.Equal(addr1, clients[0].Target()) } @@ -213,138 +155,3 @@ func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { assert.Equal(addr1, clients[0].Target()) } } - -func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - lookup := dns.NewMockLookupForTest(t) - target := "testgrpc:12345" - ip1 := net.ParseIP("192.168.0.1") - ip2 := net.ParseIP("192.168.0.2") - targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) - targetWithIp2 := fmt.Sprintf("%s (%s)", target, ip2) - lookup.Set("testgrpc", []net.IP{ip1}) - client, dnsMonitor := NewGrpcClientsForTest(t, target, lookup) - ch := client.getWakeupChannelForTesting() - - ctx, cancel := context.WithTimeout(ctx, testTimeout) - defer cancel() - - // Wait for initial check to be done to make sure internal dnsmonitor goroutine is waiting. - if err := dnsMonitor.WaitForTicker(ctx); err != nil { - require.NoError(err) - } - - test.DrainWakeupChannel(ch) - dnsMonitor.CheckHostnames() - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(targetWithIp1, clients[0].Target()) - assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) - } - - lookup.Set("testgrpc", []net.IP{ip1, ip2}) - test.DrainWakeupChannel(ch) - dnsMonitor.CheckHostnames() - waitForEvent(ctx, t, ch) - - if clients := client.GetClients(); assert.Len(clients, 2) { - assert.Equal(targetWithIp1, clients[0].Target()) - assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) - assert.Equal(targetWithIp2, clients[1].Target()) - assert.True(clients[1].ip.Equal(ip2), "Expected IP %s, got %s", ip2, clients[1].ip) - } - - lookup.Set("testgrpc", []net.IP{ip2}) - test.DrainWakeupChannel(ch) - dnsMonitor.CheckHostnames() - waitForEvent(ctx, t, ch) - - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(targetWithIp2, clients[0].Target()) - assert.True(clients[0].ip.Equal(ip2), "Expected IP %s, got %s", ip2, clients[0].ip) - } - }) -} - -func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { - t.Parallel() - assert := assert.New(t) - lookup := dns.NewMockLookupForTest(t) - target := "testgrpc:12345" - ip1 := net.ParseIP("192.168.0.1") - targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) - client, dnsMonitor := NewGrpcClientsForTest(t, target, lookup) - ch := client.getWakeupChannelForTesting() - - testCtx, testCtxCancel := context.WithTimeout(context.Background(), testTimeout) - defer testCtxCancel() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - require.NoError(t, client.WaitForInitialized(ctx)) - - assert.Empty(client.GetClients()) - - lookup.Set("testgrpc", []net.IP{ip1}) - test.DrainWakeupChannel(ch) - dnsMonitor.CheckHostnames() - waitForEvent(testCtx, t, ch) - - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(targetWithIp1, clients[0].Target()) - assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) - } -} - -func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest - test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - require := require.New(t) - serverKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - clientKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - - serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) - clientCert := internal.GenerateSelfSignedCertificateForTesting(t, "Testing client", clientKey) - - dir := t.TempDir() - serverPrivkeyFile := path.Join(dir, "server-privkey.pem") - serverPubkeyFile := path.Join(dir, "server-pubkey.pem") - serverCertFile := path.Join(dir, "server-cert.pem") - require.NoError(internal.WritePrivateKey(serverKey, serverPrivkeyFile)) - require.NoError(internal.WritePublicKey(&serverKey.PublicKey, serverPubkeyFile)) - require.NoError(internal.WriteCertificate(serverCert, serverCertFile)) - clientPrivkeyFile := path.Join(dir, "client-privkey.pem") - clientPubkeyFile := path.Join(dir, "client-pubkey.pem") - clientCertFile := path.Join(dir, "client-cert.pem") - require.NoError(internal.WritePrivateKey(clientKey, clientPrivkeyFile)) - require.NoError(internal.WritePublicKey(&clientKey.PublicKey, clientPubkeyFile)) - require.NoError(internal.WriteCertificate(clientCert, clientCertFile)) - - serverConfig := goconf.NewConfigFile() - serverConfig.AddOption("grpc", "servercertificate", serverCertFile) - serverConfig.AddOption("grpc", "serverkey", serverPrivkeyFile) - serverConfig.AddOption("grpc", "clientca", clientCertFile) - _, addr := NewGrpcServerForTestWithConfig(t, serverConfig) - - clientConfig := goconf.NewConfigFile() - clientConfig.AddOption("grpc", "targets", addr) - clientConfig.AddOption("grpc", "clientcertificate", clientCertFile) - clientConfig.AddOption("grpc", "clientkey", clientPrivkeyFile) - clientConfig.AddOption("grpc", "serverca", serverCertFile) - clients, _ := NewGrpcClientsForTestWithConfig(t, clientConfig, nil, nil) - - ctx, cancel1 := context.WithTimeout(context.Background(), time.Second) - defer cancel1() - - require.NoError(clients.WaitForInitialized(ctx)) - - for _, client := range clients.GetClients() { - _, _, err := client.GetServerId(ctx) - require.NoError(err) - } - }) -} diff --git a/grpc_remote_client.go b/grpc_remote_client.go index 85fbd95..4c34137 100644 --- a/grpc_remote_client.go +++ b/grpc_remote_client.go @@ -35,6 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -54,7 +55,7 @@ func getMD(md metadata.MD, key string) string { type remoteGrpcClient struct { logger log.Logger hub *Hub - client RpcSessions_ProxySessionServer + client grpc.RpcSessions_ProxySessionServer sessionId string remoteAddr string @@ -68,7 +69,7 @@ type remoteGrpcClient struct { messages chan WritableClientMessage } -func newRemoteGrpcClient(hub *Hub, request RpcSessions_ProxySessionServer) (*remoteGrpcClient, error) { +func newRemoteGrpcClient(hub *Hub, request grpc.RpcSessions_ProxySessionServer) (*remoteGrpcClient, error) { md, found := metadata.FromIncomingContext(request.Context()) if !found { return nil, errors.New("no metadata provided") @@ -224,7 +225,7 @@ func (c *remoteGrpcClient) run() error { continue } - if err := c.client.Send(&ServerSessionMessage{ + if err := c.client.Send(&grpc.ServerSessionMessage{ Message: data, }); err != nil { return fmt.Errorf("error sending %+v to remote client for session %s: %w", msg, c.sessionId, err) diff --git a/grpc_server.go b/grpc_server.go index 4ab15d9..1675193 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -23,14 +23,11 @@ package signaling import ( "context" - "crypto/sha256" - "encoding/hex" "encoding/json" "errors" "fmt" "net" "net/url" - "os" "github.com/dlintw/goconf" "google.golang.org/grpc" @@ -40,27 +37,18 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + rpc "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( - GrpcServerId string - ErrNoProxyMcu = errors.New("no proxy mcu") ) func init() { RegisterGrpcServerStats() - - hostname, err := os.Hostname() - if err != nil { - hostname = internal.RandomString(8) - } - md := sha256.New() - fmt.Fprintf(md, "%s-%s-%d", internal.RandomString(32), hostname, os.Getpid()) - GrpcServerId = hex.EncodeToString(md.Sum(nil)) } type GrpcServerHub interface { @@ -74,10 +62,10 @@ type GrpcServerHub interface { } type GrpcServer struct { - UnimplementedRpcBackendServer - UnimplementedRpcInternalServer - UnimplementedRpcMcuServer - UnimplementedRpcSessionsServer + rpc.UnimplementedRpcBackendServer + rpc.UnimplementedRpcInternalServer + rpc.UnimplementedRpcMcuServer + rpc.UnimplementedRpcSessionsServer logger log.Logger version string @@ -100,7 +88,7 @@ func NewGrpcServer(ctx context.Context, cfg *goconf.ConfigFile, version string) } logger := log.LoggerFromContext(ctx) - creds, err := NewReloadableCredentials(logger, cfg, true) + creds, err := rpc.NewReloadableCredentials(logger, cfg, true) if err != nil { return nil, err } @@ -112,12 +100,12 @@ func NewGrpcServer(ctx context.Context, cfg *goconf.ConfigFile, version string) creds: creds, conn: conn, listener: listener, - serverId: GrpcServerId, + serverId: rpc.ServerId, } - RegisterRpcBackendServer(conn, result) - RegisterRpcInternalServer(conn, result) - RegisterRpcSessionsServer(conn, result) - RegisterRpcMcuServer(conn, result) + rpc.RegisterRpcBackendServer(conn, result) + rpc.RegisterRpcInternalServer(conn, result) + rpc.RegisterRpcSessionsServer(conn, result) + rpc.RegisterRpcMcuServer(conn, result) return result, nil } @@ -129,14 +117,18 @@ func (s *GrpcServer) Run() error { return s.conn.Serve(s.listener) } +type SimpleCloser interface { + Close() +} + func (s *GrpcServer) Close() { s.conn.GracefulStop() - if cr, ok := s.creds.(*reloadableCredentials); ok { + if cr, ok := s.creds.(SimpleCloser); ok { cr.Close() } } -func (s *GrpcServer) LookupResumeId(ctx context.Context, request *LookupResumeIdRequest) (*LookupResumeIdReply, error) { +func (s *GrpcServer) LookupResumeId(ctx context.Context, request *rpc.LookupResumeIdRequest) (*rpc.LookupResumeIdReply, error) { statsGrpcServerCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging s.logger.Printf("Lookup session for resume id %s", request.ResumeId) @@ -145,12 +137,12 @@ func (s *GrpcServer) LookupResumeId(ctx context.Context, request *LookupResumeId return nil, status.Error(codes.NotFound, "no such room session id") } - return &LookupResumeIdReply{ + return &rpc.LookupResumeIdReply{ SessionId: string(session.PublicId()), }, nil } -func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSessionIdRequest) (*LookupSessionIdReply, error) { +func (s *GrpcServer) LookupSessionId(ctx context.Context, request *rpc.LookupSessionIdRequest) (*rpc.LookupSessionIdReply, error) { statsGrpcServerCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging s.logger.Printf("Lookup session id for room session id %s", request.RoomSessionId) @@ -174,12 +166,12 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *LookupSession session.Close() } } - return &LookupSessionIdReply{ + return &rpc.LookupSessionIdReply{ SessionId: string(sid), }, nil } -func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCallRequest) (*IsSessionInCallReply, error) { +func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *rpc.IsSessionInCallRequest) (*rpc.IsSessionInCallReply, error) { statsGrpcServerCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging s.logger.Printf("Check if session %s is in call %s on %s", request.SessionId, request.RoomId, request.BackendUrl) @@ -188,7 +180,7 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa return nil, status.Error(codes.NotFound, "no such session id") } - result := &IsSessionInCallReply{} + result := &rpc.IsSessionInCallReply{} room := session.GetRoom() if room == nil || room.Id() != request.GetRoomId() || !room.Backend().HasUrl(request.GetBackendUrl()) || (session.ClientType() != api.HelloClientTypeInternal && !room.IsSessionInCall(session)) { @@ -200,22 +192,22 @@ func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *IsSessionInCa return result, nil } -func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetInternalSessionsRequest) (*GetInternalSessionsReply, error) { +func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *rpc.GetInternalSessionsRequest) (*rpc.GetInternalSessionsReply, error) { statsGrpcServerCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging - s.logger.Printf("Get internal sessions from %s on %v (fallback %s)", request.RoomId, request.BackendUrls, request.BackendUrl) + s.logger.Printf("Get internal sessions from %s on %v (fallback %s)", request.RoomId, request.BackendUrls, request.BackendUrl) // nolint var backendUrls []string if len(request.BackendUrls) > 0 { backendUrls = request.BackendUrls - } else if request.BackendUrl != "" { - backendUrls = append(backendUrls, request.BackendUrl) + } else if request.BackendUrl != "" { // nolint + backendUrls = append(backendUrls, request.BackendUrl) // nolint } else { // Only compat backend. backendUrls = []string{""} } - result := &GetInternalSessionsReply{} + result := &rpc.GetInternalSessionsReply{} processed := make(map[string]bool) for _, bu := range backendUrls { var parsed *url.URL @@ -247,7 +239,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetIntern defer room.mu.RUnlock() for session := range room.internalSessions { - result.InternalSessions = append(result.InternalSessions, &InternalSessionData{ + result.InternalSessions = append(result.InternalSessions, &rpc.InternalSessionData{ SessionId: string(session.PublicId()), InCall: uint32(session.GetInCall()), Features: session.GetFeatures(), @@ -255,7 +247,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetIntern } for session := range room.virtualSessions { - result.VirtualSessions = append(result.VirtualSessions, &VirtualSessionData{ + result.VirtualSessions = append(result.VirtualSessions, &rpc.VirtualSessionData{ SessionId: string(session.PublicId()), InCall: uint32(session.GetInCall()), }) @@ -265,7 +257,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *GetIntern return result, nil } -func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherIdRequest) (*GetPublisherIdReply, error) { +func (s *GrpcServer) GetPublisherId(ctx context.Context, request *rpc.GetPublisherIdRequest) (*rpc.GetPublisherIdReply, error) { statsGrpcServerCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging s.logger.Printf("Get %s publisher id for session %s", request.StreamType, request.SessionId) @@ -279,13 +271,14 @@ func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherId return nil, status.Error(codes.NotFound, "no such session") } - publisher := clientSession.GetOrWaitForPublisher(ctx, StreamType(request.StreamType)) - if publisher, ok := publisher.(*mcuProxyPublisher); ok { - reply := &GetPublisherIdReply{ + publisher := clientSession.GetOrWaitForPublisher(ctx, sfu.StreamType(request.StreamType)) + if publisher, ok := publisher.(sfu.PublisherWithConnectionUrlAndIP); ok { + connUrl, ip := publisher.GetConnectionURL() + reply := &rpc.GetPublisherIdReply{ PublisherId: publisher.Id(), - ProxyUrl: publisher.conn.rawUrl, + ProxyUrl: connUrl, } - if ip := publisher.conn.ip; ip != nil { + if len(ip) > 0 { reply.Ip = ip.String() } var err error @@ -303,15 +296,15 @@ func (s *GrpcServer) GetPublisherId(ctx context.Context, request *GetPublisherId return nil, status.Error(codes.NotFound, "no such publisher") } -func (s *GrpcServer) GetServerId(ctx context.Context, request *GetServerIdRequest) (*GetServerIdReply, error) { +func (s *GrpcServer) GetServerId(ctx context.Context, request *rpc.GetServerIdRequest) (*rpc.GetServerIdReply, error) { statsGrpcServerCalls.WithLabelValues("GetServerId").Inc() - return &GetServerIdReply{ + return &rpc.GetServerIdReply{ ServerId: s.serverId, Version: s.version, }, nil } -func (s *GrpcServer) GetTransientData(ctx context.Context, request *GetTransientDataRequest) (*GetTransientDataReply, error) { +func (s *GrpcServer) GetTransientData(ctx context.Context, request *rpc.GetTransientDataRequest) (*rpc.GetTransientDataReply, error) { statsGrpcServerCalls.WithLabelValues("GetTransientData").Inc() backendUrls := request.BackendUrls @@ -320,7 +313,7 @@ func (s *GrpcServer) GetTransientData(ctx context.Context, request *GetTransient backendUrls = []string{""} } - result := &GetTransientDataReply{} + result := &rpc.GetTransientDataReply{} processed := make(map[string]bool) for _, bu := range backendUrls { var parsed *url.URL @@ -354,10 +347,10 @@ func (s *GrpcServer) GetTransientData(ctx context.Context, request *GetTransient } if result.Entries == nil { - result.Entries = make(map[string]*GrpcTransientDataEntry) + result.Entries = make(map[string]*rpc.GrpcTransientDataEntry) } for k, v := range entries { - e := &GrpcTransientDataEntry{} + e := &rpc.GrpcTransientDataEntry{} var err error if e.Value, err = json.Marshal(v.Value); err != nil { return nil, status.Errorf(codes.Internal, "error marshalling data: %s", err) @@ -372,7 +365,7 @@ func (s *GrpcServer) GetTransientData(ctx context.Context, request *GetTransient return result, nil } -func (s *GrpcServer) GetSessionCount(ctx context.Context, request *GetSessionCountRequest) (*GetSessionCountReply, error) { +func (s *GrpcServer) GetSessionCount(ctx context.Context, request *rpc.GetSessionCountRequest) (*rpc.GetSessionCountReply, error) { statsGrpcServerCalls.WithLabelValues("SessionCount").Inc() u, err := url.Parse(request.Url) @@ -385,18 +378,18 @@ func (s *GrpcServer) GetSessionCount(ctx context.Context, request *GetSessionCou return nil, status.Error(codes.NotFound, "no such backend") } - return &GetSessionCountReply{ + return &rpc.GetSessionCountReply{ Count: uint32(backend.Len()), }, nil } -func (s *GrpcServer) ProxySession(request RpcSessions_ProxySessionServer) error { +func (s *GrpcServer) ProxySession(request rpc.RpcSessions_ProxySessionServer) error { statsGrpcServerCalls.WithLabelValues("ProxySession").Inc() hub, ok := s.hub.(*Hub) if !ok { return status.Error(codes.Internal, "invalid hub type") - } + client, err := newRemoteGrpcClient(hub, request) if err != nil { return err diff --git a/grpc_server_test.go b/grpc_server_test.go index 4b46258..05bfb72 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -41,13 +41,19 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" + rpc "github.com/strukturag/nextcloud-spreed-signaling/grpc" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) +type CertificateReloadWaiter interface { + WaitForCertificateReload(ctx context.Context, counter uint64) error +} + func (s *GrpcServer) WaitForCertificateReload(ctx context.Context, counter uint64) error { - c, ok := s.creds.(*reloadableCredentials) + c, ok := s.creds.(CertificateReloadWaiter) if !ok { return errors.New("no reloadable credentials found") } @@ -55,8 +61,12 @@ func (s *GrpcServer) WaitForCertificateReload(ctx context.Context, counter uint6 return c.WaitForCertificateReload(ctx, counter) } +type CertPoolReloadWaiter interface { + WaitForCertPoolReload(ctx context.Context, counter uint64) error +} + func (s *GrpcServer) WaitForCertPoolReload(ctx context.Context, counter uint64) error { - c, ok := s.creds.(*reloadableCredentials) + c, ok := s.creds.(CertPoolReloadWaiter) if !ok { return errors.New("no reloadable credentials found") } @@ -212,7 +222,7 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { RootCAs: pool, Certificates: []tls.Certificate{pair1}, } - client1, err := NewGrpcClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg1))) + client1, err := rpc.NewClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg1))) require.NoError(err) defer client1.Close() // nolint @@ -241,7 +251,7 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { RootCAs: pool, Certificates: []tls.Certificate{pair2}, } - client2, err := NewGrpcClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg2))) + client2, err := rpc.NewClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg2))) require.NoError(err) defer client2.Close() // nolint @@ -252,3 +262,53 @@ func Test_GrpcServer_ReloadCA(t *testing.T) { _, _, err = client2.GetServerId(ctx2) require.NoError(err) } + +func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { + require := require.New(t) + serverKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + clientKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) + clientCert := internal.GenerateSelfSignedCertificateForTesting(t, "Testing client", clientKey) + + dir := t.TempDir() + serverPrivkeyFile := path.Join(dir, "server-privkey.pem") + serverPubkeyFile := path.Join(dir, "server-pubkey.pem") + serverCertFile := path.Join(dir, "server-cert.pem") + require.NoError(internal.WritePrivateKey(serverKey, serverPrivkeyFile)) + require.NoError(internal.WritePublicKey(&serverKey.PublicKey, serverPubkeyFile)) + require.NoError(internal.WriteCertificate(serverCert, serverCertFile)) + clientPrivkeyFile := path.Join(dir, "client-privkey.pem") + clientPubkeyFile := path.Join(dir, "client-pubkey.pem") + clientCertFile := path.Join(dir, "client-cert.pem") + require.NoError(internal.WritePrivateKey(clientKey, clientPrivkeyFile)) + require.NoError(internal.WritePublicKey(&clientKey.PublicKey, clientPubkeyFile)) + require.NoError(internal.WriteCertificate(clientCert, clientCertFile)) + + serverConfig := goconf.NewConfigFile() + serverConfig.AddOption("grpc", "servercertificate", serverCertFile) + serverConfig.AddOption("grpc", "serverkey", serverPrivkeyFile) + serverConfig.AddOption("grpc", "clientca", clientCertFile) + _, addr := NewGrpcServerForTestWithConfig(t, serverConfig) + + clientConfig := goconf.NewConfigFile() + clientConfig.AddOption("grpc", "targets", addr) + clientConfig.AddOption("grpc", "clientcertificate", clientCertFile) + clientConfig.AddOption("grpc", "clientkey", clientPrivkeyFile) + clientConfig.AddOption("grpc", "serverca", serverCertFile) + clients, _ := grpctest.NewClientsForTestWithConfig(t, clientConfig, nil, nil) + + ctx, cancel1 := context.WithTimeout(context.Background(), time.Second) + defer cancel1() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + _, _, err := client.GetServerId(ctx) + require.NoError(err) + } + }) +} diff --git a/grpc_stats_prometheus.go b/grpc_stats_prometheus.go index 36f8995..ff3e04c 100644 --- a/grpc_stats_prometheus.go +++ b/grpc_stats_prometheus.go @@ -28,24 +28,6 @@ import ( ) var ( - statsGrpcClients = prometheus.NewGauge(prometheus.GaugeOpts{ // +checklocksignore: Global readonly variable. - Namespace: "signaling", - Subsystem: "grpc", - Name: "clients", - Help: "The current number of GRPC clients", - }) - statsGrpcClientCalls = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "grpc", - Name: "client_calls_total", - Help: "The total number of GRPC client calls", - }, []string{"method"}) - - grpcClientStats = []prometheus.Collector{ - statsGrpcClients, - statsGrpcClientCalls, - } - statsGrpcServerCalls = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "signaling", Subsystem: "grpc", @@ -58,10 +40,6 @@ var ( } ) -func RegisterGrpcClientStats() { - metrics.RegisterAll(grpcClientStats...) -} - func RegisterGrpcServerStats() { metrics.RegisterAll(grpcServerStats...) } diff --git a/hub.go b/hub.go index e6eb2fa..622197c 100644 --- a/hub.go +++ b/hub.go @@ -58,8 +58,11 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -184,7 +187,7 @@ type Hub struct { decodeCaches []*container.LruCache[*SessionIdData] - mcu Mcu + mcu sfu.SFU mcuTimeout time.Duration internalClientsSecret []byte @@ -215,7 +218,7 @@ type Hub struct { etcdClient etcd.Client rpcServer *GrpcServer - rpcClients *GrpcClients + rpcClients *grpc.Clients throttler async.Throttler @@ -226,7 +229,7 @@ type Hub struct { blockedCandidates atomic.Pointer[container.IPList] } -func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { +func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEvents, rpcServer *GrpcServer, rpcClients *grpc.Clients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { logger := log.LoggerFromContext(ctx) hashKey, _ := config.GetStringOptionWithEnv(cfg, "sessions", "hashkey") switch len(hashKey) { @@ -370,7 +373,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven WriteBufferSize: websocketWriteBufferSize, WriteBufferPool: websocketWriteBufferPool, Subprotocols: []string{ - JanusEventsSubprotocol, + janus.EventsSubprotocol, }, }, sessionIds: sessionIds, @@ -472,7 +475,7 @@ func (h *Hub) getWelcomeMessage() *api.ServerMessage { return h.welcome.Load().(*api.ServerMessage) } -func (h *Hub) SetMcu(mcu Mcu) { +func (h *Hub) SetMcu(mcu sfu.SFU) { h.mcu = mcu // Create copy of message so it can be updated concurrently. welcome := *h.getWelcomeMessage() @@ -764,12 +767,12 @@ func (h *Hub) GetBackend(u *url.URL) *talk.Backend { } func (h *Hub) CreateProxyToken(publisherId string) (string, error) { - proxy, ok := h.mcu.(*mcuProxy) + withToken, ok := h.mcu.(sfu.WithToken) if !ok { return "", ErrNoProxyMcu } - return proxy.createToken(publisherId) + return withToken.CreateToken(publisherId) } // +checklocks:h.mu @@ -1016,7 +1019,7 @@ func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backe defer cancel() for _, client := range h.rpcClients.GetClients() { wg.Add(1) - go func(c *GrpcClient) { + go func(c *grpc.Client) { defer wg.Done() count, err := c.GetSessionCount(ctx, session.BackendUrl()) @@ -1191,8 +1194,8 @@ func (h *Hub) sendHelloResponse(session *ClientSession, message *api.ClientMessa } type remoteClientInfo struct { - client *GrpcClient - response *LookupResumeIdReply + client *grpc.Client + response *grpc.LookupResumeIdReply } func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, message *api.ClientMessage) bool { @@ -1201,7 +1204,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, mes return false } - var clients []*GrpcClient + var clients []*grpc.Client if h.rpcClients != nil { clients = h.rpcClients.GetClients() } @@ -1219,7 +1222,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, mes var remoteClient atomic.Pointer[remoteClientInfo] for _, c := range clients { wg.Add(1) - go func(client *GrpcClient) { + go func(client *grpc.Client) { defer wg.Done() if client.IsSelf() { @@ -2126,7 +2129,7 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { return } - publisher := session.GetPublisher(StreamTypeScreen) + publisher := session.GetPublisher(sfu.StreamTypeScreen) if publisher == nil { return } @@ -2248,7 +2251,7 @@ func (h *Hub) processMessageMsg(sess Session, message *api.ClientMessage) { ctx, cancel := context.WithTimeout(session.Context(), h.mcuTimeout) defer cancel() - mc, err := recipient.GetOrCreateSubscriber(ctx, h.mcu, session.PublicId(), StreamType(clientData.RoomType)) + mc, err := recipient.GetOrCreateSubscriber(ctx, h.mcu, session.PublicId(), sfu.StreamType(clientData.RoomType)) if err != nil { h.logger.Printf("Could not create MCU subscriber for session %s to send %+v to %s: %s", session.PublicId(), clientData, recipient.PublicId(), err) sendMcuClientNotFound(session, message) @@ -2675,7 +2678,7 @@ func isAllowedToUpdateTransientDataKey(session Session, key string) bool { return true } - if sid, found := strings.CutPrefix(key, TransientSessionDataPrefix); found { + if sid, found := strings.CutPrefix(key, api.TransientSessionDataPrefix); found { // Session data may only be modified by the session itself. return sid == string(session.PublicId()) } @@ -2754,10 +2757,10 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi defer cancel() for _, client := range clients { wg.Add(1) - go func(client *GrpcClient) { + go func(client *grpc.Client) { defer wg.Done() - inCall, err := client.IsSessionInCall(rpcCtx, recipientSessionId, senderRoom, senderSession.BackendUrl()) + inCall, err := client.IsSessionInCall(rpcCtx, recipientSessionId, senderRoom.Id(), senderSession.BackendUrl()) if errors.Is(err, context.Canceled) { return } else if err != nil { @@ -2808,7 +2811,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *api.Clie ctx, cancel := context.WithTimeout(session.Context(), h.mcuTimeout) defer cancel() - var mc McuClient + var mc sfu.Client var err error var clientType string switch data.Type { @@ -2827,13 +2830,13 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *api.Clie } clientType = "subscriber" - mc, err = session.GetOrCreateSubscriber(ctx, h.mcu, message.Recipient.SessionId, StreamType(data.RoomType)) + mc, err = session.GetOrCreateSubscriber(ctx, h.mcu, message.Recipient.SessionId, sfu.StreamType(data.RoomType)) case "sendoffer": // Will be sent directly. return case "offer": clientType = "publisher" - mc, err = session.GetOrCreatePublisher(ctx, h.mcu, StreamType(data.RoomType), data) + mc, err = session.GetOrCreatePublisher(ctx, h.mcu, sfu.StreamType(data.RoomType), data) if err, ok := err.(*PermissionError); ok { h.logger.Printf("Session %s is not allowed to offer %s, ignoring (%s)", session.PublicId(), data.RoomType, err) sendNotAllowed(session, client_message, "Not allowed to publish.") @@ -2846,7 +2849,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *api.Clie } clientType = "subscriber" - mc = session.GetSubscriber(message.Recipient.SessionId, StreamType(data.RoomType)) + mc = session.GetSubscriber(message.Recipient.SessionId, sfu.StreamType(data.RoomType)) default: if data.Type == "candidate" && api.FilterCandidate(data.Candidate, h.allowedCandidates.Load(), h.blockedCandidates.Load()) { // Silently ignore filtered candidates. @@ -2861,10 +2864,10 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *api.Clie } clientType = "publisher" - mc = session.GetPublisher(StreamType(data.RoomType)) + mc = session.GetPublisher(sfu.StreamType(data.RoomType)) } else { clientType = "subscriber" - mc = session.GetSubscriber(message.Recipient.SessionId, StreamType(data.RoomType)) + mc = session.GetSubscriber(message.Recipient.SessionId, sfu.StreamType(data.RoomType)) } } if err != nil { @@ -2893,7 +2896,7 @@ func (h *Hub) processMcuMessage(session *ClientSession, client_message *api.Clie }) } -func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient McuClient, message *api.MessageClientMessage, data *api.MessageClientMessageData, response api.StringMap) { +func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient sfu.Client, message *api.MessageClientMessage, data *api.MessageClientMessageData, response api.StringMap) { var response_message *api.ServerMessage switch response["type"] { case "answer": @@ -3150,8 +3153,8 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { } ctx := log.NewLoggerContext(r.Context(), h.logger) - if conn.Subprotocol() == JanusEventsSubprotocol { - RunJanusEventsHandler(ctx, h.mcu, conn, addr, agent) + if conn.Subprotocol() == janus.EventsSubprotocol { + janus.RunEventsHandler(ctx, h.mcu, conn, addr, agent) return } diff --git a/hub_sfu_janus_test.go b/hub_sfu_janus_test.go new file mode 100644 index 0000000..803daa7 --- /dev/null +++ b/hub_sfu_janus_test.go @@ -0,0 +1,757 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + sfujanus "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + janustest "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/test" +) + +type JanusSFU interface { + sfu.SFU + + SetStats(stats sfujanus.Stats) + Settings() *sfujanus.Settings +} + +func newMcuJanusForTesting(t *testing.T) (JanusSFU, *janustest.JanusGateway) { + gateway := janustest.NewJanusGateway(t) + + config := goconf.NewConfigFile() + if strings.Contains(t.Name(), "Filter") { + config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") + } + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + mcu, err := sfujanus.NewJanusSFUWithGateway(ctx, gateway, config) + require.NoError(t, err) + t.Cleanup(func() { + mcu.Stop() + }) + + require.NoError(t, mcu.Start(ctx)) + return mcu.(JanusSFU), gateway +} + +type mockJanusStats struct { + called atomic.Bool + + mu sync.Mutex + // +checklocks:mu + value map[sfu.StreamType]int +} + +func (s *mockJanusStats) Value(streamType sfu.StreamType) int { + s.mu.Lock() + defer s.mu.Unlock() + + return s.value[streamType] +} + +func (s *mockJanusStats) IncSubscriber(streamType sfu.StreamType) { + s.called.Store(true) + + s.mu.Lock() + defer s.mu.Unlock() + + if s.value == nil { + s.value = make(map[sfu.StreamType]int) + } + s.value[streamType]++ +} + +func (s *mockJanusStats) DecSubscriber(streamType sfu.StreamType) { + s.called.Store(true) + + s.mu.Lock() + defer s.mu.Unlock() + + if s.value == nil { + s.value = make(map[sfu.StreamType]int) + } + s.value[streamType]-- +} + +func Test_JanusSubscriberNoSuchRoom(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) +} + +func test_JanusSubscriberAlreadyJoined(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + if strings.Contains(t.Name(), "AttachError") { + MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + } + + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) +} + +func Test_JanusSubscriberAlreadyJoined(t *testing.T) { + t.Parallel() + test_JanusSubscriberAlreadyJoined(t) +} + +func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { + t.Parallel() + test_JanusSubscriberAlreadyJoined(t) +} + +func Test_JanusSubscriberTimeout(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) + + oldTimeout := mcu.Settings().Timeout() + mcu.Settings().SetTimeout(100 * time.Millisecond) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint + + mcu.Settings().SetTimeout(oldTimeout) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) +} + +func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) + + sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) + require.NotNil(sess2) + session2 := sess2.(*ClientSession) + + sub := session2.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo) + require.NotNil(sub) + + subscriber := sub.(sfujanus.Subscriber) + handle := subscriber.JanusHandle() + require.NotNil(handle) + + for ctx.Err() == nil { + if handle = subscriber.JanusHandle(); handle == nil { + break + } + + time.Sleep(time.Millisecond) + } + + assert.Nil(handle, "subscriber should have been closed") +} + +func Test_JanusSubscriberRoomDestroyed(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) + + sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) + require.NotNil(sess2) + session2 := sess2.(*ClientSession) + + sub := session2.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo) + require.NotNil(sub) + + subscriber := sub.(sfujanus.Subscriber) + handle := subscriber.JanusHandle() + require.NotNil(handle) + + for ctx.Err() == nil { + if handle = subscriber.JanusHandle(); handle == nil { + break + } + + time.Sleep(time.Millisecond) + } + + assert.Nil(handle, "subscriber should have been closed") +} + +func Test_JanusSubscriberUpdateOffer(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + hub, _, _, server := CreateHubForTest(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) + require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + // Give message processing some time. + time.Sleep(10 * time.Millisecond) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + // Simulate request from the backend that sessions joined the call. + users1 := []api.StringMap{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + }, + { + "sessionId": hello2.Hello.SessionId, + "inCall": 1, + }, + } + room := hub.getRoom(roomId) + require.NotNil(room, "Could not find room %s", roomId) + room.PublishUsersInCallChanged(users1, users1) + checkReceiveClientEvent(ctx, t, client1, "update", nil) + checkReceiveClientEvent(ctx, t, client2, "update", nil) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + })) + + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) + + // Test MCU will trigger an updated offer. + client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioOnly) +} diff --git a/hub_sfu_proxy_test.go b/hub_sfu_proxy_test.go new file mode 100644 index 0000000..f21e78d --- /dev/null +++ b/hub_sfu_proxy_test.go @@ -0,0 +1,610 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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" + "errors" + "net/url" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/mock" + proxytest "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/test" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" + "github.com/strukturag/nextcloud-spreed-signaling/talk" +) + +type mockGrpcServerHub struct { + proxy atomic.Pointer[sfu.WithToken] + sessionsLock sync.Mutex + // +checklocks:sessionsLock + sessionByPublicId map[api.PublicSessionId]Session +} + +func (h *mockGrpcServerHub) setProxy(t *testing.T, proxy sfu.SFU) { + t.Helper() + + wt, ok := proxy.(sfu.WithToken) + require.True(t, ok, "need a sfu with token support") + h.proxy.Store(&wt) +} + +func (h *mockGrpcServerHub) addSession(session *ClientSession) { + h.sessionsLock.Lock() + defer h.sessionsLock.Unlock() + if h.sessionByPublicId == nil { + h.sessionByPublicId = make(map[api.PublicSessionId]Session) + } + h.sessionByPublicId[session.PublicId()] = session +} + +func (h *mockGrpcServerHub) removeSession(session *ClientSession) { + h.sessionsLock.Lock() + defer h.sessionsLock.Unlock() + delete(h.sessionByPublicId, session.PublicId()) +} + +func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId api.PrivateSessionId) Session { + return nil +} + +func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId api.PublicSessionId) Session { + h.sessionsLock.Lock() + defer h.sessionsLock.Unlock() + return h.sessionByPublicId[sessionId] +} + +func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { + return "", nil +} + +func (h *mockGrpcServerHub) GetBackend(u *url.URL) *talk.Backend { + return nil +} + +func (h *mockGrpcServerHub) GetRoomForBackend(roomId string, backend *talk.Backend) *Room { + return nil +} + +func (h *mockGrpcServerHub) CreateProxyToken(publisherId string) (string, error) { + proxy := h.proxy.Load() + if proxy == nil { + return "", errors.New("not a proxy mcu") + } + + return (*proxy).CreateToken(publisherId) +} + +func Test_ProxyRemotePublisher(t *testing.T) { + t.Parallel() + + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 1, nil) + hub1.setProxy(t, mcu1) + mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 2, nil) + hub2.setProxy(t, mcu2) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[sfu.StreamType]sfu.Publisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[sfu.StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} + +func Test_ProxyMultipleRemotePublisher(t *testing.T) { + t.Parallel() + + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer3, addr3 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + hub3 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + grpcServer3.hub = hub3 + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/three", []byte("{\"address\":\""+addr3+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "US") + server3 := testserver.NewProxyServerForTest(t, "US") + + mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + server3, + }, + }, 1, nil) + hub1.setProxy(t, mcu1) + mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + server3, + }, + }, 2, nil) + hub2.setProxy(t, mcu2) + mcu3, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + server3, + }, + }, 3, nil) + hub3.setProxy(t, mcu3) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[sfu.StreamType]sfu.Publisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[sfu.StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + sub1Listener := mock.NewListener("subscriber-public-1") + sub1Initiator := mock.NewInitiator("US") + sub1, err := mcu2.NewSubscriber(ctx, sub1Listener, pubId, sfu.StreamTypeVideo, sub1Initiator) + require.NoError(t, err) + + defer sub1.Close(context.Background()) + + sub2Listener := mock.NewListener("subscriber-public-2") + sub2Initiator := mock.NewInitiator("US") + sub2, err := mcu3.NewSubscriber(ctx, sub2Listener, pubId, sfu.StreamTypeVideo, sub2Initiator) + require.NoError(t, err) + + defer sub2.Close(context.Background()) +} + +func Test_ProxyRemotePublisherWait(t *testing.T) { + t.Parallel() + + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 1, nil) + hub1.setProxy(t, mcu1) + mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 2, nil) + hub2.setProxy(t, mcu2) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[sfu.StreamType]sfu.Publisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + + done := make(chan struct{}) + go func() { + defer close(done) + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + if !assert.NoError(t, err) { + return + } + + defer sub.Close(context.Background()) + }() + + // Give subscriber goroutine some time to start + time.Sleep(100 * time.Millisecond) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[sfu.StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + select { + case <-done: + case <-ctx.Done(): + assert.NoError(t, ctx.Err()) + } +} + +func Test_ProxyRemotePublisherTemporary(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 1, nil) + hub1.setProxy(t, mcu1) + mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server2, + }, + }, 2, nil) + hub2.setProxy(t, mcu2) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[sfu.StreamType]sfu.Publisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[sfu.StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + type connectionCounter interface { + ConnectionsCount() int + } + + if counter2, ok := mcu2.(connectionCounter); assert.True(ok) { + assert.Equal(1, counter2.ConnectionsCount()) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) + + if connSub, ok := sub.(sfu.SubscriberWithConnectionUrlAndIP); assert.True(ok) { + url, ip := connSub.GetConnectionURL() + assert.Equal(server1.URL(), url) + assert.Empty(ip) + } + + // The temporary connection has been added + if counter2, ok := mcu2.(connectionCounter); assert.True(ok) { + assert.Equal(2, counter2.ConnectionsCount()) + } + + sub.Close(context.Background()) + + // Wait for temporary connection to be removed. +loop: + for { + select { + case <-ctx.Done(): + assert.NoError(ctx.Err()) + default: + if counter2, ok := mcu2.(connectionCounter); assert.True(ok) { + if counter2.ConnectionsCount() == 1 { + break loop + } + } + } + } +} + +func Test_ProxyConnectToken(t *testing.T) { + t.Parallel() + + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + // Signaling server instances are in a cluster but don't share their proxies, + // i.e. they are only known to their local proxy, not the one of the other + // signaling server - so the connection token must be passed between them. + mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 1, nil) + hub1.setProxy(t, mcu1) + mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server2, + }, + }, 2, nil) + hub2.setProxy(t, mcu2) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[sfu.StreamType]sfu.Publisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[sfu.StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} + +func Test_ProxyPublisherToken(t *testing.T) { + t.Parallel() + + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := NewGrpcServerForTest(t) + grpcServer2, addr2 := NewGrpcServerForTest(t) + + hub1 := &mockGrpcServerHub{} + hub2 := &mockGrpcServerHub{} + grpcServer1.hub = hub1 + grpcServer2.hub = hub2 + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "US") + + // Signaling server instances are in a cluster but don't share their proxies, + // i.e. they are only known to their local proxy, not the one of the other + // signaling server - so the connection token must be passed between them. + // Also the subscriber is connecting from a different country, so a remote + // stream will be created that needs a valid token from the remote proxy. + mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 1, nil) + hub1.setProxy(t, mcu1) + mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server2, + }, + }, 2, nil) + hub2.setProxy(t, mcu2) + // Support remote subscribers for the tests. + server1.Servers = append(server1.Servers, server2) + server2.Servers = append(server2.Servers, server1) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + session1 := &ClientSession{ + publicId: pubId, + publishers: make(map[sfu.StreamType]sfu.Publisher), + } + hub1.addSession(session1) + defer hub1.removeSession(session1) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + session1.mu.Lock() + session1.publishers[sfu.StreamTypeVideo] = pub + session1.publisherWaiters.Wakeup() + session1.mu.Unlock() + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("US") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} diff --git a/hub_test.go b/hub_test.go index f1ad48f..1b1c7d1 100644 --- a/hub_test.go +++ b/hub_test.go @@ -58,10 +58,12 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async/eventstest" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/nats" + sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -256,7 +258,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http }) config1, err := getConfigFunc(server1) require.NoError(err) - client1, _ := NewGrpcClientsForTest(t, addr2, nil) + client1, _ := grpctest.NewClientsForTest(t, addr2, nil) h1, err := NewHub(ctx, config1, events1, grpcServer1, client1, nil, r1, "no-version") require.NoError(err) b1, err := NewBackendServer(ctx, config1, h1, "no-version") @@ -270,7 +272,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http }) config2, err := getConfigFunc(server2) require.NoError(err) - client2, _ := NewGrpcClientsForTest(t, addr1, nil) + client2, _ := grpctest.NewClientsForTest(t, addr1, nil) h2, err := NewHub(ctx, config2, events2, grpcServer2, client2, nil, r2, "no-version") require.NoError(err) b2, err := NewBackendServer(ctx, config2, h2, "no-version") @@ -1991,11 +1993,11 @@ func TestClientMessageToSessionId(t *testing.T) { hub1, hub2, server1, server2 = CreateClusteredHubsForTest(t) } - mcu1 := NewTestMCU(t) + mcu1 := sfutest.NewSFU(t) hub1.SetMcu(mcu1) if hub1 != hub2 { - mcu2 := NewTestMCU(t) + mcu2 := sfutest.NewSFU(t) hub2.SetMcu(mcu2) } @@ -2666,7 +2668,7 @@ func TestJoinRoomMcuBandwidth(t *testing.T) { require := require.New(t) assert := assert.New(t) hub, _, _, server := CreateHubForTest(t) - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) hub.SetMcu(mcu) mcu.SetBandwidthLimits(1000, 2000) @@ -2705,7 +2707,7 @@ func TestJoinRoomPreferMcuBandwidth(t *testing.T) { return config, nil }) - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) hub.SetMcu(mcu) // The MCU bandwidth limits overwrite any backend limits. @@ -3614,7 +3616,7 @@ func TestClientSendOfferPermissions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3701,7 +3703,7 @@ func TestClientSendOfferPermissionsAudioOnly(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3763,7 +3765,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3840,7 +3842,7 @@ loop: } for _, pub := range pubs { - if pub.isClosed() { + if pub.IsClosed() { break loop } } @@ -3859,7 +3861,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -3939,7 +3941,7 @@ loop: } for _, pub := range pubs { - if !assert.False(pub.isClosed(), "publisher was closed") { + if !assert.False(pub.IsClosed(), "publisher was closed") { break loop } } @@ -3971,7 +3973,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -4370,7 +4372,7 @@ func TestClientSendOffer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -4435,7 +4437,7 @@ func TestClientUnshareScreen(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - mcu := NewTestMCU(t) + mcu := sfutest.NewSFU(t) require.NoError(mcu.Start(ctx)) defer mcu.Stop() @@ -4469,7 +4471,7 @@ func TestClientUnshareScreen(t *testing.T) { publisher := mcu.GetPublisher(hello.Hello.SessionId) require.NotNil(publisher, "No publisher for %s found", hello.Hello.SessionId) - require.False(publisher.isClosed(), "Publisher %s should not be closed", hello.Hello.SessionId) + require.False(publisher.IsClosed(), "Publisher %s should not be closed", hello.Hello.SessionId) old := cleanupScreenPublisherDelay cleanupScreenPublisherDelay = time.Millisecond @@ -4488,7 +4490,7 @@ func TestClientUnshareScreen(t *testing.T) { time.Sleep(10 * time.Millisecond) - require.True(publisher.isClosed(), "Publisher %s should be closed", hello.Hello.SessionId) + require.True(publisher.IsClosed(), "Publisher %s should be closed", hello.Hello.SessionId) } func TestVirtualClientSessions(t *testing.T) { diff --git a/transient_data_test.go b/hub_transient_data_test.go similarity index 80% rename from transient_data_test.go rename to hub_transient_data_test.go index e4e9053..c350dc6 100644 --- a/transient_data_test.go +++ b/hub_transient_data_test.go @@ -24,128 +24,15 @@ package signaling import ( "context" "net/http/httptest" - "sync" "testing" - "testing/synctest" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/test" ) -func Test_TransientData(t *testing.T) { - t.Parallel() - test.SynctestTest(t, func(t *testing.T) { - assert := assert.New(t) - data := NewTransientData() - assert.False(data.Set("foo", nil)) - assert.True(data.Set("foo", "bar")) - assert.False(data.Set("foo", "bar")) - assert.True(data.Set("foo", "baz")) - assert.False(data.CompareAndSet("foo", "bar", "lala")) - assert.True(data.CompareAndSet("foo", "baz", "lala")) - assert.False(data.CompareAndSet("test", nil, nil)) - assert.True(data.CompareAndSet("test", nil, "123")) - assert.False(data.CompareAndSet("test", nil, "456")) - assert.False(data.CompareAndRemove("test", "1234")) - assert.True(data.CompareAndRemove("test", "123")) - assert.False(data.Remove("lala")) - assert.True(data.Remove("foo")) - - assert.True(data.SetTTL("test", "1234", time.Millisecond)) - assert.Equal("1234", data.GetData()["test"]) - // Data is removed after the TTL - start := time.Now() - time.Sleep(time.Millisecond) - synctest.Wait() - assert.Equal(time.Millisecond, time.Since(start)) - assert.Nil(data.GetData()["test"]) - - assert.True(data.SetTTL("test", "1234", time.Millisecond)) - assert.Equal("1234", data.GetData()["test"]) - assert.True(data.SetTTL("test", "2345", 3*time.Millisecond)) - assert.Equal("2345", data.GetData()["test"]) - start = time.Now() - // Data is removed after the TTL only if the value still matches - time.Sleep(2 * time.Millisecond) - synctest.Wait() - assert.Equal("2345", data.GetData()["test"]) - // Data is removed after the (second) TTL - time.Sleep(time.Millisecond) - synctest.Wait() - assert.Equal(3*time.Millisecond, time.Since(start)) - assert.Nil(data.GetData()["test"]) - - // Setting existing key will update the TTL - assert.True(data.SetTTL("test", "1234", time.Millisecond)) - assert.False(data.SetTTL("test", "1234", 3*time.Millisecond)) - start = time.Now() - // Data still exists after the first TTL - time.Sleep(2 * time.Millisecond) - synctest.Wait() - assert.Equal("1234", data.GetData()["test"]) - // Data is removed after the (updated) TTL - time.Sleep(time.Millisecond) - synctest.Wait() - assert.Equal(3*time.Millisecond, time.Since(start)) - assert.Nil(data.GetData()["test"]) - }) -} - -type MockTransientListener struct { - mu sync.Mutex - sending chan struct{} - done chan struct{} - - // +checklocks:mu - data *TransientData -} - -func (l *MockTransientListener) SendMessage(message *api.ServerMessage) bool { - close(l.sending) - - time.Sleep(10 * time.Millisecond) - - l.mu.Lock() - defer l.mu.Unlock() - defer close(l.done) - - time.Sleep(10 * time.Millisecond) - - return true -} - -func (l *MockTransientListener) Close() { - l.mu.Lock() - defer l.mu.Unlock() - - l.data.RemoveListener(l) -} - -func Test_TransientDataDeadlock(t *testing.T) { - t.Parallel() - data := NewTransientData() - - listener := &MockTransientListener{ - sending: make(chan struct{}), - done: make(chan struct{}), - - data: data, - } - data.AddListener(listener) - - go func() { - <-listener.sending - listener.Close() - }() - - data.Set("foo", "bar") - <-listener.done -} - func Test_TransientMessages(t *testing.T) { t.Parallel() for _, subtest := range clusteredTests { diff --git a/mcu_janus_test.go b/mcu_janus_test.go deleted file mode 100644 index 1b1ba15..0000000 --- a/mcu_janus_test.go +++ /dev/null @@ -1,2106 +0,0 @@ -/** - * 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" - "maps" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/dlintw/goconf" - "github.com/notedit/janus-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/mock" -) - -func TestMcuJanusStats(t *testing.T) { - t.Parallel() - collectAndLint(t, janusMcuStats...) -} - -type TestJanusHandle struct { - id uint64 - - sdp atomic.Value -} - -type TestJanusRoom struct { - id uint64 - - publisher atomic.Pointer[TestJanusHandle] -} - -type TestJanusHandler func(room *TestJanusRoom, body api.StringMap, jsep api.StringMap) (any, *janus.ErrorMsg) - -type TestJanusGateway struct { - t *testing.T - - sid atomic.Uint64 - tid atomic.Uint64 - hid atomic.Uint64 // +checklocksignore: Atomic - rid atomic.Uint64 // +checklocksignore: Atomic - mu sync.Mutex - - // +checklocks:mu - sessions map[uint64]*JanusSession - // +checklocks:mu - transactions map[uint64]*transaction - // +checklocks:mu - handles map[uint64]*TestJanusHandle - // +checklocks:mu - rooms map[uint64]*TestJanusRoom - // +checklocks:mu - handlers map[string]TestJanusHandler - - // +checklocks:mu - attachCount int - // +checklocks:mu - joinCount int - - // +checklocks:mu - handleRooms map[*TestJanusHandle]*TestJanusRoom -} - -func NewTestJanusGateway(t *testing.T) *TestJanusGateway { - gateway := &TestJanusGateway{ - t: t, - - sessions: make(map[uint64]*JanusSession), - transactions: make(map[uint64]*transaction), - handles: make(map[uint64]*TestJanusHandle), - rooms: make(map[uint64]*TestJanusRoom), - handlers: make(map[string]TestJanusHandler), - - handleRooms: make(map[*TestJanusHandle]*TestJanusRoom), - } - - t.Cleanup(func() { - assert := assert.New(t) - gateway.mu.Lock() - defer gateway.mu.Unlock() - assert.Empty(gateway.sessions) - assert.Empty(gateway.transactions) - assert.Empty(gateway.handles) - assert.Empty(gateway.rooms) - assert.Empty(gateway.handleRooms) - }) - - return gateway -} - -func (g *TestJanusGateway) registerHandlers(handlers map[string]TestJanusHandler) { - g.mu.Lock() - defer g.mu.Unlock() - maps.Copy(g.handlers, handlers) -} - -func (g *TestJanusGateway) Info(ctx context.Context) (*InfoMsg, error) { - return &InfoMsg{ - Name: "TestJanus", - Version: 1400, - VersionString: "1.4.0", - Author: "struktur AG", - DataChannels: true, - EventHandlers: true, - FullTrickle: true, - Plugins: map[string]janus.PluginInfo{ - pluginVideoRoom: { - Name: "Test VideoRoom plugin", - VersionString: "0.0.0", - Author: "struktur AG", - }, - }, - Events: map[string]janus.PluginInfo{ - eventWebsocket: { - Name: "Test Websocket events", - VersionString: "0.0.0", - Author: "struktur AG", - }, - }, - }, nil -} - -func (g *TestJanusGateway) Create(ctx context.Context) (*JanusSession, error) { - sid := g.sid.Add(1) - session := &JanusSession{ - Id: sid, - Handles: make(map[uint64]*JanusHandle), - gateway: g, - } - g.mu.Lock() - defer g.mu.Unlock() - g.sessions[sid] = session - return session, nil -} - -func (g *TestJanusGateway) Close() error { - return nil -} - -func (g *TestJanusGateway) simulateEvent(delay time.Duration, session *JanusSession, handle *TestJanusHandle, event any) { - go func() { - time.Sleep(delay) - session.Lock() - h, found := session.Handles[handle.id] - session.Unlock() - if found { - h.Events <- event - } - }() -} - -// +checklocks:g.mu -func (g *TestJanusGateway) processMessage(session *JanusSession, handle *TestJanusHandle, body api.StringMap, jsep api.StringMap) any { - request := body["request"].(string) - switch request { - case "create": - room := &TestJanusRoom{ - id: g.rid.Add(1), - } - g.rooms[room.id] = room - - return &janus.SuccessMsg{ - PluginData: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "room": room.id, - }, - }, - } - case "join": - rid := body["room"].(float64) - room := g.rooms[uint64(rid)] - error_code := JANUS_OK - if body["ptype"] == "subscriber" { - if strings.Contains(g.t.Name(), "NoSuchRoom") { - g.joinCount++ - if g.joinCount == 1 { - error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM - } - } else if strings.Contains(g.t.Name(), "AlreadyJoined") { - g.joinCount++ - if g.joinCount == 1 { - error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED - } - } else if strings.Contains(g.t.Name(), "SubscriberTimeout") { - g.joinCount++ - if g.joinCount == 1 { - error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED - } - } - } - if error_code != JANUS_OK { - return &janus.EventMsg{ - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "error_code": error_code, - }, - }, - } - } - - if room == nil { - return &janus.EventMsg{ - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, - }, - }, - } - } - - g.handleRooms[handle] = room - switch body["ptype"] { - case "publisher": - if !assert.True(g.t, room.publisher.CompareAndSwap(nil, handle)) { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED, - Reason: "Already publisher in this room", - }, - } - } - - return &janus.EventMsg{ - Session: session.Id, - Handle: handle.id, - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "room": room.id, - }, - }, - } - case "subscriber": - publisher := room.publisher.Load() - if publisher == nil || publisher.sdp.Load() == nil { - return &janus.EventMsg{ - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "error_code": JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED, - }, - }, - } - } - - sdp := publisher.sdp.Load() - - // Simulate "connected" event for subscriber. - g.simulateEvent(15*time.Millisecond, session, handle, &janus.WebRTCUpMsg{ - Session: session.Id, - Handle: handle.id, - }) - - if strings.Contains(g.t.Name(), "CloseEmptyStreams") { - // Simulate stream update event with no active streams. - g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ - Session: session.Id, - Handle: handle.id, - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "videoroom": "updated", - "streams": []any{ - api.StringMap{ - "type": "audio", - "active": false, - }, - }, - }, - }, - }) - } - - if strings.Contains(g.t.Name(), "SubscriberRoomDestroyed") { - // Simulate event that subscriber room has been destroyed. - g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ - Session: session.Id, - Handle: handle.id, - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "videoroom": "destroyed", - }, - }, - }) - } - - if strings.Contains(g.t.Name(), "SubscriberUpdateOffer") { - // Simulate event that subscriber receives new offer. - g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ - Session: session.Id, - Handle: handle.id, - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "videoroom": "event", - "configured": "ok", - }, - }, - Jsep: map[string]any{ - "type": "offer", - "sdp": mock.MockSdpOfferAudioOnly, - }, - }) - } - - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "offer", - "sdp": sdp.(string), - }, - } - } - case "destroy": - rid := body["room"].(float64) - room := g.rooms[uint64(rid)] - if room == nil { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, - Reason: "Room not found", - }, - } - } - - assert.Equal(g.t, room.id, uint64(rid)) - delete(g.rooms, uint64(rid)) - for h, r := range g.handleRooms { - if r.id == room.id { - delete(g.handleRooms, h) - } - } - - return &janus.SuccessMsg{ - PluginData: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{}, - }, - } - default: - var room *TestJanusRoom - if roomId, found := body["room"]; found { - rid := roomId.(float64) - if room = g.rooms[uint64(rid)]; room == nil { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, - Reason: "Room not found", - }, - } - } - } else { - if room, found = g.handleRooms[handle]; !found { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_INVALID_REQUEST, - Reason: "No joined to a room yet.", - }, - } - } - } - - handler, found := g.handlers[request] - if found { - var err *janus.ErrorMsg - result, err := handler(room, body, jsep) - if err != nil { - result = err - } else { - switch request { - case "start": - g.handleRooms[handle] = room - case "configure": - if sdp, found := jsep["sdp"]; found { - handle.sdp.Store(sdp.(string)) - if !strings.Contains(g.t.Name(), "SubscriberTimeout") { - // Simulate "connected" event for publisher. - g.simulateEvent(10*time.Millisecond, session, handle, &janus.WebRTCUpMsg{ - Session: session.Id, - Handle: handle.id, - }) - } - } - } - } - return result - } - } - - return nil -} - -func (g *TestJanusGateway) processRequest(msg api.StringMap) any { - method, found := msg["janus"] - if !found { - return nil - } - - sid := msg["session_id"].(float64) - g.mu.Lock() - defer g.mu.Unlock() - session := g.sessions[uint64(sid)] - if session == nil { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_ERROR_SESSION_NOT_FOUND, - Reason: "Session not found", - }, - } - } - - switch method { - case "attach": - if strings.Contains(g.t.Name(), "AlreadyJoinedAttachError") { - g.attachCount++ - if g.attachCount == 4 { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_ERROR_UNKNOWN, - Reason: "Fail for test", - }, - } - } - } - - handle := &TestJanusHandle{ - id: g.hid.Add(1), - } - - g.handles[handle.id] = handle - - return &janus.SuccessMsg{ - Data: janus.SuccessData{ - ID: handle.id, - }, - } - case "detach": - hid := msg["handle_id"].(float64) - handle, found := g.handles[uint64(hid)] - if found { - delete(g.handles, handle.id) - } - if handle == nil { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_ERROR_HANDLE_NOT_FOUND, - Reason: "Handle not found", - }, - } - } - - return &janus.AckMsg{} - case "destroy": - delete(g.sessions, session.Id) - return &janus.AckMsg{} - case "message", "trickle": - hid := msg["handle_id"].(float64) - handle, found := g.handles[uint64(hid)] - if !found { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_ERROR_HANDLE_NOT_FOUND, - Reason: "Handle not found", - }, - } - } - - var result any - switch method { - case "message": - body, ok := api.ConvertStringMap(msg["body"]) - assert.True(g.t, ok, "not a string map: %+v", msg["body"]) - if jsepOb, found := msg["jsep"]; found { - if jsep, ok := api.ConvertStringMap(jsepOb); assert.True(g.t, ok, "not a string map: %+v", jsepOb) { - result = g.processMessage(session, handle, body, jsep) - } - } else { - result = g.processMessage(session, handle, body, nil) - } - case "trickle": - room, found := g.handleRooms[handle] - if !found { - return &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_VIDEOROOM_ERROR_INVALID_REQUEST, - Reason: "No joined to a room yet.", - }, - } - } - - handler, found := g.handlers[method.(string)] - if found { - var err *janus.ErrorMsg - result, err = handler(room, msg, nil) - if err != nil { - result = err - } - } - } - - if ev, ok := result.(*janus.EventMsg); ok { - if ev.Session == 0 { - ev.Session = uint64(sid) - } - if ev.Handle == 0 { - ev.Handle = handle.id - } - } - return result - } - - return nil -} - -func (g *TestJanusGateway) send(msg api.StringMap, t *transaction) (uint64, error) { - tid := g.tid.Add(1) - - data, err := json.Marshal(msg) - require.NoError(g.t, err) - err = json.Unmarshal(data, &msg) - require.NoError(g.t, err) - - go t.run() - - g.mu.Lock() - defer g.mu.Unlock() - g.transactions[tid] = t - - go func() { - result := g.processRequest(msg) - if !assert.NotNil(g.t, result, "Unsupported request %+v", msg) { - result = &janus.ErrorMsg{ - Err: janus.ErrorData{ - Code: JANUS_ERROR_UNKNOWN, - Reason: "Not implemented", - }, - } - } - - t.add(result) - }() - - return tid, nil -} - -func (g *TestJanusGateway) removeTransaction(id uint64) { - g.mu.Lock() - defer g.mu.Unlock() - if t, found := g.transactions[id]; found { - delete(g.transactions, id) - t.quit() - } -} - -func (g *TestJanusGateway) removeSession(session *JanusSession) { - g.mu.Lock() - defer g.mu.Unlock() - delete(g.sessions, session.Id) -} - -func newMcuJanusForTesting(t *testing.T) (*mcuJanus, *TestJanusGateway) { - gateway := NewTestJanusGateway(t) - - config := goconf.NewConfigFile() - if strings.Contains(t.Name(), "Filter") { - config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") - } - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - mcu, err := NewMcuJanus(ctx, "", config) - require.NoError(t, err) - t.Cleanup(func() { - mcu.Stop() - }) - - mcuJanus := mcu.(*mcuJanus) - mcuJanus.createJanusGateway = func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) { - return gateway, nil - } - require.NoError(t, mcu.Start(ctx)) - return mcuJanus, gateway -} - -type TestMcuListener struct { - id api.PublicSessionId -} - -func (t *TestMcuListener) PublicId() api.PublicSessionId { - return t.id -} - -func (t *TestMcuListener) OnUpdateOffer(client McuClient, offer api.StringMap) { - -} - -func (t *TestMcuListener) OnIceCandidate(client McuClient, candidate any) { - -} - -func (t *TestMcuListener) OnIceCompleted(client McuClient) { - -} - -func (t *TestMcuListener) SubscriberSidUpdated(subscriber McuSubscriber) { - -} - -func (t *TestMcuListener) PublisherClosed(publisher McuPublisher) { - -} - -func (t *TestMcuListener) SubscriberClosed(subscriber McuSubscriber) { - -} - -type TestMcuController struct { - id api.PublicSessionId -} - -func (c *TestMcuController) PublisherId() api.PublicSessionId { - return c.id -} - -func (c *TestMcuController) StartPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error { - // TODO: Check parameters? - return nil -} - -func (c *TestMcuController) StopPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error { - // TODO: Check parameters? - return nil -} - -func (c *TestMcuController) GetStreams(ctx context.Context) ([]PublisherStream, error) { - streams := []PublisherStream{ - { - Mid: "0", - Mindex: 0, - Type: "audio", - Codec: "opus", - }, - } - return streams, nil -} - -type TestMcuInitiator struct { - country geoip.Country -} - -func (i *TestMcuInitiator) Country() geoip.Country { - return i.country -} - -func Test_JanusPublisherFilterOffer(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - if assert.NotNil(jsep) { - // The SDP received by Janus will be filtered from blocked candidates. - if sdpValue, found := jsep["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - - return &janus.EventMsg{ - Jsep: api.StringMap{ - "sdp": mock.MockSdpAnswerAudioOnly, - }, - }, nil - }, - "trickle": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.AckMsg{}, nil - }, - }) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) - defer pub.Close(context.Background()) - - // Send offer containing candidates that will be blocked / filtered. - data := &api.MessageClientMessageData{ - Type: "offer", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioOnly, - }, - } - require.NoError(data.CheckValid()) - - var wg sync.WaitGroup - wg.Add(1) - pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer wg.Done() - - if assert.NoError(err) { - if sdpValue, found := m["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - }) - wg.Wait() - - data = &api.MessageClientMessageData{ - Type: "candidate", - Payload: api.StringMap{ - "candidate": api.StringMap{ - "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", - }, - }, - } - require.NoError(data.CheckValid()) - wg.Add(1) - pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer wg.Done() - - assert.ErrorContains(err, "filtered") - assert.Empty(m) - }) - wg.Wait() - - data = &api.MessageClientMessageData{ - Type: "candidate", - Payload: api.StringMap{ - "candidate": api.StringMap{ - "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", - }, - }, - } - require.NoError(data.CheckValid()) - wg.Add(1) - pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer wg.Done() - - assert.NoError(err) - assert.Empty(m) - }) - wg.Wait() -} - -func Test_JanusSubscriberFilterAnswer(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "start": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - if assert.NotNil(jsep) { - // The SDP received by Janus will be filtered from blocked candidates. - if sdpValue, found := jsep["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpAnswerAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - - return &janus.EventMsg{ - Plugindata: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "room": room.id, - "started": true, - "videoroom": "event", - }, - }, - }, nil - }, - "trickle": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.AckMsg{}, nil - }, - }) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) - defer pub.Close(context.Background()) - - listener2 := &TestMcuListener{ - id: pubId, - } - - initiator2 := &TestMcuInitiator{ - country: "DE", - } - sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) - require.NoError(err) - defer sub.Close(context.Background()) - - // Send answer containing candidates that will be blocked / filtered. - data := &api.MessageClientMessageData{ - Type: "answer", - Payload: api.StringMap{ - "sdp": mock.MockSdpAnswerAudioOnly, - }, - } - require.NoError(data.CheckValid()) - - var wg sync.WaitGroup - wg.Add(1) - sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer wg.Done() - - if assert.NoError(err) { - assert.Empty(m) - } - }) - wg.Wait() - - data = &api.MessageClientMessageData{ - Type: "candidate", - Payload: api.StringMap{ - "candidate": api.StringMap{ - "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", - }, - }, - } - require.NoError(data.CheckValid()) - wg.Add(1) - sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer wg.Done() - - assert.ErrorContains(err, "filtered") - assert.Empty(m) - }) - wg.Wait() - - data = &api.MessageClientMessageData{ - Type: "candidate", - Payload: api.StringMap{ - "candidate": api.StringMap{ - "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", - }, - }, - } - require.NoError(data.CheckValid()) - wg.Add(1) - sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer wg.Done() - - assert.NoError(err) - assert.Empty(m) - }) - wg.Wait() -} - -func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - if assert.NotNil(jsep) { - if sdpValue, found := jsep["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpOfferAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - - return &janus.EventMsg{ - Jsep: api.StringMap{ - "sdp": mock.MockSdpAnswerAudioOnly, - }, - }, nil - }, - }) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) - defer pub.Close(context.Background()) - - data := &api.MessageClientMessageData{ - Type: "offer", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioOnly, - }, - } - require.NoError(data.CheckValid()) - - done := make(chan struct{}) - pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer close(done) - - if assert.NoError(err) { - if sdpValue, found := m["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - }) - <-done - - if sb, ok := pub.(*mcuJanusPublisher); assert.True(ok, "expected publisher with streams support, got %T", pub) { - if streams, err := sb.GetStreams(ctx); assert.NoError(err) { - if assert.Len(streams, 1) { - stream := streams[0] - assert.Equal("audio", stream.Type) - assert.Equal("audio", stream.Mid) - assert.Equal(0, stream.Mindex) - assert.False(stream.Disabled) - assert.Equal("opus", stream.Codec) - assert.False(stream.Stereo) - assert.False(stream.Fec) - assert.False(stream.Dtx) - } - } - } -} - -func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - if assert.NotNil(jsep) { - _, found := jsep["sdp"] - assert.True(found) - } - - return &janus.EventMsg{ - Jsep: api.StringMap{ - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) - defer pub.Close(context.Background()) - - data := &api.MessageClientMessageData{ - Type: "offer", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - } - require.NoError(data.CheckValid()) - - // Defer sending of offer / answer so "GetStreams" will wait. - go func() { - done := make(chan struct{}) - pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer close(done) - - if assert.NoError(err) { - if sdpValue, found := m["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - }) - <-done - }() - - if sb, ok := pub.(*mcuJanusPublisher); assert.True(ok, "expected publisher with streams support, got %T", pub) { - if streams, err := sb.GetStreams(ctx); assert.NoError(err) { - if assert.Len(streams, 2) { - stream := streams[0] - assert.Equal("audio", stream.Type) - assert.Equal("audio", stream.Mid) - assert.Equal(0, stream.Mindex) - assert.False(stream.Disabled) - assert.Equal("opus", stream.Codec) - assert.False(stream.Stereo) - assert.False(stream.Fec) - assert.False(stream.Dtx) - - stream = streams[1] - assert.Equal("video", stream.Type) - assert.Equal("video", stream.Mid) - assert.Equal(1, stream.Mindex) - assert.False(stream.Disabled) - assert.Equal("H264", stream.Codec) - assert.Equal("4d0028", stream.ProfileH264) - } - } - } -} - -type mockBandwidthStats struct { - incoming uint64 - outgoing uint64 -} - -func (s *mockBandwidthStats) SetBandwidth(incoming uint64, outgoing uint64) { - s.incoming = incoming - s.outgoing = outgoing -} - -func Test_JanusPublisherSubscriber(t *testing.T) { - t.Parallel() - - stats := &mockBandwidthStats{} - require := require.New(t) - assert := assert.New(t) - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{}) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - // Bandwidth for unknown handles is ignored. - mcu.UpdateBandwidth(1234, "video", api.BandwidthFromBytes(100), api.BandwidthFromBytes(200)) - mcu.updateBandwidthStats(stats) - assert.EqualValues(0, stats.incoming) - assert.EqualValues(0, stats.outgoing) - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) - defer pub.Close(context.Background()) - - janusPub, ok := pub.(*mcuJanusPublisher) - require.True(ok) - - assert.Nil(mcu.Bandwidth()) - assert.Nil(janusPub.Bandwidth()) - mcu.UpdateBandwidth(janusPub.Handle(), "video", api.BandwidthFromBytes(1000), api.BandwidthFromBytes(2000)) - if bw := janusPub.Bandwidth(); assert.NotNil(bw) { - assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) - assert.Equal(api.BandwidthFromBytes(2000), bw.Received) - } - if bw := mcu.Bandwidth(); assert.NotNil(bw) { - assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) - assert.Equal(api.BandwidthFromBytes(2000), bw.Received) - } - mcu.updateBandwidthStats(stats) - assert.EqualValues(2000, stats.incoming) - assert.EqualValues(1000, stats.outgoing) - - listener2 := &TestMcuListener{ - id: pubId, - } - - initiator2 := &TestMcuInitiator{ - country: "DE", - } - sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) - require.NoError(err) - defer sub.Close(context.Background()) - - janusSub, ok := sub.(*mcuJanusSubscriber) - require.True(ok) - - assert.Nil(janusSub.Bandwidth()) - mcu.UpdateBandwidth(janusSub.Handle(), "video", api.BandwidthFromBytes(3000), api.BandwidthFromBytes(4000)) - if bw := janusSub.Bandwidth(); assert.NotNil(bw) { - assert.Equal(api.BandwidthFromBytes(3000), bw.Sent) - assert.Equal(api.BandwidthFromBytes(4000), bw.Received) - } - if bw := mcu.Bandwidth(); assert.NotNil(bw) { - assert.Equal(api.BandwidthFromBytes(4000), bw.Sent) - assert.Equal(api.BandwidthFromBytes(6000), bw.Received) - } - assert.EqualValues(2000, stats.incoming) - assert.EqualValues(1000, stats.outgoing) - mcu.updateBandwidthStats(stats) - assert.EqualValues(6000, stats.incoming) - assert.EqualValues(4000, stats.outgoing) -} - -func Test_JanusSubscriberPublisher(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{}) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - ready := make(chan struct{}) - done := make(chan struct{}) - - go func() { - defer close(done) - time.Sleep(100 * time.Millisecond) - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - if !assert.NoError(err) { - return - } - - defer func() { - <-ready - pub.Close(context.Background()) - }() - }() - - listener2 := &TestMcuListener{ - id: pubId, - } - - initiator2 := &TestMcuInitiator{ - country: "DE", - } - sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) - require.NoError(err) - defer sub.Close(context.Background()) - close(ready) - <-done -} - -func Test_JanusSubscriberRequestOffer(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - var originalOffer atomic.Value - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - if assert.NotNil(jsep) { - if sdp, found := jsep["sdp"]; assert.True(found) { - originalOffer.Store(strings.ReplaceAll(sdp.(string), "\r\n", "\n")) - } - } - - return &janus.EventMsg{ - Jsep: api.StringMap{ - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("publisher-id") - listener1 := &TestMcuListener{ - id: pubId, - } - - settings1 := NewPublisherSettings{} - initiator1 := &TestMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) - require.NoError(err) - defer pub.Close(context.Background()) - - listener2 := &TestMcuListener{ - id: pubId, - } - - initiator2 := &TestMcuInitiator{ - country: "DE", - } - sub, err := mcu.NewSubscriber(ctx, listener2, pubId, StreamTypeVideo, initiator2) - require.NoError(err) - defer sub.Close(context.Background()) - - go func() { - data := &api.MessageClientMessageData{ - Type: "offer", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - } - if !assert.NoError(data.CheckValid()) { - return - } - - done := make(chan struct{}) - pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer close(done) - - if assert.NoError(err) { - if sdpValue, found := m["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - assert.Equal(mock.MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - }) - <-done - }() - - data := &api.MessageClientMessageData{ - Type: "requestoffer", - } - require.NoError(data.CheckValid()) - - done := make(chan struct{}) - sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { - defer close(done) - - if assert.NoError(err) { - if sdpValue, found := m["sdp"]; assert.True(found) { - sdpText, ok := sdpValue.(string) - if assert.True(ok) { - if sdp := originalOffer.Load(); assert.NotNil(sdp) { - assert.Equal(sdp.(string), strings.ReplaceAll(sdpText, "\r\n", "\n")) - } - } - } - } - }) - <-done -} - -func Test_JanusRemotePublisher(t *testing.T) { - t.Parallel() - assert := assert.New(t) - require := require.New(t) - - var added atomic.Int32 - var removed atomic.Int32 - - mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "add_remote_publisher": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - assert.Nil(jsep) - if streams := body["streams"].([]any); assert.Len(streams, 1) { - if stream, ok := api.ConvertStringMap(streams[0]); assert.True(ok, "not a string map: %+v", streams[0]) { - assert.Equal("0", stream["mid"]) - assert.EqualValues(0, stream["mindex"]) - assert.Equal("audio", stream["type"]) - assert.Equal("opus", stream["codec"]) - } - } - added.Add(1) - return &janus.SuccessMsg{ - PluginData: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{ - "id": 12345, - "port": 10000, - "rtcp_port": 10001, - }, - }, - }, nil - }, - "remove_remote_publisher": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - assert.Nil(jsep) - removed.Add(1) - return &janus.SuccessMsg{ - PluginData: janus.PluginData{ - Plugin: pluginVideoRoom, - Data: api.StringMap{}, - }, - }, nil - }, - }) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - listener1 := &TestMcuListener{ - id: "publisher-id", - } - - controller := &TestMcuController{ - id: listener1.id, - } - - pub, err := mcu.NewRemotePublisher(ctx, listener1, controller, StreamTypeVideo) - require.NoError(err) - defer pub.Close(context.Background()) - - assert.EqualValues(1, added.Load()) - assert.EqualValues(0, removed.Load()) - - listener2 := &TestMcuListener{ - id: "subscriber-id", - } - - sub, err := mcu.NewRemoteSubscriber(ctx, listener2, pub) - require.NoError(err) - defer sub.Close(context.Background()) - - pub.Close(context.Background()) - - assert.EqualValues(1, added.Load()) - // The publisher is ref-counted, and still referenced by the subscriber. - assert.EqualValues(0, removed.Load()) - - sub.Close(context.Background()) - - assert.EqualValues(1, added.Load()) - assert.EqualValues(1, removed.Load()) -} - -type mockJanusStats struct { - called atomic.Bool - - mu sync.Mutex - // +checklocks:mu - value map[StreamType]int -} - -func (s *mockJanusStats) Value(streamType StreamType) int { - s.mu.Lock() - defer s.mu.Unlock() - - return s.value[streamType] -} - -func (s *mockJanusStats) IncSubscriber(streamType StreamType) { - s.called.Store(true) - - s.mu.Lock() - defer s.mu.Unlock() - - if s.value == nil { - s.value = make(map[StreamType]int) - } - s.value[streamType]++ -} - -func (s *mockJanusStats) DecSubscriber(streamType StreamType) { - s.called.Store(true) - - s.mu.Lock() - defer s.mu.Unlock() - - if s.value == nil { - s.value = make(map[StreamType]int) - } - s.value[streamType]-- -} - -func Test_JanusSubscriberNoSuchRoom(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.stats = stats - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) -} - -func test_JanusSubscriberAlreadyJoined(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.stats = stats - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - if strings.Contains(t.Name(), "AttachError") { - MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - } - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) -} - -func Test_JanusSubscriberAlreadyJoined(t *testing.T) { - t.Parallel() - test_JanusSubscriberAlreadyJoined(t) -} - -func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { - t.Parallel() - test_JanusSubscriberAlreadyJoined(t) -} - -func Test_JanusSubscriberTimeout(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.stats = stats - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - oldTimeout := mcu.settings.timeout.Swap(100 * int64(time.Millisecond)) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - - mcu.settings.timeout.Store(oldTimeout) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) -} - -func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.stats = stats - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) - - sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) - require.NotNil(sess2) - session2 := sess2.(*ClientSession) - - sub := session2.GetSubscriber(hello1.Hello.SessionId, StreamTypeVideo) - require.NotNil(sub) - - subscriber := sub.(*mcuJanusSubscriber) - handle := subscriber.handle.Load() - require.NotNil(handle) - - for ctx.Err() == nil { - if handle = subscriber.handle.Load(); handle == nil { - break - } - - time.Sleep(time.Millisecond) - } - - assert.Nil(handle, "subscriber should have been closed") -} - -func Test_JanusSubscriberRoomDestroyed(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.stats = stats - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) - - sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) - require.NotNil(sess2) - session2 := sess2.(*ClientSession) - - sub := session2.GetSubscriber(hello1.Hello.SessionId, StreamTypeVideo) - require.NotNil(sub) - - subscriber := sub.(*mcuJanusSubscriber) - handle := subscriber.handle.Load() - require.NotNil(handle) - - for ctx.Err() == nil { - if handle = subscriber.handle.Load(); handle == nil { - break - } - - time.Sleep(time.Millisecond) - } - - assert.Nil(handle, "subscriber should have been closed") -} - -func Test_JanusSubscriberUpdateOffer(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.stats = stats - gateway.registerHandlers(map[string]TestJanusHandler{ - "configure": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.id) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) - - // Test MCU will trigger an updated offer. - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioOnly) -} diff --git a/mcu_proxy_test.go b/mcu_proxy_test.go deleted file mode 100644 index a5ef519..0000000 --- a/mcu_proxy_test.go +++ /dev/null @@ -1,2606 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2020 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/rand" - "crypto/rsa" - "encoding/json" - "errors" - "fmt" - "io" - "net" - "net/http" - "net/http/httptest" - "net/url" - "path" - "slices" - "strconv" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/dlintw/goconf" - "github.com/golang-jwt/jwt/v5" - "github.com/gorilla/websocket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/talk" -) - -const ( - timeoutTestTimeout = 100 * time.Millisecond -) - -func TestMcuProxyStats(t *testing.T) { - t.Parallel() - collectAndLint(t, proxyMcuStats...) -} - -func newProxyConnectionWithCountry(country geoip.Country) *mcuProxyConnection { - conn := &mcuProxyConnection{} - conn.country.Store(country) - return conn -} - -func Test_sortConnectionsForCountry(t *testing.T) { - t.Parallel() - conn_de := newProxyConnectionWithCountry("DE") - conn_at := newProxyConnectionWithCountry("AT") - conn_jp := newProxyConnectionWithCountry("JP") - conn_us := newProxyConnectionWithCountry("US") - - testcases := map[geoip.Country][][]*mcuProxyConnection{ - // Direct country match - "DE": { - {conn_at, conn_jp, conn_de}, - {conn_de, conn_at, conn_jp}, - }, - // Direct country match - "AT": { - {conn_at, conn_jp, conn_de}, - {conn_at, conn_de, conn_jp}, - }, - // Continent match - "CH": { - {conn_de, conn_jp, conn_at}, - {conn_de, conn_at, conn_jp}, - }, - // Direct country match - "JP": { - {conn_de, conn_jp, conn_at}, - {conn_jp, conn_de, conn_at}, - }, - // Continent match - "CN": { - {conn_de, conn_jp, conn_at}, - {conn_jp, conn_de, conn_at}, - }, - // Continent match - "RU": { - {conn_us, conn_de, conn_jp, conn_at}, - {conn_de, conn_at, conn_us, conn_jp}, - }, - // No match - "AU": { - {conn_us, conn_de, conn_jp, conn_at}, - {conn_us, conn_de, conn_jp, conn_at}, - }, - } - - for country, test := range testcases { - t.Run(string(country), func(t *testing.T) { - t.Parallel() - sorted := sortConnectionsForCountry(test[0], country, nil) - for idx, conn := range sorted { - assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) - } - }) - } -} - -func Test_sortConnectionsForCountryWithOverride(t *testing.T) { - t.Parallel() - conn_de := newProxyConnectionWithCountry("DE") - conn_at := newProxyConnectionWithCountry("AT") - conn_jp := newProxyConnectionWithCountry("JP") - conn_us := newProxyConnectionWithCountry("US") - - testcases := map[geoip.Country][][]*mcuProxyConnection{ - // Direct country match - "DE": { - {conn_at, conn_jp, conn_de}, - {conn_de, conn_at, conn_jp}, - }, - // Direct country match - "AT": { - {conn_at, conn_jp, conn_de}, - {conn_at, conn_de, conn_jp}, - }, - // Continent match - "CH": { - {conn_de, conn_jp, conn_at}, - {conn_de, conn_at, conn_jp}, - }, - // Direct country match - "JP": { - {conn_de, conn_jp, conn_at}, - {conn_jp, conn_de, conn_at}, - }, - // Continent match - "CN": { - {conn_de, conn_jp, conn_at}, - {conn_jp, conn_de, conn_at}, - }, - // Continent match - "RU": { - {conn_us, conn_de, conn_jp, conn_at}, - {conn_de, conn_at, conn_us, conn_jp}, - }, - // No match - "AR": { - {conn_us, conn_de, conn_jp, conn_at}, - {conn_us, conn_de, conn_jp, conn_at}, - }, - // No match but override (OC -> AS / NA) - "AU": { - {conn_us, conn_jp}, - {conn_us, conn_jp}, - }, - // No match but override (AF -> EU) - "ZA": { - {conn_de, conn_at}, - {conn_de, conn_at}, - }, - } - - continentMap := ContinentsMap{ - // Use European connections for Africa. - "AF": {"EU"}, - // Use Asian and North American connections for Oceania. - "OC": {"AS", "NA"}, - } - for country, test := range testcases { - t.Run(string(country), func(t *testing.T) { - t.Parallel() - sorted := sortConnectionsForCountry(test[0], country, continentMap) - for idx, conn := range sorted { - assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) - } - }) - } -} - -type proxyServerClientHandler func(msg *ProxyClientMessage) (*ProxyServerMessage, error) - -type testProxyServerPublisher struct { - id api.PublicSessionId -} - -type testProxyServerSubscriber struct { - id string - sid string - pub *testProxyServerPublisher - - remoteUrl string -} - -type testProxyServerClient struct { - t *testing.T - - server *TestProxyServerHandler - // +checklocks:mu - ws *websocket.Conn - processMessage proxyServerClientHandler - - mu sync.Mutex - sessionId api.PublicSessionId -} - -func (c *testProxyServerClient) processHello(msg *ProxyClientMessage) (*ProxyServerMessage, error) { - if msg.Type != "hello" { - return nil, fmt.Errorf("expected hello, got %+v", msg) - } - - if msg.Hello.ResumeId != "" { - client := c.server.getClient(msg.Hello.ResumeId) - if client == nil { - response := &ProxyServerMessage{ - Id: msg.Id, - Type: "error", - Error: &api.Error{ - Code: "no_such_session", - }, - } - return response, nil - } - - c.sessionId = msg.Hello.ResumeId - c.server.setClient(c.sessionId, c) - response := &ProxyServerMessage{ - Id: msg.Id, - Type: "hello", - Hello: &HelloProxyServerMessage{ - Version: "1.0", - SessionId: c.sessionId, - Server: &api.WelcomeServerMessage{ - Version: "1.0", - Country: c.server.country, - }, - }, - } - c.processMessage = c.processRegularMessage - return response, nil - } - - token, err := jwt.ParseWithClaims(msg.Hello.Token, &TokenClaims{}, func(token *jwt.Token) (any, error) { - claims, ok := token.Claims.(*TokenClaims) - if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { - return nil, errors.New("unsupported claims type") - } - - key, found := c.server.tokens[claims.Issuer] - if !assert.True(c.t, found) { - return nil, errors.New("no key found for issuer") - } - - return key, nil - }) - if assert.NoError(c.t, err) { - if assert.True(c.t, token.Valid) { - _, ok := token.Claims.(*TokenClaims) - assert.True(c.t, ok) - } - } - - response := &ProxyServerMessage{ - Id: msg.Id, - Type: "hello", - Hello: &HelloProxyServerMessage{ - Version: "1.0", - SessionId: c.sessionId, - Server: &api.WelcomeServerMessage{ - Version: "1.0", - Country: c.server.country, - }, - }, - } - c.processMessage = c.processRegularMessage - return response, nil -} - -func (c *testProxyServerClient) processRegularMessage(msg *ProxyClientMessage) (*ProxyServerMessage, error) { - var handler proxyServerClientHandler - switch msg.Type { - case "command": - handler = c.processCommandMessage - } - - if handler == nil { - response := msg.NewWrappedErrorServerMessage(fmt.Errorf("type \"%s\" is not implemented", msg.Type)) - return response, nil - } - - return handler(msg) -} - -func (c *testProxyServerClient) processCommandMessage(msg *ProxyClientMessage) (*ProxyServerMessage, error) { - var response *ProxyServerMessage - switch msg.Command.Type { - case "create-publisher": - if strings.Contains(c.t.Name(), "ProxyPublisherTimeout") { - time.Sleep(2 * timeoutTestTimeout) - defer c.server.Wakeup() - } - pub := c.server.createPublisher() - - if assert.NotNil(c.t, msg.Command.PublisherSettings) { - if assert.NotEqualValues(c.t, 0, msg.Command.PublisherSettings.Bitrate) { - assert.Equal(c.t, msg.Command.Bitrate, msg.Command.PublisherSettings.Bitrate) - } - assert.Equal(c.t, msg.Command.MediaTypes, msg.Command.PublisherSettings.MediaTypes) - if strings.Contains(c.t.Name(), "Codecs") { - assert.Equal(c.t, "opus,g722", msg.Command.PublisherSettings.AudioCodec) - assert.Equal(c.t, "vp9,vp8,av1", msg.Command.PublisherSettings.VideoCodec) - } else { - assert.Empty(c.t, msg.Command.PublisherSettings.AudioCodec) - assert.Empty(c.t, msg.Command.PublisherSettings.VideoCodec) - } - } - - response = &ProxyServerMessage{ - Id: msg.Id, - Type: "command", - Command: &CommandProxyServerMessage{ - Id: string(pub.id), - Bitrate: msg.Command.Bitrate, - }, - } - c.server.updateLoad(1) - case "delete-publisher": - if strings.Contains(c.t.Name(), "ProxyPublisherTimeout") { - defer c.server.Wakeup() - } - - if pub, found := c.server.deletePublisher(api.PublicSessionId(msg.Command.ClientId)); !found { - response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.ClientId)) - } else { - response = &ProxyServerMessage{ - Id: msg.Id, - Type: "command", - Command: &CommandProxyServerMessage{ - Id: string(pub.id), - }, - } - c.server.updateLoad(-1) - } - case "create-subscriber": - var pub *testProxyServerPublisher - if msg.Command.RemoteUrl != "" { - for _, server := range c.server.servers { - if server.URL != msg.Command.RemoteUrl { - continue - } - - token, err := jwt.ParseWithClaims(msg.Command.RemoteToken, &TokenClaims{}, func(token *jwt.Token) (any, error) { - claims, ok := token.Claims.(*TokenClaims) - if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { - return nil, errors.New("unsupported claims type") - } - - key, found := server.tokens[claims.Issuer] - if !assert.True(c.t, found) { - return nil, errors.New("no key found for issuer") - } - - return key, nil - }) - if assert.NoError(c.t, err) { - if claims, ok := token.Claims.(*TokenClaims); assert.True(c.t, token.Valid) && assert.True(c.t, ok) { - assert.EqualValues(c.t, msg.Command.PublisherId, claims.Subject) - } - } - - pub = server.getPublisher(msg.Command.PublisherId) - break - } - } else { - pub = c.server.getPublisher(msg.Command.PublisherId) - } - - if pub == nil { - response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.PublisherId)) - } else { - if strings.Contains(c.t.Name(), "ProxySubscriberTimeout") { - time.Sleep(2 * timeoutTestTimeout) - defer c.server.Wakeup() - } - sub := c.server.createSubscriber(pub) - response = &ProxyServerMessage{ - Id: msg.Id, - Type: "command", - Command: &CommandProxyServerMessage{ - Id: sub.id, - Sid: sub.sid, - }, - } - c.server.updateLoad(1) - } - case "delete-subscriber": - if strings.Contains(c.t.Name(), "ProxySubscriberTimeout") { - defer c.server.Wakeup() - } - if sub, found := c.server.deleteSubscriber(msg.Command.ClientId); !found { - response = msg.NewWrappedErrorServerMessage(fmt.Errorf("subscriber %s not found", msg.Command.ClientId)) - } else { - if msg.Command.RemoteUrl != sub.remoteUrl { - response = msg.NewWrappedErrorServerMessage(fmt.Errorf("remote subscriber %s not found", msg.Command.ClientId)) - return response, nil - } - - response = &ProxyServerMessage{ - Id: msg.Id, - Type: "command", - Command: &CommandProxyServerMessage{ - Id: sub.id, - }, - } - c.server.updateLoad(-1) - } - } - if response == nil { - response = msg.NewWrappedErrorServerMessage(fmt.Errorf("command \"%s\" is not implemented", msg.Command.Type)) - } - - return response, nil -} - -func (c *testProxyServerClient) close() { - c.mu.Lock() - defer c.mu.Unlock() - - if c.ws != nil { - c.ws.Close() - c.ws = nil - } -} - -func (c *testProxyServerClient) handleSendMessageError(fmt string, msg *ProxyServerMessage, err error) { - c.t.Helper() - - if !errors.Is(err, websocket.ErrCloseSent) || msg.Type != "event" || msg.Event.Type != "update-load" { - assert.Fail(c.t, "error while sending message", fmt, msg, err) - } -} - -func (c *testProxyServerClient) sendMessage(msg *ProxyServerMessage) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.ws == nil { - return - } - - data, err := json.Marshal(msg) - if err != nil { - c.handleSendMessageError("error marshalling %+v: %s", msg, err) - return - } - - w, err := c.ws.NextWriter(websocket.TextMessage) - if err != nil { - c.handleSendMessageError("error creating writer for %+v: %s", msg, err) - return - } - - if _, err := w.Write(data); err != nil { - c.handleSendMessageError("error sending %+v: %s", msg, err) - return - } - - if err := w.Close(); err != nil { - c.handleSendMessageError("error during close of sending %+v: %s", msg, err) - } - - if msg.CloseAfterSend(nil) { - c.ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) // nolint - c.ws.Close() - } -} - -func (c *testProxyServerClient) run() { - defer func() { - c.mu.Lock() - defer c.mu.Unlock() - - c.server.expireSession(30*time.Second, c) - c.ws = nil - }() - c.processMessage = c.processHello - assert := assert.New(c.t) - for { - c.mu.Lock() - ws := c.ws - c.mu.Unlock() - if ws == nil { - break - } - - msgType, reader, err := ws.NextReader() - if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { - assert.NoError(err) - } - return - } - - body, err := io.ReadAll(reader) - if !assert.NoError(err) { - continue - } - - if !assert.Equal(websocket.TextMessage, msgType, "unexpected message type for %s", string(body)) { - continue - } - - var msg ProxyClientMessage - if err := json.Unmarshal(body, &msg); !assert.NoError(err, "could not decode message %s", string(body)) { - continue - } - - if err := msg.CheckValid(); !assert.NoError(err, "invalid message %s", string(body)) { - continue - } - - response, err := c.processMessage(&msg) - if !assert.NoError(err) { - continue - } - - c.sendMessage(response) - if response.Type == "hello" { - c.server.sendLoad(c) - } - } -} - -type TestProxyServerHandler struct { - t *testing.T - - URL string - server *httptest.Server - servers []*TestProxyServerHandler - tokens map[string]*rsa.PublicKey - upgrader *websocket.Upgrader - country geoip.Country - - mu sync.Mutex - load atomic.Uint64 - incoming atomic.Pointer[float64] - outgoing atomic.Pointer[float64] - // +checklocks:mu - clients map[api.PublicSessionId]*testProxyServerClient - // +checklocks:mu - publishers map[api.PublicSessionId]*testProxyServerPublisher - // +checklocks:mu - subscribers map[string]*testProxyServerSubscriber - - wakeupChan chan struct{} -} - -func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { - h.mu.Lock() - defer h.mu.Unlock() - pub := &testProxyServerPublisher{ - id: api.PublicSessionId(internal.RandomString(32)), - } - - for { - if _, found := h.publishers[pub.id]; !found { - break - } - - pub.id = api.PublicSessionId(internal.RandomString(32)) - } - h.publishers[pub.id] = pub - return pub -} - -func (h *TestProxyServerHandler) getPublisher(id api.PublicSessionId) *testProxyServerPublisher { - h.mu.Lock() - defer h.mu.Unlock() - - return h.publishers[id] -} - -func (h *TestProxyServerHandler) deletePublisher(id api.PublicSessionId) (*testProxyServerPublisher, bool) { - h.mu.Lock() - defer h.mu.Unlock() - - pub, found := h.publishers[id] - if !found { - return nil, false - } - - delete(h.publishers, id) - return pub, true -} - -func (h *TestProxyServerHandler) createSubscriber(pub *testProxyServerPublisher) *testProxyServerSubscriber { - h.mu.Lock() - defer h.mu.Unlock() - - sub := &testProxyServerSubscriber{ - id: internal.RandomString(32), - sid: internal.RandomString(8), - pub: pub, - } - - for { - if _, found := h.subscribers[sub.id]; !found { - break - } - - sub.id = internal.RandomString(32) - } - h.subscribers[sub.id] = sub - return sub -} - -func (h *TestProxyServerHandler) deleteSubscriber(id string) (*testProxyServerSubscriber, bool) { - h.mu.Lock() - defer h.mu.Unlock() - - sub, found := h.subscribers[id] - if !found { - return nil, false - } - - delete(h.subscribers, id) - return sub, true -} - -func (h *TestProxyServerHandler) UpdateBandwidth(incoming float64, outgoing float64) { - h.incoming.Store(&incoming) - h.outgoing.Store(&outgoing) - - h.mu.Lock() - defer h.mu.Unlock() - - msg := h.getLoadMessage(h.load.Load()) - for _, c := range h.clients { - c.sendMessage(msg) - } -} - -func (h *TestProxyServerHandler) Clear(incoming bool, outgoing bool) { - if incoming { - h.incoming.Store(nil) - } - if outgoing { - h.outgoing.Store(nil) - } - - h.mu.Lock() - defer h.mu.Unlock() - - msg := h.getLoadMessage(h.load.Load()) - for _, c := range h.clients { - c.sendMessage(msg) - } -} - -func (h *TestProxyServerHandler) getLoadMessage(load uint64) *ProxyServerMessage { - msg := &ProxyServerMessage{ - Type: "event", - Event: &EventProxyServerMessage{ - Type: "update-load", - Load: load, - }, - } - - incoming := h.incoming.Load() - outgoing := h.outgoing.Load() - if incoming != nil || outgoing != nil { - msg.Event.Bandwidth = &EventProxyServerBandwidth{ - Incoming: incoming, - Outgoing: outgoing, - } - } - return msg -} - -func (h *TestProxyServerHandler) updateLoad(delta int64) { - if delta == 0 { - return - } - - var load uint64 - if delta > 0 { - load = h.load.Add(uint64(delta)) - } else { - load = h.load.Add(^uint64(delta - 1)) - } - - h.mu.Lock() - defer h.mu.Unlock() - - msg := h.getLoadMessage(load) - for _, c := range h.clients { - c.sendMessage(msg) - } -} - -func (h *TestProxyServerHandler) sendLoad(c *testProxyServerClient) { - msg := h.getLoadMessage(h.load.Load()) - c.sendMessage(msg) -} - -func (h *TestProxyServerHandler) expireSession(timeout time.Duration, client *testProxyServerClient) { - timer := time.AfterFunc(timeout, func() { - h.removeClient(client) - }) - - h.t.Cleanup(func() { - timer.Stop() - }) -} - -func (h *TestProxyServerHandler) removeClient(client *testProxyServerClient) { - h.mu.Lock() - defer h.mu.Unlock() - - delete(h.clients, client.sessionId) -} - -func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ws, err := h.upgrader.Upgrade(w, r, nil) - if !assert.NoError(h.t, err) { - return - } - - client := &testProxyServerClient{ - t: h.t, - server: h, - ws: ws, - sessionId: api.PublicSessionId(internal.RandomString(32)), - } - - h.setClient(client.sessionId, client) - - go client.run() -} - -func (h *TestProxyServerHandler) getClient(sessionId api.PublicSessionId) *testProxyServerClient { - h.mu.Lock() - defer h.mu.Unlock() - - return h.clients[sessionId] -} - -func (h *TestProxyServerHandler) setClient(sessionId api.PublicSessionId, client *testProxyServerClient) { - h.mu.Lock() - defer h.mu.Unlock() - - if prev, found := h.clients[sessionId]; found { - prev.sendMessage(&ProxyServerMessage{ - Type: "bye", - Bye: &ByeProxyServerMessage{ - Reason: "session_resumed", - }, - }) - prev.close() - } - - h.clients[sessionId] = client -} - -func (h *TestProxyServerHandler) Wakeup() { - h.wakeupChan <- struct{}{} -} - -func (h *TestProxyServerHandler) WaitForWakeup(ctx context.Context) error { - select { - case <-h.wakeupChan: - return nil - case <-ctx.Done(): - return ctx.Err() - } -} - -func (h *TestProxyServerHandler) GetSingleClient() *testProxyServerClient { - h.mu.Lock() - defer h.mu.Unlock() - - for _, c := range h.clients { - return c - } - - return nil -} - -func (h *TestProxyServerHandler) ClearClients() { - h.mu.Lock() - defer h.mu.Unlock() - - clear(h.clients) -} - -func NewProxyServerForTest(t *testing.T, country geoip.Country) *TestProxyServerHandler { - t.Helper() - - upgrader := websocket.Upgrader{} - proxyHandler := &TestProxyServerHandler{ - t: t, - tokens: make(map[string]*rsa.PublicKey), - upgrader: &upgrader, - country: country, - clients: make(map[api.PublicSessionId]*testProxyServerClient), - publishers: make(map[api.PublicSessionId]*testProxyServerPublisher), - subscribers: make(map[string]*testProxyServerSubscriber), - wakeupChan: make(chan struct{}), - } - server := httptest.NewUnstartedServer(proxyHandler) - if !strings.Contains(t.Name(), "DnsDiscovery") { - server.Start() - } - proxyHandler.server = server - proxyHandler.URL = server.URL - t.Cleanup(func() { - server.Close() - proxyHandler.mu.Lock() - defer proxyHandler.mu.Unlock() - for _, c := range proxyHandler.clients { - c.close() - } - }) - - return proxyHandler -} - -type proxyTestOptions struct { - etcd *etcdtest.Server - servers []*TestProxyServerHandler -} - -func newMcuProxyForTestWithOptions(t *testing.T, options proxyTestOptions, idx int, lookup *dns.MockLookup) (*mcuProxy, *goconf.ConfigFile) { - t.Helper() - require := require.New(t) - if options.etcd == nil { - options.etcd = etcdtest.NewServerForTest(t) - } - grpcClients, dnsMonitor := NewGrpcClientsWithEtcdForTest(t, options.etcd, lookup) - - tokenKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - dir := t.TempDir() - privkeyFile := path.Join(dir, "privkey.pem") - pubkeyFile := path.Join(dir, "pubkey.pem") - require.NoError(internal.WritePrivateKey(tokenKey, privkeyFile)) - require.NoError(internal.WritePublicKey(&tokenKey.PublicKey, pubkeyFile)) - - cfg := goconf.NewConfigFile() - cfg.AddOption("mcu", "urltype", "static") - if strings.Contains(t.Name(), "DnsDiscovery") { - cfg.AddOption("mcu", "dnsdiscovery", "true") - } - cfg.AddOption("mcu", "proxytimeout", strconv.Itoa(int(testTimeout.Seconds()))) - var urls []string - waitingMap := make(map[string]bool) - if len(options.servers) == 0 { - options.servers = []*TestProxyServerHandler{ - NewProxyServerForTest(t, "DE"), - } - } - tokenId := fmt.Sprintf("test-token-%d", idx) - for _, s := range options.servers { - s.servers = options.servers - s.tokens[tokenId] = &tokenKey.PublicKey - urls = append(urls, s.URL) - waitingMap[s.URL] = true - } - cfg.AddOption("mcu", "url", strings.Join(urls, " ")) - cfg.AddOption("mcu", "token_id", tokenId) - cfg.AddOption("mcu", "token_key", privkeyFile) - - etcdConfig := goconf.NewConfigFile() - etcdConfig.AddOption("etcd", "endpoints", options.etcd.URL().String()) - etcdConfig.AddOption("etcd", "loglevel", "error") - - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - etcdClient, err := etcd.NewClient(logger, etcdConfig, "") - require.NoError(err) - t.Cleanup(func() { - assert.NoError(t, etcdClient.Close()) - }) - - mcu, err := NewMcuProxy(ctx, cfg, etcdClient, grpcClients, dnsMonitor) - require.NoError(err) - t.Cleanup(func() { - mcu.Stop() - }) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - require.NoError(mcu.Start(ctx)) - - proxy := mcu.(*mcuProxy) - - require.NoError(proxy.WaitForConnections(ctx)) - - for len(waitingMap) > 0 { - require.NoError(ctx.Err()) - - for u := range waitingMap { - proxy.connectionsMu.RLock() - connections := proxy.connections - proxy.connectionsMu.RUnlock() - for _, c := range connections { - if c.rawUrl == u && c.IsConnected() && c.SessionId() != "" { - delete(waitingMap, u) - break - } - } - } - - time.Sleep(time.Millisecond) - } - - return proxy, cfg -} - -func newMcuProxyForTestWithServers(t *testing.T, servers []*TestProxyServerHandler, idx int, lookup *dns.MockLookup) *mcuProxy { - t.Helper() - - proxy, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: servers, - }, idx, lookup) - return proxy -} - -func newMcuProxyForTest(t *testing.T, idx int, lookup *dns.MockLookup) *mcuProxy { - t.Helper() - server := NewProxyServerForTest(t, "DE") - - return newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{server}, idx, lookup) -} - -func Test_ProxyAddRemoveConnections(t *testing.T) { - t.Parallel() - assert := assert.New(t) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - server1 := NewProxyServerForTest(t, "DE") - mcu, config := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{ - server1, - }, - }, 0, nil) - - server2 := NewProxyServerForTest(t, "DE") - server1.servers = append(server1.servers, server2) - server2.servers = server1.servers - server2.tokens = server1.tokens - urls1 := []string{ - server1.URL, - server2.URL, - } - config.AddOption("mcu", "url", strings.Join(urls1, " ")) - mcu.Reload(config) - - mcu.connectionsMu.RLock() - assert.Len(mcu.connections, 2) - mcu.connectionsMu.RUnlock() - - // Wait until connection is established. - waitCtx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - - for waitCtx.Err() == nil { - mcu.connectionsMu.RLock() - notAllConnected := slices.ContainsFunc(mcu.connections, func(conn *mcuProxyConnection) bool { - return !conn.IsConnected() - }) - mcu.connectionsMu.RUnlock() - if notAllConnected { - time.Sleep(time.Millisecond) - continue - } - - break - } - assert.NoError(waitCtx.Err(), "error while waiting for connection to be established") - - urls2 := []string{ - server2.URL, - } - config.AddOption("mcu", "url", strings.Join(urls2, " ")) - mcu.Reload(config) - - // Removing the connections takes a short while (asynchronously, closed when unused). - waitCtx, cancel = context.WithTimeout(ctx, time.Second) - defer cancel() - - for waitCtx.Err() == nil { - mcu.connectionsMu.RLock() - if len(mcu.connections) != 1 { - mcu.connectionsMu.RUnlock() - time.Sleep(time.Millisecond) - continue - } - - assert.Len(mcu.connections, 1) - assert.Equal(server2.URL, mcu.connections[0].rawUrl) - mcu.connectionsMu.RUnlock() - break - } - assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") -} - -func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { - t.Parallel() - assert := assert.New(t) - require := require.New(t) - - lookup := dns.NewMockLookupForTest(t) - - server1 := NewProxyServerForTest(t, "DE") - server1.server.Start() - server1.URL = server1.server.URL - h, port, err := net.SplitHostPort(server1.server.Listener.Addr().String()) - require.NoError(err) - ip1 := net.ParseIP(h) - require.NotNil(ip1, "failed for %s", h) - - require.Contains(server1.URL, ip1.String()) - server1.URL = strings.ReplaceAll(server1.URL, ip1.String(), "proxydomain.invalid") - u1, err := url.Parse(server1.URL) - require.NoError(err) - lookup.Set(u1.Hostname(), []net.IP{ - ip1, - }) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{ - server1, - }, - }, 0, lookup) - - if connections := mcu.getConnections(); assert.Len(connections, 1) && assert.NotNil(connections[0].ip) { - assert.True(ip1.Equal(connections[0].ip), "ip addresses differ: expected %s, got %s", ip1.String(), connections[0].ip.String()) - } - - dnsMonitor := mcu.config.(*proxyConfigStatic).dnsMonitor - require.NotNil(dnsMonitor) - - server2 := NewProxyServerForTest(t, "DE") - l, err := net.Listen("tcp", "127.0.0.2:"+port) - require.NoError(err) - assert.NoError(server2.server.Listener.Close()) - server2.server.Listener = l - server2.server.Start() - - server2.URL = server2.server.URL - h, _, err = net.SplitHostPort(server2.server.Listener.Addr().String()) - require.NoError(err) - ip2 := net.ParseIP(h) - require.NotNil(ip2, "failed for %s", h) - require.Contains(server2.URL, ip2.String()) - server2.URL = strings.ReplaceAll(server2.URL, ip2.String(), "proxydomain.invalid") - - server1.servers = append(server1.servers, server2) - server2.servers = server1.servers - server2.tokens = server1.tokens - - lookup.Set(u1.Hostname(), []net.IP{ - ip1, - ip2, - }) - dnsMonitor.CheckHostnames() - - // Wait until connection is established. - waitCtx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - - for waitCtx.Err() == nil { - mcu.connectionsMu.RLock() - if len(mcu.connections) != 2 { - mcu.connectionsMu.RUnlock() - time.Sleep(time.Millisecond) - continue - } - - notAllConnected := slices.ContainsFunc(mcu.connections, func(conn *mcuProxyConnection) bool { - return !conn.IsConnected() - }) - mcu.connectionsMu.RUnlock() - if notAllConnected { - time.Sleep(time.Millisecond) - continue - } - - break - } - assert.NoError(waitCtx.Err(), "error while waiting for connection to be established") - - lookup.Set(u1.Hostname(), []net.IP{ - ip2, - }) - dnsMonitor.CheckHostnames() - - // Removing the connections takes a short while (asynchronously, closed when unused). - waitCtx, cancel = context.WithTimeout(ctx, time.Second) - defer cancel() - - for waitCtx.Err() == nil { - mcu.connectionsMu.RLock() - if len(mcu.connections) != 1 { - mcu.connectionsMu.RUnlock() - time.Sleep(time.Millisecond) - continue - } - - assert.Len(mcu.connections, 1) - assert.Equal(server1.URL, mcu.connections[0].rawUrl) - if assert.NotNil(mcu.connections[0].ip) { - assert.True(ip2.Equal(mcu.connections[0].ip), "ip addresses differ: expected %s, got %s", ip2.String(), mcu.connections[0].ip.String()) - } - mcu.connectionsMu.RUnlock() - break - } - assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") -} - -func Test_ProxyPublisherSubscriber(t *testing.T) { - t.Parallel() - mcu := newMcuProxyForTest(t, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} - -func Test_ProxyPublisherCodecs(t *testing.T) { - t.Parallel() - mcu := newMcuProxyForTest(t, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - AudioCodec: "opus,g722", - VideoCodec: "vp9,vp8,av1", - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) -} - -func Test_ProxyWaitForPublisher(t *testing.T) { - t.Parallel() - mcu := newMcuProxyForTest(t, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - done := make(chan struct{}) - go func() { - defer close(done) - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - if !assert.NoError(t, err) { - return - } - - defer sub.Close(context.Background()) - }() - - // Give subscriber goroutine some time to start - time.Sleep(100 * time.Millisecond) - - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - select { - case <-done: - case <-ctx.Done(): - assert.NoError(t, ctx.Err()) - } - defer pub.Close(context.Background()) -} - -func Test_ProxyPublisherBandwidth(t *testing.T) { - t.Parallel() - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - server1, - server2, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pub1Id := api.PublicSessionId("the-publisher-1") - pub1Sid := "1234567890" - pub1Listener := &MockMcuListener{ - publicId: pub1Id + "-public", - } - pub1Initiator := &MockMcuInitiator{ - country: "DE", - } - pub1, err := mcu.NewPublisher(ctx, pub1Listener, pub1Id, pub1Sid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pub1Initiator) - require.NoError(t, err) - - defer pub1.Close(context.Background()) - - if pub1.(*mcuProxyPublisher).conn.rawUrl == server1.URL { - server1.UpdateBandwidth(100, 0) - } else { - server2.UpdateBandwidth(100, 0) - } - - // Wait until proxy has been updated - for assert.NoError(t, ctx.Err()) { - mcu.connectionsMu.RLock() - connections := mcu.connections - mcu.connectionsMu.RUnlock() - missing := true - for _, c := range connections { - if c.Bandwidth() != nil { - missing = false - break - } - } - if !missing { - break - } - time.Sleep(time.Millisecond) - } - - pub2Id := api.PublicSessionId("the-publisher-2") - pub2id := "1234567890" - pub2Listener := &MockMcuListener{ - publicId: pub2Id + "-public", - } - pub2Initiator := &MockMcuInitiator{ - country: "DE", - } - pub2, err := mcu.NewPublisher(ctx, pub2Listener, pub2Id, pub2id, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pub2Initiator) - require.NoError(t, err) - - defer pub2.Close(context.Background()) - - assert.NotEqual(t, pub1.(*mcuProxyPublisher).conn.rawUrl, pub2.(*mcuProxyPublisher).conn.rawUrl) -} - -func Test_ProxyPublisherBandwidthOverload(t *testing.T) { - t.Parallel() - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - server1, - server2, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pub1Id := api.PublicSessionId("the-publisher-1") - pub1Sid := "1234567890" - pub1Listener := &MockMcuListener{ - publicId: pub1Id + "-public", - } - pub1Initiator := &MockMcuInitiator{ - country: "DE", - } - pub1, err := mcu.NewPublisher(ctx, pub1Listener, pub1Id, pub1Sid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pub1Initiator) - require.NoError(t, err) - - defer pub1.Close(context.Background()) - - // If all servers are bandwidth loaded, select the one with the least usage. - if pub1.(*mcuProxyPublisher).conn.rawUrl == server1.URL { - server1.UpdateBandwidth(100, 0) - server2.UpdateBandwidth(102, 0) - } else { - server1.UpdateBandwidth(102, 0) - server2.UpdateBandwidth(100, 0) - } - - // Wait until proxy has been updated - for assert.NoError(t, ctx.Err()) { - mcu.connectionsMu.RLock() - connections := mcu.connections - mcu.connectionsMu.RUnlock() - missing := false - for _, c := range connections { - if c.Bandwidth() == nil { - missing = true - break - } - } - if !missing { - break - } - time.Sleep(time.Millisecond) - } - - pub2Id := api.PublicSessionId("the-publisher-2") - pub2id := "1234567890" - pub2Listener := &MockMcuListener{ - publicId: pub2Id + "-public", - } - pub2Initiator := &MockMcuInitiator{ - country: "DE", - } - pub2, err := mcu.NewPublisher(ctx, pub2Listener, pub2Id, pub2id, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pub2Initiator) - require.NoError(t, err) - - defer pub2.Close(context.Background()) - - assert.Equal(t, pub1.(*mcuProxyPublisher).conn.rawUrl, pub2.(*mcuProxyPublisher).conn.rawUrl) -} - -func Test_ProxyPublisherLoad(t *testing.T) { - t.Parallel() - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - server1, - server2, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pub1Id := api.PublicSessionId("the-publisher-1") - pub1Sid := "1234567890" - pub1Listener := &MockMcuListener{ - publicId: pub1Id + "-public", - } - pub1Initiator := &MockMcuInitiator{ - country: "DE", - } - pub1, err := mcu.NewPublisher(ctx, pub1Listener, pub1Id, pub1Sid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pub1Initiator) - require.NoError(t, err) - - defer pub1.Close(context.Background()) - - // Make sure connections are re-sorted. - mcu.nextSort.Store(0) - time.Sleep(100 * time.Millisecond) - - pub2Id := api.PublicSessionId("the-publisher-2") - pub2id := "1234567890" - pub2Listener := &MockMcuListener{ - publicId: pub2Id + "-public", - } - pub2Initiator := &MockMcuInitiator{ - country: "DE", - } - pub2, err := mcu.NewPublisher(ctx, pub2Listener, pub2Id, pub2id, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pub2Initiator) - require.NoError(t, err) - - defer pub2.Close(context.Background()) - - assert.NotEqual(t, pub1.(*mcuProxyPublisher).conn.rawUrl, pub2.(*mcuProxyPublisher).conn.rawUrl) -} - -func Test_ProxyPublisherCountry(t *testing.T) { - t.Parallel() - serverDE := NewProxyServerForTest(t, "DE") - serverUS := NewProxyServerForTest(t, "US") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - serverDE, - serverUS, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubDEId := api.PublicSessionId("the-publisher-de") - pubDESid := "1234567890" - pubDEListener := &MockMcuListener{ - publicId: pubDEId + "-public", - } - pubDEInitiator := &MockMcuInitiator{ - country: "DE", - } - pubDE, err := mcu.NewPublisher(ctx, pubDEListener, pubDEId, pubDESid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubDEInitiator) - require.NoError(t, err) - - defer pubDE.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pubDE.(*mcuProxyPublisher).conn.rawUrl) - - pubUSId := api.PublicSessionId("the-publisher-us") - pubUSSid := "1234567890" - pubUSListener := &MockMcuListener{ - publicId: pubUSId + "-public", - } - pubUSInitiator := &MockMcuInitiator{ - country: "US", - } - pubUS, err := mcu.NewPublisher(ctx, pubUSListener, pubUSId, pubUSSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubUSInitiator) - require.NoError(t, err) - - defer pubUS.Close(context.Background()) - - assert.Equal(t, serverUS.URL, pubUS.(*mcuProxyPublisher).conn.rawUrl) -} - -func Test_ProxyPublisherContinent(t *testing.T) { - t.Parallel() - serverDE := NewProxyServerForTest(t, "DE") - serverUS := NewProxyServerForTest(t, "US") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - serverDE, - serverUS, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubDEId := api.PublicSessionId("the-publisher-de") - pubDESid := "1234567890" - pubDEListener := &MockMcuListener{ - publicId: pubDEId + "-public", - } - pubDEInitiator := &MockMcuInitiator{ - country: "DE", - } - pubDE, err := mcu.NewPublisher(ctx, pubDEListener, pubDEId, pubDESid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubDEInitiator) - require.NoError(t, err) - - defer pubDE.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pubDE.(*mcuProxyPublisher).conn.rawUrl) - - pubFRId := api.PublicSessionId("the-publisher-fr") - pubFRSid := "1234567890" - pubFRListener := &MockMcuListener{ - publicId: pubFRId + "-public", - } - pubFRInitiator := &MockMcuInitiator{ - country: "FR", - } - pubFR, err := mcu.NewPublisher(ctx, pubFRListener, pubFRId, pubFRSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubFRInitiator) - require.NoError(t, err) - - defer pubFR.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pubFR.(*mcuProxyPublisher).conn.rawUrl) -} - -func Test_ProxySubscriberCountry(t *testing.T) { - t.Parallel() - serverDE := NewProxyServerForTest(t, "DE") - serverUS := NewProxyServerForTest(t, "US") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - serverDE, - serverUS, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pub.(*mcuProxyPublisher).conn.rawUrl) - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "US", - } - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) - - assert.Equal(t, serverUS.URL, sub.(*mcuProxySubscriber).conn.rawUrl) -} - -func Test_ProxySubscriberContinent(t *testing.T) { - t.Parallel() - serverDE := NewProxyServerForTest(t, "DE") - serverUS := NewProxyServerForTest(t, "US") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - serverDE, - serverUS, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pub.(*mcuProxyPublisher).conn.rawUrl) - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "FR", - } - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) - - assert.Equal(t, serverDE.URL, sub.(*mcuProxySubscriber).conn.rawUrl) -} - -func Test_ProxySubscriberBandwidth(t *testing.T) { - t.Parallel() - serverDE := NewProxyServerForTest(t, "DE") - serverUS := NewProxyServerForTest(t, "US") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - serverDE, - serverUS, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pub.(*mcuProxyPublisher).conn.rawUrl) - - serverDE.UpdateBandwidth(0, 100) - - // Wait until proxy has been updated - for assert.NoError(t, ctx.Err()) { - mcu.connectionsMu.RLock() - connections := mcu.connections - mcu.connectionsMu.RUnlock() - missing := true - for _, c := range connections { - if c.Bandwidth() != nil { - missing = false - break - } - } - if !missing { - break - } - time.Sleep(time.Millisecond) - } - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "US", - } - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) - - assert.Equal(t, serverUS.URL, sub.(*mcuProxySubscriber).conn.rawUrl) -} - -func Test_ProxySubscriberBandwidthOverload(t *testing.T) { - t.Parallel() - serverDE := NewProxyServerForTest(t, "DE") - serverUS := NewProxyServerForTest(t, "US") - mcu := newMcuProxyForTestWithServers(t, []*TestProxyServerHandler{ - serverDE, - serverUS, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - assert.Equal(t, serverDE.URL, pub.(*mcuProxyPublisher).conn.rawUrl) - - serverDE.UpdateBandwidth(0, 100) - serverUS.UpdateBandwidth(0, 102) - - // Wait until proxy has been updated - for assert.NoError(t, ctx.Err()) { - mcu.connectionsMu.RLock() - connections := mcu.connections - mcu.connectionsMu.RUnlock() - missing := false - for _, c := range connections { - if c.Bandwidth() == nil { - missing = true - break - } - } - if !missing { - break - } - time.Sleep(time.Millisecond) - } - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "US", - } - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) - - assert.Equal(t, serverDE.URL, sub.(*mcuProxySubscriber).conn.rawUrl) -} - -type mockGrpcServerHub struct { - proxy atomic.Pointer[mcuProxy] - sessionsLock sync.Mutex - // +checklocks:sessionsLock - sessionByPublicId map[api.PublicSessionId]Session -} - -func (h *mockGrpcServerHub) addSession(session *ClientSession) { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - if h.sessionByPublicId == nil { - h.sessionByPublicId = make(map[api.PublicSessionId]Session) - } - h.sessionByPublicId[session.PublicId()] = session -} - -func (h *mockGrpcServerHub) removeSession(session *ClientSession) { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - delete(h.sessionByPublicId, session.PublicId()) -} - -func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId api.PrivateSessionId) Session { - return nil -} - -func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId api.PublicSessionId) Session { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - return h.sessionByPublicId[sessionId] -} - -func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { - return "", nil -} - -func (h *mockGrpcServerHub) GetBackend(u *url.URL) *talk.Backend { - return nil -} - -func (h *mockGrpcServerHub) GetRoomForBackend(roomId string, backend *talk.Backend) *Room { - return nil -} - -func (h *mockGrpcServerHub) CreateProxyToken(publisherId string) (string, error) { - proxy := h.proxy.Load() - if proxy == nil { - return "", errors.New("not a proxy mcu") - } - - return proxy.createToken(publisherId) -} - -func Test_ProxyRemotePublisher(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - - mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - }, - }, 1, nil) - hub1.proxy.Store(mcu1) - mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - }, - }, 2, nil) - hub2.proxy.Store(mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[StreamType]McuPublisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} - -func Test_ProxyMultipleRemotePublisher(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - grpcServer3, addr3 := NewGrpcServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - hub3 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - grpcServer3.hub = hub3 - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - embedEtcd.SetValue("/grpctargets/three", []byte("{\"address\":\""+addr3+"\"}")) - - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "US") - server3 := NewProxyServerForTest(t, "US") - - mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - server3, - }, - }, 1, nil) - hub1.proxy.Store(mcu1) - mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - server3, - }, - }, 2, nil) - hub2.proxy.Store(mcu2) - mcu3, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - server3, - }, - }, 3, nil) - hub3.proxy.Store(mcu3) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[StreamType]McuPublisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - sub1Listener := &MockMcuListener{ - publicId: "subscriber-public-1", - } - sub1Initiator := &MockMcuInitiator{ - country: "US", - } - sub1, err := mcu2.NewSubscriber(ctx, sub1Listener, pubId, StreamTypeVideo, sub1Initiator) - require.NoError(t, err) - - defer sub1.Close(context.Background()) - - sub2Listener := &MockMcuListener{ - publicId: "subscriber-public-2", - } - sub2Initiator := &MockMcuInitiator{ - country: "US", - } - sub2, err := mcu3.NewSubscriber(ctx, sub2Listener, pubId, StreamTypeVideo, sub2Initiator) - require.NoError(t, err) - - defer sub2.Close(context.Background()) -} - -func Test_ProxyRemotePublisherWait(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - - mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - }, - }, 1, nil) - hub1.proxy.Store(mcu1) - mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - server2, - }, - }, 2, nil) - hub2.proxy.Store(mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[StreamType]McuPublisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - - done := make(chan struct{}) - go func() { - defer close(done) - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - if !assert.NoError(t, err) { - return - } - - defer sub.Close(context.Background()) - }() - - // Give subscriber goroutine some time to start - time.Sleep(100 * time.Millisecond) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - select { - case <-done: - case <-ctx.Done(): - assert.NoError(t, ctx.Err()) - } -} - -func Test_ProxyRemotePublisherTemporary(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - - mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - }, - }, 1, nil) - hub1.proxy.Store(mcu1) - mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server2, - }, - }, 2, nil) - hub2.proxy.Store(mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[StreamType]McuPublisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - mcu2.connectionsMu.RLock() - count := len(mcu2.connections) - mcu2.connectionsMu.RUnlock() - assert.Equal(t, 1, count) - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) - - assert.Equal(t, server1.URL, sub.(*mcuProxySubscriber).conn.rawUrl) - - // The temporary connection has been added - mcu2.connectionsMu.RLock() - count = len(mcu2.connections) - mcu2.connectionsMu.RUnlock() - assert.Equal(t, 2, count) - - sub.Close(context.Background()) - - // Wait for temporary connection to be removed. -loop: - for { - select { - case <-ctx.Done(): - assert.NoError(t, ctx.Err()) - default: - mcu2.connectionsMu.RLock() - count = len(mcu2.connections) - mcu2.connectionsMu.RUnlock() - if count == 1 { - break loop - } - } - } -} - -func Test_ProxyConnectToken(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "DE") - - // Signaling server instances are in a cluster but don't share their proxies, - // i.e. they are only known to their local proxy, not the one of the other - // signaling server - so the connection token must be passed between them. - mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - }, - }, 1, nil) - hub1.proxy.Store(mcu1) - mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server2, - }, - }, 2, nil) - hub2.proxy.Store(mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[StreamType]McuPublisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} - -func Test_ProxyPublisherToken(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := NewProxyServerForTest(t, "DE") - server2 := NewProxyServerForTest(t, "US") - - // Signaling server instances are in a cluster but don't share their proxies, - // i.e. they are only known to their local proxy, not the one of the other - // signaling server - so the connection token must be passed between them. - // Also the subscriber is connecting from a different country, so a remote - // stream will be created that needs a valid token from the remote proxy. - mcu1, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server1, - }, - }, 1, nil) - hub1.proxy.Store(mcu1) - mcu2, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - etcd: embedEtcd, - servers: []*TestProxyServerHandler{ - server2, - }, - }, 2, nil) - hub2.proxy.Store(mcu2) - // Support remote subscribers for the tests. - server1.servers = append(server1.servers, server2) - server2.servers = append(server2.servers, server1) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[StreamType]McuPublisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "US", - } - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} - -func Test_ProxyPublisherTimeout(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - server := NewProxyServerForTest(t, "DE") - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{server}, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - settings := mcu.settings.(*mcuProxySettings) - settings.timeout.Store(timeoutTestTimeout.Nanoseconds()) - - // Creating the publisher will timeout locally. - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - if pub != nil { - defer pub.Close(context.Background()) - } - assert.ErrorContains(err, "no MCU connection available") - - // Wait for publisher to be created on the proxy side. - require.NoError(server.WaitForWakeup(ctx), "publisher not created") - - // The local side will remove the (unused) publisher from the proxy. - require.NoError(server.WaitForWakeup(ctx), "unused publisher not deleted") -} - -func Test_ProxySubscriberTimeout(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - server := NewProxyServerForTest(t, "DE") - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{server}, - }, 0, nil) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := &MockMcuListener{ - publicId: pubId + "-public", - } - pubInitiator := &MockMcuInitiator{ - country: "DE", - } - - pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, StreamTypeVideo, NewPublisherSettings{ - MediaTypes: MediaTypeVideo | MediaTypeAudio, - }, pubInitiator) - require.NoError(err) - defer pub.Close(context.Background()) - - subListener := &MockMcuListener{ - publicId: "subscriber-public", - } - subInitiator := &MockMcuInitiator{ - country: "DE", - } - - settings := mcu.settings.(*mcuProxySettings) - settings.timeout.Store(timeoutTestTimeout.Nanoseconds()) - - // Creating the subscriber will timeout locally. - sub, err := mcu.NewSubscriber(ctx, subListener, pubId, StreamTypeVideo, subInitiator) - if sub != nil { - defer sub.Close(context.Background()) - } - assert.ErrorIs(err, context.DeadlineExceeded) - - // Wait for subscriber to be created on the proxy side. - require.NoError(server.WaitForWakeup(ctx), "subscriber not created") - - // The local side will remove the (unused) subscriber from the proxy. - require.NoError(server.WaitForWakeup(ctx), "unused subscriber not deleted") -} - -func Test_ProxyReconnectAfter(t *testing.T) { - t.Parallel() - reasons := []string{ - "session_resumed", - "session_expired", - "session_closed", - "unknown_reason", - } - for _, reason := range reasons { - t.Run(reason, func(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - server := NewProxyServerForTest(t, "DE") - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{server}, - }, 0, nil) - - connections := mcu.getSortedConnections(nil) - require.Len(connections, 1) - sessionId := connections[0].SessionId() - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - client := server.GetSingleClient() - require.NotNil(client) - - client.sendMessage(&ProxyServerMessage{ - Type: "bye", - Bye: &ByeProxyServerMessage{ - Reason: reason, - }, - }) - - // The "bye" will close the connection and reset the session id. - assert.NoError(mcu.WaitForDisconnected(ctx)) - - // The client will automatically reconnect. - time.Sleep(10 * time.Millisecond) - assert.NoError(mcu.WaitForConnections(ctx)) - - if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { - assert.NotEqual(sessionId, connections[0].SessionId()) - } - }) - } -} - -func Test_ProxyReconnectAfterShutdown(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - server := NewProxyServerForTest(t, "DE") - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{server}, - }, 0, nil) - - connections := mcu.getSortedConnections(nil) - require.Len(connections, 1) - sessionId := connections[0].SessionId() - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - client := server.GetSingleClient() - require.NotNil(client) - - client.sendMessage(&ProxyServerMessage{ - Type: "event", - Event: &EventProxyServerMessage{ - Type: "shutdown-scheduled", - }, - }) - - // Force reconnect. - client.close() - assert.NoError(mcu.WaitForDisconnected(ctx)) - - // The client will automatically reconnect and resume the session. - time.Sleep(10 * time.Millisecond) - assert.NoError(mcu.WaitForConnections(ctx)) - - if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { - assert.Equal(sessionId, connections[0].SessionId()) - } -} - -func Test_ProxyResume(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - server := NewProxyServerForTest(t, "DE") - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{server}, - }, 0, nil) - - connections := mcu.getSortedConnections(nil) - require.Len(connections, 1) - sessionId := connections[0].SessionId() - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - client := server.GetSingleClient() - require.NotNil(client) - - // Force reconnect. - client.close() - assert.NoError(mcu.WaitForDisconnected(ctx)) - - // The client will automatically reconnect. - time.Sleep(10 * time.Millisecond) - assert.NoError(mcu.WaitForConnections(ctx)) - - if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { - assert.Equal(sessionId, connections[0].SessionId()) - } -} - -func Test_ProxyResumeFail(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - server := NewProxyServerForTest(t, "DE") - mcu, _ := newMcuProxyForTestWithOptions(t, proxyTestOptions{ - servers: []*TestProxyServerHandler{server}, - }, 0, nil) - - connections := mcu.getSortedConnections(nil) - require.Len(connections, 1) - sessionId := connections[0].SessionId() - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - client := server.GetSingleClient() - require.NotNil(client) - server.ClearClients() - - // Force reconnect. - client.close() - assert.NoError(mcu.WaitForDisconnected(ctx)) - - // The client will automatically reconnect. - time.Sleep(10 * time.Millisecond) - assert.NoError(mcu.WaitForConnections(ctx)) - - if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { - assert.NotEqual(sessionId, connections[0].SessionId()) - } -} diff --git a/stats_prometheus_test.go b/metrics/test/metrics.go similarity index 92% rename from stats_prometheus_test.go rename to metrics/test/metrics.go index dd2e169..98ca16a 100644 --- a/stats_prometheus_test.go +++ b/metrics/test/metrics.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 test import ( "fmt" @@ -42,7 +42,7 @@ func ResetStatsValue[T prometheus.Gauge](t *testing.T, collector T) { }) } -func assertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta float64) { +func AssertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta float64) { t.Helper() ch := make(chan *prometheus.Desc, 1) @@ -58,7 +58,7 @@ func assertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta }) } -func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64) { // nolint:unused +func CheckStatsValue(t *testing.T, collector prometheus.Collector, value float64) { // nolint:unused // Make sure test is not executed with "t.Parallel()" t.Setenv("PARALLEL_CHECK", "1") @@ -92,7 +92,7 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 } } -func collectAndLint(t *testing.T, collectors ...prometheus.Collector) { +func CollectAndLint(t *testing.T, collectors ...prometheus.Collector) { assert := assert.New(t) for _, collector := range collectors { problems, err := testutil.CollectAndLint(collector) diff --git a/api_proxy.go b/proxy/api.go similarity index 72% rename from api_proxy.go rename to proxy/api.go index b551cee..1cfad5d 100644 --- a/api_proxy.go +++ b/proxy/api.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 proxy import ( "encoding/json" @@ -30,9 +30,10 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) -type ProxyClientMessage struct { +type ClientMessage struct { json.Marshaler json.Unmarshaler @@ -43,16 +44,16 @@ type ProxyClientMessage struct { Type string `json:"type"` // Filled for type "hello" - Hello *HelloProxyClientMessage `json:"hello,omitempty"` + Hello *HelloClientMessage `json:"hello,omitempty"` - Bye *ByeProxyClientMessage `json:"bye,omitempty"` + Bye *ByeClientMessage `json:"bye,omitempty"` - Command *CommandProxyClientMessage `json:"command,omitempty"` + Command *CommandClientMessage `json:"command,omitempty"` - Payload *PayloadProxyClientMessage `json:"payload,omitempty"` + Payload *PayloadClientMessage `json:"payload,omitempty"` } -func (m *ProxyClientMessage) 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) @@ -60,7 +61,7 @@ func (m *ProxyClientMessage) String() string { return string(data) } -func (m *ProxyClientMessage) CheckValid() error { +func (m *ClientMessage) CheckValid() error { switch m.Type { case "": return errors.New("type missing") @@ -93,20 +94,20 @@ func (m *ProxyClientMessage) CheckValid() error { return nil } -func (m *ProxyClientMessage) NewErrorServerMessage(e *api.Error) *ProxyServerMessage { - return &ProxyServerMessage{ +func (m *ClientMessage) NewErrorServerMessage(e *api.Error) *ServerMessage { + return &ServerMessage{ Id: m.Id, Type: "error", Error: e, } } -func (m *ProxyClientMessage) NewWrappedErrorServerMessage(e error) *ProxyServerMessage { +func (m *ClientMessage) NewWrappedErrorServerMessage(e error) *ServerMessage { return m.NewErrorServerMessage(api.NewError("internal_error", e.Error())) } -// ProxyServerMessage is a message that is sent from the server to a client. -type ProxyServerMessage struct { +// ServerMessage is a message that is sent from the server to a client. +type ServerMessage struct { json.Marshaler json.Unmarshaler @@ -116,18 +117,18 @@ type ProxyServerMessage struct { Error *api.Error `json:"error,omitempty"` - Hello *HelloProxyServerMessage `json:"hello,omitempty"` + Hello *HelloServerMessage `json:"hello,omitempty"` - Bye *ByeProxyServerMessage `json:"bye,omitempty"` + Bye *ByeServerMessage `json:"bye,omitempty"` - Command *CommandProxyServerMessage `json:"command,omitempty"` + Command *CommandServerMessage `json:"command,omitempty"` - Payload *PayloadProxyServerMessage `json:"payload,omitempty"` + Payload *PayloadServerMessage `json:"payload,omitempty"` - Event *EventProxyServerMessage `json:"event,omitempty"` + Event *EventServerMessage `json:"event,omitempty"` } -func (r *ProxyServerMessage) String() string { +func (r *ServerMessage) String() string { data, err := json.Marshal(r) if err != nil { return fmt.Sprintf("Could not serialize %#v: %s", r, err) @@ -135,7 +136,7 @@ func (r *ProxyServerMessage) String() string { return string(data) } -func (r *ProxyServerMessage) CloseAfterSend(session api.RoomAware) bool { +func (r *ServerMessage) CloseAfterSend(session api.RoomAware) bool { switch r.Type { case "bye": return true @@ -150,7 +151,7 @@ type TokenClaims struct { jwt.RegisteredClaims } -type HelloProxyClientMessage struct { +type HelloClientMessage struct { Version string `json:"version"` ResumeId api.PublicSessionId `json:"resumeid"` @@ -161,7 +162,7 @@ type HelloProxyClientMessage struct { Token string `json:"token"` } -func (m *HelloProxyClientMessage) CheckValid() error { +func (m *HelloClientMessage) CheckValid() error { if m.Version != api.HelloVersionV1 { return fmt.Errorf("unsupported hello version: %s", m.Version) } @@ -173,7 +174,7 @@ func (m *HelloProxyClientMessage) CheckValid() error { return nil } -type HelloProxyServerMessage struct { +type HelloServerMessage struct { Version string `json:"version"` SessionId api.PublicSessionId `json:"sessionid"` @@ -182,44 +183,34 @@ type HelloProxyServerMessage struct { // Type "bye" -type ByeProxyClientMessage struct { +type ByeClientMessage struct { } -func (m *ByeProxyClientMessage) CheckValid() error { +func (m *ByeClientMessage) CheckValid() error { // No additional validation required. return nil } -type ByeProxyServerMessage struct { +type ByeServerMessage struct { Reason string `json:"reason"` } // Type "command" -type NewPublisherSettings struct { - Bitrate api.Bandwidth `json:"bitrate,omitempty"` - MediaTypes MediaType `json:"mediatypes,omitempty"` - - AudioCodec string `json:"audiocodec,omitempty"` - VideoCodec string `json:"videocodec,omitempty"` - VP9Profile string `json:"vp9_profile,omitempty"` - H264Profile string `json:"h264_profile,omitempty"` -} - -type CommandProxyClientMessage struct { +type CommandClientMessage struct { Type string `json:"type"` Sid string `json:"sid,omitempty"` - StreamType StreamType `json:"streamType,omitempty"` + StreamType sfu.StreamType `json:"streamType,omitempty"` PublisherId api.PublicSessionId `json:"publisherId,omitempty"` ClientId string `json:"clientId,omitempty"` // Deprecated: use PublisherSettings instead. Bitrate api.Bandwidth `json:"bitrate,omitempty"` // Deprecated: use PublisherSettings instead. - MediaTypes MediaType `json:"mediatypes,omitempty"` + MediaTypes sfu.MediaType `json:"mediatypes,omitempty"` - PublisherSettings *NewPublisherSettings `json:"publisherSettings,omitempty"` + PublisherSettings *sfu.NewPublisherSettings `json:"publisherSettings,omitempty"` RemoteUrl string `json:"remoteUrl,omitempty"` remoteUrl *url.URL @@ -230,7 +221,7 @@ type CommandProxyClientMessage struct { RtcpPort int `json:"rtcpPort,omitempty"` } -func (m *CommandProxyClientMessage) CheckValid() error { +func (m *CommandClientMessage) CheckValid() error { switch m.Type { case "": return errors.New("type missing") @@ -266,18 +257,18 @@ func (m *CommandProxyClientMessage) CheckValid() error { return nil } -type CommandProxyServerMessage struct { +type CommandServerMessage struct { Id string `json:"id,omitempty"` Sid string `json:"sid,omitempty"` Bitrate api.Bandwidth `json:"bitrate,omitempty"` - Streams []PublisherStream `json:"streams,omitempty"` + Streams []sfu.PublisherStream `json:"streams,omitempty"` } // Type "payload" -type PayloadProxyClientMessage struct { +type PayloadClientMessage struct { Type string `json:"type"` ClientId string `json:"clientId"` @@ -285,7 +276,7 @@ type PayloadProxyClientMessage struct { Payload api.StringMap `json:"payload,omitempty"` } -func (m *PayloadProxyClientMessage) CheckValid() error { +func (m *PayloadClientMessage) CheckValid() error { switch m.Type { case "": return errors.New("type missing") @@ -308,7 +299,7 @@ func (m *PayloadProxyClientMessage) CheckValid() error { return nil } -type PayloadProxyServerMessage struct { +type PayloadServerMessage struct { Type string `json:"type"` ClientId string `json:"clientId"` @@ -317,7 +308,7 @@ type PayloadProxyServerMessage struct { // Type "event" -type EventProxyServerBandwidth struct { +type EventServerBandwidth struct { // Incoming is the bandwidth utilization for publishers in percent. Incoming *float64 `json:"incoming,omitempty"` // Outgoing is the bandwidth utilization for subscribers in percent. @@ -329,7 +320,7 @@ type EventProxyServerBandwidth struct { Sent api.Bandwidth `json:"sent,omitempty"` } -func (b *EventProxyServerBandwidth) String() string { +func (b *EventServerBandwidth) String() string { switch { case b.Incoming != nil && b.Outgoing != nil: return fmt.Sprintf("bandwidth: incoming=%.3f%%, outgoing=%.3f%%", *b.Incoming, *b.Outgoing) @@ -342,31 +333,31 @@ func (b *EventProxyServerBandwidth) String() string { } } -func (b EventProxyServerBandwidth) AllowIncoming() bool { +func (b EventServerBandwidth) AllowIncoming() bool { return b.Incoming == nil || *b.Incoming < 100 } -func (b EventProxyServerBandwidth) AllowOutgoing() bool { +func (b EventServerBandwidth) AllowOutgoing() bool { return b.Outgoing == nil || *b.Outgoing < 100 } -type EventProxyServerMessage struct { +type EventServerMessage struct { Type string `json:"type"` ClientId string `json:"clientId,omitempty"` Load uint64 `json:"load,omitempty"` Sid string `json:"sid,omitempty"` - Bandwidth *EventProxyServerBandwidth `json:"bandwidth,omitempty"` + Bandwidth *EventServerBandwidth `json:"bandwidth,omitempty"` } // Information on a proxy in the etcd cluster. -type ProxyInformationEtcd struct { +type InformationEtcd struct { Address string `json:"address"` } -func (p *ProxyInformationEtcd) CheckValid() error { +func (p *InformationEtcd) CheckValid() error { if p.Address == "" { return errors.New("address missing") } diff --git a/api_proxy_easyjson.go b/proxy/api_easyjson.go similarity index 73% rename from api_proxy_easyjson.go rename to proxy/api_easyjson.go index c79385b..820e366 100644 --- a/api_proxy_easyjson.go +++ b/proxy/api_easyjson.go @@ -1,6 +1,6 @@ // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package signaling +package proxy import ( json "encoding/json" @@ -9,6 +9,7 @@ import ( jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" api "github.com/strukturag/nextcloud-spreed-signaling/api" + sfu "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) // suppress unused package warning @@ -19,7 +20,7 @@ var ( _ easyjson.Marshaler ) -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexer.Lexer, out *TokenClaims) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(in *jlexer.Lexer, out *TokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -117,7 +118,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwriter.Writer, in TokenClaims) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(out *jwriter.Writer, in TokenClaims) { out.RawByte('{') first := true _ = first @@ -193,27 +194,27 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwri // MarshalJSON supports json.Marshaler interface func (v TokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlexer.Lexer, out *ProxyServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(in *jlexer.Lexer, out *ServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -259,7 +260,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex out.Hello = nil } else { if out.Hello == nil { - out.Hello = new(HelloProxyServerMessage) + out.Hello = new(HelloServerMessage) } if in.IsNull() { in.Skip() @@ -273,7 +274,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex out.Bye = nil } else { if out.Bye == nil { - out.Bye = new(ByeProxyServerMessage) + out.Bye = new(ByeServerMessage) } if in.IsNull() { in.Skip() @@ -287,7 +288,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex out.Command = nil } else { if out.Command == nil { - out.Command = new(CommandProxyServerMessage) + out.Command = new(CommandServerMessage) } if in.IsNull() { in.Skip() @@ -301,7 +302,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex out.Payload = nil } else { if out.Payload == nil { - out.Payload = new(PayloadProxyServerMessage) + out.Payload = new(PayloadServerMessage) } if in.IsNull() { in.Skip() @@ -315,7 +316,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex out.Event = nil } else { if out.Event == nil { - out.Event = new(EventProxyServerMessage) + out.Event = new(EventServerMessage) } if in.IsNull() { in.Skip() @@ -333,7 +334,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwriter.Writer, in ProxyServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(out *jwriter.Writer, in ServerMessage) { out.RawByte('{') first := true _ = first @@ -387,252 +388,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwr } // MarshalJSON supports json.Marshaler interface -func (v ProxyServerMessage) MarshalJSON() ([]byte, error) { +func (v ServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v ProxyServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling1(w, v) +func (v ServerMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *ProxyServerMessage) UnmarshalJSON(data []byte) error { +func (v *ServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ProxyServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling1(l, v) +func (v *ServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *ProxyInformationEtcd) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "address": - if in.IsNull() { - in.Skip() - } else { - out.Address = string(in.String()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in ProxyInformationEtcd) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"address\":" - out.RawString(prefix[1:]) - out.String(string(in.Address)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v ProxyInformationEtcd) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v ProxyInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *ProxyInformationEtcd) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ProxyInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v) -} -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(in *jlexer.Lexer, out *ProxyClientMessage) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "id": - if in.IsNull() { - in.Skip() - } else { - out.Id = string(in.String()) - } - case "type": - if in.IsNull() { - in.Skip() - } else { - out.Type = string(in.String()) - } - case "hello": - if in.IsNull() { - in.Skip() - out.Hello = nil - } else { - if out.Hello == nil { - out.Hello = new(HelloProxyClientMessage) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Hello).UnmarshalEasyJSON(in) - } - } - case "bye": - if in.IsNull() { - in.Skip() - out.Bye = nil - } else { - if out.Bye == nil { - out.Bye = new(ByeProxyClientMessage) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Bye).UnmarshalEasyJSON(in) - } - } - case "command": - if in.IsNull() { - in.Skip() - out.Command = nil - } else { - if out.Command == nil { - out.Command = new(CommandProxyClientMessage) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Command).UnmarshalEasyJSON(in) - } - } - case "payload": - if in.IsNull() { - in.Skip() - out.Payload = nil - } else { - if out.Payload == nil { - out.Payload = new(PayloadProxyClientMessage) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Payload).UnmarshalEasyJSON(in) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling3(out *jwriter.Writer, in ProxyClientMessage) { - out.RawByte('{') - first := true - _ = first - if in.Id != "" { - const prefix string = ",\"id\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Id)) - } - { - const prefix string = ",\"type\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Type)) - } - if in.Hello != nil { - const prefix string = ",\"hello\":" - out.RawString(prefix) - (*in.Hello).MarshalEasyJSON(out) - } - if in.Bye != nil { - const prefix string = ",\"bye\":" - out.RawString(prefix) - (*in.Bye).MarshalEasyJSON(out) - } - if in.Command != nil { - const prefix string = ",\"command\":" - out.RawString(prefix) - (*in.Command).MarshalEasyJSON(out) - } - if in.Payload != nil { - const prefix string = ",\"payload\":" - out.RawString(prefix) - (*in.Payload).MarshalEasyJSON(out) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v ProxyClientMessage) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling3(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v ProxyClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling3(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *ProxyClientMessage) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ProxyClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling3(l, v) -} -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlexer.Lexer, out *PayloadProxyServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(in *jlexer.Lexer, out *PayloadServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -690,7 +468,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwriter.Writer, in PayloadProxyServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(out *jwriter.Writer, in PayloadServerMessage) { out.RawByte('{') first := true _ = first @@ -735,29 +513,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling4(out *jwr } // MarshalJSON supports json.Marshaler interface -func (v PayloadProxyServerMessage) MarshalJSON() ([]byte, error) { +func (v PayloadServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling4(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v PayloadProxyServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling4(w, v) +func (v PayloadServerMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *PayloadProxyServerMessage) UnmarshalJSON(data []byte) error { +func (v *PayloadServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *PayloadProxyServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling4(l, v) +func (v *PayloadServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlexer.Lexer, out *PayloadProxyClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(in *jlexer.Lexer, out *PayloadClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -825,7 +603,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwriter.Writer, in PayloadProxyClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(out *jwriter.Writer, in PayloadClientMessage) { out.RawByte('{') first := true _ = first @@ -873,29 +651,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling5(out *jwr } // MarshalJSON supports json.Marshaler interface -func (v PayloadProxyClientMessage) MarshalJSON() ([]byte, error) { +func (v PayloadClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling5(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v PayloadProxyClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling5(w, v) +func (v PayloadClientMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *PayloadProxyClientMessage) UnmarshalJSON(data []byte) error { +func (v *PayloadClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *PayloadProxyClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling5(l, v) +func (v *PayloadClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlexer.Lexer, out *NewPublisherSettings) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(in *jlexer.Lexer, out *InformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -909,41 +687,11 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex key := in.UnsafeFieldName(false) in.WantColon() switch key { - case "bitrate": + case "address": if in.IsNull() { in.Skip() } else { - out.Bitrate = api.Bandwidth(in.Uint64()) - } - case "mediatypes": - if in.IsNull() { - in.Skip() - } else { - out.MediaTypes = MediaType(in.Int()) - } - case "audiocodec": - if in.IsNull() { - in.Skip() - } else { - out.AudioCodec = string(in.String()) - } - case "videocodec": - if in.IsNull() { - in.Skip() - } else { - out.VideoCodec = string(in.String()) - } - case "vp9_profile": - if in.IsNull() { - in.Skip() - } else { - out.VP9Profile = string(in.String()) - } - case "h264_profile": - if in.IsNull() { - in.Skip() - } else { - out.H264Profile = string(in.String()) + out.Address = string(in.String()) } default: in.SkipRecursive() @@ -955,93 +703,42 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling6(out *jwriter.Writer, in NewPublisherSettings) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(out *jwriter.Writer, in InformationEtcd) { out.RawByte('{') first := true _ = first - if in.Bitrate != 0 { - const prefix string = ",\"bitrate\":" - first = false + { + const prefix string = ",\"address\":" out.RawString(prefix[1:]) - out.Uint64(uint64(in.Bitrate)) - } - if in.MediaTypes != 0 { - const prefix string = ",\"mediatypes\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int(int(in.MediaTypes)) - } - if in.AudioCodec != "" { - const prefix string = ",\"audiocodec\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.AudioCodec)) - } - if in.VideoCodec != "" { - const prefix string = ",\"videocodec\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.VideoCodec)) - } - if in.VP9Profile != "" { - const prefix string = ",\"vp9_profile\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.VP9Profile)) - } - if in.H264Profile != "" { - const prefix string = ",\"h264_profile\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.H264Profile)) + out.String(string(in.Address)) } out.RawByte('}') } // MarshalJSON supports json.Marshaler interface -func (v NewPublisherSettings) MarshalJSON() ([]byte, error) { +func (v InformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling6(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v NewPublisherSettings) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling6(w, v) +func (v InformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *NewPublisherSettings) UnmarshalJSON(data []byte) error { +func (v *InformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *NewPublisherSettings) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) +func (v *InformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *HelloProxyServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(in *jlexer.Lexer, out *HelloServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1091,7 +788,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in HelloProxyServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(out *jwriter.Writer, in HelloServerMessage) { out.RawByte('{') first := true _ = first @@ -1114,29 +811,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr } // MarshalJSON supports json.Marshaler interface -func (v HelloProxyServerMessage) MarshalJSON() ([]byte, error) { +func (v HelloServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v HelloProxyServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) +func (v HelloServerMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *HelloProxyServerMessage) UnmarshalJSON(data []byte) error { +func (v *HelloServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *HelloProxyServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) +func (v *HelloServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *HelloProxyClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(in *jlexer.Lexer, out *HelloClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1205,7 +902,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in HelloProxyClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(out *jwriter.Writer, in HelloClientMessage) { out.RawByte('{') first := true _ = first @@ -1242,29 +939,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr } // MarshalJSON supports json.Marshaler interface -func (v HelloProxyClientMessage) MarshalJSON() ([]byte, error) { +func (v HelloClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v HelloProxyClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) +func (v HelloClientMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *HelloProxyClientMessage) UnmarshalJSON(data []byte) error { +func (v *HelloClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *HelloProxyClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) +func (v *HelloClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *EventProxyServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1308,7 +1005,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex out.Bandwidth = nil } else { if out.Bandwidth == nil { - out.Bandwidth = new(EventProxyServerBandwidth) + out.Bandwidth = new(EventServerBandwidth) } if in.IsNull() { in.Skip() @@ -1326,7 +1023,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in EventProxyServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -1359,29 +1056,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr } // MarshalJSON supports json.Marshaler interface -func (v EventProxyServerMessage) MarshalJSON() ([]byte, error) { +func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v EventProxyServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) +func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *EventProxyServerMessage) UnmarshalJSON(data []byte) error { +func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *EventProxyServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) +func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *EventProxyServerBandwidth) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(in *jlexer.Lexer, out *EventServerBandwidth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1445,7 +1142,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in EventProxyServerBandwidth) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(out *jwriter.Writer, in EventServerBandwidth) { out.RawByte('{') first := true _ = first @@ -1489,29 +1186,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw } // MarshalJSON supports json.Marshaler interface -func (v EventProxyServerBandwidth) MarshalJSON() ([]byte, error) { +func (v EventServerBandwidth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v EventProxyServerBandwidth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) +func (v EventServerBandwidth) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *EventProxyServerBandwidth) UnmarshalJSON(data []byte) error { +func (v *EventServerBandwidth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *EventProxyServerBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) +func (v *EventServerBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *CommandProxyServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(in *jlexer.Lexer, out *CommandServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1551,16 +1248,16 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Delim('[') if out.Streams == nil { if !in.IsDelim(']') { - out.Streams = make([]PublisherStream, 0, 0) + out.Streams = make([]sfu.PublisherStream, 0, 0) } else { - out.Streams = []PublisherStream{} + out.Streams = []sfu.PublisherStream{} } } else { out.Streams = (out.Streams)[:0] } for !in.IsDelim(']') { - var v8 PublisherStream - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in, &v8) + var v8 sfu.PublisherStream + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu(in, &v8) out.Streams = append(out.Streams, v8) in.WantComma() } @@ -1576,7 +1273,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in CommandProxyServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(out *jwriter.Writer, in CommandServerMessage) { out.RawByte('{') first := true _ = first @@ -1620,7 +1317,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw if v9 > 0 { out.RawByte(',') } - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out, v10) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu(out, v10) } out.RawByte(']') } @@ -1629,29 +1326,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw } // MarshalJSON supports json.Marshaler interface -func (v CommandProxyServerMessage) MarshalJSON() ([]byte, error) { +func (v CommandServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v CommandProxyServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) +func (v CommandServerMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *CommandProxyServerMessage) UnmarshalJSON(data []byte) error { +func (v *CommandServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *CommandProxyServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) +func (v *CommandServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *PublisherStream) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu(in *jlexer.Lexer, out *sfu.PublisherStream) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1765,7 +1462,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in PublisherStream) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu(out *jwriter.Writer, in sfu.PublisherStream) { out.RawByte('{') first := true _ = first @@ -1846,7 +1543,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw } out.RawByte('}') } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *CommandProxyClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(in *jlexer.Lexer, out *CommandClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1876,7 +1573,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle if in.IsNull() { in.Skip() } else { - out.StreamType = StreamType(in.String()) + out.StreamType = sfu.StreamType(in.String()) } case "publisherId": if in.IsNull() { @@ -1900,7 +1597,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle if in.IsNull() { in.Skip() } else { - out.MediaTypes = MediaType(in.Int()) + out.MediaTypes = sfu.MediaType(in.Int()) } case "publisherSettings": if in.IsNull() { @@ -1908,13 +1605,9 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle out.PublisherSettings = nil } else { if out.PublisherSettings == nil { - out.PublisherSettings = new(NewPublisherSettings) - } - if in.IsNull() { - in.Skip() - } else { - (*out.PublisherSettings).UnmarshalEasyJSON(in) + out.PublisherSettings = new(sfu.NewPublisherSettings) } + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu1(in, out.PublisherSettings) } case "remoteUrl": if in.IsNull() { @@ -1956,7 +1649,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in CommandProxyClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(out *jwriter.Writer, in CommandClientMessage) { out.RawByte('{') first := true _ = first @@ -1998,7 +1691,7 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw if in.PublisherSettings != nil { const prefix string = ",\"publisherSettings\":" out.RawString(prefix) - (*in.PublisherSettings).MarshalEasyJSON(out) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu1(out, *in.PublisherSettings) } if in.RemoteUrl != "" { const prefix string = ",\"remoteUrl\":" @@ -2029,29 +1722,309 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw } // MarshalJSON supports json.Marshaler interface -func (v CommandProxyClientMessage) MarshalJSON() ([]byte, error) { +func (v CommandClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v CommandProxyClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) +func (v CommandClientMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *CommandProxyClientMessage) UnmarshalJSON(data []byte) error { +func (v *CommandClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *CommandProxyClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) +func (v *CommandClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *ByeProxyServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu1(in *jlexer.Lexer, out *sfu.NewPublisherSettings) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "bitrate": + if in.IsNull() { + in.Skip() + } else { + out.Bitrate = api.Bandwidth(in.Uint64()) + } + case "mediatypes": + if in.IsNull() { + in.Skip() + } else { + out.MediaTypes = sfu.MediaType(in.Int()) + } + case "audiocodec": + if in.IsNull() { + in.Skip() + } else { + out.AudioCodec = string(in.String()) + } + case "videocodec": + if in.IsNull() { + in.Skip() + } else { + out.VideoCodec = string(in.String()) + } + case "vp9_profile": + if in.IsNull() { + in.Skip() + } else { + out.VP9Profile = string(in.String()) + } + case "h264_profile": + if in.IsNull() { + in.Skip() + } else { + out.H264Profile = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu1(out *jwriter.Writer, in sfu.NewPublisherSettings) { + out.RawByte('{') + first := true + _ = first + if in.Bitrate != 0 { + const prefix string = ",\"bitrate\":" + first = false + out.RawString(prefix[1:]) + out.Uint64(uint64(in.Bitrate)) + } + if in.MediaTypes != 0 { + const prefix string = ",\"mediatypes\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int(int(in.MediaTypes)) + } + if in.AudioCodec != "" { + const prefix string = ",\"audiocodec\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.AudioCodec)) + } + if in.VideoCodec != "" { + const prefix string = ",\"videocodec\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.VideoCodec)) + } + if in.VP9Profile != "" { + const prefix string = ",\"vp9_profile\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.VP9Profile)) + } + if in.H264Profile != "" { + const prefix string = ",\"h264_profile\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.H264Profile)) + } + out.RawByte('}') +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(in *jlexer.Lexer, out *ClientMessage) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "id": + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } + case "type": + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } + case "hello": + if in.IsNull() { + in.Skip() + out.Hello = nil + } else { + if out.Hello == nil { + out.Hello = new(HelloClientMessage) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Hello).UnmarshalEasyJSON(in) + } + } + case "bye": + if in.IsNull() { + in.Skip() + out.Bye = nil + } else { + if out.Bye == nil { + out.Bye = new(ByeClientMessage) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Bye).UnmarshalEasyJSON(in) + } + } + case "command": + if in.IsNull() { + in.Skip() + out.Command = nil + } else { + if out.Command == nil { + out.Command = new(CommandClientMessage) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Command).UnmarshalEasyJSON(in) + } + } + case "payload": + if in.IsNull() { + in.Skip() + out.Payload = nil + } else { + if out.Payload == nil { + out.Payload = new(PayloadClientMessage) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Payload).UnmarshalEasyJSON(in) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(out *jwriter.Writer, in ClientMessage) { + out.RawByte('{') + first := true + _ = first + if in.Id != "" { + const prefix string = ",\"id\":" + first = false + out.RawString(prefix[1:]) + out.String(string(in.Id)) + } + { + const prefix string = ",\"type\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Type)) + } + if in.Hello != nil { + const prefix string = ",\"hello\":" + out.RawString(prefix) + (*in.Hello).MarshalEasyJSON(out) + } + if in.Bye != nil { + const prefix string = ",\"bye\":" + out.RawString(prefix) + (*in.Bye).MarshalEasyJSON(out) + } + if in.Command != nil { + const prefix string = ",\"command\":" + out.RawString(prefix) + (*in.Command).MarshalEasyJSON(out) + } + if in.Payload != nil { + const prefix string = ",\"payload\":" + out.RawString(prefix) + (*in.Payload).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ClientMessage) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ClientMessage) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2081,7 +2054,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in ByeProxyServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -2094,29 +2067,29 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw } // MarshalJSON supports json.Marshaler interface -func (v ByeProxyServerMessage) MarshalJSON() ([]byte, error) { +func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v ByeProxyServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) +func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *ByeProxyServerMessage) UnmarshalJSON(data []byte) error { +func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ByeProxyServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) +func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(l, v) } -func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *ByeProxyClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2140,7 +2113,7 @@ func easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in ByeProxyClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -2148,25 +2121,25 @@ func easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw } // MarshalJSON supports json.Marshaler interface -func (v ByeProxyClientMessage) MarshalJSON() ([]byte, error) { +func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v ByeProxyClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1c8542dbEncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) +func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *ByeProxyClientMessage) UnmarshalJSON(data []byte) error { +func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ByeProxyClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1c8542dbDecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) +func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(l, v) } diff --git a/remotesession.go b/remotesession.go index 8d71bc3..2b56333 100644 --- a/remotesession.go +++ b/remotesession.go @@ -30,6 +30,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -37,13 +38,13 @@ type RemoteSession struct { logger log.Logger hub *Hub client *Client - remoteClient *GrpcClient + remoteClient *grpc.Client sessionId api.PublicSessionId - proxy atomic.Pointer[SessionProxy] + proxy atomic.Pointer[grpc.SessionProxy] } -func NewRemoteSession(hub *Hub, client *Client, remoteClient *GrpcClient, sessionId api.PublicSessionId) (*RemoteSession, error) { +func NewRemoteSession(hub *Hub, client *Client, remoteClient *grpc.Client, sessionId api.PublicSessionId) (*RemoteSession, error) { remoteSession := &RemoteSession{ logger: hub.logger, hub: hub, @@ -86,7 +87,7 @@ func (s *RemoteSession) Start(message *api.ClientMessage) error { return s.sendMessage(message) } -func (s *RemoteSession) OnProxyMessage(msg *ServerSessionMessage) error { +func (s *RemoteSession) OnProxyMessage(msg *grpc.ServerSessionMessage) error { var message *api.ServerMessage if err := json.Unmarshal(msg.Message, &message); err != nil { return err @@ -116,7 +117,7 @@ func (s *RemoteSession) sendProxyMessage(message []byte) error { return errors.New("proxy already closed") } - msg := &ClientSessionMessage{ + msg := &grpc.ClientSessionMessage{ Message: message, } return proxy.Send(msg) diff --git a/room.go b/room.go index da84c91..268fbfa 100644 --- a/room.go +++ b/room.go @@ -37,6 +37,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" @@ -100,7 +101,7 @@ type Room struct { // Timestamps of last backend requests for the different types. lastRoomRequests map[string]int64 - transientData *TransientData + transientData *api.TransientData } func getRoomIdForBackend(id string, backend *talk.Backend) string { @@ -138,7 +139,7 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, asyncEvents ev lastRoomRequests: make(map[string]int64), - transientData: NewTransientData(), + transientData: api.NewTransientData(), } if err := asyncEvents.RegisterBackendRoomListener(roomId, backend, room); err != nil { @@ -523,7 +524,7 @@ func (r *Room) RemoveSession(session Session) bool { delete(r.roomSessionData, sid) if len(r.sessions) > 0 { r.mu.Unlock() - if err := r.RemoveTransientData(TransientSessionDataPrefix + string(sid)); err != nil { + if err := r.RemoveTransientData(api.TransientSessionDataPrefix + string(sid)); err != nil { r.logger.Printf("Error removing transient data for session %s", sid) } r.PublishSessionLeft(session) @@ -537,7 +538,7 @@ func (r *Room) RemoveSession(session Session) bool { r.unsubscribeBackend() r.doClose() r.mu.Unlock() - if err := r.RemoveTransientData(TransientSessionDataPrefix + string(sid)); err != nil { + if err := r.RemoveTransientData(api.TransientSessionDataPrefix + string(sid)); err != nil { r.logger.Printf("Error removing transient data for session %s", sid) } // Still need to publish an event so sessions on other servers get notified. @@ -641,7 +642,7 @@ func (r *Room) PublishSessionLeft(session Session) { } // +checklocksread:r.mu -func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSessionId]*InternalSessionData, virtual map[api.PublicSessionId]*VirtualSessionData) { +func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSessionId]*grpc.InternalSessionData, virtual map[api.PublicSessionId]*grpc.VirtualSessionData) { if r.hub.rpcClients == nil { return nil, nil } @@ -656,7 +657,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSes var wg sync.WaitGroup for _, client := range r.hub.rpcClients.GetClients() { wg.Add(1) - go func(c *GrpcClient) { + go func(c *grpc.Client) { defer wg.Done() clientInternal, clientVirtual, err := c.GetInternalSessions(ctx, r.Id(), r.Backend().Urls()) @@ -668,11 +669,11 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSes mu.Lock() defer mu.Unlock() if internal == nil { - internal = make(map[api.PublicSessionId]*InternalSessionData, len(clientInternal)) + internal = make(map[api.PublicSessionId]*grpc.InternalSessionData, len(clientInternal)) } maps.Copy(internal, clientInternal) if virtual == nil { - virtual = make(map[api.PublicSessionId]*VirtualSessionData, len(clientVirtual)) + virtual = make(map[api.PublicSessionId]*grpc.VirtualSessionData, len(clientVirtual)) } maps.Copy(virtual, clientVirtual) }(client) @@ -1321,13 +1322,13 @@ func (r *Room) fetchInitialTransientData() { var wg sync.WaitGroup var mu sync.Mutex // +checklocks:mu - var initial TransientDataEntries + var initial api.TransientDataEntries for _, client := range r.hub.rpcClients.GetClients() { wg.Add(1) - go func(c *GrpcClient) { + go func(c *grpc.Client) { defer wg.Done() - data, err := c.GetTransientData(ctx, r) + data, err := c.GetTransientData(ctx, r.Id(), r.Backend()) if err != nil { r.logger.Printf("Received error while getting transient data for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) return @@ -1339,7 +1340,7 @@ func (r *Room) fetchInitialTransientData() { mu.Lock() defer mu.Unlock() if initial == nil { - initial = make(TransientDataEntries) + initial = make(api.TransientDataEntries) } maps.Copy(initial, data) }(client) diff --git a/roomsessions_builtin.go b/roomsessions_builtin.go index cb85f7c..2a416e7 100644 --- a/roomsessions_builtin.go +++ b/roomsessions_builtin.go @@ -28,6 +28,7 @@ import ( "sync/atomic" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" ) @@ -38,10 +39,10 @@ type BuiltinRoomSessions struct { // +checklocks:mu roomSessionToSessionid map[api.RoomSessionId]api.PublicSessionId - clients *GrpcClients + clients *grpc.Clients } -func NewBuiltinRoomSessions(clients *GrpcClients) (RoomSessions, error) { +func NewBuiltinRoomSessions(clients *grpc.Clients) (RoomSessions, error) { return &BuiltinRoomSessions{ sessionIdToRoomSession: make(map[api.PublicSessionId]api.RoomSessionId), roomSessionToSessionid: make(map[api.RoomSessionId]api.PublicSessionId), @@ -122,7 +123,7 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId logger := log.LoggerFromContext(ctx) for _, client := range clients { wg.Add(1) - go func(client *GrpcClient) { + go func(client *grpc.Client) { defer wg.Done() sid, err := client.LookupSessionId(lookupctx, roomSessionId, disconnectReason) diff --git a/mcu_common.go b/sfu/common.go similarity index 54% rename from mcu_common.go rename to sfu/common.go index 0f5d6ab..ce92e16 100644 --- a/mcu_common.go +++ b/sfu/common.go @@ -19,33 +19,30 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package sfu import ( "context" "errors" - "sync/atomic" + "fmt" + "net" "time" "github.com/dlintw/goconf" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) const ( - McuTypeJanus = "janus" - McuTypeProxy = "proxy" + TypeJanus = "janus" + TypeProxy = "proxy" - McuTypeDefault = McuTypeJanus + TypeDefault = TypeJanus ) var ( - defaultMaxStreamBitrate = api.BandwidthFromMegabits(1) - defaultMaxScreenBitrate = api.BandwidthFromMegabits(2) - ErrNotConnected = errors.New("not connected") ) @@ -57,25 +54,25 @@ const ( MediaTypeScreen MediaType = 1 << 2 ) -type McuListener interface { +type Listener interface { PublicId() api.PublicSessionId - OnUpdateOffer(client McuClient, offer api.StringMap) + OnUpdateOffer(client Client, offer api.StringMap) - OnIceCandidate(client McuClient, candidate any) - OnIceCompleted(client McuClient) + OnIceCandidate(client Client, candidate any) + OnIceCompleted(client Client) - SubscriberSidUpdated(subscriber McuSubscriber) + SubscriberSidUpdated(subscriber Subscriber) - PublisherClosed(publisher McuPublisher) - SubscriberClosed(subscriber McuSubscriber) + PublisherClosed(publisher Publisher) + SubscriberClosed(subscriber Subscriber) } -type McuInitiator interface { +type Initiator interface { Country() geoip.Country } -type McuSettings interface { +type Settings interface { MaxStreamBitrate() api.Bandwidth MaxScreenBitrate() api.Bandwidth Timeout() time.Duration @@ -83,51 +80,17 @@ type McuSettings interface { Reload(config *goconf.ConfigFile) } -type mcuCommonSettings struct { - logger log.Logger +type NewPublisherSettings struct { + Bitrate api.Bandwidth `json:"bitrate,omitempty"` + MediaTypes MediaType `json:"mediatypes,omitempty"` - maxStreamBitrate api.AtomicBandwidth - maxScreenBitrate api.AtomicBandwidth - - timeout atomic.Int64 + AudioCodec string `json:"audiocodec,omitempty"` + VideoCodec string `json:"videocodec,omitempty"` + VP9Profile string `json:"vp9_profile,omitempty"` + H264Profile string `json:"h264_profile,omitempty"` } -func (s *mcuCommonSettings) MaxStreamBitrate() api.Bandwidth { - return s.maxStreamBitrate.Load() -} - -func (s *mcuCommonSettings) MaxScreenBitrate() api.Bandwidth { - return s.maxScreenBitrate.Load() -} - -func (s *mcuCommonSettings) Timeout() time.Duration { - return time.Duration(s.timeout.Load()) -} - -func (s *mcuCommonSettings) setTimeout(timeout time.Duration) { - s.timeout.Store(int64(timeout)) -} - -func (s *mcuCommonSettings) load(config *goconf.ConfigFile) error { - maxStreamBitrateValue, _ := config.GetInt("mcu", "maxstreambitrate") - if maxStreamBitrateValue <= 0 { - maxStreamBitrateValue = int(defaultMaxStreamBitrate.Bits()) - } - maxStreamBitrate := api.BandwidthFromBits(uint64(maxStreamBitrateValue)) - s.logger.Printf("Maximum bandwidth %s per publishing stream", maxStreamBitrate) - s.maxStreamBitrate.Store(maxStreamBitrate) - - maxScreenBitrateValue, _ := config.GetInt("mcu", "maxscreenbitrate") - if maxScreenBitrateValue <= 0 { - maxScreenBitrateValue = int(defaultMaxScreenBitrate.Bits()) - } - maxScreenBitrate := api.BandwidthFromBits(uint64(maxScreenBitrateValue)) - s.logger.Printf("Maximum bandwidth %s per screensharing stream", maxScreenBitrate) - s.maxScreenBitrate.Store(maxScreenBitrate) - return nil -} - -type Mcu interface { +type SFU interface { Start(ctx context.Context) error Stop() Reload(config *goconf.ConfigFile) @@ -139,8 +102,8 @@ type Mcu interface { GetServerInfoSfu() *talk.BackendServerInfoSfu GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) - NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) - NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) + NewPublisher(ctx context.Context, listener Listener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator Initiator) (Publisher, error) + NewSubscriber(ctx context.Context, listener Listener, publisher api.PublicSessionId, streamType StreamType, initiator Initiator) (Subscriber, error) } // PublisherStream contains the available properties when creating a @@ -175,14 +138,18 @@ type PublisherStream struct { type RemotePublisherController interface { PublisherId() api.PublicSessionId - StartPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error - StopPublishing(ctx context.Context, publisher McuRemotePublisherProperties) error + StartPublishing(ctx context.Context, publisher RemotePublisherProperties) error + StopPublishing(ctx context.Context, publisher RemotePublisherProperties) error GetStreams(ctx context.Context) ([]PublisherStream, error) } -type RemoteMcu interface { - NewRemotePublisher(ctx context.Context, listener McuListener, controller RemotePublisherController, streamType StreamType) (McuRemotePublisher, error) - NewRemoteSubscriber(ctx context.Context, listener McuListener, publisher McuRemotePublisher) (McuRemoteSubscriber, error) +type RemoteSfu interface { + NewRemotePublisher(ctx context.Context, listener Listener, controller RemotePublisherController, streamType StreamType) (RemotePublisher, error) + NewRemoteSubscriber(ctx context.Context, listener Listener, publisher RemotePublisher) (RemoteSubscriber, error) +} + +type WithToken interface { + CreateToken(subject string) (string, error) } type StreamType string @@ -206,14 +173,20 @@ func IsValidStreamType(s string) bool { } } -type McuClientBandwidthInfo struct { +type StreamId string + +func GetStreamId(publisherId api.PublicSessionId, streamType StreamType) StreamId { + return StreamId(fmt.Sprintf("%s|%s", publisherId, streamType)) +} + +type ClientBandwidthInfo struct { // Sent is the outgoing bandwidth. Sent api.Bandwidth // Received is the incoming bandwidth. Received api.Bandwidth } -type McuClient interface { +type Client interface { Id() string Sid() string StreamType() StreamType @@ -225,14 +198,14 @@ type McuClient interface { SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) } -type McuClientWithBandwidth interface { - McuClient +type ClientWithBandwidth interface { + Client - Bandwidth() *McuClientBandwidthInfo + Bandwidth() *ClientBandwidthInfo } -type McuPublisher interface { - McuClient +type Publisher interface { + Client PublisherId() api.PublicSessionId @@ -240,36 +213,48 @@ type McuPublisher interface { SetMedia(MediaType) } -type McuPublisherWithStreams interface { - McuPublisher +type PublisherWithStreams interface { + Publisher GetStreams(ctx context.Context) ([]PublisherStream, error) } -type McuRemoteAwarePublisher interface { - McuPublisher +type RemoteAwarePublisher interface { + Publisher PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error } -type McuSubscriber interface { - McuClient +type PublisherWithConnectionUrlAndIP interface { + Publisher + + GetConnectionURL() (string, net.IP) +} + +type Subscriber interface { + Client Publisher() api.PublicSessionId } -type McuRemotePublisherProperties interface { +type SubscriberWithConnectionUrlAndIP interface { + Subscriber + + GetConnectionURL() (string, net.IP) +} + +type RemotePublisherProperties interface { Port() int RtcpPort() int } -type McuRemotePublisher interface { - McuClient +type RemotePublisher interface { + Client - McuRemotePublisherProperties + RemotePublisherProperties } -type McuRemoteSubscriber interface { - McuSubscriber +type RemoteSubscriber interface { + Subscriber } diff --git a/sfu/internal/settings.go b/sfu/internal/settings.go new file mode 100644 index 0000000..7596105 --- /dev/null +++ b/sfu/internal/settings.go @@ -0,0 +1,81 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 internal + +import ( + "sync/atomic" + "time" + + "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +var ( + defaultMaxStreamBitrate = api.BandwidthFromMegabits(1) + defaultMaxScreenBitrate = api.BandwidthFromMegabits(2) +) + +type CommonSettings struct { + Logger log.Logger + + maxStreamBitrate api.AtomicBandwidth + maxScreenBitrate api.AtomicBandwidth + + timeout atomic.Int64 +} + +func (s *CommonSettings) MaxStreamBitrate() api.Bandwidth { + return s.maxStreamBitrate.Load() +} + +func (s *CommonSettings) MaxScreenBitrate() api.Bandwidth { + return s.maxScreenBitrate.Load() +} + +func (s *CommonSettings) Timeout() time.Duration { + return time.Duration(s.timeout.Load()) +} + +func (s *CommonSettings) SetTimeout(timeout time.Duration) { + s.timeout.Store(timeout.Nanoseconds()) +} + +func (s *CommonSettings) Load(config *goconf.ConfigFile) error { + maxStreamBitrateValue, _ := config.GetInt("mcu", "maxstreambitrate") + if maxStreamBitrateValue <= 0 { + maxStreamBitrateValue = int(defaultMaxStreamBitrate.Bits()) + } + maxStreamBitrate := api.BandwidthFromBits(uint64(maxStreamBitrateValue)) + s.Logger.Printf("Maximum bandwidth %s per publishing stream", maxStreamBitrate) + s.maxStreamBitrate.Store(maxStreamBitrate) + + maxScreenBitrateValue, _ := config.GetInt("mcu", "maxscreenbitrate") + if maxScreenBitrateValue <= 0 { + maxScreenBitrateValue = int(defaultMaxScreenBitrate.Bits()) + } + maxScreenBitrate := api.BandwidthFromBits(uint64(maxScreenBitrateValue)) + s.Logger.Printf("Maximum bandwidth %s per screensharing stream", maxScreenBitrate) + s.maxScreenBitrate.Store(maxScreenBitrate) + return nil +} diff --git a/sfu/internal/stats_prometheus.go b/sfu/internal/stats_prometheus.go new file mode 100644 index 0000000..2424296 --- /dev/null +++ b/sfu/internal/stats_prometheus.go @@ -0,0 +1,83 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 internal + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/strukturag/nextcloud-spreed-signaling/metrics" +) + +var ( + StatsPublishersCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "publishers", + Help: "The current number of publishers", + }, []string{"type"}) + StatsPublishersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "publishers_total", + Help: "The total number of created publishers", + }, []string{"type"}) + StatsSubscribersCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "subscribers", + Help: "The current number of subscribers", + }, []string{"type"}) + StatsSubscribersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "subscribers_total", + Help: "The total number of created subscribers", + }, []string{"type"}) + StatsWaitingForPublisherTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "nopublisher_total", + Help: "The total number of subscribe requests where no publisher exists", + }, []string{"type"}) + StatsMcuMessagesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "messages_total", + Help: "The total number of MCU messages", + }, []string{"type"}) + + commonMcuStats = []prometheus.Collector{ + StatsPublishersCurrent, + StatsPublishersTotal, + StatsSubscribersCurrent, + StatsSubscribersTotal, + StatsWaitingForPublisherTotal, + StatsMcuMessagesTotal, + } +) + +func RegisterCommonStats() { + metrics.RegisterAll(commonMcuStats...) +} + +func UnregisterCommonStats() { + metrics.UnregisterAll(commonMcuStats...) +} diff --git a/sfu/internal/stats_prometheus_test.go b/sfu/internal/stats_prometheus_test.go new file mode 100644 index 0000000..e10bdc6 --- /dev/null +++ b/sfu/internal/stats_prometheus_test.go @@ -0,0 +1,33 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 internal + +import ( + "testing" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" +) + +func TestCommonMcuStats(t *testing.T) { + t.Parallel() + test.CollectAndLint(t, commonMcuStats...) +} diff --git a/mcu_janus_client.go b/sfu/janus/client.go similarity index 68% rename from mcu_janus_client.go rename to sfu/janus/client.go index 1444bde..b9b4501 100644 --- a/mcu_janus_client.go +++ b/sfu/janus/client.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 janus import ( "context" @@ -28,29 +28,29 @@ import ( "sync" "sync/atomic" - "github.com/notedit/janus-go" - "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) -type mcuJanusClient struct { +type janusClient struct { logger log.Logger - mcu *mcuJanus - listener McuListener + mcu *janusSFU + listener sfu.Listener mu sync.Mutex id uint64 session uint64 roomId uint64 sid string - streamType StreamType + streamType sfu.StreamType maxBitrate api.Bandwidth // +checklocks:mu - bandwidth map[string]*McuClientBandwidthInfo + bandwidth map[string]*sfu.ClientBandwidthInfo - handle atomic.Pointer[JanusHandle] + handle atomic.Pointer[janus.Handle] handleId atomic.Uint64 closeChan chan struct{} deferred chan func() @@ -63,43 +63,43 @@ type mcuJanusClient struct { handleMedia func(event *janus.MediaMsg) } -func (c *mcuJanusClient) Id() string { +func (c *janusClient) Id() string { return strconv.FormatUint(c.id, 10) } -func (c *mcuJanusClient) Sid() string { +func (c *janusClient) Sid() string { return c.sid } -func (c *mcuJanusClient) Handle() uint64 { +func (c *janusClient) Handle() uint64 { return c.handleId.Load() } -func (c *mcuJanusClient) StreamType() StreamType { +func (c *janusClient) StreamType() sfu.StreamType { return c.streamType } -func (c *mcuJanusClient) MaxBitrate() api.Bandwidth { +func (c *janusClient) MaxBitrate() api.Bandwidth { return c.maxBitrate } -func (c *mcuJanusClient) Close(ctx context.Context) { +func (c *janusClient) Close(ctx context.Context) { } -func (c *mcuJanusClient) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { +func (c *janusClient) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { } -func (c *mcuJanusClient) UpdateBandwidth(media string, sent api.Bandwidth, received api.Bandwidth) { +func (c *janusClient) UpdateBandwidth(media string, sent api.Bandwidth, received api.Bandwidth) { c.mu.Lock() defer c.mu.Unlock() if c.bandwidth == nil { - c.bandwidth = make(map[string]*McuClientBandwidthInfo) + c.bandwidth = make(map[string]*sfu.ClientBandwidthInfo) } info, found := c.bandwidth[media] if !found { - info = &McuClientBandwidthInfo{} + info = &sfu.ClientBandwidthInfo{} c.bandwidth[media] = info } @@ -107,7 +107,7 @@ func (c *mcuJanusClient) UpdateBandwidth(media string, sent api.Bandwidth, recei info.Received = received } -func (c *mcuJanusClient) Bandwidth() *McuClientBandwidthInfo { +func (c *janusClient) Bandwidth() *sfu.ClientBandwidthInfo { c.mu.Lock() defer c.mu.Unlock() @@ -115,7 +115,7 @@ func (c *mcuJanusClient) Bandwidth() *McuClientBandwidthInfo { return nil } - result := &McuClientBandwidthInfo{} + result := &sfu.ClientBandwidthInfo{} for _, info := range c.bandwidth { result.Received += info.Received result.Sent += info.Sent @@ -123,11 +123,11 @@ func (c *mcuJanusClient) Bandwidth() *McuClientBandwidthInfo { return result } -func (c *mcuJanusClient) closeClient(ctx context.Context) bool { +func (c *janusClient) closeClient(ctx context.Context) bool { if handle := c.handle.Swap(nil); handle != nil { close(c.closeChan) if _, err := handle.Detach(ctx); err != nil { - if e, ok := err.(*janus.ErrorMsg); !ok || e.Err.Code != JANUS_ERROR_HANDLE_NOT_FOUND { + if e, ok := err.(*janus.ErrorMsg); !ok || e.Err.Code != janus.JANUS_ERROR_HANDLE_NOT_FOUND { c.logger.Println("Could not detach client", handle.Id, err) } } @@ -137,7 +137,7 @@ func (c *mcuJanusClient) closeClient(ctx context.Context) bool { return false } -func (c *mcuJanusClient) run(handle *JanusHandle, closeChan <-chan struct{}) { +func (c *janusClient) run(handle *janus.Handle, closeChan <-chan struct{}) { loop: for { select { @@ -155,7 +155,7 @@ loop: c.handleConnected(t) case *janus.SlowLinkMsg: c.handleSlowLink(t) - case *TrickleMsg: + case *janus.TrickleMsg: c.handleTrickle(t) default: c.logger.Println("Received unsupported event type", msg, reflect.TypeOf(msg)) @@ -168,10 +168,10 @@ loop: } } -func (c *mcuJanusClient) sendOffer(ctx context.Context, offer api.StringMap, callback func(error, api.StringMap)) { +func (c *janusClient) sendOffer(ctx context.Context, offer api.StringMap, callback func(error, api.StringMap)) { handle := c.handle.Load() if handle == nil { - callback(ErrNotConnected, nil) + callback(sfu.ErrNotConnected, nil) return } @@ -190,10 +190,10 @@ func (c *mcuJanusClient) sendOffer(ctx context.Context, offer api.StringMap, cal callback(nil, answer_msg.Jsep) } -func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer api.StringMap, callback func(error, api.StringMap)) { +func (c *janusClient) sendAnswer(ctx context.Context, answer api.StringMap, callback func(error, api.StringMap)) { handle := c.handle.Load() if handle == nil { - callback(ErrNotConnected, nil) + callback(sfu.ErrNotConnected, nil) return } @@ -211,10 +211,10 @@ func (c *mcuJanusClient) sendAnswer(ctx context.Context, answer api.StringMap, c callback(nil, nil) } -func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, api.StringMap)) { +func (c *janusClient) sendCandidate(ctx context.Context, candidate any, callback func(error, api.StringMap)) { handle := c.handle.Load() if handle == nil { - callback(ErrNotConnected, nil) + callback(sfu.ErrNotConnected, nil) return } @@ -225,7 +225,7 @@ func (c *mcuJanusClient) sendCandidate(ctx context.Context, candidate any, callb callback(nil, nil) } -func (c *mcuJanusClient) handleTrickle(event *TrickleMsg) { +func (c *janusClient) handleTrickle(event *janus.TrickleMsg) { if event.Candidate.Completed { c.listener.OnIceCompleted(c) } else { @@ -233,10 +233,10 @@ func (c *mcuJanusClient) handleTrickle(event *TrickleMsg) { } } -func (c *mcuJanusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { +func (c *janusClient) selectStream(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { handle := c.handle.Load() if handle == nil { - callback(ErrNotConnected, nil) + callback(sfu.ErrNotConnected, nil) return } diff --git a/mcu_janus_events_handler.go b/sfu/janus/events_handler.go similarity index 72% rename from mcu_janus_events_handler.go rename to sfu/janus/events_handler.go index 2044938..9a326ba 100644 --- a/mcu_janus_events_handler.go +++ b/sfu/janus/events_handler.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 janus import ( "context" @@ -38,39 +38,57 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) const ( - JanusEventsSubprotocol = "janus-events" + EventsSubprotocol = "janus-events" - JanusEventTypeSession = 1 + janusEventTypeSession = 1 - JanusEventTypeHandle = 2 + janusEventTypeHandle = 2 - JanusEventTypeExternal = 4 + janusEventTypeExternal = 4 - JanusEventTypeJSEP = 8 + janusEventTypeJSEP = 8 - JanusEventTypeWebRTC = 16 - JanusEventSubTypeWebRTCICE = 1 - JanusEventSubTypeWebRTCLocalCandidate = 2 - JanusEventSubTypeWebRTCRemoteCandidate = 3 - JanusEventSubTypeWebRTCSelectedPair = 4 - JanusEventSubTypeWebRTCDTLS = 5 - JanusEventSubTypeWebRTCPeerConnection = 6 + janusEventTypeWebRTC = 16 + janusEventSubTypeWebRTCICE = 1 + janusEventSubTypeWebRTCLocalCandidate = 2 + janusEventSubTypeWebRTCRemoteCandidate = 3 + janusEventSubTypeWebRTCSelectedPair = 4 + janusEventSubTypeWebRTCDTLS = 5 + janusEventSubTypeWebRTCPeerConnection = 6 - JanusEventTypeMedia = 32 - JanusEventSubTypeMediaState = 1 - JanusEventSubTypeMediaSlowLink = 2 - JanusEventSubTypeMediaStats = 3 + janusEventTypeMedia = 32 + janusEventSubTypeMediaState = 1 + janusEventSubTypeMediaSlowLink = 2 + janusEventSubTypeMediaStats = 3 - JanusEventTypePlugin = 64 + janusEventTypePlugin = 64 - JanusEventTypeTransport = 128 + janusEventTypeTransport = 128 - JanusEventTypeCore = 256 - JanusEventSubTypeCoreStatusStartup = 1 - JanusEventSubTypeCoreStatusShutdown = 2 + janusEventTypeCore = 256 + janusEventSubTypeCoreStatusStartup = 1 + janusEventSubTypeCoreStatusShutdown = 2 + + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 64 * 1024 +) + +var ( + bufferPool pool.BufferPool ) func unmarshalEvent[T any](data json.RawMessage) (*T, error) { @@ -91,7 +109,7 @@ func marshalEvent[T any](e T) string { return string(data) } -type JanusEvent struct { +type janusEvent struct { Emitter string `json:"emitter"` Type int `json:"type"` SubType int `json:"subtype,omitempty"` @@ -102,90 +120,90 @@ type JanusEvent struct { Event json.RawMessage `json:"event"` } -func (e JanusEvent) String() string { +func (e janusEvent) String() string { return marshalEvent(e) } -func (e JanusEvent) Decode() (any, error) { +func (e janusEvent) Decode() (any, error) { switch e.Type { - case JanusEventTypeSession: - return unmarshalEvent[JanusEventSession](e.Event) - case JanusEventTypeHandle: - return unmarshalEvent[JanusEventHandle](e.Event) - case JanusEventTypeExternal: - return unmarshalEvent[JanusEventExternal](e.Event) - case JanusEventTypeJSEP: - return unmarshalEvent[JanusEventJSEP](e.Event) - case JanusEventTypeWebRTC: + case janusEventTypeSession: + return unmarshalEvent[janusEventSession](e.Event) + case janusEventTypeHandle: + return unmarshalEvent[janusEventHandle](e.Event) + case janusEventTypeExternal: + return unmarshalEvent[janusEventExternal](e.Event) + case janusEventTypeJSEP: + return unmarshalEvent[janusEventJSEP](e.Event) + case janusEventTypeWebRTC: switch e.SubType { - case JanusEventSubTypeWebRTCICE: - return unmarshalEvent[JanusEventWebRTCICE](e.Event) - case JanusEventSubTypeWebRTCLocalCandidate: - return unmarshalEvent[JanusEventWebRTCLocalCandidate](e.Event) - case JanusEventSubTypeWebRTCRemoteCandidate: - return unmarshalEvent[JanusEventWebRTCRemoteCandidate](e.Event) - case JanusEventSubTypeWebRTCSelectedPair: - return unmarshalEvent[JanusEventWebRTCSelectedPair](e.Event) - case JanusEventSubTypeWebRTCDTLS: - return unmarshalEvent[JanusEventWebRTCDTLS](e.Event) - case JanusEventSubTypeWebRTCPeerConnection: - return unmarshalEvent[JanusEventWebRTCPeerConnection](e.Event) + case janusEventSubTypeWebRTCICE: + return unmarshalEvent[janusEventWebRTCICE](e.Event) + case janusEventSubTypeWebRTCLocalCandidate: + return unmarshalEvent[janusEventWebRTCLocalCandidate](e.Event) + case janusEventSubTypeWebRTCRemoteCandidate: + return unmarshalEvent[janusEventWebRTCRemoteCandidate](e.Event) + case janusEventSubTypeWebRTCSelectedPair: + return unmarshalEvent[janusEventWebRTCSelectedPair](e.Event) + case janusEventSubTypeWebRTCDTLS: + return unmarshalEvent[janusEventWebRTCDTLS](e.Event) + case janusEventSubTypeWebRTCPeerConnection: + return unmarshalEvent[janusEventWebRTCPeerConnection](e.Event) } - case JanusEventTypeMedia: + case janusEventTypeMedia: switch e.SubType { - case JanusEventSubTypeMediaState: - return unmarshalEvent[JanusEventMediaState](e.Event) - case JanusEventSubTypeMediaSlowLink: - return unmarshalEvent[JanusEventMediaSlowLink](e.Event) - case JanusEventSubTypeMediaStats: - return unmarshalEvent[JanusEventMediaStats](e.Event) + case janusEventSubTypeMediaState: + return unmarshalEvent[janusEventMediaState](e.Event) + case janusEventSubTypeMediaSlowLink: + return unmarshalEvent[janusEventMediaSlowLink](e.Event) + case janusEventSubTypeMediaStats: + return unmarshalEvent[janusEventMediaStats](e.Event) } - case JanusEventTypePlugin: - return unmarshalEvent[JanusEventPlugin](e.Event) - case JanusEventTypeTransport: - return unmarshalEvent[JanusEventTransport](e.Event) - case JanusEventTypeCore: + case janusEventTypePlugin: + return unmarshalEvent[janusEventPlugin](e.Event) + case janusEventTypeTransport: + return unmarshalEvent[janusEventTransport](e.Event) + case janusEventTypeCore: switch e.SubType { - case JanusEventSubTypeCoreStatusStartup: - event, err := unmarshalEvent[JanusEventCoreStartup](e.Event) + case janusEventSubTypeCoreStatusStartup: + event, err := unmarshalEvent[janusEventCoreStartup](e.Event) if err != nil { return nil, err } switch event.Status { case "started": - return unmarshalEvent[JanusEventStatusStartupInfo](event.Info) + return unmarshalEvent[janusEventStatusStartupInfo](event.Info) case "update": - return unmarshalEvent[JanusEventStatusUpdateInfo](event.Info) + return unmarshalEvent[janusEventStatusUpdateInfo](event.Info) } return event, nil - case JanusEventSubTypeCoreStatusShutdown: - return unmarshalEvent[JanusEventCoreShutdown](e.Event) + case janusEventSubTypeCoreStatusShutdown: + return unmarshalEvent[janusEventCoreShutdown](e.Event) } } return nil, fmt.Errorf("unsupported event type %d", e.Type) } -type JanusEventSessionTransport struct { +type janusEventSessionTransport struct { Transport string `json:"transport"` ID string `json:"id"` } // type=1 -type JanusEventSession struct { +type janusEventSession struct { Name string `json:"name"` // "created", "destroyed", "timeout" - Transport *JanusEventSessionTransport `json:"transport,omitempty"` + Transport *janusEventSessionTransport `json:"transport,omitempty"` } -func (e JanusEventSession) String() string { +func (e janusEventSession) String() string { return marshalEvent(e) } // type=2 -type JanusEventHandle struct { +type janusEventHandle struct { Name string `json:"name"` // "attached", "detached" Plugin string `json:"plugin"` Token string `json:"token,omitempty"` @@ -193,22 +211,22 @@ type JanusEventHandle struct { OpaqueId string `json:"opaque_id,omitempty"` } -func (e JanusEventHandle) String() string { +func (e janusEventHandle) String() string { return marshalEvent(e) } // type=4 -type JanusEventExternal struct { +type janusEventExternal struct { Schema string `json:"schema"` Data json.RawMessage `json:"data"` } -func (e JanusEventExternal) String() string { +func (e janusEventExternal) String() string { return marshalEvent(e) } // type=8 -type JanusEventJSEP struct { +type janusEventJSEP struct { Owner string `json:"owner"` Jsep struct { Type string `json:"type"` @@ -216,44 +234,44 @@ type JanusEventJSEP struct { } `json:"jsep"` } -func (e JanusEventJSEP) String() string { +func (e janusEventJSEP) String() string { return marshalEvent(e) } // type=16, subtype=1 -type JanusEventWebRTCICE struct { +type janusEventWebRTCICE struct { ICE string `json:"ice"` // "gathering", "connecting", "connected", "ready" StreamID int `json:"stream_id"` ComponentID int `json:"component_id"` } -func (e JanusEventWebRTCICE) String() string { +func (e janusEventWebRTCICE) String() string { return marshalEvent(e) } // type=16, subtype=2 -type JanusEventWebRTCLocalCandidate struct { +type janusEventWebRTCLocalCandidate struct { LocalCandidate string `json:"local-candidate"` StreamID int `json:"stream_id"` ComponentID int `json:"component_id"` } -func (e JanusEventWebRTCLocalCandidate) String() string { +func (e janusEventWebRTCLocalCandidate) String() string { return marshalEvent(e) } // type=16, subtype=3 -type JanusEventWebRTCRemoteCandidate struct { +type janusEventWebRTCRemoteCandidate struct { RemoteCandidate string `json:"remote-candidate"` StreamID int `json:"stream_id"` ComponentID int `json:"component_id"` } -func (e JanusEventWebRTCRemoteCandidate) String() string { +func (e janusEventWebRTCRemoteCandidate) String() string { return marshalEvent(e) } -type JanusEventCandidate struct { +type janusEventCandidate struct { Address string `json:"address"` Port int `json:"port"` Type string `json:"type"` @@ -261,34 +279,34 @@ type JanusEventCandidate struct { Family int `json:"family"` } -func (e JanusEventCandidate) String() string { +func (e janusEventCandidate) String() string { return marshalEvent(e) } -type JanusEventCandidates struct { - Local JanusEventCandidate `json:"local"` - Remote JanusEventCandidate `json:"remote"` +type janusEventCandidates struct { + Local janusEventCandidate `json:"local"` + Remote janusEventCandidate `json:"remote"` } -func (e JanusEventCandidates) String() string { +func (e janusEventCandidates) String() string { return marshalEvent(e) } // type=16, subtype=4 -type JanusEventWebRTCSelectedPair struct { +type janusEventWebRTCSelectedPair struct { StreamID int `json:"stream_id"` ComponentID int `json:"component_id"` SelectedPair string `json:"selected-pair"` - Candidates JanusEventCandidates `json:"candidates"` + Candidates janusEventCandidates `json:"candidates"` } -func (e JanusEventWebRTCSelectedPair) String() string { +func (e janusEventWebRTCSelectedPair) String() string { return marshalEvent(e) } // type=16, subtype=5 -type JanusEventWebRTCDTLS struct { +type janusEventWebRTCDTLS struct { DTLS string `json:"dtls"` // "trying", "connected" StreamID int `json:"stream_id"` @@ -297,22 +315,22 @@ type JanusEventWebRTCDTLS struct { Retransmissions int `json:"retransmissions"` } -func (e JanusEventWebRTCDTLS) String() string { +func (e janusEventWebRTCDTLS) String() string { return marshalEvent(e) } // type=16, subtype=6 -type JanusEventWebRTCPeerConnection struct { +type janusEventWebRTCPeerConnection struct { Connection string `json:"connection"` // "webrtcup", "hangup" Reason string `json:"reason,omitempty"` // Only if "connection" == "hangup" } -func (e JanusEventWebRTCPeerConnection) String() string { +func (e janusEventWebRTCPeerConnection) String() string { return marshalEvent(e) } // type=32, subtype=1 -type JanusEventMediaState struct { +type janusEventMediaState struct { Media string `json:"media"` // "audio", "video" MID string `json:"mid"` SubStream *int `json:"substream,omitempty"` @@ -320,34 +338,34 @@ type JanusEventMediaState struct { Seconds int `json:"seconds"` } -func (e JanusEventMediaState) String() string { +func (e janusEventMediaState) String() string { return marshalEvent(e) } // type=32, subtype=2 -type JanusEventMediaSlowLink struct { +type janusEventMediaSlowLink struct { Media string `json:"media"` // "audio", "video" MID string `json:"mid"` SlowLink string `json:"slow_link"` // "uplink", "downlink" LostLastSec int `json:"lost_lastsec"` } -func (e JanusEventMediaSlowLink) String() string { +func (e janusEventMediaSlowLink) String() string { return marshalEvent(e) } -type JanusMediaStatsRTTValues struct { +type janusMediaStatsRTTValues struct { NTP uint32 `json:"ntp"` LSR uint32 `json:"lsr"` DLSR uint32 `json:"dlsr"` } -func (e JanusMediaStatsRTTValues) String() string { +func (e janusMediaStatsRTTValues) String() string { return marshalEvent(e) } // type=32, subtype=3 -type JanusEventMediaStats struct { +type janusEventMediaStats struct { MID string `json:"mid"` MIndex int `json:"mindex"` Media string `json:"media"` // "audio", "video", "video-sim1", "video-sim2" @@ -372,7 +390,7 @@ type JanusEventMediaStats struct { // Only for audio / video on layer 0 RTT uint32 `json:"rtt,omitempty"` // Only for audio / video on layer 0 if RTCP is active - RTTValues *JanusMediaStatsRTTValues `json:"rtt-values,omitempty"` + RTTValues *janusMediaStatsRTTValues `json:"rtt-values,omitempty"` // For all media on all layers PacketsReceived uint32 `json:"packets-received"` @@ -384,38 +402,38 @@ type JanusEventMediaStats struct { REMBBitrate uint32 `json:"remb-bitrate"` } -func (e JanusEventMediaStats) String() string { +func (e janusEventMediaStats) String() string { return marshalEvent(e) } // type=64 -type JanusEventPlugin struct { +type janusEventPlugin struct { Plugin string `json:"plugin"` Data json.RawMessage `json:"data"` } -func (e JanusEventPlugin) String() string { +func (e janusEventPlugin) String() string { return marshalEvent(e) } -type JanusEventTransportWebsocket struct { +type janusEventTransportWebsocket struct { Event string `json:"event"` AdminApi bool `json:"admin_api,omitempty"` IP string `json:"ip,omitempty"` } // type=128 -type JanusEventTransport struct { +type janusEventTransport struct { Transport string `json:"transport"` Id string `json:"id"` - Data JanusEventTransportWebsocket `json:"data"` + Data janusEventTransportWebsocket `json:"data"` } -func (e JanusEventTransport) String() string { +func (e janusEventTransport) String() string { return marshalEvent(e) } -type JanusEventDependenciesInfo struct { +type janusEventDependenciesInfo struct { Glib2 string `json:"glib2"` Jansson string `json:"jansson"` Libnice string `json:"libnice"` @@ -424,11 +442,11 @@ type JanusEventDependenciesInfo struct { Crypto string `json:"crypto"` } -func (e JanusEventDependenciesInfo) String() string { +func (e janusEventDependenciesInfo) String() string { return marshalEvent(e) } -type JanusEventPluginInfo struct { +type janusEventPluginInfo struct { Name string `json:"name"` Author string `json:"author"` Description string `json:"description"` @@ -436,12 +454,12 @@ type JanusEventPluginInfo struct { Version int `json:"version"` } -func (e JanusEventPluginInfo) String() string { +func (e janusEventPluginInfo) String() string { return marshalEvent(e) } // type=256, subtype=1, status="startup" -type JanusEventStatusStartupInfo struct { +type janusEventStatusStartupInfo struct { Janus string `json:"janus"` Version int `json:"version"` VersionString string `json:"version_string"` @@ -486,58 +504,58 @@ type JanusEventStatusStartupInfo struct { OpaqueIdInAPI bool `json:"opaqueid_in_api"` WebRTCEncryption bool `json:"webrtc_encryption"` - Dependencies *JanusEventDependenciesInfo `json:"dependencies,omitempty"` - Transports map[string]JanusEventPluginInfo `json:"transports,omitempty"` - Events map[string]JanusEventPluginInfo `json:"events,omitempty"` - Loggers map[string]JanusEventPluginInfo `json:"loggers,omitempty"` - Plugins map[string]JanusEventPluginInfo `json:"plugins,omitempty"` + Dependencies *janusEventDependenciesInfo `json:"dependencies,omitempty"` + Transports map[string]janusEventPluginInfo `json:"transports,omitempty"` + Events map[string]janusEventPluginInfo `json:"events,omitempty"` + Loggers map[string]janusEventPluginInfo `json:"loggers,omitempty"` + Plugins map[string]janusEventPluginInfo `json:"plugins,omitempty"` } -func (e JanusEventStatusStartupInfo) String() string { +func (e janusEventStatusStartupInfo) String() string { return marshalEvent(e) } // type=256, subtype=1, status="update" -type JanusEventStatusUpdateInfo struct { +type janusEventStatusUpdateInfo struct { Sessions int `json:"sessions"` Handles int `json:"handles"` PeerConnections int `json:"peerconnections"` StatsPeriod int `json:"stats-period"` } -func (e JanusEventStatusUpdateInfo) String() string { +func (e janusEventStatusUpdateInfo) String() string { return marshalEvent(e) } // type=256, subtype=1 -type JanusEventCoreStartup struct { +type janusEventCoreStartup struct { Status string `json:"status"` Info json.RawMessage `json:"info"` } -func (e JanusEventCoreStartup) String() string { +func (e janusEventCoreStartup) String() string { return marshalEvent(e) } // type=256, subtype=2 -type JanusEventCoreShutdown struct { +type janusEventCoreShutdown struct { Status string `json:"status"` Signum int `json:"signum"` } -func (e JanusEventCoreShutdown) String() string { +func (e janusEventCoreShutdown) String() string { return marshalEvent(e) } -type McuEventHandler interface { +type EventHandler interface { UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) } -type ValueCounter struct { +type valueCounter struct { values map[string]uint64 } -func (c *ValueCounter) Update(key string, value uint64) uint64 { +func (c *valueCounter) Update(key string, value uint64) uint64 { if c.values == nil { c.values = make(map[string]uint64) } @@ -561,16 +579,16 @@ func (c *ValueCounter) Update(key string, value uint64) uint64 { type handleStats struct { codecs map[string]string - bytesReceived ValueCounter - bytesSent ValueCounter + bytesReceived valueCounter + bytesSent valueCounter - nacksReceived ValueCounter - nacksSent ValueCounter + nacksReceived valueCounter + nacksSent valueCounter - lostLocal ValueCounter - lostRemote ValueCounter + lostLocal valueCounter + lostRemote valueCounter - retransmissionsReceived ValueCounter + retransmissionsReceived valueCounter } func (h *handleStats) Codec(media string, codec string) { @@ -618,12 +636,12 @@ func (h *handleStats) LostRemote(media string, lost uint64) { statsJanusMediaLostTotal.WithLabelValues(media, "remote").Add(float64(delta)) } -type JanusEventsHandler struct { +type EventsHandler struct { mu sync.Mutex logger log.Logger ctx context.Context - mcu McuEventHandler + mcu EventHandler // +checklocks:mu conn *websocket.Conn addr string @@ -632,17 +650,17 @@ type JanusEventsHandler struct { supportsHandles bool handleStats map[uint64]*handleStats - events chan JanusEvent + events chan janusEvent } -func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, addr string, agent string) { +func RunEventsHandler(ctx context.Context, mcu sfu.SFU, conn *websocket.Conn, addr string, agent string) { deadline := time.Now().Add(time.Second) if mcu == nil { conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "no mcu configured"), deadline) // nolint return } - m, ok := mcu.(McuEventHandler) + m, ok := mcu.(EventHandler) if !ok { conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "mcu does not support events"), deadline) // nolint return @@ -653,7 +671,7 @@ func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, a return } - client, err := NewJanusEventsHandler(ctx, m, conn, addr, agent) + client, err := NewEventsHandler(ctx, m, conn, addr, agent) if err != nil { logger := log.LoggerFromContext(ctx) logger.Printf("Could not create Janus events handler for %s: %s", addr, err) @@ -664,8 +682,8 @@ func RunJanusEventsHandler(ctx context.Context, mcu Mcu, conn *websocket.Conn, a client.Run() } -func NewJanusEventsHandler(ctx context.Context, mcu McuEventHandler, conn *websocket.Conn, addr string, agent string) (*JanusEventsHandler, error) { - handler := &JanusEventsHandler{ +func NewEventsHandler(ctx context.Context, mcu EventHandler, conn *websocket.Conn, addr string, agent string) (*EventsHandler, error) { + handler := &EventsHandler{ logger: log.LoggerFromContext(ctx), ctx: ctx, mcu: mcu, @@ -673,13 +691,13 @@ func NewJanusEventsHandler(ctx context.Context, mcu McuEventHandler, conn *webso addr: addr, agent: agent, - events: make(chan JanusEvent, 1), + events: make(chan janusEvent, 1), } return handler, nil } -func (h *JanusEventsHandler) Run() { +func (h *EventsHandler) Run() { h.logger.Printf("Processing Janus events from %s", h.addr) go h.writePump() go h.processEvents() @@ -687,7 +705,7 @@ func (h *JanusEventsHandler) Run() { h.readPump() } -func (h *JanusEventsHandler) close() { +func (h *EventsHandler) close() { h.mu.Lock() conn := h.conn h.conn = nil @@ -700,7 +718,7 @@ func (h *JanusEventsHandler) close() { } } -func (h *JanusEventsHandler) readPump() { +func (h *EventsHandler) readPump() { h.mu.Lock() conn := h.conn h.mu.Unlock() @@ -749,9 +767,9 @@ func (h *JanusEventsHandler) readPump() { break } - var events []JanusEvent + var events []janusEvent if data := decodeBuffer.Bytes(); data[0] != '[' { - var event JanusEvent + var event janusEvent if err := json.Unmarshal(data, &event); err != nil { h.logger.Printf("Error decoding message %s from %s: %v", decodeBuffer.String(), h.addr, err) bufferPool.Put(decodeBuffer) @@ -774,7 +792,7 @@ func (h *JanusEventsHandler) readPump() { } } -func (h *JanusEventsHandler) sendPing() bool { +func (h *EventsHandler) sendPing() bool { h.mu.Lock() defer h.mu.Unlock() if h.conn == nil { @@ -792,7 +810,7 @@ func (h *JanusEventsHandler) sendPing() bool { return true } -func (h *JanusEventsHandler) writePump() { +func (h *EventsHandler) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() @@ -811,7 +829,7 @@ func (h *JanusEventsHandler) writePump() { } } -func (h *JanusEventsHandler) processEvents() { +func (h *EventsHandler) processEvents() { for { select { case event := <-h.events: @@ -822,13 +840,13 @@ func (h *JanusEventsHandler) processEvents() { } } -func (h *JanusEventsHandler) deleteHandleStats(event JanusEvent) { +func (h *EventsHandler) deleteHandleStats(event janusEvent) { if event.HandleId != 0 { delete(h.handleStats, event.HandleId) } } -func (h *JanusEventsHandler) getHandleStats(event JanusEvent) *handleStats { +func (h *EventsHandler) getHandleStats(event janusEvent) *handleStats { if !h.supportsHandles { // Only create per-handle stats if enabled in Janus. Otherwise the // handleStats map will never be cleaned up. @@ -848,7 +866,7 @@ func (h *JanusEventsHandler) getHandleStats(event JanusEvent) *handleStats { return stats } -func (h *JanusEventsHandler) processEvent(event JanusEvent) { +func (h *EventsHandler) processEvent(event janusEvent) { evt, err := event.Decode() if err != nil { h.logger.Printf("Error decoding event %s (%s)", event, err) @@ -856,23 +874,23 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { } switch evt := evt.(type) { - case *JanusEventHandle: + case *janusEventHandle: switch evt.Name { case "attached": h.supportsHandles = true case "detached": h.deleteHandleStats(event) } - case *JanusEventWebRTCICE: + case *janusEventWebRTCICE: statsJanusICEStateTotal.WithLabelValues(evt.ICE).Inc() - case *JanusEventWebRTCDTLS: + case *janusEventWebRTCDTLS: statsJanusDTLSStateTotal.WithLabelValues(evt.DTLS).Inc() - case *JanusEventWebRTCPeerConnection: + case *janusEventWebRTCPeerConnection: statsJanusPeerConnectionStateTotal.WithLabelValues(evt.Connection, evt.Reason).Inc() - case *JanusEventWebRTCSelectedPair: + case *janusEventWebRTCSelectedPair: statsJanusSelectedCandidateTotal.WithLabelValues("local", evt.Candidates.Local.Type, evt.Candidates.Local.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Local.Family)).Inc() statsJanusSelectedCandidateTotal.WithLabelValues("remote", evt.Candidates.Remote.Type, evt.Candidates.Remote.Transport, fmt.Sprintf("ipv%d", evt.Candidates.Remote.Family)).Inc() - case *JanusEventMediaSlowLink: + case *janusEventMediaSlowLink: var direction string // "uplink" is Janus -> client, "downlink" is client -> Janus. if evt.SlowLink == "uplink" { @@ -881,7 +899,7 @@ func (h *JanusEventsHandler) processEvent(event JanusEvent) { direction = "incoming" } statsJanusSlowLinkTotal.WithLabelValues(evt.Media, direction).Inc() - case *JanusEventMediaStats: + case *janusEventMediaStats: if rtt := evt.RTT; rtt > 0 { statsJanusMediaRTT.WithLabelValues(evt.Media).Observe(float64(rtt)) } diff --git a/mcu_janus_events_handler_test.go b/sfu/janus/events_handler_test.go similarity index 78% rename from mcu_janus_events_handler_test.go rename to sfu/janus/events_handler_test.go index afadd0d..66cf697 100644 --- a/mcu_janus_events_handler_test.go +++ b/sfu/janus/events_handler_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 janus import ( "context" @@ -40,13 +40,16 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" ) type TestJanusEventsServerHandler struct { t *testing.T upgrader websocket.Upgrader - mcu Mcu + mcu sfu.SFU addr string wg sync.WaitGroup } @@ -59,7 +62,7 @@ func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http. conn, err := h.upgrader.Upgrade(w, r, nil) assert.NoError(err) - if conn.Subprotocol() == JanusEventsSubprotocol { + if conn.Subprotocol() == EventsSubprotocol { addr := h.addr if addr == "" { addr = r.RemoteAddr @@ -69,7 +72,7 @@ func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http. } logger := log.NewLoggerForTest(h.t) ctx := log.NewLoggerContext(r.Context(), logger) - RunJanusEventsHandler(ctx, h.mcu, conn, addr, r.Header.Get("User-Agent")) + RunEventsHandler(ctx, h.mcu, conn, addr, r.Header.Get("User-Agent")) return } @@ -87,7 +90,7 @@ func NewTestJanusEventsHandlerServer(t *testing.T) (*httptest.Server, string, *T t: t, upgrader: websocket.Upgrader{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, }, } @@ -114,7 +117,7 @@ func TestJanusEventsHandlerNoMcu(t *testing.T) { dialer := websocket.Dialer{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, } conn, response, err := dialer.DialContext(ctx, url, nil) @@ -123,7 +126,7 @@ func TestJanusEventsHandlerNoMcu(t *testing.T) { assert.NoError(conn.Close()) }() - assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assert.Equal(EventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) var ce *websocket.CloseError require.NoError(conn.SetReadDeadline(time.Now().Add(testTimeout))) @@ -142,14 +145,14 @@ func TestJanusEventsHandlerInvalidMcu(t *testing.T) { _, url, handler := NewTestJanusEventsHandlerServer(t) - handler.mcu = &mcuProxy{} + handler.mcu = sfutest.NewSFU(t) ctx, cancel := context.WithTimeout(t.Context(), testTimeout) defer cancel() dialer := websocket.Dialer{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, } conn, response, err := dialer.DialContext(ctx, url, nil) @@ -158,7 +161,7 @@ func TestJanusEventsHandlerInvalidMcu(t *testing.T) { assert.NoError(conn.Close()) }() - assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assert.Equal(EventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) var ce *websocket.CloseError require.NoError(conn.SetReadDeadline(time.Now().Add(testTimeout))) @@ -177,7 +180,7 @@ func TestJanusEventsHandlerPublicIP(t *testing.T) { _, url, handler := NewTestJanusEventsHandlerServer(t) - handler.mcu = &mcuJanus{} + handler.mcu = &janusSFU{} handler.addr = "1.2.3.4" ctx, cancel := context.WithTimeout(t.Context(), testTimeout) @@ -185,7 +188,7 @@ func TestJanusEventsHandlerPublicIP(t *testing.T) { dialer := websocket.Dialer{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, } conn, response, err := dialer.DialContext(ctx, url, nil) @@ -194,7 +197,7 @@ func TestJanusEventsHandlerPublicIP(t *testing.T) { assert.NoError(conn.Close()) }() - assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assert.Equal(EventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) var ce *websocket.CloseError require.NoError(conn.SetReadDeadline(time.Now().Add(testTimeout))) @@ -207,7 +210,7 @@ func TestJanusEventsHandlerPublicIP(t *testing.T) { } type TestMcuWithEvents struct { - TestMCU + sfutest.SFU t *testing.T mu sync.Mutex @@ -256,7 +259,7 @@ func (m *TestMcuWithEvents) WaitForUpdates(ctx context.Context, waitForIdx int) } type janusEventSender struct { - events []JanusEvent + events []janusEvent } func (s *janusEventSender) SendSingle(t *testing.T, conn *websocket.Conn) { @@ -288,7 +291,7 @@ func (s *janusEventSender) AddEvent(t *testing.T, eventType int, eventSubtype in assert.Equal(s.String(), string(data)) } - message := JanusEvent{ + message := janusEvent{ Type: eventType, SubType: eventSubtype, HandleId: handleId, @@ -315,7 +318,7 @@ func TestJanusEventsHandlerDifferentTypes(t *testing.T) { dialer := websocket.Dialer{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, } conn, response, err := dialer.DialContext(ctx, url, nil) @@ -324,186 +327,186 @@ func TestJanusEventsHandlerDifferentTypes(t *testing.T) { assert.NoError(conn.Close()) }() - assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assert.Equal(EventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) var sender janusEventSender sender.AddEvent( t, - JanusEventTypeSession, + janusEventTypeSession, 0, 1, - JanusEventSession{ + janusEventSession{ Name: "created", }, ) sender.AddEvent( t, - JanusEventTypeHandle, + janusEventTypeHandle, 0, 1, - JanusEventHandle{ + janusEventHandle{ Name: "attached", }, ) sender.AddEvent( t, - JanusEventTypeExternal, + janusEventTypeExternal, 0, 0, - JanusEventExternal{ + janusEventExternal{ Schema: "test-external", }, ) sender.AddEvent( t, - JanusEventTypeJSEP, + janusEventTypeJSEP, 0, 1, - JanusEventJSEP{ + janusEventJSEP{ Owner: "testing", }, ) sender.AddEvent( t, - JanusEventTypeWebRTC, - JanusEventSubTypeWebRTCICE, + janusEventTypeWebRTC, + janusEventSubTypeWebRTCICE, 1, - JanusEventWebRTCICE{ + janusEventWebRTCICE{ ICE: "gathering", }, ) sender.AddEvent( t, - JanusEventTypeWebRTC, - JanusEventSubTypeWebRTCLocalCandidate, + janusEventTypeWebRTC, + janusEventSubTypeWebRTCLocalCandidate, 1, - JanusEventWebRTCLocalCandidate{ + janusEventWebRTCLocalCandidate{ LocalCandidate: "invalid-candidate", }, ) sender.AddEvent( t, - JanusEventTypeWebRTC, - JanusEventSubTypeWebRTCRemoteCandidate, + janusEventTypeWebRTC, + janusEventSubTypeWebRTCRemoteCandidate, 1, - JanusEventWebRTCRemoteCandidate{ + janusEventWebRTCRemoteCandidate{ RemoteCandidate: "invalid-candidate", }, ) sender.AddEvent( t, - JanusEventTypeWebRTC, - JanusEventSubTypeWebRTCSelectedPair, + janusEventTypeWebRTC, + janusEventSubTypeWebRTCSelectedPair, 1, - JanusEventWebRTCSelectedPair{ + janusEventWebRTCSelectedPair{ SelectedPair: "invalid-pair", }, ) sender.AddEvent( t, - JanusEventTypeWebRTC, - JanusEventSubTypeWebRTCDTLS, + janusEventTypeWebRTC, + janusEventSubTypeWebRTCDTLS, 1, - JanusEventWebRTCDTLS{ + janusEventWebRTCDTLS{ DTLS: "trying", }, ) sender.AddEvent( t, - JanusEventTypeWebRTC, - JanusEventSubTypeWebRTCPeerConnection, + janusEventTypeWebRTC, + janusEventSubTypeWebRTCPeerConnection, 1, - JanusEventWebRTCPeerConnection{ + janusEventWebRTCPeerConnection{ Connection: "webrtcup", }, ) sender.AddEvent( t, - JanusEventTypeMedia, - JanusEventSubTypeMediaState, + janusEventTypeMedia, + janusEventSubTypeMediaState, 1, - JanusEventMediaState{ + janusEventMediaState{ Media: "audio", }, ) sender.AddEvent( t, - JanusEventTypeMedia, - JanusEventSubTypeMediaSlowLink, + janusEventTypeMedia, + janusEventSubTypeMediaSlowLink, 1, - JanusEventMediaSlowLink{ + janusEventMediaSlowLink{ Media: "audio", }, ) sender.AddEvent( t, - JanusEventTypePlugin, + janusEventTypePlugin, 0, 1, - JanusEventPlugin{ + janusEventPlugin{ Plugin: "test-plugin", }, ) sender.AddEvent( t, - JanusEventTypeTransport, + janusEventTypeTransport, 0, 1, - JanusEventTransport{ + janusEventTransport{ Transport: "test-transport", }, ) sender.AddEvent( t, - JanusEventTypeCore, - JanusEventSubTypeCoreStatusStartup, + janusEventTypeCore, + janusEventSubTypeCoreStatusStartup, 0, - JanusEventCoreStartup{ + janusEventCoreStartup{ Status: "started", }, ) sender.AddEvent( t, - JanusEventTypeCore, - JanusEventSubTypeCoreStatusStartup, + janusEventTypeCore, + janusEventSubTypeCoreStatusStartup, 0, - JanusEventCoreStartup{ + janusEventCoreStartup{ Status: "update", }, ) sender.AddEvent( t, - JanusEventTypeCore, - JanusEventSubTypeCoreStatusShutdown, + janusEventTypeCore, + janusEventSubTypeCoreStatusShutdown, 0, - JanusEventCoreShutdown{ + janusEventCoreShutdown{ Status: "shutdown", }, ) sender.AddEvent( t, - JanusEventTypeMedia, - JanusEventSubTypeMediaStats, + janusEventTypeMedia, + janusEventSubTypeMediaStats, 1, - JanusEventMediaStats{ + janusEventMediaStats{ Media: "audio", BytesSentLastSec: 100, BytesReceivedLastSec: 200, @@ -532,7 +535,7 @@ func TestJanusEventsHandlerNotGrouped(t *testing.T) { dialer := websocket.Dialer{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, } conn, response, err := dialer.DialContext(ctx, url, nil) @@ -541,31 +544,31 @@ func TestJanusEventsHandlerNotGrouped(t *testing.T) { assert.NoError(conn.Close()) }() - assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assert.Equal(EventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) - assertCollectorChangeBy(t, statsJanusMediaNACKTotal.WithLabelValues("audio", "incoming"), 20) - assertCollectorChangeBy(t, statsJanusMediaNACKTotal.WithLabelValues("audio", "outgoing"), 30) - assertCollectorChangeBy(t, statsJanusMediaRetransmissionsTotal.WithLabelValues("audio"), 40) - assertCollectorChangeBy(t, statsJanusMediaLostTotal.WithLabelValues("audio", "local"), 50) - assertCollectorChangeBy(t, statsJanusMediaLostTotal.WithLabelValues("audio", "remote"), 60) + metricstest.AssertCollectorChangeBy(t, statsJanusMediaNACKTotal.WithLabelValues("audio", "incoming"), 20) + metricstest.AssertCollectorChangeBy(t, statsJanusMediaNACKTotal.WithLabelValues("audio", "outgoing"), 30) + metricstest.AssertCollectorChangeBy(t, statsJanusMediaRetransmissionsTotal.WithLabelValues("audio"), 40) + metricstest.AssertCollectorChangeBy(t, statsJanusMediaLostTotal.WithLabelValues("audio", "local"), 50) + metricstest.AssertCollectorChangeBy(t, statsJanusMediaLostTotal.WithLabelValues("audio", "remote"), 60) var sender janusEventSender sender.AddEvent( t, - JanusEventTypeHandle, + janusEventTypeHandle, 0, 1, - JanusEventHandle{ + janusEventHandle{ Name: "attached", }, ) sender.SendSingle(t, conn) sender.AddEvent( t, - JanusEventTypeMedia, - JanusEventSubTypeMediaStats, + janusEventTypeMedia, + janusEventSubTypeMediaStats, 1, - JanusEventMediaStats{ + janusEventMediaStats{ Media: "audio", BytesSentLastSec: 100, BytesReceivedLastSec: 200, @@ -583,10 +586,10 @@ func TestJanusEventsHandlerNotGrouped(t *testing.T) { sender.SendSingle(t, conn) sender.AddEvent( t, - JanusEventTypeHandle, + janusEventTypeHandle, 0, 1, - JanusEventHandle{ + janusEventHandle{ Name: "detached", }, ) @@ -611,7 +614,7 @@ func TestJanusEventsHandlerGrouped(t *testing.T) { dialer := websocket.Dialer{ Subprotocols: []string{ - JanusEventsSubprotocol, + EventsSubprotocol, }, } conn, response, err := dialer.DialContext(ctx, url, nil) @@ -620,15 +623,15 @@ func TestJanusEventsHandlerGrouped(t *testing.T) { assert.NoError(conn.Close()) }() - assert.Equal(JanusEventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) + assert.Equal(EventsSubprotocol, response.Header.Get("Sec-WebSocket-Protocol")) var sender janusEventSender sender.AddEvent( t, - JanusEventTypeMedia, - JanusEventSubTypeMediaStats, + janusEventTypeMedia, + janusEventSubTypeMediaStats, 1, - JanusEventMediaStats{ + janusEventMediaStats{ Media: "audio", BytesSentLastSec: 100, BytesReceivedLastSec: 200, @@ -636,10 +639,10 @@ func TestJanusEventsHandlerGrouped(t *testing.T) { ) sender.AddEvent( t, - JanusEventTypeMedia, - JanusEventSubTypeMediaStats, + janusEventTypeMedia, + janusEventSubTypeMediaStats, 1, - JanusEventMediaStats{ + janusEventMediaStats{ Media: "video", BytesSentLastSec: 200, BytesReceivedLastSec: 300, @@ -655,7 +658,7 @@ func TestValueCounter(t *testing.T) { assert := assert.New(t) - var c ValueCounter + var c valueCounter assert.EqualValues(0, c.Update("foo", 0)) assert.EqualValues(10, c.Update("foo", 10)) assert.EqualValues(0, c.Update("foo", 10)) diff --git a/mcu_janus.go b/sfu/janus/janus.go similarity index 70% rename from mcu_janus.go rename to sfu/janus/janus.go index 9e9274a..57ac60e 100644 --- a/mcu_janus.go +++ b/sfu/janus/janus.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 janus import ( "context" @@ -32,13 +32,15 @@ import ( "time" "github.com/dlintw/goconf" - "github.com/notedit/janus-go" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "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/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -54,23 +56,20 @@ const ( initialReconnectInterval = 1 * time.Second maxReconnectInterval = 16 * time.Second + + // MCU requests will be cancelled if they take too long. + defaultMcuTimeoutSeconds = 10 ) var ( ErrRemoteStreamsNotSupported = errors.New("need Janus 1.1.0 for remote streams") - streamTypeUserIds = map[StreamType]uint64{ - StreamTypeVideo: videoPublisherUserId, - StreamTypeScreen: screenPublisherUserId, + streamTypeUserIds = map[sfu.StreamType]uint64{ + sfu.StreamTypeVideo: videoPublisherUserId, + sfu.StreamTypeScreen: screenPublisherUserId, } ) -type StreamId string - -func getStreamId(publisherId api.PublicSessionId, streamType StreamType) StreamId { - return StreamId(fmt.Sprintf("%s|%s", publisherId, streamType)) -} - func getPluginValue(data janus.PluginData, pluginName string, key string) any { if data.Plugin != pluginName { return nil @@ -146,21 +145,21 @@ type clientInterface interface { NotifyReconnected() - Bandwidth() *McuClientBandwidthInfo + Bandwidth() *sfu.ClientBandwidthInfo UpdateBandwidth(media string, sent api.Bandwidth, received api.Bandwidth) } -type mcuJanusSettings struct { - mcuCommonSettings +type Settings struct { + sfuinternal.CommonSettings allowedCandidates atomic.Pointer[container.IPList] blockedCandidates atomic.Pointer[container.IPList] } -func newMcuJanusSettings(ctx context.Context, config *goconf.ConfigFile) (*mcuJanusSettings, error) { - settings := &mcuJanusSettings{ - mcuCommonSettings: mcuCommonSettings{ - logger: log.LoggerFromContext(ctx), +func newJanusSettings(ctx context.Context, config *goconf.ConfigFile) (*Settings, error) { + settings := &Settings{ + CommonSettings: sfuinternal.CommonSettings{ + Logger: log.LoggerFromContext(ctx), }, } if err := settings.load(config); err != nil { @@ -170,8 +169,8 @@ func newMcuJanusSettings(ctx context.Context, config *goconf.ConfigFile) (*mcuJa return settings, nil } -func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { - if err := s.mcuCommonSettings.load(config); err != nil { +func (s *Settings) load(config *goconf.ConfigFile) error { + if err := s.Load(config); err != nil { return err } @@ -180,8 +179,8 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { mcuTimeoutSeconds = defaultMcuTimeoutSeconds } mcuTimeout := time.Duration(mcuTimeoutSeconds) * time.Second - s.logger.Printf("Using a timeout of %s for MCU requests", mcuTimeout) - s.setTimeout(mcuTimeout) + s.Logger.Printf("Using a timeout of %s for MCU requests", mcuTimeout) + s.SetTimeout(mcuTimeout) if value, _ := config.GetString("mcu", "allowedcandidates"); value != "" { allowed, err := container.ParseIPList(value) @@ -189,10 +188,10 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { return fmt.Errorf("invalid allowedcandidates: %w", err) } - s.logger.Printf("Candidates allowlist: %s", allowed) + s.Logger.Printf("Candidates allowlist: %s", allowed) s.allowedCandidates.Store(allowed) } else { - s.logger.Printf("No candidates allowlist") + s.Logger.Printf("No candidates allowlist") s.allowedCandidates.Store(nil) } if value, _ := config.GetString("mcu", "blockedcandidates"); value != "" { @@ -201,54 +200,54 @@ func (s *mcuJanusSettings) load(config *goconf.ConfigFile) error { return fmt.Errorf("invalid blockedcandidates: %w", err) } - s.logger.Printf("Candidates blocklist: %s", blocked) + s.Logger.Printf("Candidates blocklist: %s", blocked) s.blockedCandidates.Store(blocked) } else { - s.logger.Printf("No candidates blocklist") + s.Logger.Printf("No candidates blocklist") s.blockedCandidates.Store(nil) } return nil } -func (s *mcuJanusSettings) Reload(config *goconf.ConfigFile) { +func (s *Settings) Reload(config *goconf.ConfigFile) { if err := s.load(config); err != nil { - s.logger.Printf("Error reloading MCU settings: %s", err) + s.Logger.Printf("Error reloading MCU settings: %s", err) } } -type mcuJanusStats interface { - IncSubscriber(streamType StreamType) - DecSubscriber(streamType StreamType) +type Stats interface { + IncSubscriber(streamType sfu.StreamType) + DecSubscriber(streamType sfu.StreamType) } type prometheusJanusStats struct{} -func (s *prometheusJanusStats) IncSubscriber(streamType StreamType) { - statsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() +func (s *prometheusJanusStats) IncSubscriber(streamType sfu.StreamType) { + sfuinternal.StatsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() } -func (s *prometheusJanusStats) DecSubscriber(streamType StreamType) { - statsSubscribersCurrent.WithLabelValues(string(streamType)).Dec() +func (s *prometheusJanusStats) DecSubscriber(streamType sfu.StreamType) { + sfuinternal.StatsSubscribersCurrent.WithLabelValues(string(streamType)).Dec() } -type mcuJanus struct { +type janusSFU struct { logger log.Logger url string mu sync.Mutex - settings *mcuJanusSettings - stats mcuJanusStats + settings *Settings + stats Stats - createJanusGateway func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) + createJanusGateway func(ctx context.Context, wsURL string, listener janus.GatewayListener) (janus.GatewayInterface, error) - gw JanusGatewayInterface - session *JanusSession - handle *JanusHandle + gw janus.GatewayInterface + session *janus.Session + handle *janus.Handle version int - info atomic.Pointer[InfoMsg] + info atomic.Pointer[janus.InfoMsg] closeChan chan struct{} @@ -258,11 +257,11 @@ type mcuJanus struct { clientId atomic.Uint64 // +checklocks:mu - publishers map[StreamId]*mcuJanusPublisher + publishers map[sfu.StreamId]*janusPublisher publisherCreated async.Notifier publisherConnected async.Notifier // +checklocks:mu - remotePublishers map[StreamId]*mcuJanusRemotePublisher + remotePublishers map[sfu.StreamId]*janusRemotePublisher reconnectTimer *time.Timer reconnectInterval time.Duration @@ -275,13 +274,13 @@ type mcuJanus struct { func emptyOnConnected() {} func emptyOnDisconnected() {} -func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mcu, error) { - settings, err := newMcuJanusSettings(ctx, config) +func NewJanusSFU(ctx context.Context, url string, config *goconf.ConfigFile) (sfu.SFU, error) { + settings, err := newJanusSettings(ctx, config) if err != nil { return nil, err } - mcu := &mcuJanus{ + mcu := &janusSFU{ logger: log.LoggerFromContext(ctx), url: url, settings: settings, @@ -289,11 +288,11 @@ func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mc closeChan: make(chan struct{}, 1), clients: make(map[uint64]clientInterface), - publishers: make(map[StreamId]*mcuJanusPublisher), - remotePublishers: make(map[StreamId]*mcuJanusRemotePublisher), + publishers: make(map[sfu.StreamId]*janusPublisher), + remotePublishers: make(map[sfu.StreamId]*janusRemotePublisher), - createJanusGateway: func(ctx context.Context, wsURL string, listener GatewayListener) (JanusGatewayInterface, error) { - return NewJanusGateway(ctx, wsURL, listener) + createJanusGateway: func(ctx context.Context, wsURL string, listener janus.GatewayListener) (janus.GatewayInterface, error) { + return janus.NewGateway(ctx, wsURL, listener) }, reconnectInterval: initialReconnectInterval, } @@ -312,7 +311,28 @@ func NewMcuJanus(ctx context.Context, url string, config *goconf.ConfigFile) (Mc return mcu, nil } -func (m *mcuJanus) disconnect() { +func NewJanusSFUWithGateway(ctx context.Context, gateway janus.GatewayInterface, config *goconf.ConfigFile) (sfu.SFU, error) { + sfu, err := NewJanusSFU(ctx, "", config) + if err != nil { + return nil, err + } + + sfuJanus := sfu.(*janusSFU) + sfuJanus.createJanusGateway = func(ctx context.Context, wsURL string, listener janus.GatewayListener) (janus.GatewayInterface, error) { + return gateway, nil + } + return sfu, nil +} + +func (m *janusSFU) SetStats(stats Stats) { + m.stats = stats +} + +func (m *janusSFU) Settings() *Settings { + return m.settings +} + +func (m *janusSFU) disconnect() { if handle := m.handle; handle != nil { m.handle = nil m.closeChan <- struct{}{} @@ -334,18 +354,18 @@ func (m *mcuJanus) disconnect() { } } -func (m *mcuJanus) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { +func (m *janusSFU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { return m.settings.MaxStreamBitrate(), m.settings.MaxScreenBitrate() } -func (m *mcuJanus) Bandwidth() (result *McuClientBandwidthInfo) { +func (m *janusSFU) Bandwidth() (result *sfu.ClientBandwidthInfo) { m.muClients.RLock() defer m.muClients.RUnlock() for _, client := range m.clients { if bandwidth := client.Bandwidth(); bandwidth != nil { if result == nil { - result = &McuClientBandwidthInfo{} + result = &sfu.ClientBandwidthInfo{} } result.Received += bandwidth.Received result.Sent += bandwidth.Sent @@ -369,7 +389,7 @@ var ( defaultJanusBandwidthStats = &prometheusJanusBandwidthStats{} ) -func (m *mcuJanus) updateBandwidthStats(stats janusBandwidthStats) { +func (m *janusSFU) updateBandwidthStats(stats janusBandwidthStats) { if info := m.info.Load(); info != nil { if !info.EventHandlers { // Event handlers are disabled, no stats will be available. @@ -393,7 +413,7 @@ func (m *mcuJanus) updateBandwidthStats(stats janusBandwidthStats) { } } -func (m *mcuJanus) reconnect(ctx context.Context) error { +func (m *janusSFU) reconnect(ctx context.Context) error { m.disconnect() gw, err := m.createJanusGateway(ctx, m.url, m) if err != nil { @@ -405,7 +425,7 @@ func (m *mcuJanus) reconnect(ctx context.Context) error { return nil } -func (m *mcuJanus) doReconnect(ctx context.Context) { +func (m *janusSFU) doReconnect(ctx context.Context) { if err := m.reconnect(ctx); err != nil { m.scheduleReconnect(err) return @@ -426,7 +446,7 @@ func (m *mcuJanus) doReconnect(ctx context.Context) { m.notifyClientsReconnected() } -func (m *mcuJanus) notifyClientsReconnected() { +func (m *janusSFU) notifyClientsReconnected() { m.muClients.RLock() defer m.muClients.RUnlock() @@ -446,7 +466,7 @@ func (m *mcuJanus) notifyClientsReconnected() { } } -func (m *mcuJanus) scheduleReconnect(err error) { +func (m *janusSFU) scheduleReconnect(err error) { m.mu.Lock() defer m.mu.Unlock() m.reconnectTimer.Reset(m.reconnectInterval) @@ -459,20 +479,20 @@ func (m *mcuJanus) scheduleReconnect(err error) { m.reconnectInterval = min(m.reconnectInterval*2, maxReconnectInterval) } -func (m *mcuJanus) ConnectionInterrupted() { +func (m *janusSFU) ConnectionInterrupted() { m.scheduleReconnect(nil) m.notifyOnDisconnected() } -func (m *mcuJanus) isMultistream() bool { +func (m *janusSFU) isMultistream() bool { return m.version >= 1000 } -func (m *mcuJanus) hasRemotePublisher() bool { +func (m *janusSFU) hasRemotePublisher() bool { return m.version >= 1100 } -func (m *mcuJanus) Start(ctx context.Context) error { +func (m *janusSFU) Start(ctx context.Context) error { if m.url == "" { if err := m.reconnect(ctx); err != nil { return err @@ -535,21 +555,21 @@ func (m *mcuJanus) Start(ctx context.Context) error { return nil } -func (m *mcuJanus) registerClient(client clientInterface) { +func (m *janusSFU) registerClient(client clientInterface) { m.muClients.Lock() defer m.muClients.Unlock() m.clients[client.Handle()] = client } -func (m *mcuJanus) unregisterClient(client clientInterface) { +func (m *janusSFU) unregisterClient(client clientInterface) { m.muClients.Lock() defer m.muClients.Unlock() delete(m.clients, client.Handle()) } -func (m *mcuJanus) run() { +func (m *janusSFU) run() { ticker := time.NewTicker(keepaliveInterval) defer ticker.Stop() @@ -569,20 +589,20 @@ loop: } } -func (m *mcuJanus) Stop() { +func (m *janusSFU) Stop() { m.disconnect() m.reconnectTimer.Stop() } -func (m *mcuJanus) IsConnected() bool { +func (m *janusSFU) IsConnected() bool { return m.handle != nil } -func (m *mcuJanus) Info() *InfoMsg { +func (m *janusSFU) Info() *janus.InfoMsg { return m.info.Load() } -func (m *mcuJanus) GetServerInfoSfu() *talk.BackendServerInfoSfu { +func (m *janusSFU) GetServerInfoSfu() *talk.BackendServerInfoSfu { janus := &talk.BackendServerInfoSfuJanus{ Url: m.url, } @@ -614,11 +634,11 @@ func (m *mcuJanus) GetServerInfoSfu() *talk.BackendServerInfoSfu { return sfu } -func (m *mcuJanus) Reload(config *goconf.ConfigFile) { +func (m *janusSFU) Reload(config *goconf.ConfigFile) { m.settings.Reload(config) } -func (m *mcuJanus) SetOnConnected(f func()) { +func (m *janusSFU) SetOnConnected(f func()) { if f == nil { f = emptyOnConnected } @@ -626,12 +646,12 @@ func (m *mcuJanus) SetOnConnected(f func()) { m.onConnected.Store(f) } -func (m *mcuJanus) notifyOnConnected() { +func (m *janusSFU) notifyOnConnected() { f := m.onConnected.Load().(func()) f() } -func (m *mcuJanus) SetOnDisconnected(f func()) { +func (m *janusSFU) SetOnDisconnected(f func()) { if f == nil { f = emptyOnDisconnected } @@ -639,12 +659,12 @@ func (m *mcuJanus) SetOnDisconnected(f func()) { m.onDisconnected.Store(f) } -func (m *mcuJanus) notifyOnDisconnected() { +func (m *janusSFU) notifyOnDisconnected() { f := m.onDisconnected.Load().(func()) f() } -type mcuJanusConnectionStats struct { +type janusConnectionStats struct { Url string `json:"url"` Connected bool `json:"connected"` Publishers int64 `json:"publishers"` @@ -652,8 +672,8 @@ type mcuJanusConnectionStats struct { Uptime *time.Time `json:"uptime,omitempty"` } -func (m *mcuJanus) GetStats() any { - result := mcuJanusConnectionStats{ +func (m *janusSFU) GetStats() any { + result := janusConnectionStats{ Url: m.url, } if m.session != nil { @@ -669,40 +689,40 @@ func (m *mcuJanus) GetStats() any { return result } -func (m *mcuJanus) sendKeepalive(ctx context.Context) { +func (m *janusSFU) sendKeepalive(ctx context.Context) { if _, err := m.session.KeepAlive(ctx); err != nil { m.logger.Println("Could not send keepalive request", err) if e, ok := err.(*janus.ErrorMsg); ok { switch e.Err.Code { - case JANUS_ERROR_SESSION_NOT_FOUND: + case janus.JANUS_ERROR_SESSION_NOT_FOUND: m.scheduleReconnect(err) } } } } -func (m *mcuJanus) SubscriberConnected(id string, publisher api.PublicSessionId, streamType StreamType) { +func (m *janusSFU) SubscriberConnected(id string, publisher api.PublicSessionId, streamType sfu.StreamType) { m.mu.Lock() defer m.mu.Unlock() - if p, found := m.publishers[getStreamId(publisher, streamType)]; found { + if p, found := m.publishers[sfu.GetStreamId(publisher, streamType)]; found { p.stats.AddSubscriber(id) } } -func (m *mcuJanus) SubscriberDisconnected(id string, publisher api.PublicSessionId, streamType StreamType) { +func (m *janusSFU) SubscriberDisconnected(id string, publisher api.PublicSessionId, streamType sfu.StreamType) { m.mu.Lock() defer m.mu.Unlock() - if p, found := m.publishers[getStreamId(publisher, streamType)]; found { + if p, found := m.publishers[sfu.GetStreamId(publisher, streamType)]; found { p.stats.RemoveSubscriber(id) } } -func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, id api.PublicSessionId, streamType StreamType, settings NewPublisherSettings) (uint64, api.Bandwidth, error) { +func (m *janusSFU) createPublisherRoom(ctx context.Context, handle *janus.Handle, id api.PublicSessionId, streamType sfu.StreamType, settings sfu.NewPublisherSettings) (uint64, api.Bandwidth, error) { create_msg := api.StringMap{ "request": "create", - "description": getStreamId(id, streamType), + "description": sfu.GetStreamId(id, streamType), // We publish every stream in its own Janus room. "publishers": 1, // Do not use the video-orientation RTP extension as it breaks video @@ -722,7 +742,7 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, create_msg["h264_profile"] = profile } var maxBitrate api.Bandwidth - if streamType == StreamTypeScreen { + if streamType == sfu.StreamTypeScreen { maxBitrate = m.settings.MaxScreenBitrate() } else { maxBitrate = m.settings.MaxStreamBitrate() @@ -754,10 +774,10 @@ func (m *mcuJanus) createPublisherRoom(ctx context.Context, handle *JanusHandle, return roomId, bitrate, nil } -func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id api.PublicSessionId, streamType StreamType, settings NewPublisherSettings) (*JanusHandle, uint64, uint64, api.Bandwidth, error) { +func (m *janusSFU) getOrCreatePublisherHandle(ctx context.Context, id api.PublicSessionId, streamType sfu.StreamType, settings sfu.NewPublisherSettings) (*janus.Handle, uint64, uint64, api.Bandwidth, error) { session := m.session if session == nil { - return nil, 0, 0, 0, ErrNotConnected + return nil, 0, 0, 0, sfu.ErrNotConnected } handle, err := session.Attach(ctx, pluginVideoRoom) if err != nil { @@ -792,7 +812,7 @@ func (m *mcuJanus) getOrCreatePublisherHandle(ctx context.Context, id api.Public return handle, response.Session, roomId, bitrate, nil } -func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *janusSFU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } @@ -802,8 +822,8 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id ap return nil, err } - client := &mcuJanusPublisher{ - mcuJanusClient: mcuJanusClient{ + client := &janusPublisher{ + janusClient: janusClient{ logger: m.logger, mcu: m, listener: listener, @@ -824,28 +844,28 @@ func (m *mcuJanus) NewPublisher(ctx context.Context, listener McuListener, id ap } client.handle.Store(handle) client.handleId.Store(handle.Id) - client.mcuJanusClient.handleEvent = client.handleEvent - client.mcuJanusClient.handleHangup = client.handleHangup - client.mcuJanusClient.handleDetached = client.handleDetached - client.mcuJanusClient.handleConnected = client.handleConnected - client.mcuJanusClient.handleSlowLink = client.handleSlowLink - client.mcuJanusClient.handleMedia = client.handleMedia + client.janusClient.handleEvent = client.handleEvent + client.janusClient.handleHangup = client.handleHangup + client.janusClient.handleDetached = client.handleDetached + client.janusClient.handleConnected = client.handleConnected + client.janusClient.handleSlowLink = client.handleSlowLink + client.janusClient.handleMedia = client.handleMedia m.registerClient(client) m.logger.Printf("Publisher %s is using handle %d", client.id, handle.Id) go client.run(handle, client.closeChan) m.mu.Lock() - m.publishers[getStreamId(id, streamType)] = client - m.publisherCreated.Notify(string(getStreamId(id, streamType))) + m.publishers[sfu.GetStreamId(id, streamType)] = client + m.publisherCreated.Notify(string(sfu.GetStreamId(id, streamType))) m.mu.Unlock() - statsPublishersCurrent.WithLabelValues(string(streamType)).Inc() - statsPublishersTotal.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsPublishersCurrent.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsPublishersTotal.WithLabelValues(string(streamType)).Inc() return client, nil } -func (m *mcuJanus) getPublisher(ctx context.Context, publisher api.PublicSessionId, streamType StreamType) (*mcuJanusPublisher, error) { +func (m *janusSFU) getPublisher(ctx context.Context, publisher api.PublicSessionId, streamType sfu.StreamType) (*janusPublisher, error) { // Do the direct check immediately as this should be the normal case. - key := getStreamId(publisher, streamType) + key := sfu.GetStreamId(publisher, streamType) m.mu.Lock() if result, found := m.publishers[key]; found { m.mu.Unlock() @@ -870,8 +890,8 @@ func (m *mcuJanus) getPublisher(ctx context.Context, publisher api.PublicSession } } -func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher api.PublicSessionId, streamType StreamType) (*JanusHandle, *mcuJanusPublisher, error) { - var pub *mcuJanusPublisher +func (m *janusSFU) getOrCreateSubscriberHandle(ctx context.Context, publisher api.PublicSessionId, streamType sfu.StreamType) (*janus.Handle, *janusPublisher, error) { + var pub *janusPublisher var err error if pub, err = m.getPublisher(ctx, publisher, streamType); err != nil { return nil, nil, err @@ -879,7 +899,7 @@ func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher ap session := m.session if session == nil { - return nil, nil, ErrNotConnected + return nil, nil, sfu.ErrNotConnected } handle, err := session.Attach(ctx, pluginVideoRoom) @@ -891,7 +911,7 @@ func (m *mcuJanus) getOrCreateSubscriberHandle(ctx context.Context, publisher ap return handle, pub, nil } -func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *janusSFU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } @@ -901,8 +921,8 @@ func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publ return nil, err } - client := &mcuJanusSubscriber{ - mcuJanusClient: mcuJanusClient{ + client := &janusSubscriber{ + janusClient: janusClient{ logger: m.logger, mcu: m, listener: listener, @@ -920,23 +940,23 @@ func (m *mcuJanus) NewSubscriber(ctx context.Context, listener McuListener, publ } client.handle.Store(handle) client.handleId.Store(handle.Id) - client.mcuJanusClient.handleEvent = client.handleEvent - client.mcuJanusClient.handleHangup = client.handleHangup - client.mcuJanusClient.handleDetached = client.handleDetached - client.mcuJanusClient.handleConnected = client.handleConnected - client.mcuJanusClient.handleSlowLink = client.handleSlowLink - client.mcuJanusClient.handleMedia = client.handleMedia + client.janusClient.handleEvent = client.handleEvent + client.janusClient.handleHangup = client.handleHangup + client.janusClient.handleDetached = client.handleDetached + client.janusClient.handleConnected = client.handleConnected + client.janusClient.handleSlowLink = client.handleSlowLink + client.janusClient.handleMedia = client.handleMedia m.registerClient(client) go client.run(handle, client.closeChan) m.stats.IncSubscriber(streamType) - statsSubscribersTotal.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsSubscribersTotal.WithLabelValues(string(streamType)).Inc() return client, nil } -func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller RemotePublisherController, streamType StreamType, settings NewPublisherSettings) (*mcuJanusRemotePublisher, error) { +func (m *janusSFU) getOrCreateRemotePublisher(ctx context.Context, controller sfu.RemotePublisherController, streamType sfu.StreamType, settings sfu.NewPublisherSettings) (*janusRemotePublisher, error) { m.mu.Lock() defer m.mu.Unlock() - pub, found := m.remotePublishers[getStreamId(controller.PublisherId(), streamType)] + pub, found := m.remotePublishers[sfu.GetStreamId(controller.PublisherId(), streamType)] if found { return pub, nil } @@ -952,7 +972,7 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re session := m.session if session == nil { - return nil, ErrNotConnected + return nil, sfu.ErrNotConnected } handle, err := session.Attach(ctx, pluginVideoRoom) @@ -985,9 +1005,9 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re port := getPluginIntValue(m.logger, response.PluginData, pluginVideoRoom, "port") rtcp_port := getPluginIntValue(m.logger, response.PluginData, pluginVideoRoom, "rtcp_port") - pub = &mcuJanusRemotePublisher{ - mcuJanusPublisher: mcuJanusPublisher{ - mcuJanusClient: mcuJanusClient{ + pub = &janusRemotePublisher{ + janusPublisher: janusPublisher{ + janusClient: janusClient{ logger: m.logger, mcu: m, @@ -1014,24 +1034,24 @@ func (m *mcuJanus) getOrCreateRemotePublisher(ctx context.Context, controller Re } pub.handle.Store(handle) pub.handleId.Store(handle.Id) - pub.mcuJanusClient.handleEvent = pub.handleEvent - pub.mcuJanusClient.handleHangup = pub.handleHangup - pub.mcuJanusClient.handleDetached = pub.handleDetached - pub.mcuJanusClient.handleConnected = pub.handleConnected - pub.mcuJanusClient.handleSlowLink = pub.handleSlowLink - pub.mcuJanusClient.handleMedia = pub.handleMedia + pub.janusClient.handleEvent = pub.handleEvent + pub.janusClient.handleHangup = pub.handleHangup + pub.janusClient.handleDetached = pub.handleDetached + pub.janusClient.handleConnected = pub.handleConnected + pub.janusClient.handleSlowLink = pub.handleSlowLink + pub.janusClient.handleMedia = pub.handleMedia if err := controller.StartPublishing(ctx, pub); err != nil { go pub.Close(context.Background()) return nil, err } - m.remotePublishers[getStreamId(controller.PublisherId(), streamType)] = pub + m.remotePublishers[sfu.GetStreamId(controller.PublisherId(), streamType)] = pub return pub, nil } -func (m *mcuJanus) NewRemotePublisher(ctx context.Context, listener McuListener, controller RemotePublisherController, streamType StreamType) (McuRemotePublisher, error) { +func (m *janusSFU) NewRemotePublisher(ctx context.Context, listener sfu.Listener, controller sfu.RemotePublisherController, streamType sfu.StreamType) (sfu.RemotePublisher, error) { if _, found := streamTypeUserIds[streamType]; !found { return nil, fmt.Errorf("unsupported stream type %s", streamType) } @@ -1040,7 +1060,7 @@ func (m *mcuJanus) NewRemotePublisher(ctx context.Context, listener McuListener, return nil, ErrRemoteStreamsNotSupported } - pub, err := m.getOrCreateRemotePublisher(ctx, controller, streamType, NewPublisherSettings{}) + pub, err := m.getOrCreateRemotePublisher(ctx, controller, streamType, sfu.NewPublisherSettings{}) if err != nil { return nil, err } @@ -1049,15 +1069,15 @@ func (m *mcuJanus) NewRemotePublisher(ctx context.Context, listener McuListener, return pub, nil } -func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener, publisher McuRemotePublisher) (McuRemoteSubscriber, error) { - pub, ok := publisher.(*mcuJanusRemotePublisher) +func (m *janusSFU) NewRemoteSubscriber(ctx context.Context, listener sfu.Listener, publisher sfu.RemotePublisher) (sfu.RemoteSubscriber, error) { + pub, ok := publisher.(*janusRemotePublisher) if !ok { return nil, errors.New("unsupported remote publisher") } session := m.session if session == nil { - return nil, ErrNotConnected + return nil, sfu.ErrNotConnected } handle, err := session.Attach(ctx, pluginVideoRoom) @@ -1067,9 +1087,9 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener m.logger.Printf("Attached subscriber to room %d of publisher %s in plugin %s in session %d as %d", pub.roomId, pub.id, pluginVideoRoom, session.Id, handle.Id) - client := &mcuJanusRemoteSubscriber{ - mcuJanusSubscriber: mcuJanusSubscriber{ - mcuJanusClient: mcuJanusClient{ + client := &janusRemoteSubscriber{ + janusSubscriber: janusSubscriber{ + janusClient: janusClient{ logger: m.logger, mcu: m, listener: listener, @@ -1090,20 +1110,20 @@ func (m *mcuJanus) NewRemoteSubscriber(ctx context.Context, listener McuListener pub.addRef() client.handle.Store(handle) client.handleId.Store(handle.Id) - client.mcuJanusClient.handleEvent = client.handleEvent - client.mcuJanusClient.handleHangup = client.handleHangup - client.mcuJanusClient.handleDetached = client.handleDetached - client.mcuJanusClient.handleConnected = client.handleConnected - client.mcuJanusClient.handleSlowLink = client.handleSlowLink - client.mcuJanusClient.handleMedia = client.handleMedia + client.janusClient.handleEvent = client.handleEvent + client.janusClient.handleHangup = client.handleHangup + client.janusClient.handleDetached = client.handleDetached + client.janusClient.handleConnected = client.handleConnected + client.janusClient.handleSlowLink = client.handleSlowLink + client.janusClient.handleMedia = client.handleMedia m.registerClient(client) go client.run(handle, client.closeChan) m.stats.IncSubscriber(publisher.StreamType()) - statsSubscribersTotal.WithLabelValues(string(publisher.StreamType())).Inc() + sfuinternal.StatsSubscribersTotal.WithLabelValues(string(publisher.StreamType())).Inc() return client, nil } -func (m *mcuJanus) UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) { +func (m *janusSFU) UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) { m.muClients.RLock() defer m.muClients.RUnlock() diff --git a/janus_client.go b/sfu/janus/janus/janus.go similarity index 84% rename from janus_client.go rename to sfu/janus/janus/janus.go index a3a7500..f70ab8c 100644 --- a/janus_client.go +++ b/sfu/janus/janus/janus.go @@ -26,7 +26,7 @@ * * Added error handling and improve functionality. */ -package signaling +package janus import ( "bytes" @@ -144,6 +144,23 @@ var msgtypes = map[string]func() any{ "trickle": func() any { return &TrickleMsg{} }, } +type ( + EventMsg = janus.EventMsg + HangupMsg = janus.HangupMsg + DetachedMsg = janus.DetachedMsg + WebRTCUpMsg = janus.WebRTCUpMsg + SlowLinkMsg = janus.SlowLinkMsg + MediaMsg = janus.MediaMsg + AckMsg = janus.AckMsg + SuccessMsg = janus.SuccessMsg + SuccessData = janus.SuccessData + ErrorMsg = janus.ErrorMsg + ErrorData = janus.ErrorData + + PluginInfo = janus.PluginInfo + PluginData = janus.PluginData +) + type InfoDependencies struct { Glib2 string `json:"glib2"` Jansson string `json:"jansson"` @@ -186,13 +203,13 @@ func unexpected(request string) error { return fmt.Errorf("unexpected response received to '%s' request", request) } -type transaction struct { +type Transaction struct { ch chan any incoming chan any closer *internal.Closer } -func (t *transaction) run() { +func (t *Transaction) Run() { for { select { case msg := <-t.incoming: @@ -203,16 +220,16 @@ func (t *transaction) run() { } } -func (t *transaction) add(msg any) { +func (t *Transaction) Add(msg any) { t.incoming <- msg } -func (t *transaction) quit() { +func (t *Transaction) Quit() { t.closer.Close() } -func newTransaction() *transaction { - t := &transaction{ +func newTransaction() *Transaction { + t := &Transaction{ ch: make(chan any, 1), incoming: make(chan any, 8), closer: internal.NewCloser(), @@ -220,7 +237,7 @@ func newTransaction() *transaction { return t } -func newRequest(method string) (api.StringMap, *transaction) { +func newRequest(method string) (api.StringMap, *Transaction) { req := make(api.StringMap, 8) req["janus"] = method return req, newTransaction() @@ -236,24 +253,24 @@ type dummyGatewayListener struct { func (l *dummyGatewayListener) ConnectionInterrupted() { } -type JanusGatewayInterface interface { +type GatewayInterface interface { Info(context.Context) (*InfoMsg, error) - Create(context.Context) (*JanusSession, error) + Create(context.Context) (*Session, error) Close() error - send(api.StringMap, *transaction) (uint64, error) - removeTransaction(uint64) + Send(api.StringMap, *Transaction) (uint64, error) + RemoveTransaction(uint64) - removeSession(*JanusSession) + RemoveSession(*Session) } // Gateway represents a connection to an instance of the Janus Gateway. -type JanusGateway struct { +type Gateway struct { listener GatewayListener // Sessions is a map of the currently active sessions to the gateway. // +checklocks:Mutex - Sessions map[uint64]*JanusSession + Sessions map[uint64]*Session // Access to the Sessions map should be synchronized with the Gateway.Lock() // and Gateway.Unlock() methods provided by the embedded sync.Mutex. @@ -263,7 +280,7 @@ type JanusGateway struct { conn *websocket.Conn nextTransaction atomic.Uint64 // +checklocks:Mutex - transactions map[uint64]*transaction + transactions map[uint64]*Transaction closer *internal.Closer @@ -289,7 +306,7 @@ type JanusGateway struct { // return gateway, nil // } -func NewJanusGateway(ctx context.Context, wsURL string, listener GatewayListener) (*JanusGateway, error) { +func NewGateway(ctx context.Context, wsURL string, listener GatewayListener) (*Gateway, error) { conn, _, err := janusDialer.DialContext(ctx, wsURL, nil) if err != nil { return nil, err @@ -298,11 +315,11 @@ func NewJanusGateway(ctx context.Context, wsURL string, listener GatewayListener if listener == nil { listener = new(dummyGatewayListener) } - gateway := &JanusGateway{ + gateway := &Gateway{ conn: conn, listener: listener, - transactions: make(map[uint64]*transaction), - Sessions: make(map[uint64]*JanusSession), + transactions: make(map[uint64]*Transaction), + Sessions: make(map[uint64]*Session), closer: internal.NewCloser(), } @@ -312,7 +329,7 @@ func NewJanusGateway(ctx context.Context, wsURL string, listener GatewayListener } // Close closes the underlying connection to the Gateway. -func (gateway *JanusGateway) Close() error { +func (gateway *Gateway) Close() error { gateway.closer.Close() gateway.writeMu.Lock() if gateway.conn == nil { @@ -327,7 +344,7 @@ func (gateway *JanusGateway) Close() error { return err } -func (gateway *JanusGateway) cancelTransactions() { +func (gateway *Gateway) cancelTransactions() { msg := &janus.ErrorMsg{ Err: janus.ErrorData{ Code: 500, @@ -336,16 +353,16 @@ func (gateway *JanusGateway) cancelTransactions() { } gateway.Lock() for _, t := range gateway.transactions { - go func(t *transaction) { - t.add(msg) - t.quit() + go func(t *Transaction) { + t.Add(msg) + t.Quit() }(t) } clear(gateway.transactions) gateway.Unlock() } -func (gateway *JanusGateway) removeTransaction(id uint64) { +func (gateway *Gateway) RemoveTransaction(id uint64) { gateway.Lock() t, found := gateway.transactions[id] if found { @@ -353,11 +370,11 @@ func (gateway *JanusGateway) removeTransaction(id uint64) { } gateway.Unlock() if t != nil { - t.quit() + t.Quit() } } -func (gateway *JanusGateway) send(msg api.StringMap, t *transaction) (uint64, error) { +func (gateway *Gateway) Send(msg api.StringMap, t *Transaction) (uint64, error) { id := gateway.nextTransaction.Add(1) msg["transaction"] = strconv.FormatUint(id, 10) data, err := json.Marshal(msg) @@ -365,7 +382,7 @@ func (gateway *JanusGateway) send(msg api.StringMap, t *transaction) (uint64, er return 0, err } - go t.run() + go t.Run() gateway.Lock() gateway.transactions[id] = t gateway.Unlock() @@ -373,14 +390,14 @@ func (gateway *JanusGateway) send(msg api.StringMap, t *transaction) (uint64, er gateway.writeMu.Lock() if gateway.conn == nil { gateway.writeMu.Unlock() - gateway.removeTransaction(id) + gateway.RemoveTransaction(id) return 0, errors.New("not connected") } err = gateway.conn.WriteMessage(websocket.TextMessage, data) gateway.writeMu.Unlock() if err != nil { - gateway.removeTransaction(id) + gateway.RemoveTransaction(id) return 0, err } return id, nil @@ -390,7 +407,7 @@ func passMsg(ch chan any, msg any) { ch <- msg } -func (gateway *JanusGateway) ping() { +func (gateway *Gateway) ping() { ticker := time.NewTicker(time.Second * 30) defer ticker.Stop() @@ -415,7 +432,7 @@ loop: } } -func (gateway *JanusGateway) recv() { +func (gateway *Gateway) recv() { var decodeBuffer bytes.Buffer for { // Read message from Gateway @@ -522,12 +539,12 @@ func (gateway *JanusGateway) recv() { } // Pass msg - transaction.add(msg) + transaction.Add(msg) } } } -func waitForMessage(ctx context.Context, t *transaction) (any, error) { +func waitForMessage(ctx context.Context, t *Transaction) (any, error) { select { case <-ctx.Done(): return nil, ctx.Err() @@ -538,13 +555,13 @@ func waitForMessage(ctx context.Context, t *transaction) (any, error) { // Info sends an info request to the Gateway. // On success, an InfoMsg will be returned and error will be nil. -func (gateway *JanusGateway) Info(ctx context.Context) (*InfoMsg, error) { +func (gateway *Gateway) Info(ctx context.Context) (*InfoMsg, error) { req, ch := newRequest("info") - id, err := gateway.send(req, ch) + id, err := gateway.Send(req, ch) if err != nil { return nil, err } - defer gateway.removeTransaction(id) + defer gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -563,13 +580,13 @@ func (gateway *JanusGateway) Info(ctx context.Context) (*InfoMsg, error) { // Create sends a create request to the Gateway. // On success, a new Session will be returned and error will be nil. -func (gateway *JanusGateway) Create(ctx context.Context) (*JanusSession, error) { +func (gateway *Gateway) Create(ctx context.Context) (*Session, error) { req, ch := newRequest("create") - id, err := gateway.send(req, ch) + id, err := gateway.Send(req, ch) if err != nil { return nil, err } - defer gateway.removeTransaction(id) + defer gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -584,10 +601,10 @@ func (gateway *JanusGateway) Create(ctx context.Context) (*JanusSession, error) } // Create new session - session := new(JanusSession) + session := new(Session) session.gateway = gateway session.Id = success.Data.ID - session.Handles = make(map[uint64]*JanusHandle) + session.Handles = make(map[uint64]*Handle) // Store this session gateway.Lock() @@ -597,44 +614,52 @@ func (gateway *JanusGateway) Create(ctx context.Context) (*JanusSession, error) return session, nil } -func (gateway *JanusGateway) removeSession(session *JanusSession) { +func (gateway *Gateway) RemoveSession(session *Session) { gateway.Lock() defer gateway.Unlock() delete(gateway.Sessions, session.Id) } // Session represents a session instance on the Janus Gateway. -type JanusSession struct { +type Session struct { // Id is the session_id of this session Id uint64 // Handles is a map of plugin handles within this session // +checklocks:Mutex - Handles map[uint64]*JanusHandle + Handles map[uint64]*Handle // Access to the Handles map should be synchronized with the Session.Lock() // and Session.Unlock() methods provided by the embedded sync.Mutex. sync.Mutex - gateway JanusGatewayInterface + gateway GatewayInterface } -func (session *JanusSession) send(msg api.StringMap, t *transaction) (uint64, error) { +func NewSession(id uint64, g GatewayInterface) *Session { + return &Session{ + Id: id, + Handles: make(map[uint64]*Handle), + gateway: g, + } +} + +func (session *Session) send(msg api.StringMap, t *Transaction) (uint64, error) { msg["session_id"] = session.Id - return session.gateway.send(msg, t) + return session.gateway.Send(msg, t) } // Attach sends an attach request to the Gateway within this session. // plugin should be the unique string of the plugin to attach to. // On success, a new Handle will be returned and error will be nil. -func (session *JanusSession) Attach(ctx context.Context, plugin string) (*JanusHandle, error) { +func (session *Session) Attach(ctx context.Context, plugin string) (*Handle, error) { req, ch := newRequest("attach") req["plugin"] = plugin id, err := session.send(req, ch) if err != nil { return nil, err } - defer session.gateway.removeTransaction(id) + defer session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -648,7 +673,7 @@ func (session *JanusSession) Attach(ctx context.Context, plugin string) (*JanusH return nil, msg } - handle := new(JanusHandle) + handle := new(Handle) handle.session = session handle.Id = success.Data.ID handle.Events = make(chan any, 8) @@ -662,13 +687,13 @@ func (session *JanusSession) Attach(ctx context.Context, plugin string) (*JanusH // KeepAlive sends a keep-alive request to the Gateway. // On success, an AckMsg will be returned and error will be nil. -func (session *JanusSession) KeepAlive(ctx context.Context) (*janus.AckMsg, error) { +func (session *Session) KeepAlive(ctx context.Context) (*janus.AckMsg, error) { req, ch := newRequest("keepalive") id, err := session.send(req, ch) if err != nil { return nil, err } - defer session.gateway.removeTransaction(id) + defer session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -687,13 +712,13 @@ func (session *JanusSession) KeepAlive(ctx context.Context) (*janus.AckMsg, erro // Destroy sends a destroy request to the Gateway to tear down this session. // On success, the Session will be removed from the Gateway.Sessions map, an // AckMsg will be returned and error will be nil. -func (session *JanusSession) Destroy(ctx context.Context) (*janus.AckMsg, error) { +func (session *Session) Destroy(ctx context.Context) (*janus.AckMsg, error) { req, ch := newRequest("destroy") id, err := session.send(req, ch) if err != nil { return nil, err } - defer session.gateway.removeTransaction(id) + defer session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -708,13 +733,13 @@ func (session *JanusSession) Destroy(ctx context.Context) (*janus.AckMsg, error) } // Remove this session from the gateway - session.gateway.removeSession(session) + session.gateway.RemoveSession(session) return ack, nil } // Handle represents a handle to a plugin instance on the Gateway. -type JanusHandle struct { +type Handle struct { // Id is the handle_id of this plugin handle Id uint64 @@ -728,16 +753,16 @@ type JanusHandle struct { // related to this handle from the gateway. Events chan any - session *JanusSession + session *Session } -func (handle *JanusHandle) send(msg api.StringMap, t *transaction) (uint64, error) { +func (handle *Handle) send(msg api.StringMap, t *Transaction) (uint64, error) { msg["handle_id"] = handle.Id return handle.session.send(msg, t) } // send sync request -func (handle *JanusHandle) Request(ctx context.Context, body any) (*janus.SuccessMsg, error) { +func (handle *Handle) Request(ctx context.Context, body any) (*janus.SuccessMsg, error) { req, ch := newRequest("message") if body != nil { req["body"] = body @@ -746,7 +771,7 @@ func (handle *JanusHandle) Request(ctx context.Context, body any) (*janus.Succes if err != nil { return nil, err } - defer handle.session.gateway.removeTransaction(id) + defer handle.session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -766,7 +791,7 @@ func (handle *JanusHandle) Request(ctx context.Context, body any) (*janus.Succes // body should be the plugin data to be passed to the plugin, and jsep should // contain an optional SDP offer/answer to establish a WebRTC PeerConnection. // On success, an EventMsg will be returned and error will be nil. -func (handle *JanusHandle) Message(ctx context.Context, body, jsep any) (*janus.EventMsg, error) { +func (handle *Handle) Message(ctx context.Context, body, jsep any) (*janus.EventMsg, error) { req, ch := newRequest("message") if body != nil { req["body"] = body @@ -778,7 +803,7 @@ func (handle *JanusHandle) Message(ctx context.Context, body, jsep any) (*janus. if err != nil { return nil, err } - defer handle.session.gateway.removeTransaction(id) + defer handle.session.gateway.RemoveTransaction(id) GetMessage: // No tears.. msg, err := waitForMessage(ctx, ch) @@ -807,14 +832,14 @@ GetMessage: // No tears.. // } // // On success, an AckMsg will be returned and error will be nil. -func (handle *JanusHandle) Trickle(ctx context.Context, candidate any) (*janus.AckMsg, error) { +func (handle *Handle) Trickle(ctx context.Context, candidate any) (*janus.AckMsg, error) { req, ch := newRequest("trickle") req["candidate"] = candidate id, err := handle.send(req, ch) if err != nil { return nil, err } - defer handle.session.gateway.removeTransaction(id) + defer handle.session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -834,14 +859,14 @@ func (handle *JanusHandle) Trickle(ctx context.Context, candidate any) (*janus.A // a new PeerConnection with a plugin. // candidates should be an array of ICE candidates. // On success, an AckMsg will be returned and error will be nil. -func (handle *JanusHandle) TrickleMany(ctx context.Context, candidates any) (*janus.AckMsg, error) { +func (handle *Handle) TrickleMany(ctx context.Context, candidates any) (*janus.AckMsg, error) { req, ch := newRequest("trickle") req["candidates"] = candidates id, err := handle.send(req, ch) if err != nil { return nil, err } - handle.session.gateway.removeTransaction(id) + handle.session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { @@ -859,13 +884,13 @@ func (handle *JanusHandle) TrickleMany(ctx context.Context, candidates any) (*ja // Detach sends a detach request to the Gateway to remove this handle. // On success, an AckMsg will be returned and error will be nil. -func (handle *JanusHandle) Detach(ctx context.Context) (*janus.AckMsg, error) { +func (handle *Handle) Detach(ctx context.Context) (*janus.AckMsg, error) { req, ch := newRequest("detach") id, err := handle.send(req, ch) if err != nil { return nil, err } - defer handle.session.gateway.removeTransaction(id) + defer handle.session.gateway.RemoveTransaction(id) msg, err := waitForMessage(ctx, ch) if err != nil { diff --git a/sfu/janus/janus_test.go b/sfu/janus/janus_test.go new file mode 100644 index 0000000..6f58b26 --- /dev/null +++ b/sfu/janus/janus_test.go @@ -0,0 +1,881 @@ +/** + * 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 janus + +import ( + "context" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/log" + metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + janustest "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/test" +) + +const ( + testTimeout = 10 * time.Second +) + +func TestMcuJanusStats(t *testing.T) { + t.Parallel() + metricstest.CollectAndLint(t, janusMcuStats...) +} + +func newMcuJanusForTesting(t *testing.T) (*janusSFU, *janustest.JanusGateway) { + gateway := janustest.NewJanusGateway(t) + + config := goconf.NewConfigFile() + if strings.Contains(t.Name(), "Filter") { + config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") + } + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + mcu, err := NewJanusSFU(ctx, "", config) + require.NoError(t, err) + t.Cleanup(func() { + mcu.Stop() + }) + + mcuJanus := mcu.(*janusSFU) + mcuJanus.createJanusGateway = func(ctx context.Context, wsURL string, listener janus.GatewayListener) (janus.GatewayInterface, error) { + return gateway, nil + } + require.NoError(t, mcu.Start(ctx)) + return mcuJanus, gateway +} + +type TestMcuListener struct { + id api.PublicSessionId +} + +func (t *TestMcuListener) PublicId() api.PublicSessionId { + return t.id +} + +func (t *TestMcuListener) OnUpdateOffer(client sfu.Client, offer api.StringMap) { + +} + +func (t *TestMcuListener) OnIceCandidate(client sfu.Client, candidate any) { + +} + +func (t *TestMcuListener) OnIceCompleted(client sfu.Client) { + +} + +func (t *TestMcuListener) SubscriberSidUpdated(subscriber sfu.Subscriber) { + +} + +func (t *TestMcuListener) PublisherClosed(publisher sfu.Publisher) { + +} + +func (t *TestMcuListener) SubscriberClosed(subscriber sfu.Subscriber) { + +} + +type TestMcuController struct { + id api.PublicSessionId +} + +func (c *TestMcuController) PublisherId() api.PublicSessionId { + return c.id +} + +func (c *TestMcuController) StartPublishing(ctx context.Context, publisher sfu.RemotePublisherProperties) error { + // TODO: Check parameters? + return nil +} + +func (c *TestMcuController) StopPublishing(ctx context.Context, publisher sfu.RemotePublisherProperties) error { + // TODO: Check parameters? + return nil +} + +func (c *TestMcuController) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { + streams := []sfu.PublisherStream{ + { + Mid: "0", + Mindex: 0, + Type: "audio", + Codec: "opus", + }, + } + return streams, nil +} + +type TestMcuInitiator struct { + country geoip.Country +} + +func (i *TestMcuInitiator) Country() geoip.Country { + return i.country +} + +func Test_JanusPublisherFilterOffer(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + if assert.NotNil(jsep) { + // The SDP received by Janus will be filtered from blocked candidates. + if sdpValue, found := jsep["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + + return &janus.EventMsg{ + Jsep: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioOnly, + }, + }, nil + }, + "trickle": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.AckMsg{}, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + // Send offer containing candidates that will be blocked / filtered. + data := &api.MessageClientMessageData{ + Type: "offer", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioOnly, + }, + } + require.NoError(data.CheckValid()) + + var wg sync.WaitGroup + wg.Add(1) + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer wg.Done() + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + wg.Wait() + + data = &api.MessageClientMessageData{ + Type: "candidate", + Payload: api.StringMap{ + "candidate": api.StringMap{ + "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer wg.Done() + + assert.ErrorContains(err, "filtered") + assert.Empty(m) + }) + wg.Wait() + + data = &api.MessageClientMessageData{ + Type: "candidate", + Payload: api.StringMap{ + "candidate": api.StringMap{ + "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer wg.Done() + + assert.NoError(err) + assert.Empty(m) + }) + wg.Wait() +} + +func Test_JanusSubscriberFilterAnswer(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "start": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + if assert.NotNil(jsep) { + // The SDP received by Janus will be filtered from blocked candidates. + if sdpValue, found := jsep["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpAnswerAudioOnlyNoFilter, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "room": room.Id(), + "started": true, + "videoroom": "event", + }, + }, + }, nil + }, + "trickle": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.AckMsg{}, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + listener2 := &TestMcuListener{ + id: pubId, + } + + initiator2 := &TestMcuInitiator{ + country: "DE", + } + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + defer sub.Close(context.Background()) + + // Send answer containing candidates that will be blocked / filtered. + data := &api.MessageClientMessageData{ + Type: "answer", + Payload: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioOnly, + }, + } + require.NoError(data.CheckValid()) + + var wg sync.WaitGroup + wg.Add(1) + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer wg.Done() + + if assert.NoError(err) { + assert.Empty(m) + } + }) + wg.Wait() + + data = &api.MessageClientMessageData{ + Type: "candidate", + Payload: api.StringMap{ + "candidate": api.StringMap{ + "candidate": "candidate:1 1 UDP 1685987071 192.168.0.1 49203 typ srflx raddr 198.51.100.7 rport 51556", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer wg.Done() + + assert.ErrorContains(err, "filtered") + assert.Empty(m) + }) + wg.Wait() + + data = &api.MessageClientMessageData{ + Type: "candidate", + Payload: api.StringMap{ + "candidate": api.StringMap{ + "candidate": "candidate:0 1 UDP 2122194687 198.51.100.7 51556 typ host", + }, + }, + } + require.NoError(data.CheckValid()) + wg.Add(1) + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer wg.Done() + + assert.NoError(err) + assert.Empty(m) + }) + wg.Wait() +} + +func Test_JanusPublisherGetStreamsAudioOnly(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + if assert.NotNil(jsep) { + if sdpValue, found := jsep["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpOfferAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + + return &janus.EventMsg{ + Jsep: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioOnly, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + data := &api.MessageClientMessageData{ + Type: "offer", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioOnly, + }, + } + require.NoError(data.CheckValid()) + + done := make(chan struct{}) + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpAnswerAudioOnly, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + <-done + + if sb, ok := pub.(*janusPublisher); assert.True(ok, "expected publisher with streams support, got %T", pub) { + if streams, err := sb.GetStreams(ctx); assert.NoError(err) { + if assert.Len(streams, 1) { + stream := streams[0] + assert.Equal("audio", stream.Type) + assert.Equal("audio", stream.Mid) + assert.Equal(0, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("opus", stream.Codec) + assert.False(stream.Stereo) + assert.False(stream.Fec) + assert.False(stream.Dtx) + } + } + } +} + +func Test_JanusPublisherGetStreamsAudioVideo(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + if assert.NotNil(jsep) { + _, found := jsep["sdp"] + assert.True(found) + } + + return &janus.EventMsg{ + Jsep: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + data := &api.MessageClientMessageData{ + Type: "offer", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(data.CheckValid()) + + // Defer sending of offer / answer so "GetStreams" will wait. + go func() { + done := make(chan struct{}) + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + <-done + }() + + if sb, ok := pub.(*janusPublisher); assert.True(ok, "expected publisher with streams support, got %T", pub) { + if streams, err := sb.GetStreams(ctx); assert.NoError(err) { + if assert.Len(streams, 2) { + stream := streams[0] + assert.Equal("audio", stream.Type) + assert.Equal("audio", stream.Mid) + assert.Equal(0, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("opus", stream.Codec) + assert.False(stream.Stereo) + assert.False(stream.Fec) + assert.False(stream.Dtx) + + stream = streams[1] + assert.Equal("video", stream.Type) + assert.Equal("video", stream.Mid) + assert.Equal(1, stream.Mindex) + assert.False(stream.Disabled) + assert.Equal("H264", stream.Codec) + assert.Equal("4d0028", stream.ProfileH264) + } + } + } +} + +type mockBandwidthStats struct { + incoming uint64 + outgoing uint64 +} + +func (s *mockBandwidthStats) SetBandwidth(incoming uint64, outgoing uint64) { + s.incoming = incoming + s.outgoing = outgoing +} + +func Test_JanusPublisherSubscriber(t *testing.T) { + t.Parallel() + + stats := &mockBandwidthStats{} + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{}) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + // Bandwidth for unknown handles is ignored. + mcu.UpdateBandwidth(1234, "video", api.BandwidthFromBytes(100), api.BandwidthFromBytes(200)) + mcu.updateBandwidthStats(stats) + assert.EqualValues(0, stats.incoming) + assert.EqualValues(0, stats.outgoing) + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + janusPub, ok := pub.(*janusPublisher) + require.True(ok) + + assert.Nil(mcu.Bandwidth()) + assert.Nil(janusPub.Bandwidth()) + mcu.UpdateBandwidth(janusPub.Handle(), "video", api.BandwidthFromBytes(1000), api.BandwidthFromBytes(2000)) + if bw := janusPub.Bandwidth(); assert.NotNil(bw) { + assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(2000), bw.Received) + } + if bw := mcu.Bandwidth(); assert.NotNil(bw) { + assert.Equal(api.BandwidthFromBytes(1000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(2000), bw.Received) + } + mcu.updateBandwidthStats(stats) + assert.EqualValues(2000, stats.incoming) + assert.EqualValues(1000, stats.outgoing) + + listener2 := &TestMcuListener{ + id: pubId, + } + + initiator2 := &TestMcuInitiator{ + country: "DE", + } + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + defer sub.Close(context.Background()) + + janusSub, ok := sub.(*janusSubscriber) + require.True(ok) + + assert.Nil(janusSub.Bandwidth()) + mcu.UpdateBandwidth(janusSub.Handle(), "video", api.BandwidthFromBytes(3000), api.BandwidthFromBytes(4000)) + if bw := janusSub.Bandwidth(); assert.NotNil(bw) { + assert.Equal(api.BandwidthFromBytes(3000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(4000), bw.Received) + } + if bw := mcu.Bandwidth(); assert.NotNil(bw) { + assert.Equal(api.BandwidthFromBytes(4000), bw.Sent) + assert.Equal(api.BandwidthFromBytes(6000), bw.Received) + } + assert.EqualValues(2000, stats.incoming) + assert.EqualValues(1000, stats.outgoing) + mcu.updateBandwidthStats(stats) + assert.EqualValues(6000, stats.incoming) + assert.EqualValues(4000, stats.outgoing) +} + +func Test_JanusSubscriberPublisher(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{}) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + ready := make(chan struct{}) + done := make(chan struct{}) + + go func() { + defer close(done) + time.Sleep(100 * time.Millisecond) + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + if !assert.NoError(err) { + return + } + + defer func() { + <-ready + pub.Close(context.Background()) + }() + }() + + listener2 := &TestMcuListener{ + id: pubId, + } + + initiator2 := &TestMcuInitiator{ + country: "DE", + } + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + defer sub.Close(context.Background()) + close(ready) + <-done +} + +func Test_JanusSubscriberRequestOffer(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + var originalOffer atomic.Value + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + if assert.NotNil(jsep) { + if sdp, found := jsep["sdp"]; assert.True(found) { + originalOffer.Store(strings.ReplaceAll(sdp.(string), "\r\n", "\n")) + } + } + + return &janus.EventMsg{ + Jsep: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + defer pub.Close(context.Background()) + + listener2 := &TestMcuListener{ + id: pubId, + } + + initiator2 := &TestMcuInitiator{ + country: "DE", + } + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + defer sub.Close(context.Background()) + + go func() { + data := &api.MessageClientMessageData{ + Type: "offer", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + if !assert.NoError(data.CheckValid()) { + return + } + + done := make(chan struct{}) + pub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + assert.Equal(mock.MockSdpAnswerAudioAndVideo, strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + }) + <-done + }() + + data := &api.MessageClientMessageData{ + Type: "requestoffer", + } + require.NoError(data.CheckValid()) + + done := make(chan struct{}) + sub.SendMessage(ctx, &api.MessageClientMessage{}, data, func(err error, m api.StringMap) { + defer close(done) + + if assert.NoError(err) { + if sdpValue, found := m["sdp"]; assert.True(found) { + sdpText, ok := sdpValue.(string) + if assert.True(ok) { + if sdp := originalOffer.Load(); assert.NotNil(sdp) { + assert.Equal(sdp.(string), strings.ReplaceAll(sdpText, "\r\n", "\n")) + } + } + } + } + }) + <-done +} + +func Test_JanusRemotePublisher(t *testing.T) { + t.Parallel() + assert := assert.New(t) + require := require.New(t) + + var added atomic.Int32 + var removed atomic.Int32 + + mcu, gateway := newMcuJanusForTesting(t) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "add_remote_publisher": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + assert.Nil(jsep) + if streams := body["streams"].([]any); assert.Len(streams, 1) { + if stream, ok := api.ConvertStringMap(streams[0]); assert.True(ok, "not a string map: %+v", streams[0]) { + assert.Equal("0", stream["mid"]) + assert.EqualValues(0, stream["mindex"]) + assert.Equal("audio", stream["type"]) + assert.Equal("opus", stream["codec"]) + } + } + added.Add(1) + return &janus.SuccessMsg{ + PluginData: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "id": 12345, + "port": 10000, + "rtcp_port": 10001, + }, + }, + }, nil + }, + "remove_remote_publisher": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + assert.Nil(jsep) + removed.Add(1) + return &janus.SuccessMsg{ + PluginData: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{}, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + listener1 := &TestMcuListener{ + id: "publisher-id", + } + + controller := &TestMcuController{ + id: listener1.id, + } + + pub, err := mcu.NewRemotePublisher(ctx, listener1, controller, sfu.StreamTypeVideo) + require.NoError(err) + defer pub.Close(context.Background()) + + assert.EqualValues(1, added.Load()) + assert.EqualValues(0, removed.Load()) + + listener2 := &TestMcuListener{ + id: "subscriber-id", + } + + sub, err := mcu.NewRemoteSubscriber(ctx, listener2, pub) + require.NoError(err) + defer sub.Close(context.Background()) + + pub.Close(context.Background()) + + assert.EqualValues(1, added.Load()) + // The publisher is ref-counted, and still referenced by the subscriber. + assert.EqualValues(0, removed.Load()) + + sub.Close(context.Background()) + + assert.EqualValues(1, added.Load()) + assert.EqualValues(1, removed.Load()) +} diff --git a/mcu_test.go b/sfu/janus/mcu_test.go similarity index 88% rename from mcu_test.go rename to sfu/janus/mcu_test.go index 7f4e9e3..1f1714a 100644 --- a/mcu_test.go +++ b/sfu/janus/mcu_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 janus import ( "context" @@ -36,6 +36,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -98,9 +99,9 @@ func (m *TestMCU) GetServerInfoSfu() *talk.BackendServerInfoSfu { return nil } -func (m *TestMCU) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *TestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { var maxBitrate api.Bandwidth - if streamType == StreamTypeScreen { + if streamType == sfu.StreamTypeScreen { maxBitrate = TestMaxBitrateScreen } else { maxBitrate = TestMaxBitrateVideo @@ -143,7 +144,7 @@ func (m *TestMCU) GetPublisher(id api.PublicSessionId) *TestMCUPublisher { return m.publishers[id] } -func (m *TestMCU) NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *TestMCU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { m.mu.Lock() defer m.mu.Unlock() @@ -171,7 +172,7 @@ type TestMCUClient struct { id string sid string - streamType StreamType + streamType sfu.StreamType } func (c *TestMCUClient) Id() string { @@ -182,7 +183,7 @@ func (c *TestMCUClient) Sid() string { return c.sid } -func (c *TestMCUClient) StreamType() StreamType { +func (c *TestMCUClient) StreamType() sfu.StreamType { return c.streamType } @@ -204,7 +205,7 @@ func (c *TestMCUClient) isClosed() bool { type TestMCUPublisher struct { TestMCUClient - settings NewPublisherSettings + settings sfu.NewPublisherSettings sdp string } @@ -213,11 +214,11 @@ func (p *TestMCUPublisher) PublisherId() api.PublicSessionId { return api.PublicSessionId(p.id) } -func (p *TestMCUPublisher) HasMedia(mt MediaType) bool { +func (p *TestMCUPublisher) HasMedia(mt sfu.MediaType) bool { return (p.settings.MediaTypes & mt) == mt } -func (p *TestMCUPublisher) SetMedia(mt MediaType) { +func (p *TestMCUPublisher) SetMedia(mt sfu.MediaType) { p.settings.MediaTypes = mt } @@ -255,7 +256,7 @@ func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *api.Message }() } -func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]PublisherStream, error) { +func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { return nil, errors.New("not implemented") } diff --git a/mcu_janus_publisher.go b/sfu/janus/publisher.go similarity index 85% rename from mcu_janus_publisher.go rename to sfu/janus/publisher.go index cd642da..9a17eb6 100644 --- a/mcu_janus_publisher.go +++ b/sfu/janus/publisher.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 janus import ( "context" @@ -29,11 +29,13 @@ import ( "strings" "sync/atomic" - "github.com/notedit/janus-go" "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) const ( @@ -46,11 +48,11 @@ const ( sdpHasAnswer = 2 ) -type mcuJanusPublisher struct { - mcuJanusClient +type janusPublisher struct { + janusClient id api.PublicSessionId - settings NewPublisherSettings + settings sfu.NewPublisherSettings stats publisherStatsCounter sdpFlags internal.Flags sdpReady *internal.Closer @@ -58,11 +60,11 @@ type mcuJanusPublisher struct { answerSdp atomic.Pointer[sdp.SessionDescription] } -func (p *mcuJanusPublisher) PublisherId() api.PublicSessionId { +func (p *janusPublisher) PublisherId() api.PublicSessionId { return p.id } -func (p *mcuJanusPublisher) handleEvent(event *janus.EventMsg) { +func (p *janusPublisher) handleEvent(event *janus.EventMsg) { if videoroom := getPluginStringValue(event.Plugindata, pluginVideoRoom, "videoroom"); videoroom != "" { ctx := context.TODO() switch videoroom { @@ -79,22 +81,22 @@ func (p *mcuJanusPublisher) handleEvent(event *janus.EventMsg) { } } -func (p *mcuJanusPublisher) handleHangup(event *janus.HangupMsg) { +func (p *janusPublisher) handleHangup(event *janus.HangupMsg) { p.logger.Printf("Publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } -func (p *mcuJanusPublisher) handleDetached(event *janus.DetachedMsg) { +func (p *janusPublisher) handleDetached(event *janus.DetachedMsg) { p.logger.Printf("Publisher %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } -func (p *mcuJanusPublisher) handleConnected(event *janus.WebRTCUpMsg) { +func (p *janusPublisher) handleConnected(event *janus.WebRTCUpMsg) { p.logger.Printf("Publisher %d received connected", p.handleId.Load()) - p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) + p.mcu.publisherConnected.Notify(string(sfu.GetStreamId(p.id, p.streamType))) } -func (p *mcuJanusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { +func (p *janusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { p.logger.Printf("Publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { @@ -102,9 +104,9 @@ func (p *mcuJanusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { } } -func (p *mcuJanusPublisher) handleMedia(event *janus.MediaMsg) { - mediaType := StreamType(event.Type) - if mediaType == StreamTypeVideo && p.streamType == StreamTypeScreen { +func (p *janusPublisher) handleMedia(event *janus.MediaMsg) { + mediaType := sfu.StreamType(event.Type) + if mediaType == sfu.StreamTypeVideo && p.streamType == sfu.StreamTypeScreen { // We want to differentiate between audio, video and screensharing mediaType = p.streamType } @@ -112,15 +114,15 @@ func (p *mcuJanusPublisher) handleMedia(event *janus.MediaMsg) { p.stats.EnableStream(mediaType, event.Receiving) } -func (p *mcuJanusPublisher) HasMedia(mt MediaType) bool { +func (p *janusPublisher) HasMedia(mt sfu.MediaType) bool { return (p.settings.MediaTypes & mt) == mt } -func (p *mcuJanusPublisher) SetMedia(mt MediaType) { +func (p *janusPublisher) SetMedia(mt sfu.MediaType) { p.settings.MediaTypes = mt } -func (p *mcuJanusPublisher) NotifyReconnected() { +func (p *janusPublisher) NotifyReconnected() { ctx := context.TODO() handle, session, roomId, _, err := p.mcu.getOrCreatePublisherHandle(ctx, p.id, p.streamType, p.settings) if err != nil { @@ -141,7 +143,7 @@ func (p *mcuJanusPublisher) NotifyReconnected() { p.logger.Printf("Publisher %s reconnected on handle %d", p.id, p.handleId.Load()) } -func (p *mcuJanusPublisher) Close(ctx context.Context) { +func (p *janusPublisher) Close(ctx context.Context) { notify := false p.mu.Lock() if handle := p.handle.Load(); handle != nil && p.roomId != 0 { @@ -155,7 +157,7 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { p.logger.Printf("Room %d destroyed", p.roomId) } p.mcu.mu.Lock() - delete(p.mcu.publishers, getStreamId(p.id, p.streamType)) + delete(p.mcu.publishers, sfu.GetStreamId(p.id, p.streamType)) p.mcu.mu.Unlock() p.roomId = 0 notify = true @@ -166,15 +168,15 @@ func (p *mcuJanusPublisher) Close(ctx context.Context) { p.stats.Reset() if notify { - statsPublishersCurrent.WithLabelValues(string(p.streamType)).Dec() + sfuinternal.StatsPublishersCurrent.WithLabelValues(string(p.streamType)).Dec() p.mcu.unregisterClient(p) p.listener.PublisherClosed(p) } - p.mcuJanusClient.Close(ctx) + p.janusClient.Close(ctx) } -func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { - statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() +func (p *janusPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + sfuinternal.StatsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { case "offer": @@ -270,7 +272,7 @@ func getFmtpValue(fmtp string, key string) (string, bool) { return "", false } -func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, error) { +func (p *janusPublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { offerSdp := p.offerSdp.Load() answerSdp := p.answerSdp.Load() if offerSdp == nil || answerSdp == nil { @@ -287,14 +289,14 @@ func (p *mcuJanusPublisher) GetStreams(ctx context.Context) ([]PublisherStream, } } - var streams []PublisherStream + var streams []sfu.PublisherStream for idx, m := range answerSdp.MediaDescriptions { mid, found := m.Attribute(sdp.AttrKeyMID) if !found { continue } - s := PublisherStream{ + s := sfu.PublisherStream{ Mid: mid, Mindex: idx, Type: m.MediaName.Media, @@ -403,10 +405,10 @@ func getPublisherRemoteId(id api.PublicSessionId, remoteId api.PublicSessionId, return fmt.Sprintf("%s-%s@%s:%d:%d", id, remoteId, hostname, port, rtcpPort) } -func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *janusPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { handle := p.handle.Load() if handle == nil { - return ErrNotConnected + return sfu.ErrNotConnected } msg := api.StringMap{ @@ -445,10 +447,10 @@ func (p *mcuJanusPublisher) PublishRemote(ctx context.Context, remoteId api.Publ return nil } -func (p *mcuJanusPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { +func (p *janusPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { handle := p.handle.Load() if handle == nil { - return ErrNotConnected + return sfu.ErrNotConnected } msg := api.StringMap{ diff --git a/publisher_stats_counter.go b/sfu/janus/publisher_stats_counter.go similarity index 84% rename from publisher_stats_counter.go rename to sfu/janus/publisher_stats_counter.go index bafe3f3..8209253 100644 --- a/publisher_stats_counter.go +++ b/sfu/janus/publisher_stats_counter.go @@ -19,44 +19,46 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "sync" + + "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) type publisherStatsCounterStats interface { - IncPublisherStream(streamType StreamType) - DecPublisherStream(streamType StreamType) - IncSubscriberStream(streamType StreamType) - DecSubscriberStream(streamType StreamType) - AddSubscriberStreams(streamType StreamType, count int) - SubSubscriberStreams(streamType StreamType, count int) + IncPublisherStream(streamType sfu.StreamType) + DecPublisherStream(streamType sfu.StreamType) + IncSubscriberStream(streamType sfu.StreamType) + DecSubscriberStream(streamType sfu.StreamType) + AddSubscriberStreams(streamType sfu.StreamType, count int) + SubSubscriberStreams(streamType sfu.StreamType, count int) } type prometheusPublisherStats struct{} -func (s *prometheusPublisherStats) IncPublisherStream(streamType StreamType) { +func (s *prometheusPublisherStats) IncPublisherStream(streamType sfu.StreamType) { statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Inc() } -func (s *prometheusPublisherStats) DecPublisherStream(streamType StreamType) { +func (s *prometheusPublisherStats) DecPublisherStream(streamType sfu.StreamType) { statsMcuPublisherStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() } -func (s *prometheusPublisherStats) IncSubscriberStream(streamType StreamType) { +func (s *prometheusPublisherStats) IncSubscriberStream(streamType sfu.StreamType) { statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Inc() } -func (s *prometheusPublisherStats) DecSubscriberStream(streamType StreamType) { +func (s *prometheusPublisherStats) DecSubscriberStream(streamType sfu.StreamType) { statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Dec() } -func (s *prometheusPublisherStats) AddSubscriberStreams(streamType StreamType, count int) { +func (s *prometheusPublisherStats) AddSubscriberStreams(streamType sfu.StreamType, count int) { statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Add(float64(count)) } -func (s *prometheusPublisherStats) SubSubscriberStreams(streamType StreamType, count int) { +func (s *prometheusPublisherStats) SubSubscriberStreams(streamType sfu.StreamType, count int) { statsMcuSubscriberStreamTypesCurrent.WithLabelValues(string(streamType)).Sub(float64(count)) } @@ -68,7 +70,7 @@ type publisherStatsCounter struct { mu sync.Mutex // +checklocks:mu - streamTypes map[StreamType]bool + streamTypes map[sfu.StreamType]bool // +checklocks:mu subscribers map[string]bool // +checklocks:mu @@ -93,7 +95,7 @@ func (c *publisherStatsCounter) Reset() { c.subscribers = nil } -func (c *publisherStatsCounter) EnableStream(streamType StreamType, enable bool) { +func (c *publisherStatsCounter) EnableStream(streamType sfu.StreamType, enable bool) { c.mu.Lock() defer c.mu.Unlock() @@ -108,7 +110,7 @@ func (c *publisherStatsCounter) EnableStream(streamType StreamType, enable bool) if enable { if c.streamTypes == nil { - c.streamTypes = make(map[StreamType]bool) + c.streamTypes = make(map[sfu.StreamType]bool) } c.streamTypes[streamType] = true stats.IncPublisherStream(streamType) diff --git a/publisher_stats_counter_test.go b/sfu/janus/publisher_stats_counter_test.go similarity index 80% rename from publisher_stats_counter_test.go rename to sfu/janus/publisher_stats_counter_test.go index d9ab73e..221b9f2 100644 --- a/publisher_stats_counter_test.go +++ b/sfu/janus/publisher_stats_counter_test.go @@ -19,74 +19,75 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) type mockPublisherStats struct { - publishers map[StreamType]int - subscribers map[StreamType]int + publishers map[sfu.StreamType]int + subscribers map[sfu.StreamType]int } -func (s *mockPublisherStats) IncPublisherStream(streamType StreamType) { +func (s *mockPublisherStats) IncPublisherStream(streamType sfu.StreamType) { if s.publishers == nil { - s.publishers = make(map[StreamType]int) + s.publishers = make(map[sfu.StreamType]int) } s.publishers[streamType]++ } -func (s *mockPublisherStats) DecPublisherStream(streamType StreamType) { +func (s *mockPublisherStats) DecPublisherStream(streamType sfu.StreamType) { if s.publishers == nil { - s.publishers = make(map[StreamType]int) + s.publishers = make(map[sfu.StreamType]int) } s.publishers[streamType]-- } -func (s *mockPublisherStats) IncSubscriberStream(streamType StreamType) { +func (s *mockPublisherStats) IncSubscriberStream(streamType sfu.StreamType) { if s.subscribers == nil { - s.subscribers = make(map[StreamType]int) + s.subscribers = make(map[sfu.StreamType]int) } s.subscribers[streamType]++ } -func (s *mockPublisherStats) DecSubscriberStream(streamType StreamType) { +func (s *mockPublisherStats) DecSubscriberStream(streamType sfu.StreamType) { if s.subscribers == nil { - s.subscribers = make(map[StreamType]int) + s.subscribers = make(map[sfu.StreamType]int) } s.subscribers[streamType]-- } -func (s *mockPublisherStats) AddSubscriberStreams(streamType StreamType, count int) { +func (s *mockPublisherStats) AddSubscriberStreams(streamType sfu.StreamType, count int) { if s.subscribers == nil { - s.subscribers = make(map[StreamType]int) + s.subscribers = make(map[sfu.StreamType]int) } s.subscribers[streamType] += count } -func (s *mockPublisherStats) SubSubscriberStreams(streamType StreamType, count int) { +func (s *mockPublisherStats) SubSubscriberStreams(streamType sfu.StreamType, count int) { if s.subscribers == nil { - s.subscribers = make(map[StreamType]int) + s.subscribers = make(map[sfu.StreamType]int) } s.subscribers[streamType] -= count } -func (s *mockPublisherStats) Publishers(streamType StreamType) int { +func (s *mockPublisherStats) Publishers(streamType sfu.StreamType) int { return s.publishers[streamType] } -func (s *mockPublisherStats) Subscribers(streamType StreamType) int { +func (s *mockPublisherStats) Subscribers(streamType sfu.StreamType) int { return s.subscribers[streamType] } func TestPublisherStatsPrometheus(t *testing.T) { t.Parallel() - RegisterJanusMcuStats() - collectAndLint(t, commonMcuStats...) + RegisterStats() } func TestPublisherStatsCounter(t *testing.T) { diff --git a/mcu_janus_publisher_test.go b/sfu/janus/publisher_test.go similarity index 85% rename from mcu_janus_publisher_test.go rename to sfu/janus/publisher_test.go index aabc3f3..52b1558 100644 --- a/mcu_janus_publisher_test.go +++ b/sfu/janus/publisher_test.go @@ -19,18 +19,20 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "context" "sync/atomic" "testing" - "github.com/notedit/janus-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + janustest "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/test" ) func TestGetFmtpValueH264(t *testing.T) { @@ -116,8 +118,8 @@ func TestJanusPublisherRemote(t *testing.T) { rtcpPort := 23456 mcu, gateway := newMcuJanusForTesting(t) - gateway.registerHandlers(map[string]TestJanusHandler{ - "publish_remotely": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "publish_remotely": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { if value, found := api.GetStringMapString[string](body, "host"); assert.True(found) { assert.Equal(hostname, value) } @@ -138,7 +140,7 @@ func TestJanusPublisherRemote(t *testing.T) { }, }, nil }, - "unpublish_remotely": func(room *TestJanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + "unpublish_remotely": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { if value, found := api.GetStringMapString[string](body, "remote_id"); assert.True(found) { if prev := remotePublishId.Load(); assert.NotNil(prev, "should have previous value") { assert.Equal(prev, value) @@ -160,17 +162,17 @@ func TestJanusPublisherRemote(t *testing.T) { id: pubId, } - settings1 := NewPublisherSettings{} + settings1 := sfu.NewPublisherSettings{} initiator1 := &TestMcuInitiator{ country: "DE", } - pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", StreamTypeVideo, settings1, initiator1) + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) require.NoError(err) defer pub.Close(context.Background()) - require.Implements((*McuRemoteAwarePublisher)(nil), pub) - remotePub, _ := pub.(McuRemoteAwarePublisher) + require.Implements((*sfu.RemoteAwarePublisher)(nil), pub) + remotePub, _ := pub.(sfu.RemoteAwarePublisher) if assert.NoError(remotePub.PublishRemote(ctx, remoteId, hostname, port, rtcpPort)) { assert.NoError(remotePub.UnpublishRemote(ctx, remoteId, hostname, port, rtcpPort)) diff --git a/mcu_janus_remote_publisher.go b/sfu/janus/remote_publisher.go similarity index 79% rename from mcu_janus_remote_publisher.go rename to sfu/janus/remote_publisher.go index 55ce873..ae96dca 100644 --- a/mcu_janus_remote_publisher.go +++ b/sfu/janus/remote_publisher.go @@ -19,45 +19,45 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "context" "sync/atomic" - "github.com/notedit/janus-go" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) -type mcuJanusRemotePublisher struct { - mcuJanusPublisher +type janusRemotePublisher struct { + janusPublisher ref atomic.Int64 - controller RemotePublisherController + controller sfu.RemotePublisherController port int rtcpPort int } -func (p *mcuJanusRemotePublisher) addRef() int64 { +func (p *janusRemotePublisher) addRef() int64 { return p.ref.Add(1) } -func (p *mcuJanusRemotePublisher) release() bool { +func (p *janusRemotePublisher) release() bool { return p.ref.Add(-1) == 0 } -func (p *mcuJanusRemotePublisher) Port() int { +func (p *janusRemotePublisher) Port() int { return p.port } -func (p *mcuJanusRemotePublisher) RtcpPort() int { +func (p *janusRemotePublisher) RtcpPort() int { return p.rtcpPort } -func (p *mcuJanusRemotePublisher) handleEvent(event *janus.EventMsg) { +func (p *janusRemotePublisher) handleEvent(event *janus.EventMsg) { if videoroom := getPluginStringValue(event.Plugindata, pluginVideoRoom, "videoroom"); videoroom != "" { ctx := context.TODO() switch videoroom { @@ -74,22 +74,22 @@ func (p *mcuJanusRemotePublisher) handleEvent(event *janus.EventMsg) { } } -func (p *mcuJanusRemotePublisher) handleHangup(event *janus.HangupMsg) { +func (p *janusRemotePublisher) handleHangup(event *janus.HangupMsg) { p.logger.Printf("Remote publisher %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } -func (p *mcuJanusRemotePublisher) handleDetached(event *janus.DetachedMsg) { +func (p *janusRemotePublisher) handleDetached(event *janus.DetachedMsg) { p.logger.Printf("Remote publisher %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } -func (p *mcuJanusRemotePublisher) handleConnected(event *janus.WebRTCUpMsg) { +func (p *janusRemotePublisher) handleConnected(event *janus.WebRTCUpMsg) { p.logger.Printf("Remote publisher %d received connected", p.handleId.Load()) - p.mcu.publisherConnected.Notify(string(getStreamId(p.id, p.streamType))) + p.mcu.publisherConnected.Notify(string(sfu.GetStreamId(p.id, p.streamType))) } -func (p *mcuJanusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { +func (p *janusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { p.logger.Printf("Remote publisher %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { @@ -97,7 +97,7 @@ func (p *mcuJanusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { } } -func (p *mcuJanusRemotePublisher) NotifyReconnected() { +func (p *janusRemotePublisher) NotifyReconnected() { ctx := context.TODO() handle, session, roomId, _, err := p.mcu.getOrCreatePublisherHandle(ctx, p.id, p.streamType, p.settings) if err != nil { @@ -118,7 +118,7 @@ func (p *mcuJanusRemotePublisher) NotifyReconnected() { p.logger.Printf("Remote publisher %s reconnected on handle %d", p.id, p.handleId.Load()) } -func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { +func (p *janusRemotePublisher) Close(ctx context.Context) { if !p.release() { return } @@ -152,7 +152,7 @@ func (p *mcuJanusRemotePublisher) Close(ctx context.Context) { p.logger.Printf("Room %d destroyed", p.roomId) } p.mcu.mu.Lock() - delete(p.mcu.remotePublishers, getStreamId(p.id, p.streamType)) + delete(p.mcu.remotePublishers, sfu.GetStreamId(p.id, p.streamType)) p.mcu.mu.Unlock() p.roomId = 0 } diff --git a/mcu_janus_remote_subscriber.go b/sfu/janus/remote_subscriber.go similarity index 82% rename from mcu_janus_remote_subscriber.go rename to sfu/janus/remote_subscriber.go index 5d984b7..60004e9 100644 --- a/mcu_janus_remote_subscriber.go +++ b/sfu/janus/remote_subscriber.go @@ -19,23 +19,23 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "context" "strconv" "sync/atomic" - "github.com/notedit/janus-go" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) -type mcuJanusRemoteSubscriber struct { - mcuJanusSubscriber +type janusRemoteSubscriber struct { + janusSubscriber - remote atomic.Pointer[mcuJanusRemotePublisher] + remote atomic.Pointer[janusRemotePublisher] } -func (p *mcuJanusRemoteSubscriber) handleEvent(event *janus.EventMsg) { +func (p *janusRemoteSubscriber) handleEvent(event *janus.EventMsg) { if videoroom := getPluginStringValue(event.Plugindata, pluginVideoRoom, "videoroom"); videoroom != "" { ctx := context.TODO() switch videoroom { @@ -59,22 +59,22 @@ func (p *mcuJanusRemoteSubscriber) handleEvent(event *janus.EventMsg) { } } -func (p *mcuJanusRemoteSubscriber) handleHangup(event *janus.HangupMsg) { +func (p *janusRemoteSubscriber) handleHangup(event *janus.HangupMsg) { p.logger.Printf("Remote subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } -func (p *mcuJanusRemoteSubscriber) handleDetached(event *janus.DetachedMsg) { +func (p *janusRemoteSubscriber) handleDetached(event *janus.DetachedMsg) { p.logger.Printf("Remote subscriber %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } -func (p *mcuJanusRemoteSubscriber) handleConnected(event *janus.WebRTCUpMsg) { +func (p *janusRemoteSubscriber) handleConnected(event *janus.WebRTCUpMsg) { p.logger.Printf("Remote subscriber %d received connected", p.handleId.Load()) p.mcu.SubscriberConnected(p.Id(), p.publisher, p.streamType) } -func (p *mcuJanusRemoteSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { +func (p *janusRemoteSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { p.logger.Printf("Remote subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { @@ -82,11 +82,11 @@ func (p *mcuJanusRemoteSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { } } -func (p *mcuJanusRemoteSubscriber) handleMedia(event *janus.MediaMsg) { +func (p *janusRemoteSubscriber) handleMedia(event *janus.MediaMsg) { // Only triggered for publishers } -func (p *mcuJanusRemoteSubscriber) NotifyReconnected() { +func (p *janusRemoteSubscriber) NotifyReconnected() { ctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() handle, pub, err := p.mcu.getOrCreateSubscriberHandle(ctx, p.publisher, p.streamType) @@ -109,8 +109,8 @@ func (p *mcuJanusRemoteSubscriber) NotifyReconnected() { p.logger.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) } -func (p *mcuJanusRemoteSubscriber) Close(ctx context.Context) { - p.mcuJanusSubscriber.Close(ctx) +func (p *janusRemoteSubscriber) Close(ctx context.Context) { + p.janusSubscriber.Close(ctx) if remote := p.remote.Swap(nil); remote != nil { remote.Close(context.Background()) diff --git a/mcu_stats_prometheus.go b/sfu/janus/stats_prometheus.go similarity index 60% rename from mcu_stats_prometheus.go rename to sfu/janus/stats_prometheus.go index 4e735db..a9c8300 100644 --- a/mcu_stats_prometheus.go +++ b/sfu/janus/stats_prometheus.go @@ -19,51 +19,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "github.com/prometheus/client_golang/prometheus" "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" ) var ( - statsPublishersCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "publishers", - Help: "The current number of publishers", - }, []string{"type"}) - statsPublishersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "publishers_total", - Help: "The total number of created publishers", - }, []string{"type"}) - statsSubscribersCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "subscribers", - Help: "The current number of subscribers", - }, []string{"type"}) - statsSubscribersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "subscribers_total", - Help: "The total number of created subscribers", - }, []string{"type"}) - statsWaitingForPublisherTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "nopublisher_total", - Help: "The total number of subscribe requests where no publisher exists", - }, []string{"type"}) - statsMcuMessagesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "messages_total", - Help: "The total number of MCU messages", - }, []string{"type"}) statsMcuSubscriberStreamTypesCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "signaling", Subsystem: "mcu", @@ -76,18 +41,6 @@ var ( Name: "publisher_streams", Help: "The current number of published media streams", }, []string{"type"}) - - commonMcuStats = []prometheus.Collector{ - statsPublishersCurrent, - statsPublishersTotal, - statsSubscribersCurrent, - statsSubscribersTotal, - statsWaitingForPublisherTotal, - statsMcuMessagesTotal, - statsMcuSubscriberStreamTypesCurrent, - statsMcuPublisherStreamTypesCurrent, - } - statsJanusBandwidthCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "signaling", Subsystem: "mcu", @@ -170,6 +123,8 @@ var ( }, []string{"media", "origin"}) janusMcuStats = []prometheus.Collector{ + statsMcuSubscriberStreamTypesCurrent, + statsMcuPublisherStreamTypesCurrent, statsJanusBandwidthCurrent, statsJanusSelectedCandidateTotal, statsJanusPeerConnectionStateTotal, @@ -184,63 +139,14 @@ var ( statsJanusMediaBytesTotal, statsJanusMediaLostTotal, } - - statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "backend_connections", - Help: "Current number of connections to signaling proxy backends", - }, []string{"country"}) - statsProxyBackendLoadCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "backend_load", - Help: "Current load of signaling proxy backends", - }, []string{"url"}) - statsProxyUsageCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "backend_usage", - Help: "The current usage of signaling proxy backends in percent", - }, []string{"url", "direction"}) - statsProxyBandwidthCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "backend_bandwidth", - Help: "The current bandwidth of signaling proxy backends in bytes per second", - }, []string{"url", "direction"}) - statsProxyNobackendAvailableTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "mcu", - Name: "no_backend_available_total", - Help: "Total number of publishing requests where no backend was available", - }, []string{"type"}) - - proxyMcuStats = []prometheus.Collector{ - statsConnectedProxyBackendsCurrent, - statsProxyBackendLoadCurrent, - statsProxyUsageCurrent, - statsProxyBandwidthCurrent, - statsProxyNobackendAvailableTotal, - } ) -func RegisterJanusMcuStats() { - metrics.RegisterAll(commonMcuStats...) +func RegisterStats() { + internal.RegisterCommonStats() metrics.RegisterAll(janusMcuStats...) } -func UnregisterJanusMcuStats() { - metrics.UnregisterAll(commonMcuStats...) +func UnregisterStats() { + internal.UnregisterCommonStats() metrics.UnregisterAll(janusMcuStats...) } - -func RegisterProxyMcuStats() { - metrics.RegisterAll(commonMcuStats...) - metrics.RegisterAll(proxyMcuStats...) -} - -func UnregisterProxyMcuStats() { - metrics.UnregisterAll(commonMcuStats...) - metrics.UnregisterAll(proxyMcuStats...) -} diff --git a/mcu_janus_stream_selection.go b/sfu/janus/stream_selection.go similarity index 99% rename from mcu_janus_stream_selection.go rename to sfu/janus/stream_selection.go index 0458909..046b579 100644 --- a/mcu_janus_stream_selection.go +++ b/sfu/janus/stream_selection.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 janus import ( "database/sql" diff --git a/mcu_janus_subscriber.go b/sfu/janus/subscriber.go similarity index 83% rename from mcu_janus_subscriber.go rename to sfu/janus/subscriber.go index 8b2f4b2..a1f99c6 100644 --- a/mcu_janus_subscriber.go +++ b/sfu/janus/subscriber.go @@ -19,29 +19,38 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package janus import ( "context" "fmt" "strconv" - "github.com/notedit/janus-go" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) -type mcuJanusSubscriber struct { - mcuJanusClient +type Subscriber interface { + JanusHandle() *janus.Handle +} + +type janusSubscriber struct { + janusClient publisher api.PublicSessionId } -func (p *mcuJanusSubscriber) Publisher() api.PublicSessionId { +func (p *janusSubscriber) JanusHandle() *janus.Handle { + return p.handle.Load() +} + +func (p *janusSubscriber) Publisher() api.PublicSessionId { return p.publisher } -func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { +func (p *janusSubscriber) handleEvent(event *janus.EventMsg) { if videoroom := getPluginStringValue(event.Plugindata, pluginVideoRoom, "videoroom"); videoroom != "" { ctx := context.TODO() switch videoroom { @@ -85,22 +94,22 @@ func (p *mcuJanusSubscriber) handleEvent(event *janus.EventMsg) { } } -func (p *mcuJanusSubscriber) handleHangup(event *janus.HangupMsg) { +func (p *janusSubscriber) handleHangup(event *janus.HangupMsg) { p.logger.Printf("Subscriber %d received hangup (%s), closing", p.handleId.Load(), event.Reason) go p.Close(context.Background()) } -func (p *mcuJanusSubscriber) handleDetached(event *janus.DetachedMsg) { +func (p *janusSubscriber) handleDetached(event *janus.DetachedMsg) { p.logger.Printf("Subscriber %d received detached, closing", p.handleId.Load()) go p.Close(context.Background()) } -func (p *mcuJanusSubscriber) handleConnected(event *janus.WebRTCUpMsg) { +func (p *janusSubscriber) handleConnected(event *janus.WebRTCUpMsg) { p.logger.Printf("Subscriber %d received connected", p.handleId.Load()) p.mcu.SubscriberConnected(p.Id(), p.publisher, p.streamType) } -func (p *mcuJanusSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { +func (p *janusSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { if event.Uplink { p.logger.Printf("Subscriber %s (%d) is reporting %d lost packets on the uplink (Janus -> client)", p.listener.PublicId(), p.handleId.Load(), event.Lost) } else { @@ -108,11 +117,11 @@ func (p *mcuJanusSubscriber) handleSlowLink(event *janus.SlowLinkMsg) { } } -func (p *mcuJanusSubscriber) handleMedia(event *janus.MediaMsg) { +func (p *janusSubscriber) handleMedia(event *janus.MediaMsg) { // Only triggered for publishers } -func (p *mcuJanusSubscriber) NotifyReconnected() { +func (p *janusSubscriber) NotifyReconnected() { ctx, cancel := context.WithTimeout(context.Background(), p.mcu.settings.Timeout()) defer cancel() handle, pub, err := p.mcu.getOrCreateSubscriberHandle(ctx, p.publisher, p.streamType) @@ -135,8 +144,8 @@ func (p *mcuJanusSubscriber) NotifyReconnected() { p.logger.Printf("Subscriber %d for publisher %s reconnected on handle %d", p.id, p.publisher, p.handleId.Load()) } -func (p *mcuJanusSubscriber) closeClient(ctx context.Context) bool { - if !p.mcuJanusClient.closeClient(ctx) { +func (p *janusSubscriber) closeClient(ctx context.Context) bool { + if !p.janusClient.closeClient(ctx) { return false } @@ -144,7 +153,7 @@ func (p *mcuJanusSubscriber) closeClient(ctx context.Context) bool { return true } -func (p *mcuJanusSubscriber) Close(ctx context.Context) { +func (p *janusSubscriber) Close(ctx context.Context) { p.mu.Lock() closed := p.closeClient(ctx) p.mu.Unlock() @@ -154,17 +163,17 @@ func (p *mcuJanusSubscriber) Close(ctx context.Context) { } p.mcu.unregisterClient(p) p.listener.SubscriberClosed(p) - p.mcuJanusClient.Close(ctx) + p.janusClient.Close(ctx) } -func (p *mcuJanusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { +func (p *janusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { handle := p.handle.Load() if handle == nil { - callback(ErrNotConnected, nil) + callback(sfu.ErrNotConnected, nil) return } - waiter := p.mcu.publisherConnected.NewWaiter(string(getStreamId(p.publisher, p.streamType))) + waiter := p.mcu.publisherConnected.NewWaiter(string(sfu.GetStreamId(p.publisher, p.streamType))) defer p.mcu.publisherConnected.Release(waiter) loggedNotPublishingYet := false @@ -194,7 +203,7 @@ retry: if error_code := getPluginIntValue(p.logger, join_response.Plugindata, pluginVideoRoom, "error_code"); error_code > 0 { switch error_code { - case JANUS_VIDEOROOM_ERROR_ALREADY_JOINED: + case janus.JANUS_VIDEOROOM_ERROR_ALREADY_JOINED: // The subscriber is already connected to the room. This can happen // if a client leaves a call but keeps the subscriber objects active. // On joining the call again, the subscriber tries to join on the @@ -205,7 +214,7 @@ retry: p.closeClient(ctx) p.mu.Unlock() - var pub *mcuJanusPublisher + var pub *janusPublisher handle, pub, err = p.mcu.getOrCreateSubscriberHandle(ctx, p.publisher, p.streamType) if err != nil { // Reconnection didn't work, need to unregister/remove subscriber @@ -230,22 +239,22 @@ retry: go p.run(handle, p.closeChan) p.logger.Printf("Already connected subscriber %d for %s, leaving and re-joining on handle %d", p.id, p.streamType, p.handleId.Load()) goto retry - case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: + case janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: fallthrough - case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: + case janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: switch error_code { - case JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: + case janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM: p.logger.Printf("Publisher %s not created yet for %s, not joining room %d as subscriber", p.publisher, p.streamType, p.roomId) p.Close(context.Background()) callback(fmt.Errorf("Publisher %s not created yet for %s", p.publisher, p.streamType), nil) return - case JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: + case janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED: p.logger.Printf("Publisher %s not sending yet for %s, wait and retry to join room %d as subscriber", p.publisher, p.streamType, p.roomId) } if !loggedNotPublishingYet { loggedNotPublishingYet = true - statsWaitingForPublisherTotal.WithLabelValues(string(p.streamType)).Inc() + sfuinternal.StatsWaitingForPublisherTotal.WithLabelValues(string(p.streamType)).Inc() } if err := waiter.Wait(ctx); err != nil { @@ -267,10 +276,10 @@ retry: callback(nil, join_response.Jsep) } -func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { +func (p *janusSubscriber) update(ctx context.Context, stream *streamSelection, callback func(error, api.StringMap)) { handle := p.handle.Load() if handle == nil { - callback(ErrNotConnected, nil) + callback(sfu.ErrNotConnected, nil) return } @@ -290,8 +299,8 @@ func (p *mcuJanusSubscriber) update(ctx context.Context, stream *streamSelection callback(nil, configure_response.Jsep) } -func (p *mcuJanusSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { - statsMcuMessagesTotal.WithLabelValues(data.Type).Inc() +func (p *janusSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + sfuinternal.StatsMcuMessagesTotal.WithLabelValues(data.Type).Inc() jsep_msg := data.Payload switch data.Type { case "requestoffer": diff --git a/sfu/janus/test/janus.go b/sfu/janus/test/janus.go new file mode 100644 index 0000000..200ff71 --- /dev/null +++ b/sfu/janus/test/janus.go @@ -0,0 +1,588 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "context" + "encoding/json" + "maps" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" +) + +const ( + pluginVideoRoom = "janus.plugin.videoroom" + eventWebsocket = "janus.eventhandler.wsevh" +) + +type JanusHandle struct { + id uint64 + + sdp atomic.Value +} + +type JanusRoom struct { + id uint64 + + publisher atomic.Pointer[JanusHandle] +} + +func (r *JanusRoom) Id() uint64 { + return r.id +} + +type JanusHandler func(room *JanusRoom, body api.StringMap, jsep api.StringMap) (any, *janus.ErrorMsg) + +type JanusGateway struct { + t *testing.T + + sid atomic.Uint64 + tid atomic.Uint64 + hid atomic.Uint64 // +checklocksignore: Atomic + rid atomic.Uint64 // +checklocksignore: Atomic + mu sync.Mutex + + // +checklocks:mu + sessions map[uint64]*janus.Session + // +checklocks:mu + transactions map[uint64]*janus.Transaction + // +checklocks:mu + handles map[uint64]*JanusHandle + // +checklocks:mu + rooms map[uint64]*JanusRoom + // +checklocks:mu + handlers map[string]JanusHandler + + // +checklocks:mu + attachCount int + // +checklocks:mu + joinCount int + + // +checklocks:mu + handleRooms map[*JanusHandle]*JanusRoom +} + +func NewJanusGateway(t *testing.T) *JanusGateway { + gateway := &JanusGateway{ + t: t, + + sessions: make(map[uint64]*janus.Session), + transactions: make(map[uint64]*janus.Transaction), + handles: make(map[uint64]*JanusHandle), + rooms: make(map[uint64]*JanusRoom), + handlers: make(map[string]JanusHandler), + + handleRooms: make(map[*JanusHandle]*JanusRoom), + } + + t.Cleanup(func() { + assert := assert.New(t) + gateway.mu.Lock() + defer gateway.mu.Unlock() + assert.Empty(gateway.sessions) + assert.Empty(gateway.transactions) + assert.Empty(gateway.handles) + assert.Empty(gateway.rooms) + assert.Empty(gateway.handleRooms) + }) + + return gateway +} + +func (g *JanusGateway) RegisterHandlers(handlers map[string]JanusHandler) { + g.mu.Lock() + defer g.mu.Unlock() + maps.Copy(g.handlers, handlers) +} + +func (g *JanusGateway) Info(ctx context.Context) (*janus.InfoMsg, error) { + return &janus.InfoMsg{ + Name: "TestJanus", + Version: 1400, + VersionString: "1.4.0", + Author: "struktur AG", + DataChannels: true, + EventHandlers: true, + FullTrickle: true, + Plugins: map[string]janus.PluginInfo{ + pluginVideoRoom: { + Name: "Test VideoRoom plugin", + VersionString: "0.0.0", + Author: "struktur AG", + }, + }, + Events: map[string]janus.PluginInfo{ + eventWebsocket: { + Name: "Test Websocket events", + VersionString: "0.0.0", + Author: "struktur AG", + }, + }, + }, nil +} + +func (g *JanusGateway) Create(ctx context.Context) (*janus.Session, error) { + sid := g.sid.Add(1) + session := janus.NewSession(sid, g) + g.mu.Lock() + defer g.mu.Unlock() + g.sessions[sid] = session + return session, nil +} + +func (g *JanusGateway) Close() error { + return nil +} + +func (g *JanusGateway) simulateEvent(delay time.Duration, session *janus.Session, handle *JanusHandle, event any) { + go func() { + time.Sleep(delay) + session.Lock() + h, found := session.Handles[handle.id] + session.Unlock() + if found { + h.Events <- event + } + }() +} + +// +checklocks:g.mu +func (g *JanusGateway) processMessage(session *janus.Session, handle *JanusHandle, body api.StringMap, jsep api.StringMap) any { + request := body["request"].(string) + switch request { + case "create": + room := &JanusRoom{ + id: g.rid.Add(1), + } + g.rooms[room.id] = room + + return &janus.SuccessMsg{ + PluginData: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "room": room.id, + }, + }, + } + case "join": + rid := body["room"].(float64) + room := g.rooms[uint64(rid)] + error_code := janus.JANUS_OK + if body["ptype"] == "subscriber" { + if strings.Contains(g.t.Name(), "NoSuchRoom") { + g.joinCount++ + if g.joinCount == 1 { + error_code = janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM + } + } else if strings.Contains(g.t.Name(), "AlreadyJoined") { + g.joinCount++ + if g.joinCount == 1 { + error_code = janus.JANUS_VIDEOROOM_ERROR_ALREADY_JOINED + } + } else if strings.Contains(g.t.Name(), "SubscriberTimeout") { + g.joinCount++ + if g.joinCount == 1 { + error_code = janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED + } + } + } + if error_code != janus.JANUS_OK { + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "error_code": error_code, + }, + }, + } + } + + if room == nil { + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "error_code": janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, + }, + }, + } + } + + g.handleRooms[handle] = room + switch body["ptype"] { + case "publisher": + if !assert.True(g.t, room.publisher.CompareAndSwap(nil, handle)) { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED, + Reason: "Already publisher in this room", + }, + } + } + + return &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "room": room.id, + }, + }, + } + case "subscriber": + publisher := room.publisher.Load() + if publisher == nil || publisher.sdp.Load() == nil { + return &janus.EventMsg{ + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "error_code": janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED, + }, + }, + } + } + + sdp := publisher.sdp.Load() + + // Simulate "connected" event for subscriber. + g.simulateEvent(15*time.Millisecond, session, handle, &janus.WebRTCUpMsg{ + Session: session.Id, + Handle: handle.id, + }) + + if strings.Contains(g.t.Name(), "CloseEmptyStreams") { + // Simulate stream update event with no active streams. + g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "videoroom": "updated", + "streams": []any{ + api.StringMap{ + "type": "audio", + "active": false, + }, + }, + }, + }, + }) + } + + if strings.Contains(g.t.Name(), "SubscriberRoomDestroyed") { + // Simulate event that subscriber room has been destroyed. + g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "videoroom": "destroyed", + }, + }, + }) + } + + if strings.Contains(g.t.Name(), "SubscriberUpdateOffer") { + // Simulate event that subscriber receives new offer. + g.simulateEvent(20*time.Millisecond, session, handle, &janus.EventMsg{ + Session: session.Id, + Handle: handle.id, + Plugindata: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{ + "videoroom": "event", + "configured": "ok", + }, + }, + Jsep: map[string]any{ + "type": "offer", + "sdp": mock.MockSdpOfferAudioOnly, + }, + }) + } + + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "offer", + "sdp": sdp.(string), + }, + } + } + case "destroy": + rid := body["room"].(float64) + room := g.rooms[uint64(rid)] + if room == nil { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, + Reason: "Room not found", + }, + } + } + + assert.Equal(g.t, room.id, uint64(rid)) + delete(g.rooms, uint64(rid)) + for h, r := range g.handleRooms { + if r.id == room.id { + delete(g.handleRooms, h) + } + } + + return &janus.SuccessMsg{ + PluginData: janus.PluginData{ + Plugin: pluginVideoRoom, + Data: api.StringMap{}, + }, + } + default: + var room *JanusRoom + if roomId, found := body["room"]; found { + rid := roomId.(float64) + if room = g.rooms[uint64(rid)]; room == nil { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM, + Reason: "Room not found", + }, + } + } + } else { + if room, found = g.handleRooms[handle]; !found { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_VIDEOROOM_ERROR_INVALID_REQUEST, + Reason: "No joined to a room yet.", + }, + } + } + } + + handler, found := g.handlers[request] + if found { + var err *janus.ErrorMsg + result, err := handler(room, body, jsep) + if err != nil { + result = err + } else { + switch request { + case "start": + g.handleRooms[handle] = room + case "configure": + if sdp, found := jsep["sdp"]; found { + handle.sdp.Store(sdp.(string)) + if !strings.Contains(g.t.Name(), "SubscriberTimeout") { + // Simulate "connected" event for publisher. + g.simulateEvent(10*time.Millisecond, session, handle, &janus.WebRTCUpMsg{ + Session: session.Id, + Handle: handle.id, + }) + } + } + } + } + return result + } + } + + return nil +} + +func (g *JanusGateway) processRequest(msg api.StringMap) any { + method, found := msg["janus"] + if !found { + return nil + } + + sid := msg["session_id"].(float64) + g.mu.Lock() + defer g.mu.Unlock() + session := g.sessions[uint64(sid)] + if session == nil { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_ERROR_SESSION_NOT_FOUND, + Reason: "Session not found", + }, + } + } + + switch method { + case "attach": + if strings.Contains(g.t.Name(), "AlreadyJoinedAttachError") { + g.attachCount++ + if g.attachCount == 4 { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_ERROR_UNKNOWN, + Reason: "Fail for test", + }, + } + } + } + + handle := &JanusHandle{ + id: g.hid.Add(1), + } + + g.handles[handle.id] = handle + + return &janus.SuccessMsg{ + Data: janus.SuccessData{ + ID: handle.id, + }, + } + case "detach": + hid := msg["handle_id"].(float64) + handle, found := g.handles[uint64(hid)] + if found { + delete(g.handles, handle.id) + } + if handle == nil { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_ERROR_HANDLE_NOT_FOUND, + Reason: "Handle not found", + }, + } + } + + return &janus.AckMsg{} + case "destroy": + delete(g.sessions, session.Id) + return &janus.AckMsg{} + case "message", "trickle": + hid := msg["handle_id"].(float64) + handle, found := g.handles[uint64(hid)] + if !found { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_ERROR_HANDLE_NOT_FOUND, + Reason: "Handle not found", + }, + } + } + + var result any + switch method { + case "message": + body, ok := api.ConvertStringMap(msg["body"]) + assert.True(g.t, ok, "not a string map: %+v", msg["body"]) + if jsepOb, found := msg["jsep"]; found { + if jsep, ok := api.ConvertStringMap(jsepOb); assert.True(g.t, ok, "not a string map: %+v", jsepOb) { + result = g.processMessage(session, handle, body, jsep) + } + } else { + result = g.processMessage(session, handle, body, nil) + } + case "trickle": + room, found := g.handleRooms[handle] + if !found { + return &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_VIDEOROOM_ERROR_INVALID_REQUEST, + Reason: "No joined to a room yet.", + }, + } + } + + handler, found := g.handlers[method.(string)] + if found { + var err *janus.ErrorMsg + result, err = handler(room, msg, nil) + if err != nil { + result = err + } + } + } + + if ev, ok := result.(*janus.EventMsg); ok { + if ev.Session == 0 { + ev.Session = uint64(sid) + } + if ev.Handle == 0 { + ev.Handle = handle.id + } + } + return result + } + + return nil +} + +func (g *JanusGateway) Send(msg api.StringMap, t *janus.Transaction) (uint64, error) { + tid := g.tid.Add(1) + + data, err := json.Marshal(msg) + require.NoError(g.t, err) + err = json.Unmarshal(data, &msg) + require.NoError(g.t, err) + + go t.Run() + + g.mu.Lock() + defer g.mu.Unlock() + g.transactions[tid] = t + + go func() { + result := g.processRequest(msg) + if !assert.NotNil(g.t, result, "Unsupported request %+v", msg) { + result = &janus.ErrorMsg{ + Err: janus.ErrorData{ + Code: janus.JANUS_ERROR_UNKNOWN, + Reason: "Not implemented", + }, + } + } + + t.Add(result) + }() + + return tid, nil +} + +func (g *JanusGateway) RemoveTransaction(id uint64) { + g.mu.Lock() + defer g.mu.Unlock() + if t, found := g.transactions[id]; found { + delete(g.transactions, id) + t.Quit() + } +} + +func (g *JanusGateway) RemoveSession(session *janus.Session) { + g.mu.Lock() + defer g.mu.Unlock() + delete(g.sessions, session.Id) +} diff --git a/mcu_common_test.go b/sfu/mock/mock.go similarity index 58% rename from mcu_common_test.go rename to sfu/mock/mock.go index bdff6fb..cc685eb 100644 --- a/mcu_common_test.go +++ b/sfu/mock/mock.go @@ -19,56 +19,62 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package mock import ( - "testing" - "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" ) -func TestCommonMcuStats(t *testing.T) { - t.Parallel() - collectAndLint(t, commonMcuStats...) -} - -type MockMcuListener struct { +type Listener struct { publicId api.PublicSessionId } -func (m *MockMcuListener) PublicId() api.PublicSessionId { +func NewListener(publicId api.PublicSessionId) *Listener { + return &Listener{ + publicId: publicId, + } +} + +func (m *Listener) PublicId() api.PublicSessionId { return m.publicId } -func (m *MockMcuListener) OnUpdateOffer(client McuClient, offer api.StringMap) { +func (m *Listener) OnUpdateOffer(client sfu.Client, offer api.StringMap) { } -func (m *MockMcuListener) OnIceCandidate(client McuClient, candidate any) { +func (m *Listener) OnIceCandidate(client sfu.Client, candidate any) { } -func (m *MockMcuListener) OnIceCompleted(client McuClient) { +func (m *Listener) OnIceCompleted(client sfu.Client) { } -func (m *MockMcuListener) SubscriberSidUpdated(subscriber McuSubscriber) { +func (m *Listener) SubscriberSidUpdated(subscriber sfu.Subscriber) { } -func (m *MockMcuListener) PublisherClosed(publisher McuPublisher) { +func (m *Listener) PublisherClosed(publisher sfu.Publisher) { } -func (m *MockMcuListener) SubscriberClosed(subscriber McuSubscriber) { +func (m *Listener) SubscriberClosed(subscriber sfu.Subscriber) { } -type MockMcuInitiator struct { +type Initiator struct { country geoip.Country } -func (m *MockMcuInitiator) Country() geoip.Country { +func NewInitiator(country geoip.Country) *Initiator { + return &Initiator{ + country: country, + } +} + +func (m *Initiator) Country() geoip.Country { return m.country } diff --git a/proxy_config.go b/sfu/proxy/config.go similarity index 95% rename from proxy_config.go rename to sfu/proxy/config.go index 2a4102c..5453f97 100644 --- a/proxy_config.go +++ b/sfu/proxy/config.go @@ -19,13 +19,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package proxy import ( "github.com/dlintw/goconf" ) -type ProxyConfig interface { +type Config interface { Start() error Stop() diff --git a/proxy_config_etcd.go b/sfu/proxy/config_etcd.go similarity index 83% rename from proxy_config_etcd.go rename to sfu/proxy/config_etcd.go index 87c4930..0dc81e5 100644 --- a/proxy_config_etcd.go +++ b/sfu/proxy/config_etcd.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 proxy import ( "context" @@ -35,9 +35,15 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" ) -type proxyConfigEtcd struct { +const ( + initialWaitDelay = time.Second + maxWaitDelay = 8 * time.Second +) + +type configEtcd struct { logger log.Logger mu sync.Mutex proxy McuProxy // +checklocksignore: Only written to from constructor. @@ -45,7 +51,7 @@ type proxyConfigEtcd struct { client etcd.Client keyPrefix string // +checklocks:mu - keyInfos map[string]*ProxyInformationEtcd + keyInfos map[string]*proxy.InformationEtcd // +checklocks:mu urlToKey map[string]string @@ -58,7 +64,7 @@ type proxyConfigEtcd struct { runningDone sync.WaitGroup } -func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, proxy McuProxy) (ProxyConfig, error) { +func NewConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, sfuProxy McuProxy) (Config, error) { if !etcdClient.IsConfigured() { return nil, errors.New("no etcd endpoints configured") } @@ -66,12 +72,12 @@ func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient initializedCtx, initializedFunc := context.WithCancel(context.Background()) closeCtx, closeFunc := context.WithCancel(context.Background()) - result := &proxyConfigEtcd{ + result := &configEtcd{ logger: logger, - proxy: proxy, + proxy: sfuProxy, client: etcdClient, - keyInfos: make(map[string]*ProxyInformationEtcd), + keyInfos: make(map[string]*proxy.InformationEtcd), urlToKey: make(map[string]string), closeCtx: closeCtx, @@ -86,7 +92,7 @@ func NewProxyConfigEtcd(logger log.Logger, config *goconf.ConfigFile, etcdClient return result, nil } -func (p *proxyConfigEtcd) configure(config *goconf.ConfigFile, fromReload bool) error { +func (p *configEtcd) configure(config *goconf.ConfigFile, fromReload bool) error { keyPrefix, _ := config.GetString("mcu", "keyprefix") if keyPrefix == "" { keyPrefix = "/%s" @@ -96,17 +102,17 @@ func (p *proxyConfigEtcd) configure(config *goconf.ConfigFile, fromReload bool) return nil } -func (p *proxyConfigEtcd) Start() error { +func (p *configEtcd) Start() error { p.client.AddListener(p) return nil } -func (p *proxyConfigEtcd) Reload(config *goconf.ConfigFile) error { +func (p *configEtcd) Reload(config *goconf.ConfigFile) error { // not implemented return nil } -func (p *proxyConfigEtcd) Stop() { +func (p *configEtcd) Stop() { firstStop := p.closeCtx.Err() == nil p.closeFunc() p.client.RemoveListener(p) @@ -118,7 +124,7 @@ func (p *proxyConfigEtcd) Stop() { } } -func (p *proxyConfigEtcd) EtcdClientCreated(client etcd.Client) { +func (p *configEtcd) EtcdClientCreated(client etcd.Client) { p.initializing.Store(true) if p.closeCtx.Err() != nil { // Stopped before etcd client was connected. @@ -188,18 +194,18 @@ func (p *proxyConfigEtcd) EtcdClientCreated(client etcd.Client) { }() } -func (p *proxyConfigEtcd) EtcdWatchCreated(client etcd.Client, key string) { +func (p *configEtcd) EtcdWatchCreated(client etcd.Client, key string) { } -func (p *proxyConfigEtcd) getProxyUrls(ctx context.Context, client etcd.Client, keyPrefix string) (*clientv3.GetResponse, error) { +func (p *configEtcd) getProxyUrls(ctx context.Context, client etcd.Client, keyPrefix string) (*clientv3.GetResponse, error) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() return client.Get(ctx, keyPrefix, clientv3.WithPrefix()) } -func (p *proxyConfigEtcd) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { - var info ProxyInformationEtcd +func (p *configEtcd) EtcdKeyUpdated(client etcd.Client, key string, data []byte, prevValue []byte) { + var info proxy.InformationEtcd if err := json.Unmarshal(data, &info); err != nil { p.logger.Printf("Could not decode proxy information %s: %s", string(data), err) return @@ -239,7 +245,7 @@ func (p *proxyConfigEtcd) EtcdKeyUpdated(client etcd.Client, key string, data [] } } -func (p *proxyConfigEtcd) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { +func (p *configEtcd) EtcdKeyDeleted(client etcd.Client, key string, prevValue []byte) { p.mu.Lock() defer p.mu.Unlock() @@ -247,7 +253,7 @@ func (p *proxyConfigEtcd) EtcdKeyDeleted(client etcd.Client, key string, prevVal } // +checklocks:p.mu -func (p *proxyConfigEtcd) removeEtcdProxyLocked(key string) { +func (p *configEtcd) removeEtcdProxyLocked(key string) { info, found := p.keyInfos[key] if !found { return diff --git a/proxy_config_etcd_test.go b/sfu/proxy/config_etcd_test.go similarity index 96% rename from proxy_config_etcd_test.go rename to sfu/proxy/config_etcd_test.go index ef9a453..a95741d 100644 --- a/proxy_config_etcd_test.go +++ b/sfu/proxy/config_etcd_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 proxy import ( "context" @@ -40,13 +40,13 @@ type TestProxyInformationEtcd struct { OtherData string `json:"otherdata,omitempty"` } -func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.TestServer, ProxyConfig) { +func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.TestServer, Config) { t.Helper() embedEtcd, client := etcdtest.NewClientForTest(t) cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "keyprefix", "proxies/") logger := log.NewLoggerForTest(t) - p, err := NewProxyConfigEtcd(logger, cfg, client, proxy) + p, err := NewConfigEtcd(logger, cfg, client, proxy) require.NoError(t, err) t.Cleanup(func() { p.Stop() diff --git a/proxy_config_static.go b/sfu/proxy/config_static.go similarity index 88% rename from proxy_config_static.go rename to sfu/proxy/config_static.go index f12f94a..f5bf4d5 100644 --- a/proxy_config_static.go +++ b/sfu/proxy/config_static.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 proxy import ( "errors" @@ -43,7 +43,7 @@ type ipList struct { ips []net.IP } -type proxyConfigStatic struct { +type configStatic struct { logger log.Logger mu sync.Mutex proxy McuProxy @@ -56,8 +56,8 @@ type proxyConfigStatic struct { connectionsMap map[string]*ipList } -func NewProxyConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *dns.Monitor) (ProxyConfig, error) { - result := &proxyConfigStatic{ +func NewConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy McuProxy, dnsMonitor *dns.Monitor) (Config, error) { + result := &configStatic{ logger: logger, proxy: proxy, dnsMonitor: dnsMonitor, @@ -72,7 +72,7 @@ func NewProxyConfigStatic(logger log.Logger, config *goconf.ConfigFile, proxy Mc return result, nil } -func (p *proxyConfigStatic) configure(cfg *goconf.ConfigFile, fromReload bool) error { +func (p *configStatic) configure(cfg *goconf.ConfigFile, fromReload bool) error { p.mu.Lock() defer p.mu.Unlock() @@ -145,7 +145,7 @@ func (p *proxyConfigStatic) configure(cfg *goconf.ConfigFile, fromReload bool) e return nil } -func (p *proxyConfigStatic) Start() error { +func (p *configStatic) Start() error { p.mu.Lock() defer p.mu.Unlock() @@ -173,7 +173,7 @@ func (p *proxyConfigStatic) Start() error { return nil } -func (p *proxyConfigStatic) Stop() { +func (p *configStatic) Stop() { p.mu.Lock() defer p.mu.Unlock() @@ -189,11 +189,11 @@ func (p *proxyConfigStatic) Stop() { } } -func (p *proxyConfigStatic) Reload(config *goconf.ConfigFile) error { +func (p *configStatic) Reload(config *goconf.ConfigFile) error { return p.configure(config, true) } -func (p *proxyConfigStatic) onLookup(entry *dns.MonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { +func (p *configStatic) onLookup(entry *dns.MonitorEntry, all []net.IP, added []net.IP, keep []net.IP, removed []net.IP) { p.mu.Lock() defer p.mu.Unlock() diff --git a/proxy_config_static_test.go b/sfu/proxy/config_static_test.go similarity index 92% rename from proxy_config_static_test.go rename to sfu/proxy/config_static_test.go index 3d3aef1..9fb093d 100644 --- a/proxy_config_static_test.go +++ b/sfu/proxy/config_static_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 proxy import ( "net" @@ -34,7 +34,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" ) -func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dns.MockLookup, urls ...string) (ProxyConfig, *dns.Monitor) { +func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dns.MockLookup, urls ...string) (Config, *dns.Monitor) { cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "url", strings.Join(urls, " ")) if dnsDiscovery { @@ -42,7 +42,7 @@ func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, looku } dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := log.NewLoggerForTest(t) - p, err := NewProxyConfigStatic(logger, cfg, proxy, dnsMonitor) + p, err := NewConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) t.Cleanup(func() { p.Stop() @@ -50,7 +50,7 @@ func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, looku return p, dnsMonitor } -func updateProxyConfigStatic(t *testing.T, config ProxyConfig, dns bool, urls ...string) { +func updateProxyConfigStatic(t *testing.T, config Config, dns bool, urls ...string) { cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "url", strings.Join(urls, " ")) if dns { diff --git a/proxy_config_test.go b/sfu/proxy/config_test.go similarity index 99% rename from proxy_config_test.go rename to sfu/proxy/config_test.go index 817d268..887989d 100644 --- a/proxy_config_test.go +++ b/sfu/proxy/config_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 proxy import ( "context" diff --git a/mcu_proxy.go b/sfu/proxy/proxy.go similarity index 74% rename from mcu_proxy.go rename to sfu/proxy/proxy.go index 781f726..469d604 100644 --- a/mcu_proxy.go +++ b/sfu/proxy/proxy.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 proxy import ( "context" @@ -51,8 +51,12 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/dns" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -71,8 +75,17 @@ const ( proxyUrlTypeStatic = "static" proxyUrlTypeEtcd = "etcd" - initialWaitDelay = time.Second - maxWaitDelay = 8 * time.Second + initialReconnectInterval = 1 * time.Second + maxReconnectInterval = 16 * time.Second + + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 defaultProxyTimeoutSeconds = 2 @@ -87,35 +100,35 @@ type McuProxy interface { RemoveConnection(url string, ips ...net.IP) } -type mcuProxyPubSubCommon struct { +type proxyPubSubCommon struct { logger log.Logger sid string - streamType StreamType + streamType sfu.StreamType maxBitrate api.Bandwidth proxyId string - conn *mcuProxyConnection - listener McuListener + conn *proxyConnection + listener sfu.Listener } -func (c *mcuProxyPubSubCommon) Id() string { +func (c *proxyPubSubCommon) Id() string { return c.proxyId } -func (c *mcuProxyPubSubCommon) Sid() string { +func (c *proxyPubSubCommon) Sid() string { return c.sid } -func (c *mcuProxyPubSubCommon) StreamType() StreamType { +func (c *proxyPubSubCommon) StreamType() sfu.StreamType { return c.streamType } -func (c *mcuProxyPubSubCommon) MaxBitrate() api.Bandwidth { +func (c *proxyPubSubCommon) MaxBitrate() api.Bandwidth { return c.maxBitrate } -func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClientMessage, callback func(error, api.StringMap)) { - c.conn.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { +func (c *proxyPubSubCommon) doSendMessage(ctx context.Context, msg *proxy.ClientMessage, callback func(error, api.StringMap)) { + c.conn.performAsyncRequest(ctx, msg, func(err error, response *proxy.ServerMessage) { if err != nil { callback(err, nil) return @@ -134,7 +147,7 @@ func (c *mcuProxyPubSubCommon) doSendMessage(ctx context.Context, msg *ProxyClie }) } -func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadProxyServerMessage) { +func (c *proxyPubSubCommon) doProcessPayload(client sfu.Client, msg *proxy.PayloadServerMessage) { switch msg.Type { case "offer": offer, ok := api.ConvertStringMap(msg.Payload["offer"]) @@ -151,16 +164,16 @@ func (c *mcuProxyPubSubCommon) doProcessPayload(client McuClient, msg *PayloadPr } } -type mcuProxyPublisher struct { - mcuProxyPubSubCommon +type proxyPublisher struct { + proxyPubSubCommon id api.PublicSessionId - settings NewPublisherSettings + settings sfu.NewPublisherSettings } -func newMcuProxyPublisher(logger log.Logger, id api.PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, settings NewPublisherSettings, proxyId string, conn *mcuProxyConnection, listener McuListener) *mcuProxyPublisher { - return &mcuProxyPublisher{ - mcuProxyPubSubCommon: mcuProxyPubSubCommon{ +func newProxyPublisher(logger log.Logger, id api.PublicSessionId, sid string, streamType sfu.StreamType, maxBitrate api.Bandwidth, settings sfu.NewPublisherSettings, proxyId string, conn *proxyConnection, listener sfu.Listener) *proxyPublisher { + return &proxyPublisher{ + proxyPubSubCommon: proxyPubSubCommon{ logger: logger, sid: sid, @@ -175,31 +188,35 @@ func newMcuProxyPublisher(logger log.Logger, id api.PublicSessionId, sid string, } } -func (p *mcuProxyPublisher) PublisherId() api.PublicSessionId { +func (p *proxyPublisher) GetConnectionURL() (string, net.IP) { + return p.conn.rawUrl, p.conn.ip +} + +func (p *proxyPublisher) PublisherId() api.PublicSessionId { return p.id } -func (p *mcuProxyPublisher) HasMedia(mt MediaType) bool { +func (p *proxyPublisher) HasMedia(mt sfu.MediaType) bool { return (p.settings.MediaTypes & mt) == mt } -func (p *mcuProxyPublisher) SetMedia(mt MediaType) { +func (p *proxyPublisher) SetMedia(mt sfu.MediaType) { // TODO: Also update mediaTypes on proxy. p.settings.MediaTypes = mt } -func (p *mcuProxyPublisher) NotifyClosed() { +func (p *proxyPublisher) NotifyClosed() { p.logger.Printf("Publisher %s at %s was closed", p.proxyId, p.conn) p.listener.PublisherClosed(p) p.conn.removePublisher(p) } -func (p *mcuProxyPublisher) Close(ctx context.Context) { +func (p *proxyPublisher) Close(ctx context.Context) { p.NotifyClosed() - msg := &ProxyClientMessage{ + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "delete-publisher", ClientId: p.proxyId, }, @@ -216,10 +233,10 @@ func (p *mcuProxyPublisher) Close(ctx context.Context) { p.logger.Printf("Deleted publisher %s at %s", p.proxyId, p.conn) } -func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { - msg := &ProxyClientMessage{ +func (p *proxyPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + msg := &proxy.ClientMessage{ Type: "payload", - Payload: &PayloadProxyClientMessage{ + Payload: &proxy.PayloadClientMessage{ Type: data.Type, ClientId: p.proxyId, Sid: data.Sid, @@ -230,11 +247,11 @@ func (p *mcuProxyPublisher) SendMessage(ctx context.Context, message *api.Messag p.doSendMessage(ctx, msg, callback) } -func (p *mcuProxyPublisher) ProcessPayload(msg *PayloadProxyServerMessage) { +func (p *proxyPublisher) ProcessPayload(msg *proxy.PayloadServerMessage) { p.doProcessPayload(p, msg) } -func (p *mcuProxyPublisher) ProcessEvent(msg *EventProxyServerMessage) { +func (p *proxyPublisher) ProcessEvent(msg *proxy.EventServerMessage) { switch msg.Type { case "ice-completed": p.listener.OnIceCompleted(p) @@ -245,16 +262,16 @@ func (p *mcuProxyPublisher) ProcessEvent(msg *EventProxyServerMessage) { } } -type mcuProxySubscriber struct { - mcuProxyPubSubCommon +type proxySubscriber struct { + proxyPubSubCommon publisherId api.PublicSessionId - publisherConn *mcuProxyConnection + publisherConn *proxyConnection } -func newMcuProxySubscriber(logger log.Logger, publisherId api.PublicSessionId, sid string, streamType StreamType, maxBitrate api.Bandwidth, proxyId string, conn *mcuProxyConnection, listener McuListener, publisherConn *mcuProxyConnection) *mcuProxySubscriber { - return &mcuProxySubscriber{ - mcuProxyPubSubCommon: mcuProxyPubSubCommon{ +func newProxySubscriber(logger log.Logger, publisherId api.PublicSessionId, sid string, streamType sfu.StreamType, maxBitrate api.Bandwidth, proxyId string, conn *proxyConnection, listener sfu.Listener, publisherConn *proxyConnection) *proxySubscriber { + return &proxySubscriber{ + proxyPubSubCommon: proxyPubSubCommon{ logger: logger, sid: sid, @@ -270,11 +287,15 @@ func newMcuProxySubscriber(logger log.Logger, publisherId api.PublicSessionId, s } } -func (s *mcuProxySubscriber) Publisher() api.PublicSessionId { +func (s *proxySubscriber) GetConnectionURL() (string, net.IP) { + return s.conn.rawUrl, s.conn.ip +} + +func (s *proxySubscriber) Publisher() api.PublicSessionId { return s.publisherId } -func (s *mcuProxySubscriber) NotifyClosed() { +func (s *proxySubscriber) NotifyClosed() { if s.publisherConn != nil { s.logger.Printf("Remote subscriber %s at %s (forwarded to %s) was closed", s.proxyId, s.conn, s.publisherConn) } else { @@ -284,12 +305,12 @@ func (s *mcuProxySubscriber) NotifyClosed() { s.conn.removeSubscriber(s) } -func (s *mcuProxySubscriber) Close(ctx context.Context) { +func (s *proxySubscriber) Close(ctx context.Context) { s.NotifyClosed() - msg := &ProxyClientMessage{ + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "delete-subscriber", ClientId: s.proxyId, }, @@ -318,10 +339,10 @@ func (s *mcuProxySubscriber) Close(ctx context.Context) { } } -func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { - msg := &ProxyClientMessage{ +func (s *proxySubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + msg := &proxy.ClientMessage{ Type: "payload", - Payload: &PayloadProxyClientMessage{ + Payload: &proxy.PayloadClientMessage{ Type: data.Type, ClientId: s.proxyId, Sid: data.Sid, @@ -332,11 +353,11 @@ func (s *mcuProxySubscriber) SendMessage(ctx context.Context, message *api.Messa s.doSendMessage(ctx, msg, callback) } -func (s *mcuProxySubscriber) ProcessPayload(msg *PayloadProxyServerMessage) { +func (s *proxySubscriber) ProcessPayload(msg *proxy.PayloadServerMessage) { s.doProcessPayload(s, msg) } -func (s *mcuProxySubscriber) ProcessEvent(msg *EventProxyServerMessage) { +func (s *proxySubscriber) ProcessEvent(msg *proxy.EventServerMessage) { switch msg.Type { case "ice-completed": s.listener.OnIceCompleted(s) @@ -350,18 +371,18 @@ func (s *mcuProxySubscriber) ProcessEvent(msg *EventProxyServerMessage) { } } -type mcuProxyCallback func(response *ProxyServerMessage) +type mcuProxyCallback func(response *proxy.ServerMessage) -type mcuProxyConnection struct { +type proxyConnection struct { logger log.Logger - proxy *mcuProxy + proxy *proxySFU rawUrl string url *url.URL ip net.IP connectToken string load atomic.Uint64 - bandwidth atomic.Pointer[EventProxyServerBandwidth] + bandwidth atomic.Pointer[proxy.EventServerBandwidth] mu sync.Mutex closer *internal.Closer closedDone *internal.Closer @@ -394,22 +415,22 @@ type mcuProxyConnection struct { publishersLock sync.RWMutex // +checklocks:publishersLock - publishers map[string]*mcuProxyPublisher + publishers map[string]*proxyPublisher // +checklocks:publishersLock - publisherIds map[StreamId]api.PublicSessionId + publisherIds map[sfu.StreamId]api.PublicSessionId subscribersLock sync.RWMutex // +checklocks:subscribersLock - subscribers map[string]*mcuProxySubscriber + subscribers map[string]*proxySubscriber } -func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token string) (*mcuProxyConnection, error) { +func newProxyConnection(proxy *proxySFU, baseUrl string, ip net.IP, token string) (*proxyConnection, error) { parsed, err := url.Parse(baseUrl) if err != nil { return nil, err } - conn := &mcuProxyConnection{ + conn := &proxyConnection{ logger: proxy.logger, proxy: proxy, rawUrl: baseUrl, @@ -419,9 +440,9 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str closer: internal.NewCloser(), closedDone: internal.NewCloser(), callbacks: make(map[string]mcuProxyCallback), - publishers: make(map[string]*mcuProxyPublisher), - publisherIds: make(map[StreamId]api.PublicSessionId), - subscribers: make(map[string]*mcuProxySubscriber), + publishers: make(map[string]*proxyPublisher), + publisherIds: make(map[sfu.StreamId]api.PublicSessionId), + subscribers: make(map[string]*proxySubscriber), } conn.reconnectInterval.Store(int64(initialReconnectInterval)) conn.load.Store(loadNotConnected) @@ -437,7 +458,7 @@ func newMcuProxyConnection(proxy *mcuProxy, baseUrl string, ip net.IP, token str return conn, nil } -func (c *mcuProxyConnection) String() string { +func (c *proxyConnection) String() string { if c.ip != nil { return fmt.Sprintf("%s (%s)", c.rawUrl, c.ip) } @@ -445,7 +466,7 @@ func (c *mcuProxyConnection) String() string { return c.rawUrl } -func (c *mcuProxyConnection) IsSameCountry(initiator McuInitiator) bool { +func (c *proxyConnection) IsSameCountry(initiator sfu.Initiator) bool { if initiator == nil { return true } @@ -463,7 +484,7 @@ func (c *mcuProxyConnection) IsSameCountry(initiator McuInitiator) bool { return initiatorCountry == connCountry } -func (c *mcuProxyConnection) IsSameContinent(initiator McuInitiator) bool { +func (c *proxyConnection) IsSameContinent(initiator sfu.Initiator) bool { if initiator == nil { return true } @@ -493,7 +514,7 @@ func (c *mcuProxyConnection) IsSameContinent(initiator McuInitiator) bool { return ContinentsOverlap(initiatorContinents, connContinents) } -type mcuProxyConnectionStats struct { +type proxyConnectionStats struct { Url string `json:"url"` IP net.IP `json:"ip,omitempty"` Connected bool `json:"connected"` @@ -505,8 +526,8 @@ type mcuProxyConnectionStats struct { Uptime *time.Time `json:"uptime,omitempty"` } -func (c *mcuProxyConnection) GetStats() *mcuProxyConnectionStats { - result := &mcuProxyConnectionStats{ +func (c *proxyConnection) GetStats() *proxyConnectionStats { + result := &proxyConnectionStats{ Url: c.url.String(), IP: c.ip, } @@ -535,27 +556,27 @@ func (c *mcuProxyConnection) GetStats() *mcuProxyConnectionStats { return result } -func (c *mcuProxyConnection) Load() uint64 { +func (c *proxyConnection) Load() uint64 { return c.load.Load() } -func (c *mcuProxyConnection) Bandwidth() *EventProxyServerBandwidth { +func (c *proxyConnection) Bandwidth() *proxy.EventServerBandwidth { return c.bandwidth.Load() } -func (c *mcuProxyConnection) Country() geoip.Country { +func (c *proxyConnection) Country() geoip.Country { return c.country.Load().(geoip.Country) } -func (c *mcuProxyConnection) Version() string { +func (c *proxyConnection) Version() string { return c.version.Load().(string) } -func (c *mcuProxyConnection) Features() []string { +func (c *proxyConnection) Features() []string { return c.features.Load().([]string) } -func (c *mcuProxyConnection) SessionId() api.PublicSessionId { +func (c *proxyConnection) SessionId() api.PublicSessionId { sid := c.sessionId.Load() if sid == nil { return "" @@ -564,29 +585,29 @@ func (c *mcuProxyConnection) SessionId() api.PublicSessionId { return sid.(api.PublicSessionId) } -func (c *mcuProxyConnection) IsConnected() bool { +func (c *proxyConnection) IsConnected() bool { c.mu.Lock() defer c.mu.Unlock() return c.conn != nil && c.helloProcessed.Load() && c.SessionId() != "" } -func (c *mcuProxyConnection) IsTemporary() bool { +func (c *proxyConnection) IsTemporary() bool { return c.temporary.Load() } -func (c *mcuProxyConnection) setTemporary() { +func (c *proxyConnection) setTemporary() { c.temporary.Store(true) } -func (c *mcuProxyConnection) clearTemporary() { +func (c *proxyConnection) clearTemporary() { c.temporary.Store(false) } -func (c *mcuProxyConnection) IsShutdownScheduled() bool { +func (c *proxyConnection) IsShutdownScheduled() bool { return c.shutdownScheduled.Load() || c.closeScheduled.Load() } -func (c *mcuProxyConnection) readPump() { +func (c *proxyConnection) readPump() { defer func() { if !c.closed.Load() { c.scheduleReconnect() @@ -635,7 +656,7 @@ func (c *mcuProxyConnection) readPump() { break } - var msg ProxyServerMessage + var msg proxy.ServerMessage if err := json.Unmarshal(message, &msg); err != nil { c.logger.Printf("Error unmarshaling %s from %s: %s", string(message), c, err) continue @@ -645,7 +666,7 @@ func (c *mcuProxyConnection) readPump() { } } -func (c *mcuProxyConnection) sendPing() bool { +func (c *proxyConnection) sendPing() bool { c.mu.Lock() defer c.mu.Unlock() if c.conn == nil { @@ -664,7 +685,7 @@ func (c *mcuProxyConnection) sendPing() bool { return true } -func (c *mcuProxyConnection) writePump() { +func (c *proxyConnection) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() @@ -684,30 +705,30 @@ func (c *mcuProxyConnection) writePump() { } } -func (c *mcuProxyConnection) start() { +func (c *proxyConnection) start() { go c.writePump() } -func (c *mcuProxyConnection) sendClose() error { +func (c *proxyConnection) sendClose() error { c.mu.Lock() defer c.mu.Unlock() if c.conn == nil { - return ErrNotConnected + return sfu.ErrNotConnected } c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint return c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) } -func (c *mcuProxyConnection) stop(ctx context.Context) { +func (c *proxyConnection) stop(ctx context.Context) { if !c.closed.CompareAndSwap(false, true) { return } c.closer.Close() if err := c.sendClose(); err != nil { - if err != ErrNotConnected { + if err != sfu.ErrNotConnected { c.logger.Printf("Could not send close message to %s: %s", c, err) } c.close() @@ -724,7 +745,7 @@ func (c *mcuProxyConnection) stop(ctx context.Context) { } } -func (c *mcuProxyConnection) close() { +func (c *proxyConnection) close() { c.helloProcessed.Store(false) c.mu.Lock() @@ -742,11 +763,11 @@ func (c *mcuProxyConnection) close() { } } -func (c *mcuProxyConnection) stopCloseIfEmpty() { +func (c *proxyConnection) stopCloseIfEmpty() { c.closeScheduled.Store(false) } -func (c *mcuProxyConnection) closeIfEmpty() bool { +func (c *proxyConnection) closeIfEmpty() bool { c.closeScheduled.Store(true) var total int64 @@ -780,8 +801,8 @@ func (c *mcuProxyConnection) closeIfEmpty() bool { return true } -func (c *mcuProxyConnection) scheduleReconnect() { - if err := c.sendClose(); err != nil && err != ErrNotConnected { +func (c *proxyConnection) scheduleReconnect() { + if err := c.sendClose(); err != nil && err != sfu.ErrNotConnected { c.logger.Printf("Could not send close message to %s: %s", c, err) } c.close() @@ -796,7 +817,7 @@ func (c *mcuProxyConnection) scheduleReconnect() { c.reconnectInterval.Store(interval) } -func (c *mcuProxyConnection) reconnect() { +func (c *proxyConnection) reconnect() { u, err := c.url.Parse("proxy") if err != nil { c.logger.Printf("Could not resolve url to proxy at %s: %s", c, err) @@ -858,7 +879,7 @@ func (c *mcuProxyConnection) reconnect() { go c.readPump() } -func (c *mcuProxyConnection) waitUntilConnected(ctx context.Context) error { +func (c *proxyConnection) waitUntilConnected(ctx context.Context) error { c.mu.Lock() defer c.mu.Unlock() @@ -874,7 +895,7 @@ func (c *mcuProxyConnection) waitUntilConnected(ctx context.Context) error { return waiter.Wait(ctx) } -func (c *mcuProxyConnection) removePublisher(publisher *mcuProxyPublisher) { +func (c *proxyConnection) removePublisher(publisher *proxyPublisher) { c.proxy.removePublisher(publisher) c.publishersLock.Lock() @@ -882,26 +903,26 @@ func (c *mcuProxyConnection) removePublisher(publisher *mcuProxyPublisher) { if _, found := c.publishers[publisher.proxyId]; found { delete(c.publishers, publisher.proxyId) - statsPublishersCurrent.WithLabelValues(string(publisher.StreamType())).Dec() + sfuinternal.StatsPublishersCurrent.WithLabelValues(string(publisher.StreamType())).Dec() } - delete(c.publisherIds, getStreamId(publisher.id, publisher.StreamType())) + delete(c.publisherIds, sfu.GetStreamId(publisher.id, publisher.StreamType())) if len(c.publishers) == 0 && (c.closeScheduled.Load() || c.IsTemporary()) { go c.closeIfEmpty() } } -func (c *mcuProxyConnection) clearPublishers() { +func (c *proxyConnection) clearPublishers() { c.publishersLock.Lock() defer c.publishersLock.Unlock() - go func(publishers map[string]*mcuProxyPublisher) { + go func(publishers map[string]*proxyPublisher) { for _, publisher := range publishers { publisher.NotifyClosed() } }(c.publishers) // Can't use clear(...) here as the map is processed by the goroutine above. - c.publishers = make(map[string]*mcuProxyPublisher) + c.publishers = make(map[string]*proxyPublisher) clear(c.publisherIds) if c.closeScheduled.Load() || c.IsTemporary() { @@ -909,13 +930,13 @@ func (c *mcuProxyConnection) clearPublishers() { } } -func (c *mcuProxyConnection) removeSubscriber(subscriber *mcuProxySubscriber) { +func (c *proxyConnection) removeSubscriber(subscriber *proxySubscriber) { c.subscribersLock.Lock() defer c.subscribersLock.Unlock() if _, found := c.subscribers[subscriber.proxyId]; found { delete(c.subscribers, subscriber.proxyId) - statsSubscribersCurrent.WithLabelValues(string(subscriber.StreamType())).Dec() + sfuinternal.StatsSubscribersCurrent.WithLabelValues(string(subscriber.StreamType())).Dec() } if len(c.subscribers) == 0 && (c.closeScheduled.Load() || c.IsTemporary()) { @@ -923,24 +944,24 @@ func (c *mcuProxyConnection) removeSubscriber(subscriber *mcuProxySubscriber) { } } -func (c *mcuProxyConnection) clearSubscribers() { +func (c *proxyConnection) clearSubscribers() { c.subscribersLock.Lock() defer c.subscribersLock.Unlock() - go func(subscribers map[string]*mcuProxySubscriber) { + go func(subscribers map[string]*proxySubscriber) { for _, subscriber := range subscribers { subscriber.NotifyClosed() } }(c.subscribers) // Can't use clear(...) here as the map is processed by the goroutine above. - c.subscribers = make(map[string]*mcuProxySubscriber) + c.subscribers = make(map[string]*proxySubscriber) if c.closeScheduled.Load() || c.IsTemporary() { go c.closeIfEmpty() } } -func (c *mcuProxyConnection) clearCallbacks() { +func (c *proxyConnection) clearCallbacks() { c.mu.Lock() defer c.mu.Unlock() @@ -948,7 +969,7 @@ func (c *mcuProxyConnection) clearCallbacks() { clear(c.deferredCallbacks) } -func (c *mcuProxyConnection) getCallback(id string) func(*ProxyServerMessage) { +func (c *proxyConnection) getCallback(id string) func(*proxy.ServerMessage) { c.mu.Lock() defer c.mu.Unlock() @@ -959,7 +980,7 @@ func (c *mcuProxyConnection) getCallback(id string) func(*ProxyServerMessage) { return callback } -func (c *mcuProxyConnection) registerDeferredCallback(msgId string, callback mcuProxyCallback) { +func (c *proxyConnection) registerDeferredCallback(msgId string, callback mcuProxyCallback) { if msgId == "" { return } @@ -974,7 +995,7 @@ func (c *mcuProxyConnection) registerDeferredCallback(msgId string, callback mcu c.deferredCallbacks[msgId] = callback } -func (c *mcuProxyConnection) getDeferredCallback(msgId string) mcuProxyCallback { +func (c *proxyConnection) getDeferredCallback(msgId string) mcuProxyCallback { c.mu.Lock() defer c.mu.Unlock() @@ -986,7 +1007,7 @@ func (c *mcuProxyConnection) getDeferredCallback(msgId string) mcuProxyCallback return result } -func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { +func (c *proxyConnection) processMessage(msg *proxy.ServerMessage) { if c.helloMsgId != "" && msg.Id == c.helloMsgId { c.helloMsgId = "" switch msg.Type { @@ -1068,7 +1089,7 @@ func (c *mcuProxyConnection) processMessage(msg *ProxyServerMessage) { } } -func (c *mcuProxyConnection) processPayload(msg *ProxyServerMessage) { +func (c *proxyConnection) processPayload(msg *proxy.ServerMessage) { payload := msg.Payload c.publishersLock.RLock() publisher, found := c.publishers[payload.ClientId] @@ -1089,7 +1110,7 @@ func (c *mcuProxyConnection) processPayload(msg *ProxyServerMessage) { c.logger.Printf("Received payload for unknown client %+v from %s", payload, c) } -func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { +func (c *proxyConnection) processEvent(msg *proxy.ServerMessage) { event := msg.Event switch event.Type { case "backend-disconnected": @@ -1152,7 +1173,7 @@ func (c *mcuProxyConnection) processEvent(msg *ProxyServerMessage) { c.logger.Printf("Received event for unknown client %+v from %s", event, c) } -func (c *mcuProxyConnection) processBye(msg *ProxyServerMessage) { +func (c *proxyConnection) processBye(msg *proxy.ServerMessage) { bye := msg.Bye switch bye.Reason { case "session_resumed": @@ -1167,12 +1188,12 @@ func (c *mcuProxyConnection) processBye(msg *ProxyServerMessage) { c.sessionId.Store(api.PublicSessionId("")) } -func (c *mcuProxyConnection) sendHello() error { +func (c *proxyConnection) sendHello() error { c.helloMsgId = strconv.FormatInt(c.msgId.Add(1), 10) - msg := &ProxyClientMessage{ + msg := &proxy.ClientMessage{ Id: c.helloMsgId, Type: "hello", - Hello: &HelloProxyClientMessage{ + Hello: &proxy.HelloClientMessage{ Version: "1.0", }, } @@ -1181,7 +1202,7 @@ func (c *mcuProxyConnection) sendHello() error { } else if c.connectToken != "" { msg.Hello.Token = c.connectToken } else { - tokenString, err := c.proxy.createToken("") + tokenString, err := c.proxy.CreateToken("") if err != nil { return err } @@ -1191,7 +1212,7 @@ func (c *mcuProxyConnection) sendHello() error { return c.sendMessage(msg) } -func (c *mcuProxyConnection) sendMessage(msg *ProxyClientMessage) error { +func (c *proxyConnection) sendMessage(msg *proxy.ClientMessage) error { c.mu.Lock() defer c.mu.Unlock() @@ -1199,24 +1220,24 @@ func (c *mcuProxyConnection) sendMessage(msg *ProxyClientMessage) error { } // +checklocks:c.mu -func (c *mcuProxyConnection) sendMessageLocked(msg *ProxyClientMessage) error { +func (c *proxyConnection) sendMessageLocked(msg *proxy.ClientMessage) error { if proxyDebugMessages { c.logger.Printf("Send message to %s: %+v", c, msg) } if c.conn == nil { - return ErrNotConnected + return sfu.ErrNotConnected } c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint return c.conn.WriteJSON(msg) } -func (c *mcuProxyConnection) performAsyncRequest(ctx context.Context, msg *ProxyClientMessage, callback func(err error, response *ProxyServerMessage)) string { +func (c *proxyConnection) performAsyncRequest(ctx context.Context, msg *proxy.ClientMessage, callback func(err error, response *proxy.ServerMessage)) string { msgId := strconv.FormatInt(c.msgId.Add(1), 10) msg.Id = msgId c.mu.Lock() defer c.mu.Unlock() - c.callbacks[msgId] = func(msg *ProxyServerMessage) { + c.callbacks[msgId] = func(msg *proxy.ServerMessage) { callback(nil, msg) } if err := c.sendMessageLocked(msg); err != nil { @@ -1235,14 +1256,14 @@ func (c *mcuProxyConnection) performAsyncRequest(ctx context.Context, msg *Proxy return msgId } -func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyClientMessage) (*ProxyServerMessage, string, error) { +func (c *proxyConnection) performSyncRequest(ctx context.Context, msg *proxy.ClientMessage) (*proxy.ServerMessage, string, error) { if err := ctx.Err(); err != nil { return nil, "", err } errChan := make(chan error, 1) - responseChan := make(chan *ProxyServerMessage, 1) - msgId := c.performAsyncRequest(ctx, msg, func(err error, response *ProxyServerMessage) { + responseChan := make(chan *proxy.ServerMessage, 1) + msgId := c.performAsyncRequest(ctx, msg, func(err error, response *proxy.ServerMessage) { if err != nil { errChan <- err } else { @@ -1260,7 +1281,7 @@ func (c *mcuProxyConnection) performSyncRequest(ctx context.Context, msg *ProxyC } } -func (c *mcuProxyConnection) deferredDeletePublisher(id api.PublicSessionId, streamType StreamType, response *ProxyServerMessage) { +func (c *proxyConnection) deferredDeletePublisher(id api.PublicSessionId, streamType sfu.StreamType, response *proxy.ServerMessage) { if response.Type == "error" { c.logger.Printf("Publisher for %s was not created at %s: %s", id, c, response.Error) return @@ -1268,9 +1289,9 @@ func (c *mcuProxyConnection) deferredDeletePublisher(id api.PublicSessionId, str proxyId := response.Command.Id c.logger.Printf("Created unused %s publisher %s on %s for %s", streamType, proxyId, c, id) - msg := &ProxyClientMessage{ + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "delete-publisher", ClientId: proxyId, }, @@ -1290,10 +1311,10 @@ func (c *mcuProxyConnection) deferredDeletePublisher(id api.PublicSessionId, str c.logger.Printf("Deleted publisher %s at %s", proxyId, c) } -func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings) (McuPublisher, error) { - msg := &ProxyClientMessage{ +func (c *proxyConnection) newPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings) (sfu.Publisher, error) { + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-publisher", Sid: sid, StreamType: streamType, @@ -1307,7 +1328,7 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe response, msgId, err := c.performSyncRequest(ctx, msg) if err != nil { if errors.Is(err, context.DeadlineExceeded) { - c.registerDeferredCallback(msgId, func(response *ProxyServerMessage) { + c.registerDeferredCallback(msgId, func(response *proxy.ServerMessage) { c.deferredDeletePublisher(id, streamType, response) }) } @@ -1318,17 +1339,17 @@ func (c *mcuProxyConnection) newPublisher(ctx context.Context, listener McuListe proxyId := response.Command.Id c.logger.Printf("Created %s publisher %s on %s for %s", streamType, proxyId, c, id) - publisher := newMcuProxyPublisher(c.logger, id, sid, streamType, response.Command.Bitrate, settings, proxyId, c, listener) + publisher := newProxyPublisher(c.logger, id, sid, streamType, response.Command.Bitrate, settings, proxyId, c, listener) c.publishersLock.Lock() c.publishers[proxyId] = publisher - c.publisherIds[getStreamId(id, streamType)] = api.PublicSessionId(proxyId) + c.publisherIds[sfu.GetStreamId(id, streamType)] = api.PublicSessionId(proxyId) c.publishersLock.Unlock() - statsPublishersCurrent.WithLabelValues(string(streamType)).Inc() - statsPublishersTotal.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsPublishersCurrent.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsPublishersTotal.WithLabelValues(string(streamType)).Inc() return publisher, nil } -func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId api.PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, response *ProxyServerMessage) { +func (c *proxyConnection) deferredDeleteSubscriber(publisherSessionId api.PublicSessionId, streamType sfu.StreamType, publisherConn *proxyConnection, response *proxy.ServerMessage) { if response.Type == "error" { c.logger.Printf("Subscriber for %s was not created at %s: %s", publisherSessionId, c, response.Error) return @@ -1337,9 +1358,9 @@ func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId api.Pub proxyId := response.Command.Id c.logger.Printf("Created unused %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) - msg := &ProxyClientMessage{ + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "delete-subscriber", ClientId: proxyId, }, @@ -1371,10 +1392,10 @@ func (c *mcuProxyConnection) deferredDeleteSubscriber(publisherSessionId api.Pub } } -func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuListener, publisherId api.PublicSessionId, publisherSessionId api.PublicSessionId, streamType StreamType) (McuSubscriber, error) { - msg := &ProxyClientMessage{ +func (c *proxyConnection) newSubscriber(ctx context.Context, listener sfu.Listener, publisherId api.PublicSessionId, publisherSessionId api.PublicSessionId, streamType sfu.StreamType) (sfu.Subscriber, error) { + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-subscriber", StreamType: streamType, PublisherId: publisherId, @@ -1384,7 +1405,7 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList response, msgId, err := c.performSyncRequest(ctx, msg) if err != nil { if errors.Is(err, context.DeadlineExceeded) { - c.registerDeferredCallback(msgId, func(response *ProxyServerMessage) { + c.registerDeferredCallback(msgId, func(response *proxy.ServerMessage) { c.deferredDeleteSubscriber(publisherSessionId, streamType, nil, response) }) } @@ -1395,30 +1416,30 @@ func (c *mcuProxyConnection) newSubscriber(ctx context.Context, listener McuList proxyId := response.Command.Id c.logger.Printf("Created %s subscriber %s on %s for %s", streamType, proxyId, c, publisherSessionId) - subscriber := newMcuProxySubscriber(c.logger, publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, nil) + subscriber := newProxySubscriber(c.logger, publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, nil) c.subscribersLock.Lock() c.subscribers[proxyId] = subscriber c.subscribersLock.Unlock() - statsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() - statsSubscribersTotal.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsSubscribersTotal.WithLabelValues(string(streamType)).Inc() return subscriber, nil } -func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener McuListener, publisherId api.PublicSessionId, publisherSessionId api.PublicSessionId, streamType StreamType, publisherConn *mcuProxyConnection, remoteToken string) (McuSubscriber, error) { +func (c *proxyConnection) newRemoteSubscriber(ctx context.Context, listener sfu.Listener, publisherId api.PublicSessionId, publisherSessionId api.PublicSessionId, streamType sfu.StreamType, publisherConn *proxyConnection, remoteToken string) (sfu.Subscriber, error) { if c == publisherConn { return c.newSubscriber(ctx, listener, publisherId, publisherSessionId, streamType) } if remoteToken == "" { var err error - if remoteToken, err = c.proxy.createToken(string(publisherId)); err != nil { + if remoteToken, err = c.proxy.CreateToken(string(publisherId)); err != nil { return nil, err } } - msg := &ProxyClientMessage{ + msg := &proxy.ClientMessage{ Type: "command", - Command: &CommandProxyClientMessage{ + Command: &proxy.CommandClientMessage{ Type: "create-subscriber", StreamType: streamType, PublisherId: publisherId, @@ -1431,7 +1452,7 @@ func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener M response, msgId, err := c.performSyncRequest(ctx, msg) if err != nil { if errors.Is(err, context.DeadlineExceeded) { - c.registerDeferredCallback(msgId, func(response *ProxyServerMessage) { + c.registerDeferredCallback(msgId, func(response *proxy.ServerMessage) { c.deferredDeleteSubscriber(publisherSessionId, streamType, publisherConn, response) }) } @@ -1442,23 +1463,23 @@ func (c *mcuProxyConnection) newRemoteSubscriber(ctx context.Context, listener M proxyId := response.Command.Id c.logger.Printf("Created remote %s subscriber %s on %s for %s (forwarded to %s)", streamType, proxyId, c, publisherSessionId, publisherConn) - subscriber := newMcuProxySubscriber(c.logger, publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, publisherConn) + subscriber := newProxySubscriber(c.logger, publisherSessionId, response.Command.Sid, streamType, response.Command.Bitrate, proxyId, c, listener, publisherConn) c.subscribersLock.Lock() c.subscribers[proxyId] = subscriber c.subscribersLock.Unlock() - statsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() - statsSubscribersTotal.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsSubscribersCurrent.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsSubscribersTotal.WithLabelValues(string(streamType)).Inc() return subscriber, nil } -type mcuProxySettings struct { - mcuCommonSettings +type proxySettings struct { + sfuinternal.CommonSettings } -func newMcuProxySettings(ctx context.Context, config *goconf.ConfigFile) (McuSettings, error) { - settings := &mcuProxySettings{ - mcuCommonSettings: mcuCommonSettings{ - logger: log.LoggerFromContext(ctx), +func newProxySettings(ctx context.Context, config *goconf.ConfigFile) (sfu.Settings, error) { + settings := &proxySettings{ + CommonSettings: sfuinternal.CommonSettings{ + Logger: log.LoggerFromContext(ctx), }, } if err := settings.load(config); err != nil { @@ -1468,8 +1489,8 @@ func newMcuProxySettings(ctx context.Context, config *goconf.ConfigFile) (McuSet return settings, nil } -func (s *mcuProxySettings) load(config *goconf.ConfigFile) error { - if err := s.mcuCommonSettings.load(config); err != nil { +func (s *proxySettings) load(config *goconf.ConfigFile) error { + if err := s.Load(config); err != nil { return err } @@ -1478,47 +1499,47 @@ func (s *mcuProxySettings) load(config *goconf.ConfigFile) error { proxyTimeoutSeconds = defaultProxyTimeoutSeconds } proxyTimeout := time.Duration(proxyTimeoutSeconds) * time.Second - s.logger.Printf("Using a timeout of %s for proxy requests", proxyTimeout) - s.setTimeout(proxyTimeout) + s.Logger.Printf("Using a timeout of %s for proxy requests", proxyTimeout) + s.SetTimeout(proxyTimeout) return nil } -func (s *mcuProxySettings) Reload(config *goconf.ConfigFile) { +func (s *proxySettings) Reload(config *goconf.ConfigFile) { if err := s.load(config); err != nil { - s.logger.Printf("Error reloading proxy settings: %s", err) + s.Logger.Printf("Error reloading proxy settings: %s", err) } } -type mcuProxy struct { +type proxySFU struct { logger log.Logger urlType string tokenId string tokenKey *rsa.PrivateKey - config ProxyConfig + config Config dialer *websocket.Dialer connectionsMu sync.RWMutex // +checklocks:connectionsMu - connections []*mcuProxyConnection + connections []*proxyConnection // +checklocks:connectionsMu - connectionsMap map[string][]*mcuProxyConnection + connectionsMap map[string][]*proxyConnection connRequests atomic.Int64 nextSort atomic.Int64 - settings McuSettings + settings sfu.Settings mu sync.RWMutex // +checklocks:mu - publishers map[StreamId]*mcuProxyConnection + publishers map[sfu.StreamId]*proxyConnection publisherWaiters async.ChannelWaiters continentsMap atomic.Value - rpcClients *GrpcClients + rpcClients *grpc.Clients } -func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, rpcClients *GrpcClients, dnsMonitor *dns.Monitor) (Mcu, error) { +func NewProxySFU(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd.Client, rpcClients *grpc.Clients, dnsMonitor *dns.Monitor) (sfu.SFU, error) { logger := log.LoggerFromContext(ctx) urlType, _ := config.GetString("mcu", "urltype") if urlType == "" { @@ -1542,12 +1563,12 @@ func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd return nil, fmt.Errorf("could not parse private key from %s: %s", tokenKeyFilename, err) } - settings, err := newMcuProxySettings(ctx, config) + settings, err := newProxySettings(ctx, config) if err != nil { return nil, err } - mcu := &mcuProxy{ + mcu := &proxySFU{ logger: logger, urlType: urlType, tokenId: tokenId, @@ -1557,10 +1578,10 @@ func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd Proxy: http.ProxyFromEnvironment, HandshakeTimeout: settings.Timeout(), }, - connectionsMap: make(map[string][]*mcuProxyConnection), + connectionsMap: make(map[string][]*proxyConnection), settings: settings, - publishers: make(map[StreamId]*mcuProxyConnection), + publishers: make(map[sfu.StreamId]*proxyConnection), rpcClients: rpcClients, } @@ -1579,9 +1600,9 @@ func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd switch urlType { case proxyUrlTypeStatic: - mcu.config, err = NewProxyConfigStatic(logger, config, mcu, dnsMonitor) + mcu.config, err = NewConfigStatic(logger, config, mcu, dnsMonitor) case proxyUrlTypeEtcd: - mcu.config, err = NewProxyConfigEtcd(logger, config, etcdClient, mcu) + mcu.config, err = NewConfigEtcd(logger, config, etcdClient, mcu) default: err = fmt.Errorf("unsupported proxy URL type %s", urlType) } @@ -1592,11 +1613,11 @@ func NewMcuProxy(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd return mcu, nil } -func (m *mcuProxy) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { +func (m *proxySFU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { return m.settings.MaxStreamBitrate(), m.settings.MaxScreenBitrate() } -func (m *mcuProxy) loadContinentsMap(cfg *goconf.ConfigFile) error { +func (m *proxySFU) loadContinentsMap(cfg *goconf.ConfigFile) error { options, err := config.GetStringOptions(cfg, "continent-overrides", false) if err != nil { return err @@ -1637,11 +1658,11 @@ func (m *mcuProxy) loadContinentsMap(cfg *goconf.ConfigFile) error { return nil } -func (m *mcuProxy) Start(ctx context.Context) error { +func (m *proxySFU) Start(ctx context.Context) error { return m.config.Start() } -func (m *mcuProxy) Stop() { +func (m *proxySFU) Stop() { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() @@ -1654,9 +1675,9 @@ func (m *mcuProxy) Stop() { m.config.Stop() } -func (m *mcuProxy) createToken(subject string) (string, error) { - claims := &TokenClaims{ - jwt.RegisteredClaims{ +func (m *proxySFU) CreateToken(subject string) (string, error) { + claims := &proxy.TokenClaims{ + RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now()), Issuer: m.tokenId, Subject: subject, @@ -1671,23 +1692,30 @@ func (m *mcuProxy) createToken(subject string) (string, error) { return tokenString, nil } -func (m *mcuProxy) getConnections() []*mcuProxyConnection { +func (m *proxySFU) getConnections() []*proxyConnection { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() return m.connections } -func (m *mcuProxy) hasConnections() bool { +func (m *proxySFU) ConnectionsCount() int { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() - return slices.ContainsFunc(m.connections, func(conn *mcuProxyConnection) bool { + return len(m.connections) +} + +func (m *proxySFU) hasConnections() bool { + m.connectionsMu.RLock() + defer m.connectionsMu.RUnlock() + + return slices.ContainsFunc(m.connections, func(conn *proxyConnection) bool { return conn.IsConnected() }) } -func (m *mcuProxy) WaitForDisconnected(ctx context.Context) error { +func (m *proxySFU) WaitForDisconnected(ctx context.Context) error { ticker := time.NewTicker(time.Millisecond) defer ticker.Stop() @@ -1701,7 +1729,7 @@ func (m *mcuProxy) WaitForDisconnected(ctx context.Context) error { return nil } -func (m *mcuProxy) WaitForConnections(ctx context.Context) error { +func (m *proxySFU) WaitForConnections(ctx context.Context) error { ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() @@ -1715,13 +1743,39 @@ func (m *mcuProxy) WaitForConnections(ctx context.Context) error { return nil } -func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) error { +func (m *proxySFU) WaitForConnectionsEstablished(ctx context.Context, waitMap map[string]bool) error { + m.connectionsMu.RLock() + defer m.connectionsMu.RUnlock() + + for len(waitMap) > 0 { + if err := ctx.Err(); err != nil { + return err + } + + for u := range waitMap { + for _, c := range m.connections { + if c.rawUrl == u && c.IsConnected() && c.SessionId() != "" { + delete(waitMap, u) + break + } + } + } + + m.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + m.connectionsMu.RLock() + } + + return nil +} + +func (m *proxySFU) AddConnection(ignoreErrors bool, url string, ips ...net.IP) error { m.connectionsMu.Lock() defer m.connectionsMu.Unlock() - var conns []*mcuProxyConnection + var conns []*proxyConnection if len(ips) == 0 { - conn, err := newMcuProxyConnection(m, url, nil, "") + conn, err := newProxyConnection(m, url, nil, "") if err != nil { if ignoreErrors { m.logger.Printf("Could not create proxy connection to %s: %s", url, err) @@ -1734,7 +1788,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e conns = append(conns, conn) } else { for _, ip := range ips { - conn, err := newMcuProxyConnection(m, url, ip, "") + conn, err := newProxyConnection(m, url, ip, "") if err != nil { if ignoreErrors { m.logger.Printf("Could not create proxy connection to %s (%s): %s", url, ip, err) @@ -1756,7 +1810,7 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e if existing, found := m.connectionsMap[url]; found { m.connectionsMap[url] = append(existing, conn) } else { - m.connectionsMap[url] = []*mcuProxyConnection{conn} + m.connectionsMap[url] = []*proxyConnection{conn} } } @@ -1764,8 +1818,8 @@ func (m *mcuProxy) AddConnection(ignoreErrors bool, url string, ips ...net.IP) e return nil } -func (m *mcuProxy) iterateConnections(url string, ips []net.IP) iter.Seq[*mcuProxyConnection] { - return func(yield func(*mcuProxyConnection) bool) { +func (m *proxySFU) iterateConnections(url string, ips []net.IP) iter.Seq[*proxyConnection] { + return func(yield func(*proxyConnection) bool) { m.connectionsMu.Lock() defer m.connectionsMu.Unlock() @@ -1794,21 +1848,21 @@ func (m *mcuProxy) iterateConnections(url string, ips []net.IP) iter.Seq[*mcuPro } } -func (m *mcuProxy) RemoveConnection(url string, ips ...net.IP) { +func (m *proxySFU) RemoveConnection(url string, ips ...net.IP) { for conn := range m.iterateConnections(url, ips) { m.logger.Printf("Removing connection to %s", conn) conn.closeIfEmpty() } } -func (m *mcuProxy) KeepConnection(url string, ips ...net.IP) { +func (m *proxySFU) KeepConnection(url string, ips ...net.IP) { for conn := range m.iterateConnections(url, ips) { conn.stopCloseIfEmpty() conn.clearTemporary() } } -func (m *mcuProxy) Reload(config *goconf.ConfigFile) { +func (m *proxySFU) Reload(config *goconf.ConfigFile) { m.settings.Reload(config) if m.settings.Timeout() != m.dialer.HandshakeTimeout { @@ -1824,7 +1878,7 @@ func (m *mcuProxy) Reload(config *goconf.ConfigFile) { } } -func (m *mcuProxy) removeConnection(c *mcuProxyConnection) { +func (m *proxySFU) removeConnection(c *proxyConnection) { m.connectionsMu.Lock() defer m.connectionsMu.Unlock() @@ -1850,21 +1904,21 @@ func (m *mcuProxy) removeConnection(c *mcuProxyConnection) { } } -func (m *mcuProxy) SetOnConnected(f func()) { +func (m *proxySFU) SetOnConnected(f func()) { // Not supported. } -func (m *mcuProxy) SetOnDisconnected(f func()) { +func (m *proxySFU) SetOnDisconnected(f func()) { // Not supported. } type mcuProxyStats struct { - Publishers int64 `json:"publishers"` - Clients int64 `json:"clients"` - Details []*mcuProxyConnectionStats `json:"details"` + Publishers int64 `json:"publishers"` + Clients int64 `json:"clients"` + Details []*proxyConnectionStats `json:"details"` } -func (m *mcuProxy) GetStats() any { +func (m *proxySFU) GetStats() any { result := &mcuProxyStats{} m.connectionsMu.RLock() @@ -1879,7 +1933,7 @@ func (m *mcuProxy) GetStats() any { return result } -func (m *mcuProxy) GetServerInfoSfu() *talk.BackendServerInfoSfu { +func (m *proxySFU) GetServerInfoSfu() *talk.BackendServerInfoSfu { m.connectionsMu.RLock() defer m.connectionsMu.RUnlock() @@ -1927,7 +1981,7 @@ func (m *mcuProxy) GetServerInfoSfu() *talk.BackendServerInfoSfu { return sfu } -func (m *mcuProxy) getContinentsMap() ContinentsMap { +func (m *proxySFU) getContinentsMap() ContinentsMap { continentsMap := m.continentsMap.Load() if continentsMap == nil { return nil @@ -1935,14 +1989,14 @@ func (m *mcuProxy) getContinentsMap() ContinentsMap { return continentsMap.(ContinentsMap) } -func (m *mcuProxy) setContinentsMap(continentsMap ContinentsMap) { +func (m *proxySFU) setContinentsMap(continentsMap ContinentsMap) { if continentsMap == nil { continentsMap = make(ContinentsMap) } m.continentsMap.Store(continentsMap) } -type mcuProxyConnectionsList []*mcuProxyConnection +type mcuProxyConnectionsList []*proxyConnection func ContinentsOverlap(a, b []geoip.Continent) bool { if len(a) == 0 || len(b) == 0 { @@ -1957,7 +2011,7 @@ func ContinentsOverlap(a, b []geoip.Continent) bool { return false } -func sortConnectionsForCountry(connections []*mcuProxyConnection, country geoip.Country, continentMap ContinentsMap) []*mcuProxyConnection { +func sortConnectionsForCountry(connections []*proxyConnection, country geoip.Country, continentMap ContinentsMap) []*proxyConnection { // Move connections in the same country to the start of the list. sorted := make(mcuProxyConnectionsList, 0, len(connections)) unprocessed := make(mcuProxyConnectionsList, 0, len(connections)) @@ -1998,7 +2052,7 @@ func sortConnectionsForCountry(connections []*mcuProxyConnection, country geoip. return sorted } -func (m *mcuProxy) getSortedConnections(initiator McuInitiator) []*mcuProxyConnection { +func (m *proxySFU) getSortedConnections(initiator sfu.Initiator) []*proxyConnection { m.connectionsMu.RLock() connections := m.connections m.connectionsMu.RUnlock() @@ -2013,7 +2067,7 @@ func (m *mcuProxy) getSortedConnections(initiator McuInitiator) []*mcuProxyConne m.nextSort.Store(now + int64(connectionSortInterval)) sorted := slices.Clone(connections) - slices.SortFunc(sorted, func(a, b *mcuProxyConnection) int { + slices.SortFunc(sorted, func(a, b *proxyConnection) int { return int(a.Load() - b.Load()) }) @@ -2031,16 +2085,16 @@ func (m *mcuProxy) getSortedConnections(initiator McuInitiator) []*mcuProxyConne return connections } -func (m *mcuProxy) removePublisher(publisher *mcuProxyPublisher) { +func (m *proxySFU) removePublisher(publisher *proxyPublisher) { m.mu.Lock() defer m.mu.Unlock() - delete(m.publishers, getStreamId(publisher.id, publisher.StreamType())) + delete(m.publishers, sfu.GetStreamId(publisher.id, publisher.StreamType())) } -func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuPublisher { +func (m *proxySFU) createPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator, connections []*proxyConnection, isAllowed func(c *proxyConnection) bool) sfu.Publisher { var maxBitrate api.Bandwidth - if streamType == StreamTypeScreen { + if streamType == sfu.StreamTypeScreen { maxBitrate = m.settings.MaxScreenBitrate() } else { maxBitrate = m.settings.MaxStreamBitrate() @@ -2068,7 +2122,7 @@ func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id } m.mu.Lock() - m.publishers[getStreamId(id, streamType)] = conn + m.publishers[sfu.GetStreamId(id, streamType)] = conn m.mu.Unlock() m.publisherWaiters.Wakeup() return publisher @@ -2077,21 +2131,21 @@ func (m *mcuProxy) createPublisher(ctx context.Context, listener McuListener, id return nil } -func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id api.PublicSessionId, sid string, streamType StreamType, settings NewPublisherSettings, initiator McuInitiator) (McuPublisher, error) { +func (m *proxySFU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { connections := m.getSortedConnections(initiator) - publisher := m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections, func(c *mcuProxyConnection) bool { + publisher := m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections, func(c *proxyConnection) bool { bw := c.Bandwidth() return bw == nil || bw.AllowIncoming() }) if publisher == nil { // No proxy has available bandwidth, select one with the lowest currently used bandwidth. - connections2 := make([]*mcuProxyConnection, 0, len(connections)) + connections2 := make([]*proxyConnection, 0, len(connections)) for _, c := range connections { if c.Bandwidth() != nil { connections2 = append(connections2, c) } } - slices.SortFunc(connections2, func(a *mcuProxyConnection, b *mcuProxyConnection) int { + slices.SortFunc(connections2, func(a *proxyConnection, b *proxyConnection) int { var incoming_a *float64 if bw := a.Bandwidth(); bw != nil { incoming_a = bw.Incoming @@ -2117,7 +2171,7 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id ap return 0 } }) - publisher = m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections2, func(c *mcuProxyConnection) bool { + publisher = m.createPublisher(ctx, listener, id, sid, streamType, settings, initiator, connections2, func(c *proxyConnection) bool { return true }) } @@ -2130,18 +2184,18 @@ func (m *mcuProxy) NewPublisher(ctx context.Context, listener McuListener, id ap return publisher, nil } -func (m *mcuProxy) getPublisherConnection(publisher api.PublicSessionId, streamType StreamType) *mcuProxyConnection { +func (m *proxySFU) getPublisherConnection(publisher api.PublicSessionId, streamType sfu.StreamType) *proxyConnection { m.mu.RLock() defer m.mu.RUnlock() - return m.publishers[getStreamId(publisher, streamType)] + return m.publishers[sfu.GetStreamId(publisher, streamType)] } -func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher api.PublicSessionId, streamType StreamType) *mcuProxyConnection { +func (m *proxySFU) waitForPublisherConnection(ctx context.Context, publisher api.PublicSessionId, streamType sfu.StreamType) *proxyConnection { m.mu.Lock() defer m.mu.Unlock() - conn := m.publishers[getStreamId(publisher, streamType)] + conn := m.publishers[sfu.GetStreamId(publisher, streamType)] if conn != nil { // Publisher was created while waiting for lock. return conn @@ -2151,13 +2205,13 @@ func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher api id := m.publisherWaiters.Add(ch) defer m.publisherWaiters.Remove(id) - statsWaitingForPublisherTotal.WithLabelValues(string(streamType)).Inc() + sfuinternal.StatsWaitingForPublisherTotal.WithLabelValues(string(streamType)).Inc() for { m.mu.Unlock() select { case <-ch: m.mu.Lock() - conn = m.publishers[getStreamId(publisher, streamType)] + conn = m.publishers[sfu.GetStreamId(publisher, streamType)] if conn != nil { return conn } @@ -2170,12 +2224,12 @@ func (m *mcuProxy) waitForPublisherConnection(ctx context.Context, publisher api type proxyPublisherInfo struct { id api.PublicSessionId - conn *mcuProxyConnection + conn *proxyConnection token string err error } -func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, info *proxyPublisherInfo, publisher api.PublicSessionId, streamType StreamType, connections []*mcuProxyConnection, isAllowed func(c *mcuProxyConnection) bool) McuSubscriber { +func (m *proxySFU) createSubscriber(ctx context.Context, listener sfu.Listener, info *proxyPublisherInfo, publisher api.PublicSessionId, streamType sfu.StreamType, connections []*proxyConnection, isAllowed func(c *proxyConnection) bool) sfu.Subscriber { for _, conn := range connections { if !isAllowed(conn) || conn.IsShutdownScheduled() || conn.IsTemporary() { continue @@ -2184,7 +2238,7 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i subctx, cancel := context.WithTimeout(ctx, m.settings.Timeout()) defer cancel() - var subscriber McuSubscriber + var subscriber sfu.Subscriber var err error if conn == info.conn { subscriber, err = conn.newSubscriber(subctx, listener, info.id, publisher, streamType) @@ -2202,12 +2256,12 @@ func (m *mcuProxy) createSubscriber(ctx context.Context, listener McuListener, i return nil } -func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publisher api.PublicSessionId, streamType StreamType, initiator McuInitiator) (McuSubscriber, error) { +func (m *proxySFU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { var publisherInfo *proxyPublisherInfo if conn := m.getPublisherConnection(publisher, streamType); conn != nil { // Fast common path: publisher is available locally. conn.publishersLock.Lock() - id, found := conn.publisherIds[getStreamId(publisher, streamType)] + id, found := conn.publisherIds[sfu.GetStreamId(publisher, streamType)] conn.publishersLock.Unlock() if !found { return nil, fmt.Errorf("unknown publisher %s", publisher) @@ -2233,7 +2287,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ cancel() // Cancel pending RPC calls. conn.publishersLock.Lock() - id, found := conn.publisherIds[getStreamId(publisher, streamType)] + id, found := conn.publisherIds[sfu.GetStreamId(publisher, streamType)] conn.publishersLock.Unlock() if !found { ch <- &proxyPublisherInfo{ @@ -2253,7 +2307,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ if clients := m.rpcClients.GetClients(); len(clients) > 0 { for _, client := range clients { wg.Add(1) - go func(client *GrpcClient) { + go func(client *grpc.Client) { defer wg.Done() id, url, ip, connectToken, publisherToken, err := client.GetPublisherId(getctx, publisher, streamType) if errors.Is(err, context.Canceled) { @@ -2272,7 +2326,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ m.connectionsMu.RLock() connections := m.connections m.connectionsMu.RUnlock() - var publisherConn *mcuProxyConnection + var publisherConn *proxyConnection for _, conn := range connections { if conn.rawUrl != url || !ip.Equal(conn.ip) { continue @@ -2284,7 +2338,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ } if publisherConn == nil { - publisherConn, err = newMcuProxyConnection(m, url, ip, connectToken) + publisherConn, err = newProxyConnection(m, url, ip, connectToken) if err != nil { m.logger.Printf("Could not create temporary connection to %s for %s publisher %s: %s", url, streamType, publisher, err) return @@ -2304,7 +2358,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ if found { conns = append(conns, publisherConn) } else { - conns = []*mcuProxyConnection{publisherConn} + conns = []*proxyConnection{publisherConn} } m.connectionsMap[url] = conns m.connectionsMu.Unlock() @@ -2345,18 +2399,18 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ connections := m.getSortedConnections(initiator) if !allowOutgoing || len(connections) > 0 && !connections[0].IsSameCountry(publisherInfo.conn) { // Connect to remote publisher through "closer" gateway. - subscriber := m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections, func(c *mcuProxyConnection) bool { + subscriber := m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections, func(c *proxyConnection) bool { bw := c.Bandwidth() return bw == nil || bw.AllowOutgoing() }) if subscriber == nil { - connections2 := make([]*mcuProxyConnection, 0, len(connections)) + connections2 := make([]*proxyConnection, 0, len(connections)) for _, c := range connections { if c.Bandwidth() != nil { connections2 = append(connections2, c) } } - slices.SortFunc(connections2, func(a *mcuProxyConnection, b *mcuProxyConnection) int { + slices.SortFunc(connections2, func(a *proxyConnection, b *proxyConnection) int { var outgoing_a *float64 if bw := a.Bandwidth(); bw != nil { outgoing_a = bw.Outgoing @@ -2382,7 +2436,7 @@ func (m *mcuProxy) NewSubscriber(ctx context.Context, listener McuListener, publ return 0 } }) - subscriber = m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections2, func(c *mcuProxyConnection) bool { + subscriber = m.createSubscriber(ctx, listener, publisherInfo, publisher, streamType, connections2, func(c *proxyConnection) bool { return true }) } diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go new file mode 100644 index 0000000..5ecfcd5 --- /dev/null +++ b/sfu/proxy/proxy_test.go @@ -0,0 +1,1239 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2020 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 proxy + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "fmt" + "net" + "net/url" + "path" + "slices" + "strconv" + "strings" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/dns" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" + "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" + metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" +) + +const ( + testTimeout = 10 * time.Second +) + +func TestMcuProxyStats(t *testing.T) { + t.Parallel() + metricstest.CollectAndLint(t, proxyMcuStats...) +} + +func newProxyConnectionWithCountry(country geoip.Country) *proxyConnection { + conn := &proxyConnection{} + conn.country.Store(country) + return conn +} + +func Test_sortConnectionsForCountry(t *testing.T) { + t.Parallel() + conn_de := newProxyConnectionWithCountry("DE") + conn_at := newProxyConnectionWithCountry("AT") + conn_jp := newProxyConnectionWithCountry("JP") + conn_us := newProxyConnectionWithCountry("US") + + testcases := map[geoip.Country][][]*proxyConnection{ + // Direct country match + "DE": { + {conn_at, conn_jp, conn_de}, + {conn_de, conn_at, conn_jp}, + }, + // Direct country match + "AT": { + {conn_at, conn_jp, conn_de}, + {conn_at, conn_de, conn_jp}, + }, + // Continent match + "CH": { + {conn_de, conn_jp, conn_at}, + {conn_de, conn_at, conn_jp}, + }, + // Direct country match + "JP": { + {conn_de, conn_jp, conn_at}, + {conn_jp, conn_de, conn_at}, + }, + // Continent match + "CN": { + {conn_de, conn_jp, conn_at}, + {conn_jp, conn_de, conn_at}, + }, + // Continent match + "RU": { + {conn_us, conn_de, conn_jp, conn_at}, + {conn_de, conn_at, conn_us, conn_jp}, + }, + // No match + "AU": { + {conn_us, conn_de, conn_jp, conn_at}, + {conn_us, conn_de, conn_jp, conn_at}, + }, + } + + for country, test := range testcases { + t.Run(string(country), func(t *testing.T) { + t.Parallel() + sorted := sortConnectionsForCountry(test[0], country, nil) + for idx, conn := range sorted { + assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) + } + }) + } +} + +func Test_sortConnectionsForCountryWithOverride(t *testing.T) { + t.Parallel() + conn_de := newProxyConnectionWithCountry("DE") + conn_at := newProxyConnectionWithCountry("AT") + conn_jp := newProxyConnectionWithCountry("JP") + conn_us := newProxyConnectionWithCountry("US") + + testcases := map[geoip.Country][][]*proxyConnection{ + // Direct country match + "DE": { + {conn_at, conn_jp, conn_de}, + {conn_de, conn_at, conn_jp}, + }, + // Direct country match + "AT": { + {conn_at, conn_jp, conn_de}, + {conn_at, conn_de, conn_jp}, + }, + // Continent match + "CH": { + {conn_de, conn_jp, conn_at}, + {conn_de, conn_at, conn_jp}, + }, + // Direct country match + "JP": { + {conn_de, conn_jp, conn_at}, + {conn_jp, conn_de, conn_at}, + }, + // Continent match + "CN": { + {conn_de, conn_jp, conn_at}, + {conn_jp, conn_de, conn_at}, + }, + // Continent match + "RU": { + {conn_us, conn_de, conn_jp, conn_at}, + {conn_de, conn_at, conn_us, conn_jp}, + }, + // No match + "AR": { + {conn_us, conn_de, conn_jp, conn_at}, + {conn_us, conn_de, conn_jp, conn_at}, + }, + // No match but override (OC -> AS / NA) + "AU": { + {conn_us, conn_jp}, + {conn_us, conn_jp}, + }, + // No match but override (AF -> EU) + "ZA": { + {conn_de, conn_at}, + {conn_de, conn_at}, + }, + } + + continentMap := ContinentsMap{ + // Use European connections for Africa. + "AF": {"EU"}, + // Use Asian and North American connections for Oceania. + "OC": {"AS", "NA"}, + } + for country, test := range testcases { + t.Run(string(country), func(t *testing.T) { + t.Parallel() + sorted := sortConnectionsForCountry(test[0], country, continentMap) + for idx, conn := range sorted { + assert.Equal(t, test[1][idx], conn, "Index %d for %s: expected %s, got %s", idx, country, test[1][idx].Country(), conn.Country()) + } + }) + } +} + +func newMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOptions, idx int, lookup *dns.MockLookup) (*proxySFU, *goconf.ConfigFile) { + t.Helper() + require := require.New(t) + if options.Etcd == nil { + options.Etcd = etcdtest.NewServerForTest(t) + } + grpcClients, dnsMonitor := grpctest.NewClientsWithEtcdForTest(t, options.Etcd, lookup) + + tokenKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + dir := t.TempDir() + privkeyFile := path.Join(dir, "privkey.pem") + pubkeyFile := path.Join(dir, "pubkey.pem") + require.NoError(internal.WritePrivateKey(tokenKey, privkeyFile)) + require.NoError(internal.WritePublicKey(&tokenKey.PublicKey, pubkeyFile)) + + cfg := goconf.NewConfigFile() + cfg.AddOption("mcu", "urltype", "static") + if strings.Contains(t.Name(), "DnsDiscovery") { + cfg.AddOption("mcu", "dnsdiscovery", "true") + } + cfg.AddOption("mcu", "proxytimeout", strconv.Itoa(int(testTimeout.Seconds()))) + var urls []string + waitingMap := make(map[string]bool) + if len(options.Servers) == 0 { + options.Servers = []testserver.ProxyTestServer{ + testserver.NewProxyServerForTest(t, "DE"), + } + } + tokenId := fmt.Sprintf("test-token-%d", idx) + for _, s := range options.Servers { + s.SetServers(options.Servers) + s.SetToken(tokenId, &tokenKey.PublicKey) + urls = append(urls, s.URL()) + waitingMap[s.URL()] = true + } + cfg.AddOption("mcu", "url", strings.Join(urls, " ")) + cfg.AddOption("mcu", "token_id", tokenId) + cfg.AddOption("mcu", "token_key", privkeyFile) + + etcdConfig := goconf.NewConfigFile() + etcdConfig.AddOption("etcd", "endpoints", options.Etcd.URL().String()) + etcdConfig.AddOption("etcd", "loglevel", "error") + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + etcdClient, err := etcd.NewClient(logger, etcdConfig, "") + require.NoError(err) + t.Cleanup(func() { + assert.NoError(t, etcdClient.Close()) + }) + + mcu, err := NewProxySFU(ctx, cfg, etcdClient, grpcClients, dnsMonitor) + require.NoError(err) + t.Cleanup(func() { + mcu.Stop() + }) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(mcu.Start(ctx)) + + proxy := mcu.(*proxySFU) + + require.NoError(proxy.WaitForConnections(ctx)) + + for len(waitingMap) > 0 { + require.NoError(ctx.Err()) + + for u := range waitingMap { + proxy.connectionsMu.RLock() + connections := proxy.connections + proxy.connectionsMu.RUnlock() + for _, c := range connections { + if c.rawUrl == u && c.IsConnected() && c.SessionId() != "" { + delete(waitingMap, u) + break + } + } + } + + time.Sleep(time.Millisecond) + } + + return proxy, cfg +} + +func newMcuProxyForTestWithServers(t *testing.T, servers []testserver.ProxyTestServer, idx int, lookup *dns.MockLookup) *proxySFU { + t.Helper() + + proxy, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: servers, + }, idx, lookup) + return proxy +} + +func newMcuProxyForTest(t *testing.T, idx int, lookup *dns.MockLookup) *proxySFU { + t.Helper() + server := testserver.NewProxyServerForTest(t, "DE") + + return newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{server}, idx, lookup) +} + +func Test_ProxyAddRemoveConnections(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + server1 := testserver.NewProxyServerForTest(t, "DE") + mcu, config := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 0, nil) + + server2 := testserver.NewProxyServerForTest(t, "DE") + server1.Servers = append(server1.Servers, server2) + server2.Servers = server1.Servers + server2.Tokens = server1.Tokens + urls1 := []string{ + server1.URL(), + server2.URL(), + } + config.AddOption("mcu", "url", strings.Join(urls1, " ")) + mcu.Reload(config) + + mcu.connectionsMu.RLock() + assert.Len(mcu.connections, 2) + mcu.connectionsMu.RUnlock() + + // Wait until connection is established. + waitCtx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + notAllConnected := slices.ContainsFunc(mcu.connections, func(conn *proxyConnection) bool { + return !conn.IsConnected() + }) + mcu.connectionsMu.RUnlock() + if notAllConnected { + time.Sleep(time.Millisecond) + continue + } + + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be established") + + urls2 := []string{ + server2.URL(), + } + config.AddOption("mcu", "url", strings.Join(urls2, " ")) + mcu.Reload(config) + + // Removing the connections takes a short while (asynchronously, closed when unused). + waitCtx, cancel = context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + if len(mcu.connections) != 1 { + mcu.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + continue + } + + assert.Len(mcu.connections, 1) + assert.Equal(server2.URL(), mcu.connections[0].rawUrl) + mcu.connectionsMu.RUnlock() + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") +} + +func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { + t.Parallel() + assert := assert.New(t) + require := require.New(t) + + lookup := dns.NewMockLookupForTest(t) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server1.Start() + h, port, err := net.SplitHostPort(server1.Listener().Addr().String()) + require.NoError(err) + ip1 := net.ParseIP(h) + require.NotNil(ip1, "failed for %s", h) + + require.Contains(server1.URL(), ip1.String()) + server1.SetURL(strings.ReplaceAll(server1.URL(), ip1.String(), "proxydomain.invalid")) + u1, err := url.Parse(server1.URL()) + require.NoError(err) + lookup.Set(u1.Hostname(), []net.IP{ + ip1, + }) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 0, lookup) + + if connections := mcu.getConnections(); assert.Len(connections, 1) && assert.NotNil(connections[0].ip) { + assert.True(ip1.Equal(connections[0].ip), "ip addresses differ: expected %s, got %s", ip1.String(), connections[0].ip.String()) + } + + dnsMonitor := mcu.config.(*configStatic).dnsMonitor + require.NotNil(dnsMonitor) + + server2 := testserver.NewProxyServerForTest(t, "DE") + l, err := net.Listen("tcp", "127.0.0.2:"+port) + require.NoError(err) + assert.NoError(server2.Listener().Close()) + server2.SetListener(l) + server2.Start() + + h, _, err = net.SplitHostPort(server2.Listener().Addr().String()) + require.NoError(err) + ip2 := net.ParseIP(h) + require.NotNil(ip2, "failed for %s", h) + require.Contains(server2.URL(), ip2.String()) + server2.SetURL(strings.ReplaceAll(server2.URL(), ip2.String(), "proxydomain.invalid")) + + server1.Servers = append(server1.Servers, server2) + server2.Servers = server1.Servers + server2.Tokens = server1.Tokens + + lookup.Set(u1.Hostname(), []net.IP{ + ip1, + ip2, + }) + dnsMonitor.CheckHostnames() + + // Wait until connection is established. + waitCtx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + if len(mcu.connections) != 2 { + mcu.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + continue + } + + notAllConnected := slices.ContainsFunc(mcu.connections, func(conn *proxyConnection) bool { + return !conn.IsConnected() + }) + mcu.connectionsMu.RUnlock() + if notAllConnected { + time.Sleep(time.Millisecond) + continue + } + + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be established") + + lookup.Set(u1.Hostname(), []net.IP{ + ip2, + }) + dnsMonitor.CheckHostnames() + + // Removing the connections takes a short while (asynchronously, closed when unused). + waitCtx, cancel = context.WithTimeout(ctx, time.Second) + defer cancel() + + for waitCtx.Err() == nil { + mcu.connectionsMu.RLock() + if len(mcu.connections) != 1 { + mcu.connectionsMu.RUnlock() + time.Sleep(time.Millisecond) + continue + } + + assert.Len(mcu.connections, 1) + assert.Equal(server1.URL(), mcu.connections[0].rawUrl) + if assert.NotNil(mcu.connections[0].ip) { + assert.True(ip2.Equal(mcu.connections[0].ip), "ip addresses differ: expected %s, got %s", ip2.String(), mcu.connections[0].ip.String()) + } + mcu.connectionsMu.RUnlock() + break + } + assert.NoError(waitCtx.Err(), "error while waiting for connection to be removed") +} + +func Test_ProxyPublisherSubscriber(t *testing.T) { + t.Parallel() + mcu := newMcuProxyForTest(t, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} + +func Test_ProxyPublisherCodecs(t *testing.T) { + t.Parallel() + mcu := newMcuProxyForTest(t, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + AudioCodec: "opus,g722", + VideoCodec: "vp9,vp8,av1", + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) +} + +func Test_ProxyWaitForPublisher(t *testing.T) { + t.Parallel() + mcu := newMcuProxyForTest(t, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + done := make(chan struct{}) + go func() { + defer close(done) + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + if !assert.NoError(t, err) { + return + } + + defer sub.Close(context.Background()) + }() + + // Give subscriber goroutine some time to start + time.Sleep(100 * time.Millisecond) + + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + select { + case <-done: + case <-ctx.Done(): + assert.NoError(t, ctx.Err()) + } + defer pub.Close(context.Background()) +} + +func Test_ProxyPublisherBandwidth(t *testing.T) { + t.Parallel() + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + server1, + server2, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pub1Id := api.PublicSessionId("the-publisher-1") + pub1Sid := "1234567890" + pub1Listener := mock.NewListener(pub1Id + "-public") + pub1Initiator := mock.NewInitiator("DE") + pub1, err := mcu.NewPublisher(ctx, pub1Listener, pub1Id, pub1Sid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pub1Initiator) + require.NoError(t, err) + + defer pub1.Close(context.Background()) + + if pub1.(*proxyPublisher).conn.rawUrl == server1.URL() { + server1.UpdateBandwidth(100, 0) + } else { + server2.UpdateBandwidth(100, 0) + } + + // Wait until proxy has been updated + for assert.NoError(t, ctx.Err()) { + mcu.connectionsMu.RLock() + connections := mcu.connections + mcu.connectionsMu.RUnlock() + missing := true + for _, c := range connections { + if c.Bandwidth() != nil { + missing = false + break + } + } + if !missing { + break + } + time.Sleep(time.Millisecond) + } + + pub2Id := api.PublicSessionId("the-publisher-2") + pub2id := "1234567890" + pub2Listener := mock.NewListener(pub2Id + "-public") + pub2Initiator := mock.NewInitiator("DE") + pub2, err := mcu.NewPublisher(ctx, pub2Listener, pub2Id, pub2id, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pub2Initiator) + require.NoError(t, err) + + defer pub2.Close(context.Background()) + + assert.NotEqual(t, pub1.(*proxyPublisher).conn.rawUrl, pub2.(*proxyPublisher).conn.rawUrl) +} + +func Test_ProxyPublisherBandwidthOverload(t *testing.T) { + t.Parallel() + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + server1, + server2, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pub1Id := api.PublicSessionId("the-publisher-1") + pub1Sid := "1234567890" + pub1Listener := mock.NewListener(pub1Id + "-public") + pub1Initiator := mock.NewInitiator("DE") + pub1, err := mcu.NewPublisher(ctx, pub1Listener, pub1Id, pub1Sid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pub1Initiator) + require.NoError(t, err) + + defer pub1.Close(context.Background()) + + // If all servers are bandwidth loaded, select the one with the least usage. + if pub1.(*proxyPublisher).conn.rawUrl == server1.URL() { + server1.UpdateBandwidth(100, 0) + server2.UpdateBandwidth(102, 0) + } else { + server1.UpdateBandwidth(102, 0) + server2.UpdateBandwidth(100, 0) + } + + // Wait until proxy has been updated + for assert.NoError(t, ctx.Err()) { + mcu.connectionsMu.RLock() + connections := mcu.connections + mcu.connectionsMu.RUnlock() + missing := false + for _, c := range connections { + if c.Bandwidth() == nil { + missing = true + break + } + } + if !missing { + break + } + time.Sleep(time.Millisecond) + } + + pub2Id := api.PublicSessionId("the-publisher-2") + pub2id := "1234567890" + pub2Listener := mock.NewListener(pub2Id + "-public") + pub2Initiator := mock.NewInitiator("DE") + pub2, err := mcu.NewPublisher(ctx, pub2Listener, pub2Id, pub2id, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pub2Initiator) + require.NoError(t, err) + + defer pub2.Close(context.Background()) + + assert.Equal(t, pub1.(*proxyPublisher).conn.rawUrl, pub2.(*proxyPublisher).conn.rawUrl) +} + +func Test_ProxyPublisherLoad(t *testing.T) { + t.Parallel() + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + server1, + server2, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pub1Id := api.PublicSessionId("the-publisher-1") + pub1Sid := "1234567890" + pub1Listener := mock.NewListener(pub1Id + "-public") + pub1Initiator := mock.NewInitiator("DE") + pub1, err := mcu.NewPublisher(ctx, pub1Listener, pub1Id, pub1Sid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pub1Initiator) + require.NoError(t, err) + + defer pub1.Close(context.Background()) + + // Make sure connections are re-sorted. + mcu.nextSort.Store(0) + time.Sleep(100 * time.Millisecond) + + pub2Id := api.PublicSessionId("the-publisher-2") + pub2id := "1234567890" + pub2Listener := mock.NewListener(pub2Id + "-public") + pub2Initiator := mock.NewInitiator("DE") + pub2, err := mcu.NewPublisher(ctx, pub2Listener, pub2Id, pub2id, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pub2Initiator) + require.NoError(t, err) + + defer pub2.Close(context.Background()) + + assert.NotEqual(t, pub1.(*proxyPublisher).conn.rawUrl, pub2.(*proxyPublisher).conn.rawUrl) +} + +func Test_ProxyPublisherCountry(t *testing.T) { + t.Parallel() + serverDE := testserver.NewProxyServerForTest(t, "DE") + serverUS := testserver.NewProxyServerForTest(t, "US") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + serverDE, + serverUS, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubDEId := api.PublicSessionId("the-publisher-de") + pubDESid := "1234567890" + pubDEListener := mock.NewListener(pubDEId + "-public") + pubDEInitiator := mock.NewInitiator("DE") + pubDE, err := mcu.NewPublisher(ctx, pubDEListener, pubDEId, pubDESid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubDEInitiator) + require.NoError(t, err) + + defer pubDE.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pubDE.(*proxyPublisher).conn.rawUrl) + + pubUSId := api.PublicSessionId("the-publisher-us") + pubUSSid := "1234567890" + pubUSListener := mock.NewListener(pubUSId + "-public") + pubUSInitiator := mock.NewInitiator("US") + pubUS, err := mcu.NewPublisher(ctx, pubUSListener, pubUSId, pubUSSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubUSInitiator) + require.NoError(t, err) + + defer pubUS.Close(context.Background()) + + assert.Equal(t, serverUS.URL(), pubUS.(*proxyPublisher).conn.rawUrl) +} + +func Test_ProxyPublisherContinent(t *testing.T) { + t.Parallel() + serverDE := testserver.NewProxyServerForTest(t, "DE") + serverUS := testserver.NewProxyServerForTest(t, "US") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + serverDE, + serverUS, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubDEId := api.PublicSessionId("the-publisher-de") + pubDESid := "1234567890" + pubDEListener := mock.NewListener(pubDEId + "-public") + pubDEInitiator := mock.NewInitiator("DE") + pubDE, err := mcu.NewPublisher(ctx, pubDEListener, pubDEId, pubDESid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubDEInitiator) + require.NoError(t, err) + + defer pubDE.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pubDE.(*proxyPublisher).conn.rawUrl) + + pubFRId := api.PublicSessionId("the-publisher-fr") + pubFRSid := "1234567890" + pubFRListener := mock.NewListener(pubFRId + "-public") + pubFRInitiator := mock.NewInitiator("FR") + pubFR, err := mcu.NewPublisher(ctx, pubFRListener, pubFRId, pubFRSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubFRInitiator) + require.NoError(t, err) + + defer pubFR.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pubFR.(*proxyPublisher).conn.rawUrl) +} + +func Test_ProxySubscriberCountry(t *testing.T) { + t.Parallel() + serverDE := testserver.NewProxyServerForTest(t, "DE") + serverUS := testserver.NewProxyServerForTest(t, "US") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + serverDE, + serverUS, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pub.(*proxyPublisher).conn.rawUrl) + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("US") + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) + + assert.Equal(t, serverUS.URL(), sub.(*proxySubscriber).conn.rawUrl) +} + +func Test_ProxySubscriberContinent(t *testing.T) { + t.Parallel() + serverDE := testserver.NewProxyServerForTest(t, "DE") + serverUS := testserver.NewProxyServerForTest(t, "US") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + serverDE, + serverUS, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pub.(*proxyPublisher).conn.rawUrl) + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("FR") + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), sub.(*proxySubscriber).conn.rawUrl) +} + +func Test_ProxySubscriberBandwidth(t *testing.T) { + t.Parallel() + serverDE := testserver.NewProxyServerForTest(t, "DE") + serverUS := testserver.NewProxyServerForTest(t, "US") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + serverDE, + serverUS, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pub.(*proxyPublisher).conn.rawUrl) + + serverDE.UpdateBandwidth(0, 100) + + // Wait until proxy has been updated + for assert.NoError(t, ctx.Err()) { + mcu.connectionsMu.RLock() + connections := mcu.connections + mcu.connectionsMu.RUnlock() + missing := true + for _, c := range connections { + if c.Bandwidth() != nil { + missing = false + break + } + } + if !missing { + break + } + time.Sleep(time.Millisecond) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("US") + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) + + assert.Equal(t, serverUS.URL(), sub.(*proxySubscriber).conn.rawUrl) +} + +func Test_ProxySubscriberBandwidthOverload(t *testing.T) { + t.Parallel() + serverDE := testserver.NewProxyServerForTest(t, "DE") + serverUS := testserver.NewProxyServerForTest(t, "US") + mcu := newMcuProxyForTestWithServers(t, []testserver.ProxyTestServer{ + serverDE, + serverUS, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), pub.(*proxyPublisher).conn.rawUrl) + + serverDE.UpdateBandwidth(0, 100) + serverUS.UpdateBandwidth(0, 102) + + // Wait until proxy has been updated + for assert.NoError(t, ctx.Err()) { + mcu.connectionsMu.RLock() + connections := mcu.connections + mcu.connectionsMu.RUnlock() + missing := false + for _, c := range connections { + if c.Bandwidth() == nil { + missing = true + break + } + } + if !missing { + break + } + time.Sleep(time.Millisecond) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("US") + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) + + assert.Equal(t, serverDE.URL(), sub.(*proxySubscriber).conn.rawUrl) +} + +func Test_ProxyPublisherTimeout(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := testserver.NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{server}, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + settings := mcu.settings.(*proxySettings) + settings.SetTimeout(testserver.TimeoutTestTimeout) + + // Creating the publisher will timeout locally. + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + if pub != nil { + defer pub.Close(context.Background()) + } + assert.ErrorContains(err, "no MCU connection available") + + // Wait for publisher to be created on the proxy side. + require.NoError(server.WaitForWakeup(ctx), "publisher not created") + + // The local side will remove the (unused) publisher from the proxy. + require.NoError(server.WaitForWakeup(ctx), "unused publisher not deleted") +} + +func Test_ProxySubscriberTimeout(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := testserver.NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{server}, + }, 0, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(err) + defer pub.Close(context.Background()) + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + + settings := mcu.settings.(*proxySettings) + settings.SetTimeout(testserver.TimeoutTestTimeout) + + // Creating the subscriber will timeout locally. + sub, err := mcu.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + if sub != nil { + defer sub.Close(context.Background()) + } + assert.ErrorIs(err, context.DeadlineExceeded) + + // Wait for subscriber to be created on the proxy side. + require.NoError(server.WaitForWakeup(ctx), "subscriber not created") + + // The local side will remove the (unused) subscriber from the proxy. + require.NoError(server.WaitForWakeup(ctx), "unused subscriber not deleted") +} + +func Test_ProxyReconnectAfter(t *testing.T) { + t.Parallel() + reasons := []string{ + "session_resumed", + "session_expired", + "session_closed", + "unknown_reason", + } + for _, reason := range reasons { + t.Run(reason, func(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := testserver.NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{server}, + }, 0, nil) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + + client.SendMessage(&proxy.ServerMessage{ + Type: "bye", + Bye: &proxy.ByeServerMessage{ + Reason: reason, + }, + }) + + // The "bye" will close the connection and reset the session id. + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.NotEqual(sessionId, connections[0].SessionId()) + } + }) + } +} + +func Test_ProxyReconnectAfterShutdown(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := testserver.NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{server}, + }, 0, nil) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + + client.SendMessage(&proxy.ServerMessage{ + Type: "event", + Event: &proxy.EventServerMessage{ + Type: "shutdown-scheduled", + }, + }) + + // Force reconnect. + client.Close() + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect and resume the session. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.Equal(sessionId, connections[0].SessionId()) + } +} + +func Test_ProxyResume(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := testserver.NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{server}, + }, 0, nil) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + + // Force reconnect. + client.Close() + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.Equal(sessionId, connections[0].SessionId()) + } +} + +func Test_ProxyResumeFail(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + server := testserver.NewProxyServerForTest(t, "DE") + mcu, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Servers: []testserver.ProxyTestServer{server}, + }, 0, nil) + + connections := mcu.getSortedConnections(nil) + require.Len(connections, 1) + sessionId := connections[0].SessionId() + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + client := server.GetSingleClient() + require.NotNil(client) + server.ClearClients() + + // Force reconnect. + client.Close() + assert.NoError(mcu.WaitForDisconnected(ctx)) + + // The client will automatically reconnect. + time.Sleep(10 * time.Millisecond) + assert.NoError(mcu.WaitForConnections(ctx)) + + if connections := mcu.getSortedConnections(nil); assert.Len(connections, 1) { + assert.NotEqual(sessionId, connections[0].SessionId()) + } +} diff --git a/sfu/proxy/stats_prometheus.go b/sfu/proxy/stats_prometheus.go new file mode 100644 index 0000000..5889959 --- /dev/null +++ b/sfu/proxy/stats_prometheus.go @@ -0,0 +1,81 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 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 proxy + +import ( + "github.com/prometheus/client_golang/prometheus" + + "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" +) + +var ( + statsConnectedProxyBackendsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "backend_connections", + Help: "Current number of connections to signaling proxy backends", + }, []string{"country"}) + statsProxyBackendLoadCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "backend_load", + Help: "Current load of signaling proxy backends", + }, []string{"url"}) + statsProxyUsageCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "backend_usage", + Help: "The current usage of signaling proxy backends in percent", + }, []string{"url", "direction"}) + statsProxyBandwidthCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "backend_bandwidth", + Help: "The current bandwidth of signaling proxy backends in bytes per second", + }, []string{"url", "direction"}) + statsProxyNobackendAvailableTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "mcu", + Name: "no_backend_available_total", + Help: "Total number of publishing requests where no backend was available", + }, []string{"type"}) + + proxyMcuStats = []prometheus.Collector{ + statsConnectedProxyBackendsCurrent, + statsProxyBackendLoadCurrent, + statsProxyUsageCurrent, + statsProxyBandwidthCurrent, + statsProxyNobackendAvailableTotal, + } +) + +func RegisterStats() { + internal.RegisterCommonStats() + metrics.RegisterAll(proxyMcuStats...) +} + +func UnregisterStats() { + internal.UnregisterCommonStats() + metrics.UnregisterAll(proxyMcuStats...) +} diff --git a/sfu/proxy/test/proxy.go b/sfu/proxy/test/proxy.go new file mode 100644 index 0000000..e6e9262 --- /dev/null +++ b/sfu/proxy/test/proxy.go @@ -0,0 +1,125 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "fmt" + "path" + "strconv" + "strings" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/dns" + "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" + "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" +) + +const ( + testTimeout = 10 * time.Second +) + +type ConnectionWaiter interface { + WaitForConnections(ctx context.Context) error + WaitForConnectionsEstablished(ctx context.Context, waitMap map[string]bool) error +} + +func NewMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOptions, idx int, lookup *dns.MockLookup) (sfu.SFU, *goconf.ConfigFile) { + t.Helper() + require := require.New(t) + require.NotEmpty(options.Servers) + if options.Etcd == nil { + options.Etcd = etcdtest.NewServerForTest(t) + } + grpcClients, dnsMonitor := grpctest.NewClientsWithEtcdForTest(t, options.Etcd, lookup) + + tokenKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + dir := t.TempDir() + privkeyFile := path.Join(dir, "privkey.pem") + pubkeyFile := path.Join(dir, "pubkey.pem") + require.NoError(internal.WritePrivateKey(tokenKey, privkeyFile)) + require.NoError(internal.WritePublicKey(&tokenKey.PublicKey, pubkeyFile)) + + cfg := goconf.NewConfigFile() + cfg.AddOption("mcu", "urltype", "static") + if strings.Contains(t.Name(), "DnsDiscovery") { + cfg.AddOption("mcu", "dnsdiscovery", "true") + } + cfg.AddOption("mcu", "proxytimeout", strconv.Itoa(int(testTimeout.Seconds()))) + var urls []string + waitingMap := make(map[string]bool) + tokenId := fmt.Sprintf("test-token-%d", idx) + for _, s := range options.Servers { + s.SetServers(options.Servers) + s.SetToken(tokenId, &tokenKey.PublicKey) + urls = append(urls, s.URL()) + waitingMap[s.URL()] = true + } + cfg.AddOption("mcu", "url", strings.Join(urls, " ")) + cfg.AddOption("mcu", "token_id", tokenId) + cfg.AddOption("mcu", "token_key", privkeyFile) + + etcdConfig := goconf.NewConfigFile() + etcdConfig.AddOption("etcd", "endpoints", options.Etcd.URL().String()) + etcdConfig.AddOption("etcd", "loglevel", "error") + + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + etcdClient, err := etcd.NewClient(logger, etcdConfig, "") + require.NoError(err) + t.Cleanup(func() { + assert.NoError(t, etcdClient.Close()) + }) + + mcu, err := proxy.NewProxySFU(ctx, cfg, etcdClient, grpcClients, dnsMonitor) + require.NoError(err) + t.Cleanup(func() { + mcu.Stop() + }) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(mcu.Start(ctx)) + + waiter, ok := mcu.(ConnectionWaiter) + require.True(ok, "can't wait for connections") + + require.NoError(waiter.WaitForConnections(ctx)) + require.NoError(waiter.WaitForConnectionsEstablished(ctx, waitingMap)) + + return mcu, cfg +} diff --git a/sfu/proxy/testserver/server.go b/sfu/proxy/testserver/server.go new file mode 100644 index 0000000..08ad5eb --- /dev/null +++ b/sfu/proxy/testserver/server.go @@ -0,0 +1,753 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 testserver + +import ( + "context" + "crypto/rsa" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/proxy" +) + +const ( + TimeoutTestTimeout = 100 * time.Millisecond +) + +type ProxyTestServer interface { + URL() string + SetServers(servers []ProxyTestServer) + SetToken(tokenId string, key *rsa.PublicKey) + + getToken(tokenId string) (*rsa.PublicKey, bool) + getPublisher(id api.PublicSessionId) *testProxyServerPublisher +} + +type ProxyTestOptions struct { + Etcd *etcdtest.Server + Servers []ProxyTestServer +} + +type proxyServerClientHandler func(msg *proxy.ClientMessage) (*proxy.ServerMessage, error) + +type testProxyServerPublisher struct { + id api.PublicSessionId +} + +type testProxyServerSubscriber struct { + id string + sid string + pub *testProxyServerPublisher + + remoteUrl string +} + +type TestProxyClient interface { + Close() + SendMessage(msg *proxy.ServerMessage) +} + +type testProxyServerClient struct { + t *testing.T + + server *TestProxyServerHandler + // +checklocks:mu + ws *websocket.Conn + processMessage proxyServerClientHandler + + mu sync.Mutex + sessionId api.PublicSessionId +} + +func (c *testProxyServerClient) processHello(msg *proxy.ClientMessage) (*proxy.ServerMessage, error) { + if msg.Type != "hello" { + return nil, fmt.Errorf("expected hello, got %+v", msg) + } + + if msg.Hello.ResumeId != "" { + client := c.server.getClient(msg.Hello.ResumeId) + if client == nil { + response := &proxy.ServerMessage{ + Id: msg.Id, + Type: "error", + Error: &api.Error{ + Code: "no_such_session", + }, + } + return response, nil + } + + c.sessionId = msg.Hello.ResumeId + c.server.setClient(c.sessionId, c) + response := &proxy.ServerMessage{ + Id: msg.Id, + Type: "hello", + Hello: &proxy.HelloServerMessage{ + Version: "1.0", + SessionId: c.sessionId, + Server: &api.WelcomeServerMessage{ + Version: "1.0", + Country: c.server.country, + }, + }, + } + c.processMessage = c.processRegularMessage + return response, nil + } + + token, err := jwt.ParseWithClaims(msg.Hello.Token, &proxy.TokenClaims{}, func(token *jwt.Token) (any, error) { + claims, ok := token.Claims.(*proxy.TokenClaims) + if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { + return nil, errors.New("unsupported claims type") + } + + key, found := c.server.Tokens[claims.Issuer] + if !assert.True(c.t, found) { + return nil, errors.New("no key found for issuer") + } + + return key, nil + }) + if assert.NoError(c.t, err) { + if assert.True(c.t, token.Valid) { + _, ok := token.Claims.(*proxy.TokenClaims) + assert.True(c.t, ok) + } + } + + response := &proxy.ServerMessage{ + Id: msg.Id, + Type: "hello", + Hello: &proxy.HelloServerMessage{ + Version: "1.0", + SessionId: c.sessionId, + Server: &api.WelcomeServerMessage{ + Version: "1.0", + Country: c.server.country, + }, + }, + } + c.processMessage = c.processRegularMessage + return response, nil +} + +func (c *testProxyServerClient) processRegularMessage(msg *proxy.ClientMessage) (*proxy.ServerMessage, error) { + var handler proxyServerClientHandler + switch msg.Type { + case "command": + handler = c.processCommandMessage + } + + if handler == nil { + response := msg.NewWrappedErrorServerMessage(fmt.Errorf("type \"%s\" is not implemented", msg.Type)) + return response, nil + } + + return handler(msg) +} + +func (c *testProxyServerClient) processCommandMessage(msg *proxy.ClientMessage) (*proxy.ServerMessage, error) { + var response *proxy.ServerMessage + switch msg.Command.Type { + case "create-publisher": + if strings.Contains(c.t.Name(), "ProxyPublisherTimeout") { + time.Sleep(2 * TimeoutTestTimeout) + defer c.server.Wakeup() + } + pub := c.server.createPublisher() + + if assert.NotNil(c.t, msg.Command.PublisherSettings) { + if assert.NotEqualValues(c.t, 0, msg.Command.PublisherSettings.Bitrate) { + assert.Equal(c.t, msg.Command.Bitrate, msg.Command.PublisherSettings.Bitrate) // nolint + } + assert.Equal(c.t, msg.Command.MediaTypes, msg.Command.PublisherSettings.MediaTypes) // nolint + if strings.Contains(c.t.Name(), "Codecs") { + assert.Equal(c.t, "opus,g722", msg.Command.PublisherSettings.AudioCodec) + assert.Equal(c.t, "vp9,vp8,av1", msg.Command.PublisherSettings.VideoCodec) + } else { + assert.Empty(c.t, msg.Command.PublisherSettings.AudioCodec) + assert.Empty(c.t, msg.Command.PublisherSettings.VideoCodec) + } + } + + response = &proxy.ServerMessage{ + Id: msg.Id, + Type: "command", + Command: &proxy.CommandServerMessage{ + Id: string(pub.id), + Bitrate: msg.Command.Bitrate, // nolint + }, + } + c.server.updateLoad(1) + case "delete-publisher": + if strings.Contains(c.t.Name(), "ProxyPublisherTimeout") { + defer c.server.Wakeup() + } + + if pub, found := c.server.deletePublisher(api.PublicSessionId(msg.Command.ClientId)); !found { + response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.ClientId)) + } else { + response = &proxy.ServerMessage{ + Id: msg.Id, + Type: "command", + Command: &proxy.CommandServerMessage{ + Id: string(pub.id), + }, + } + c.server.updateLoad(-1) + } + case "create-subscriber": + var pub *testProxyServerPublisher + if msg.Command.RemoteUrl != "" { + for _, server := range c.server.Servers { + if server.URL() != msg.Command.RemoteUrl { + continue + } + + token, err := jwt.ParseWithClaims(msg.Command.RemoteToken, &proxy.TokenClaims{}, func(token *jwt.Token) (any, error) { + claims, ok := token.Claims.(*proxy.TokenClaims) + if !assert.True(c.t, ok, "unsupported claims type: %+v", token.Claims) { + return nil, errors.New("unsupported claims type") + } + + key, found := server.getToken(claims.Issuer) + if !assert.True(c.t, found) { + return nil, errors.New("no key found for issuer") + } + + return key, nil + }) + if assert.NoError(c.t, err) { + if claims, ok := token.Claims.(*proxy.TokenClaims); assert.True(c.t, token.Valid) && assert.True(c.t, ok) { + assert.EqualValues(c.t, msg.Command.PublisherId, claims.Subject) + } + } + + pub = server.getPublisher(msg.Command.PublisherId) + break + } + } else { + pub = c.server.getPublisher(msg.Command.PublisherId) + } + + if pub == nil { + response = msg.NewWrappedErrorServerMessage(fmt.Errorf("publisher %s not found", msg.Command.PublisherId)) + } else { + if strings.Contains(c.t.Name(), "ProxySubscriberTimeout") { + time.Sleep(2 * TimeoutTestTimeout) + defer c.server.Wakeup() + } + sub := c.server.createSubscriber(pub) + response = &proxy.ServerMessage{ + Id: msg.Id, + Type: "command", + Command: &proxy.CommandServerMessage{ + Id: sub.id, + Sid: sub.sid, + }, + } + c.server.updateLoad(1) + } + case "delete-subscriber": + if strings.Contains(c.t.Name(), "ProxySubscriberTimeout") { + defer c.server.Wakeup() + } + if sub, found := c.server.deleteSubscriber(msg.Command.ClientId); !found { + response = msg.NewWrappedErrorServerMessage(fmt.Errorf("subscriber %s not found", msg.Command.ClientId)) + } else { + if msg.Command.RemoteUrl != sub.remoteUrl { + response = msg.NewWrappedErrorServerMessage(fmt.Errorf("remote subscriber %s not found", msg.Command.ClientId)) + return response, nil + } + + response = &proxy.ServerMessage{ + Id: msg.Id, + Type: "command", + Command: &proxy.CommandServerMessage{ + Id: sub.id, + }, + } + c.server.updateLoad(-1) + } + } + if response == nil { + response = msg.NewWrappedErrorServerMessage(fmt.Errorf("command \"%s\" is not implemented", msg.Command.Type)) + } + + return response, nil +} + +func (c *testProxyServerClient) Close() { + c.mu.Lock() + defer c.mu.Unlock() + + if c.ws != nil { + c.ws.Close() + c.ws = nil + } +} + +func (c *testProxyServerClient) handleSendMessageError(fmt string, msg *proxy.ServerMessage, err error) { + c.t.Helper() + + if !errors.Is(err, websocket.ErrCloseSent) || msg.Type != "event" || msg.Event.Type != "update-load" { + assert.Fail(c.t, "error while sending message", fmt, msg, err) + } +} + +func (c *testProxyServerClient) SendMessage(msg *proxy.ServerMessage) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.ws == nil { + return + } + + data, err := json.Marshal(msg) + if err != nil { + c.handleSendMessageError("error marshalling %+v: %s", msg, err) + return + } + + w, err := c.ws.NextWriter(websocket.TextMessage) + if err != nil { + c.handleSendMessageError("error creating writer for %+v: %s", msg, err) + return + } + + if _, err := w.Write(data); err != nil { + c.handleSendMessageError("error sending %+v: %s", msg, err) + return + } + + if err := w.Close(); err != nil { + c.handleSendMessageError("error during close of sending %+v: %s", msg, err) + } + + if msg.CloseAfterSend(nil) { + c.ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) // nolint + c.ws.Close() + } +} + +func (c *testProxyServerClient) run() { + defer func() { + c.mu.Lock() + defer c.mu.Unlock() + + c.server.expireSession(30*time.Second, c) + c.ws = nil + }() + c.processMessage = c.processHello + assert := assert.New(c.t) + for { + c.mu.Lock() + ws := c.ws + c.mu.Unlock() + if ws == nil { + break + } + + msgType, reader, err := ws.NextReader() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { + assert.NoError(err) + } + return + } + + body, err := io.ReadAll(reader) + if !assert.NoError(err) { + continue + } + + if !assert.Equal(websocket.TextMessage, msgType, "unexpected message type for %s", string(body)) { + continue + } + + var msg proxy.ClientMessage + if err := json.Unmarshal(body, &msg); !assert.NoError(err, "could not decode message %s", string(body)) { + continue + } + + if err := msg.CheckValid(); !assert.NoError(err, "invalid message %s", string(body)) { + continue + } + + response, err := c.processMessage(&msg) + if !assert.NoError(err) { + continue + } + + c.SendMessage(response) + if response.Type == "hello" { + c.server.sendLoad(c) + } + } +} + +type TestProxyServerHandler struct { + t *testing.T + + url string + server *httptest.Server + Servers []ProxyTestServer + Tokens map[string]*rsa.PublicKey + upgrader *websocket.Upgrader + country geoip.Country + + mu sync.Mutex + load atomic.Uint64 + incoming atomic.Pointer[float64] + outgoing atomic.Pointer[float64] + // +checklocks:mu + clients map[api.PublicSessionId]*testProxyServerClient + // +checklocks:mu + publishers map[api.PublicSessionId]*testProxyServerPublisher + // +checklocks:mu + subscribers map[string]*testProxyServerSubscriber + + wakeupChan chan struct{} +} + +func (h *TestProxyServerHandler) Start() { + h.server.Start() + h.url = h.server.URL +} + +func (h *TestProxyServerHandler) URL() string { + return h.url +} + +func (h *TestProxyServerHandler) SetURL(url string) { + h.url = url +} + +func (h *TestProxyServerHandler) Listener() net.Listener { + return h.server.Listener +} + +func (h *TestProxyServerHandler) SetListener(listener net.Listener) { + h.server.Listener = listener +} + +func (h *TestProxyServerHandler) SetServers(servers []ProxyTestServer) { + h.Servers = servers +} + +func (h *TestProxyServerHandler) SetToken(tokenId string, key *rsa.PublicKey) { + h.Tokens[tokenId] = key +} + +func (h *TestProxyServerHandler) getToken(tokenId string) (key *rsa.PublicKey, found bool) { + key, found = h.Tokens[tokenId] + return +} + +func (h *TestProxyServerHandler) createPublisher() *testProxyServerPublisher { + h.mu.Lock() + defer h.mu.Unlock() + pub := &testProxyServerPublisher{ + id: api.PublicSessionId(internal.RandomString(32)), + } + + for { + if _, found := h.publishers[pub.id]; !found { + break + } + + pub.id = api.PublicSessionId(internal.RandomString(32)) + } + h.publishers[pub.id] = pub + return pub +} + +func (h *TestProxyServerHandler) getPublisher(id api.PublicSessionId) *testProxyServerPublisher { + h.mu.Lock() + defer h.mu.Unlock() + + return h.publishers[id] +} + +func (h *TestProxyServerHandler) deletePublisher(id api.PublicSessionId) (*testProxyServerPublisher, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + pub, found := h.publishers[id] + if !found { + return nil, false + } + + delete(h.publishers, id) + return pub, true +} + +func (h *TestProxyServerHandler) createSubscriber(pub *testProxyServerPublisher) *testProxyServerSubscriber { + h.mu.Lock() + defer h.mu.Unlock() + + sub := &testProxyServerSubscriber{ + id: internal.RandomString(32), + sid: internal.RandomString(8), + pub: pub, + } + + for { + if _, found := h.subscribers[sub.id]; !found { + break + } + + sub.id = internal.RandomString(32) + } + h.subscribers[sub.id] = sub + return sub +} + +func (h *TestProxyServerHandler) deleteSubscriber(id string) (*testProxyServerSubscriber, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + sub, found := h.subscribers[id] + if !found { + return nil, false + } + + delete(h.subscribers, id) + return sub, true +} + +func (h *TestProxyServerHandler) UpdateBandwidth(incoming float64, outgoing float64) { + h.incoming.Store(&incoming) + h.outgoing.Store(&outgoing) + + h.mu.Lock() + defer h.mu.Unlock() + + msg := h.getLoadMessage(h.load.Load()) + for _, c := range h.clients { + c.SendMessage(msg) + } +} + +func (h *TestProxyServerHandler) Clear(incoming bool, outgoing bool) { + if incoming { + h.incoming.Store(nil) + } + if outgoing { + h.outgoing.Store(nil) + } + + h.mu.Lock() + defer h.mu.Unlock() + + msg := h.getLoadMessage(h.load.Load()) + for _, c := range h.clients { + c.SendMessage(msg) + } +} + +func (h *TestProxyServerHandler) getLoadMessage(load uint64) *proxy.ServerMessage { + msg := &proxy.ServerMessage{ + Type: "event", + Event: &proxy.EventServerMessage{ + Type: "update-load", + Load: load, + }, + } + + incoming := h.incoming.Load() + outgoing := h.outgoing.Load() + if incoming != nil || outgoing != nil { + msg.Event.Bandwidth = &proxy.EventServerBandwidth{ + Incoming: incoming, + Outgoing: outgoing, + } + } + return msg +} + +func (h *TestProxyServerHandler) updateLoad(delta int64) { + if delta == 0 { + return + } + + var load uint64 + if delta > 0 { + load = h.load.Add(uint64(delta)) + } else { + load = h.load.Add(^uint64(delta - 1)) + } + + h.mu.Lock() + defer h.mu.Unlock() + + msg := h.getLoadMessage(load) + for _, c := range h.clients { + c.SendMessage(msg) + } +} + +func (h *TestProxyServerHandler) sendLoad(c *testProxyServerClient) { + msg := h.getLoadMessage(h.load.Load()) + c.SendMessage(msg) +} + +func (h *TestProxyServerHandler) expireSession(timeout time.Duration, client *testProxyServerClient) { + timer := time.AfterFunc(timeout, func() { + h.removeClient(client) + }) + + h.t.Cleanup(func() { + timer.Stop() + }) +} + +func (h *TestProxyServerHandler) removeClient(client *testProxyServerClient) { + h.mu.Lock() + defer h.mu.Unlock() + + delete(h.clients, client.sessionId) +} + +func (h *TestProxyServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ws, err := h.upgrader.Upgrade(w, r, nil) + if !assert.NoError(h.t, err) { + return + } + + client := &testProxyServerClient{ + t: h.t, + server: h, + ws: ws, + sessionId: api.PublicSessionId(internal.RandomString(32)), + } + + h.setClient(client.sessionId, client) + + go client.run() +} + +func (h *TestProxyServerHandler) getClient(sessionId api.PublicSessionId) *testProxyServerClient { + h.mu.Lock() + defer h.mu.Unlock() + + return h.clients[sessionId] +} + +func (h *TestProxyServerHandler) setClient(sessionId api.PublicSessionId, client *testProxyServerClient) { + h.mu.Lock() + defer h.mu.Unlock() + + if prev, found := h.clients[sessionId]; found { + prev.SendMessage(&proxy.ServerMessage{ + Type: "bye", + Bye: &proxy.ByeServerMessage{ + Reason: "session_resumed", + }, + }) + prev.Close() + } + + h.clients[sessionId] = client +} + +func (h *TestProxyServerHandler) Wakeup() { + h.wakeupChan <- struct{}{} +} + +func (h *TestProxyServerHandler) WaitForWakeup(ctx context.Context) error { + select { + case <-h.wakeupChan: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (h *TestProxyServerHandler) GetSingleClient() TestProxyClient { + h.mu.Lock() + defer h.mu.Unlock() + + for _, c := range h.clients { + return c + } + + return nil +} + +func (h *TestProxyServerHandler) ClearClients() { + h.mu.Lock() + defer h.mu.Unlock() + + clear(h.clients) +} + +func NewProxyServerForTest(t *testing.T, country geoip.Country) *TestProxyServerHandler { + t.Helper() + + upgrader := websocket.Upgrader{} + proxyHandler := &TestProxyServerHandler{ + t: t, + Tokens: make(map[string]*rsa.PublicKey), + upgrader: &upgrader, + country: country, + clients: make(map[api.PublicSessionId]*testProxyServerClient), + publishers: make(map[api.PublicSessionId]*testProxyServerPublisher), + subscribers: make(map[string]*testProxyServerSubscriber), + wakeupChan: make(chan struct{}), + } + server := httptest.NewUnstartedServer(proxyHandler) + if !strings.Contains(t.Name(), "DnsDiscovery") { + server.Start() + } + proxyHandler.server = server + proxyHandler.url = server.URL + t.Cleanup(func() { + server.Close() + proxyHandler.mu.Lock() + defer proxyHandler.mu.Unlock() + for _, c := range proxyHandler.clients { + c.Close() + } + }) + + return proxyHandler +} diff --git a/sfu/test/sfu.go b/sfu/test/sfu.go new file mode 100644 index 0000000..130f92f --- /dev/null +++ b/sfu/test/sfu.go @@ -0,0 +1,312 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2019 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 test + +import ( + "context" + "errors" + "fmt" + "maps" + "sync" + "sync/atomic" + "testing" + + "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/talk" +) + +var ( + TestMaxBitrateScreen = api.BandwidthFromBits(12345678) + TestMaxBitrateVideo = api.BandwidthFromBits(23456789) +) + +type SFU struct { + t *testing.T + mu sync.Mutex + // +checklocks:mu + publishers map[api.PublicSessionId]*SFUPublisher + // +checklocks:mu + subscribers map[string]*SFUSubscriber + + maxStreamBitrate api.AtomicBandwidth + maxScreenBitrate api.AtomicBandwidth +} + +func NewSFU(t *testing.T) *SFU { + return &SFU{ + t: t, + + publishers: make(map[api.PublicSessionId]*SFUPublisher), + subscribers: make(map[string]*SFUSubscriber), + } +} + +func (m *SFU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { + return m.maxStreamBitrate.Load(), m.maxScreenBitrate.Load() +} + +func (m *SFU) SetBandwidthLimits(maxStreamBitrate api.Bandwidth, maxScreenBitrate api.Bandwidth) { + m.maxStreamBitrate.Store(maxStreamBitrate) + m.maxScreenBitrate.Store(maxScreenBitrate) +} + +func (m *SFU) Start(ctx context.Context) error { + return nil +} + +func (m *SFU) Stop() { +} + +func (m *SFU) Reload(config *goconf.ConfigFile) { +} + +func (m *SFU) SetOnConnected(f func()) { +} + +func (m *SFU) SetOnDisconnected(f func()) { +} + +func (m *SFU) GetStats() any { + return nil +} + +func (m *SFU) GetServerInfoSfu() *talk.BackendServerInfoSfu { + return nil +} + +func (m *SFU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { + var maxBitrate api.Bandwidth + if streamType == sfu.StreamTypeScreen { + maxBitrate = TestMaxBitrateScreen + } else { + maxBitrate = TestMaxBitrateVideo + } + publisherSettings := settings + bitrate := publisherSettings.Bitrate + if bitrate <= 0 || bitrate > maxBitrate { + publisherSettings.Bitrate = maxBitrate + } + pub := &SFUPublisher{ + SFUClient: SFUClient{ + t: m.t, + id: string(id), + sid: sid, + streamType: streamType, + }, + + settings: publisherSettings, + } + + m.mu.Lock() + defer m.mu.Unlock() + + m.publishers[id] = pub + return pub, nil +} + +func (m *SFU) GetPublishers() map[api.PublicSessionId]*SFUPublisher { + m.mu.Lock() + defer m.mu.Unlock() + + result := maps.Clone(m.publishers) + return result +} + +func (m *SFU) GetPublisher(id api.PublicSessionId) *SFUPublisher { + m.mu.Lock() + defer m.mu.Unlock() + + return m.publishers[id] +} + +func (m *SFU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { + m.mu.Lock() + defer m.mu.Unlock() + + pub := m.publishers[publisher] + if pub == nil { + return nil, errors.New("waiting for publisher not implemented yet") + } + + id := internal.RandomString(8) + sub := &SFUSubscriber{ + SFUClient: SFUClient{ + t: m.t, + id: id, + streamType: streamType, + }, + + publisher: pub, + } + return sub, nil +} + +type SFUClient struct { + t *testing.T + closed atomic.Bool + + id string + sid string + streamType sfu.StreamType +} + +func (c *SFUClient) Id() string { + return c.id +} + +func (c *SFUClient) Sid() string { + return c.sid +} + +func (c *SFUClient) StreamType() sfu.StreamType { + return c.streamType +} + +func (c *SFUClient) MaxBitrate() api.Bandwidth { + return 0 +} + +func (c *SFUClient) Close(ctx context.Context) { + if c.closed.CompareAndSwap(false, true) { + logger := log.NewLoggerForTest(c.t) + logger.Printf("Close SFU client %s", c.id) + } +} + +func (c *SFUClient) IsClosed() bool { + return c.closed.Load() +} + +type SFUPublisher struct { + SFUClient + + settings sfu.NewPublisherSettings + + sdp string +} + +func (p *SFUPublisher) Settings() sfu.NewPublisherSettings { + return p.settings +} + +func (p *SFUPublisher) PublisherId() api.PublicSessionId { + return api.PublicSessionId(p.id) +} + +func (p *SFUPublisher) HasMedia(mt sfu.MediaType) bool { + return (p.settings.MediaTypes & mt) == mt +} + +func (p *SFUPublisher) SetMedia(mt sfu.MediaType) { + p.settings.MediaTypes = mt +} + +func (p *SFUPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + go func() { + if p.IsClosed() { + callback(errors.New("already closed"), nil) + return + } + + switch data.Type { + case "offer": + sdp := data.Payload["sdp"] + if sdp, ok := sdp.(string); ok { + p.sdp = sdp + switch sdp { + case mock.MockSdpOfferAudioOnly: + callback(nil, api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioOnly, + }) + return + case mock.MockSdpOfferAudioAndVideo: + callback(nil, api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }) + return + } + } + callback(fmt.Errorf("offer payload %+v is not implemented", data.Payload), nil) + default: + callback(fmt.Errorf("message type %s is not implemented", data.Type), nil) + } + }() +} + +func (p *SFUPublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { + return nil, errors.New("not implemented") +} + +func (p *SFUPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { + return errors.New("remote publishing not supported") +} + +func (p *SFUPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { + return errors.New("remote publishing not supported") +} + +type SFUSubscriber struct { + SFUClient + + publisher *SFUPublisher +} + +func (s *SFUSubscriber) Publisher() api.PublicSessionId { + return s.publisher.PublisherId() +} + +func (s *SFUSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + go func() { + if s.IsClosed() { + callback(errors.New("already closed"), nil) + return + } + + switch data.Type { + case "requestoffer": + fallthrough + case "sendoffer": + sdp := s.publisher.sdp + if sdp == "" { + callback(errors.New("publisher not sending (no SDP)"), nil) + return + } + + callback(nil, api.StringMap{ + "type": "offer", + "sdp": sdp, + }) + case "answer": + callback(nil, nil) + default: + callback(fmt.Errorf("message type %s is not implemented", data.Type), nil) + } + }() +} From 5284aa69157bbf8e28de8d8059841b6df1025e8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 01:20:53 +0000 Subject: [PATCH 447/549] Bump sphinx-rtd-theme from 3.0.2 to 3.1.0 in /docs Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 3.0.2 to 3.1.0. - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/3.0.2...3.1.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-version: 3.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index d927513..01ffc42 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,4 +3,4 @@ markdown==3.10 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==8.2.3 -sphinx_rtd_theme==3.0.2 +sphinx_rtd_theme==3.1.0 From d40e67fb2fd156e1a3c5730b4755e07c503244da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 07:03:36 +0000 Subject: [PATCH 448/549] Bump sphinx from 8.2.3 to 9.1.0 in /docs Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.3 to 9.1.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.2.3...v9.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 01ffc42..dd6e550 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ jinja2==3.1.6 markdown==3.10 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 -sphinx==8.2.3 +sphinx==9.1.0 sphinx_rtd_theme==3.1.0 From 3c41dcdbce0486ac69a5d44a44751a1e59534fb9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 14:00:41 +0100 Subject: [PATCH 449/549] Move common client code to separate package. --- .codecov.yml | 4 + client.go => client/client.go | 133 ++++---- client/client_test.go | 315 ++++++++++++++++++ client/ip.go | 93 ++++++ client/ip_test.go | 278 ++++++++++++++++ .../stats_prometheus.go | 9 +- clientsession.go | 14 +- cmd/proxy/proxy_client.go | 28 +- cmd/proxy/proxy_server.go | 21 +- federation.go | 12 + grpc_remote_client.go | 19 +- hub.go | 154 +++------ hub_client.go | 128 +++++++ ..._test.go => hub_client_stats_prometheus.go | 34 +- hub_test.go | 237 +------------ remotesession.go | 26 +- testclient_test.go | 8 +- 17 files changed, 1024 insertions(+), 489 deletions(-) rename client.go => client/client.go (86%) create mode 100644 client/client_test.go create mode 100644 client/ip.go create mode 100644 client/ip_test.go rename client_stats_prometheus.go => client/stats_prometheus.go (87%) create mode 100644 hub_client.go rename client_test.go => hub_client_stats_prometheus.go (62%) diff --git a/.codecov.yml b/.codecov.yml index e3aba2f..7960edb 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -28,6 +28,10 @@ component_management: name: async paths: - async/** + - component_id: module_client + name: client + paths: + - client/** - component_id: module_cmd_client name: cmd/client paths: diff --git a/client.go b/client/client.go similarity index 86% rename from client.go rename to client/client.go index 0c024b2..9679526 100644 --- a/client.go +++ b/client/client.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 client import ( "bytes" @@ -80,10 +80,6 @@ type HandlerClient interface { Country() geoip.Country UserAgent() string IsConnected() bool - IsAuthenticated() bool - - GetSession() Session - SetSession(session Session) SendError(e *api.Error) bool SendByeResponse(message *api.ClientMessage) bool @@ -93,19 +89,30 @@ type HandlerClient interface { Close() } -type ClientHandler interface { - OnClosed(HandlerClient) - OnMessageReceived(HandlerClient, []byte) - OnRTTReceived(HandlerClient, time.Duration) +type Handler interface { + GetSessionId() api.PublicSessionId + + OnClosed() + OnMessageReceived([]byte) + OnRTTReceived(time.Duration) } -type ClientGeoIpHandler interface { - OnLookupCountry(HandlerClient) geoip.Country +type GeoIpHandler interface { + OnLookupCountry(addr string) geoip.Country +} + +type InRoomHandler interface { + IsInRoom(string) bool +} + +type SessionCloserHandler interface { + CloseSession() } type Client struct { - logger log.Logger - ctx context.Context + logger log.Logger + ctx context.Context + // +checklocks:mu conn *websocket.Conn addr string agent string @@ -115,9 +122,8 @@ type Client struct { handlerMu sync.RWMutex // +checklocks:handlerMu - handler ClientHandler + handler Handler - session atomic.Pointer[Session] sessionId atomic.Pointer[api.PublicSessionId] mu sync.Mutex @@ -128,42 +134,36 @@ type Client struct { messageChan chan *bytes.Buffer } -func NewClient(ctx context.Context, conn *websocket.Conn, remoteAddress string, agent string, handler ClientHandler) (*Client, error) { - remoteAddress = strings.TrimSpace(remoteAddress) - if remoteAddress == "" { - remoteAddress = "unknown remote address" - } - agent = strings.TrimSpace(agent) - if agent == "" { - agent = "unknown user agent" - } +func (c *Client) SetConn(ctx context.Context, conn *websocket.Conn, remoteAddress string, agent string, logRTT bool, handler Handler) { + c.mu.Lock() + defer c.mu.Unlock() - client := &Client{ - agent: agent, - logRTT: true, - } - client.SetConn(ctx, conn, remoteAddress, handler) - return client, nil -} - -func (c *Client) SetConn(ctx context.Context, conn *websocket.Conn, remoteAddress string, handler ClientHandler) { c.logger = log.LoggerFromContext(ctx) c.ctx = ctx c.conn = conn c.addr = remoteAddress + c.agent = agent + c.logRTT = logRTT c.SetHandler(handler) c.closer = internal.NewCloser() c.messageChan = make(chan *bytes.Buffer, 16) c.messagesDone = make(chan struct{}) } -func (c *Client) SetHandler(handler ClientHandler) { +func (c *Client) GetConn() *websocket.Conn { + c.mu.Lock() + defer c.mu.Unlock() + + return c.conn +} + +func (c *Client) SetHandler(handler Handler) { c.handlerMu.Lock() defer c.handlerMu.Unlock() c.handler = handler } -func (c *Client) getHandler() ClientHandler { +func (c *Client) getHandler() Handler { c.handlerMu.RLock() defer c.handlerMu.RUnlock() return c.handler @@ -177,27 +177,6 @@ func (c *Client) IsConnected() bool { return c.closed.Load() == 0 } -func (c *Client) IsAuthenticated() bool { - return c.GetSession() != nil -} - -func (c *Client) GetSession() Session { - session := c.session.Load() - if session == nil { - return nil - } - - return *session -} - -func (c *Client) SetSession(session Session) { - if session == nil { - c.session.Store(nil) - } else { - c.session.Store(&session) - } -} - func (c *Client) SetSessionId(sessionId api.PublicSessionId) { c.sessionId.Store(&sessionId) } @@ -205,12 +184,12 @@ func (c *Client) SetSessionId(sessionId api.PublicSessionId) { func (c *Client) GetSessionId() api.PublicSessionId { sessionId := c.sessionId.Load() if sessionId == nil { - session := c.GetSession() - if session == nil { + sessionId := c.getHandler().GetSessionId() + if sessionId == "" { return "" } - return session.PublicId() + return sessionId } return *sessionId @@ -227,8 +206,8 @@ func (c *Client) UserAgent() string { func (c *Client) Country() geoip.Country { if c.country == nil { var country geoip.Country - if handler, ok := c.getHandler().(ClientGeoIpHandler); ok { - country = handler.OnLookupCountry(c) + if handler, ok := c.getHandler().(GeoIpHandler); ok { + country = handler.OnLookupCountry(c.addr) } else { country = geoip.UnknownCountry } @@ -238,6 +217,14 @@ func (c *Client) Country() geoip.Country { return *c.country } +func (c *Client) IsInRoom(id string) bool { + if handler, ok := c.getHandler().(InRoomHandler); ok { + return handler.IsInRoom(id) + } + + return false +} + func (c *Client) Close() { if c.closed.Load() >= 2 { // Prevent reentrant call in case this was the second closing @@ -267,8 +254,7 @@ func (c *Client) doClose() { c.closer.Close() <-c.messagesDone - c.getHandler().OnClosed(c) - c.SetSession(nil) + c.getHandler().OnClosed() } } @@ -340,7 +326,7 @@ func (c *Client) ReadPump() { } } statsClientRTT.Observe(float64(rtt.Milliseconds())) - c.getHandler().OnRTTReceived(c, rtt) + c.getHandler().OnRTTReceived(rtt) } return nil }) @@ -404,7 +390,7 @@ func (c *Client) processMessages() { break } - c.getHandler().OnMessageReceived(c, buffer.Bytes()) + c.getHandler().OnMessageReceived(buffer.Bytes()) bufferPool.Put(buffer) } @@ -425,6 +411,7 @@ func (w *counterWriter) Write(p []byte) (int, error) { return written, err } +// +checklocks:c.mu func (c *Client) writeInternal(message json.Marshaler) bool { var closeData []byte @@ -512,19 +499,19 @@ func (c *Client) writeMessage(message WritableClientMessage) bool { return c.writeMessageLocked(message) } +// +checklocks:c.mu func (c *Client) writeMessageLocked(message WritableClientMessage) bool { if !c.writeInternal(message) { return false } - session := c.GetSession() - if message.CloseAfterSend(session) { - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint - c.conn.WriteMessage(websocket.CloseMessage, []byte{}) // nolint - if session != nil { - go session.Close() - } - go c.Close() + if message.CloseAfterSend(c) { + go func() { + if sc, ok := c.getHandler().(SessionCloserHandler); ok { + sc.CloseSession() + } + c.Close() + }() } return true diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 0000000..31e8c46 --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,315 @@ +/** + * 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 client + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "net/http/httptest" + "slices" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/log" +) + +func TestCounterWriter(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + var b bytes.Buffer + var written int + w := &counterWriter{ + w: &b, + counter: &written, + } + if count, err := w.Write(nil); assert.NoError(err) && assert.Equal(0, count) { + assert.Equal(0, written) + } + if count, err := w.Write([]byte("foo")); assert.NoError(err) && assert.Equal(3, count) { + assert.Equal(3, written) + } +} + +type serverClient struct { + Client + + t *testing.T + handler *testHandler + + id string + received atomic.Uint32 + sessionClosed atomic.Bool +} + +func newTestClient(h *testHandler, r *http.Request, conn *websocket.Conn, id uint64) *serverClient { + result := &serverClient{ + t: h.t, + handler: h, + id: fmt.Sprintf("session-%d", id), + } + + addr := r.RemoteAddr + if host, _, err := net.SplitHostPort(addr); err == nil { + addr = host + } + + logger := log.NewLoggerForTest(h.t) + ctx := log.NewLoggerContext(r.Context(), logger) + result.SetConn(ctx, conn, addr, r.Header.Get("User-Agent"), false, result) + return result +} + +func (c *serverClient) WaitReceived(ctx context.Context, count uint32) error { + for { + if err := ctx.Err(); err != nil { + return err + } else if c.received.Load() >= count { + return nil + } + + time.Sleep(time.Millisecond) + } +} + +func (c *serverClient) GetSessionId() api.PublicSessionId { + return api.PublicSessionId(c.id) +} + +func (c *serverClient) OnClosed() { + c.handler.removeClient(c) +} + +func (c *serverClient) OnMessageReceived(message []byte) { + switch c.received.Add(1) { + case 1: + var s string + if err := json.Unmarshal(message, &s); assert.NoError(c.t, err) { + assert.Equal(c.t, "Hello world!", s) + c.SendMessage(&api.ServerMessage{ + Type: "welcome", + Welcome: &api.WelcomeServerMessage{ + Version: "1.0", + }, + }) + } + case 2: + var s string + if err := json.Unmarshal(message, &s); assert.NoError(c.t, err) { + assert.Equal(c.t, "Send error", s) + c.SendError(api.NewError("test_error", "This is a test error.")) + } + case 3: + var s string + if err := json.Unmarshal(message, &s); assert.NoError(c.t, err) { + assert.Equal(c.t, "Send bye", s) + c.SendByeResponseWithReason(nil, "Go away!") + } + } +} + +func (c *serverClient) OnRTTReceived(rtt time.Duration) { + +} + +func (c *serverClient) OnLookupCountry(addr string) geoip.Country { + return "DE" +} + +func (c *serverClient) IsInRoom(roomId string) bool { + return false +} + +func (c *serverClient) CloseSession() { + if c.sessionClosed.Swap(true) { + assert.Fail(c.t, "session closed more than once") + } +} + +type testHandler struct { + mu sync.Mutex + + t *testing.T + + upgrader websocket.Upgrader + + id atomic.Uint64 + // +checklocks:mu + activeClients map[string]*serverClient + // +checklocks:mu + allClients []*serverClient +} + +func newTestHandler(t *testing.T) *testHandler { + return &testHandler{ + t: t, + activeClients: make(map[string]*serverClient), + } +} + +func (h *testHandler) addClient(client *serverClient) { + h.mu.Lock() + defer h.mu.Unlock() + + h.activeClients[client.id] = client + h.allClients = append(h.allClients, client) +} + +func (h *testHandler) removeClient(client *serverClient) { + h.mu.Lock() + defer h.mu.Unlock() + + delete(h.activeClients, client.id) +} + +func (h *testHandler) getClients() []*serverClient { + h.mu.Lock() + defer h.mu.Unlock() + + return slices.Clone(h.allClients) +} + +func (h *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + conn, err := h.upgrader.Upgrade(w, r, nil) + if !assert.NoError(h.t, err) { + return + } + + id := h.id.Add(1) + client := newTestClient(h, r, conn, id) + h.addClient(client) + go client.WritePump() + client.ReadPump() +} + +type localClient struct { + t *testing.T + + conn *websocket.Conn +} + +func newLocalClient(t *testing.T, url string) *localClient { + t.Helper() + + conn, _, err := websocket.DefaultDialer.DialContext(t.Context(), url, nil) + require.NoError(t, err) + return &localClient{ + t: t, + + conn: conn, + } +} + +func (c *localClient) Close() error { + err := c.conn.Close() + if errors.Is(err, net.ErrClosed) { + err = nil + } + return err +} + +func (c *localClient) WriteJSON(v any) error { + return c.conn.WriteJSON(v) +} + +func (c *localClient) ReadJSON(v any) error { + return c.conn.ReadJSON(v) +} + +func TestClient(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + serverHandler := newTestHandler(t) + + server := httptest.NewServer(serverHandler) + t.Cleanup(func() { + server.Close() + }) + + client := newLocalClient(t, strings.ReplaceAll(server.URL, "http://", "ws://")) + t.Cleanup(func() { + assert.NoError(client.Close()) + }) + + var msg api.ServerMessage + + require.NoError(client.WriteJSON("Hello world!")) + if assert.NoError(client.ReadJSON(&msg)) && + assert.Equal("welcome", msg.Type) && + assert.NotNil(msg.Welcome) { + assert.Equal("1.0", msg.Welcome.Version) + } + if clients := serverHandler.getClients(); assert.Len(clients, 1) { + assert.False(clients[0].sessionClosed.Load()) + assert.EqualValues(1, clients[0].received.Load()) + } + + require.NoError(client.WriteJSON("Send error")) + if assert.NoError(client.ReadJSON(&msg)) && + assert.Equal("error", msg.Type) && + assert.NotNil(msg.Error) { + assert.Equal("test_error", msg.Error.Code) + assert.Equal("This is a test error.", msg.Error.Message) + } + if clients := serverHandler.getClients(); assert.Len(clients, 1) { + assert.False(clients[0].sessionClosed.Load()) + assert.EqualValues(2, clients[0].received.Load()) + } + + require.NoError(client.WriteJSON("Send bye")) + if assert.NoError(client.ReadJSON(&msg)) && + assert.Equal("bye", msg.Type) && + assert.NotNil(msg.Bye) { + assert.Equal("Go away!", msg.Bye.Reason) + } + if clients := serverHandler.getClients(); assert.Len(clients, 1) { + assert.EqualValues(3, clients[0].received.Load()) + } + + // Sending a "bye" will close the connection. + var we *websocket.CloseError + if err := client.ReadJSON(&msg); assert.ErrorAs(err, &we) { + assert.Equal(websocket.CloseNormalClosure, we.Code) + assert.Empty(we.Text) + } + if clients := serverHandler.getClients(); assert.Len(clients, 1) { + assert.True(clients[0].sessionClosed.Load()) + assert.EqualValues(3, clients[0].received.Load()) + } +} diff --git a/client/ip.go b/client/ip.go new file mode 100644 index 0000000..d897278 --- /dev/null +++ b/client/ip.go @@ -0,0 +1,93 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 client + +import ( + "net" + "net/http" + "slices" + "strings" + + "github.com/strukturag/nextcloud-spreed-signaling/container" +) + +var ( + DefaultTrustedProxies = container.DefaultPrivateIPs() +) + +func GetRealUserIP(r *http.Request, trusted *container.IPList) string { + addr := r.RemoteAddr + if host, _, err := net.SplitHostPort(addr); err == nil { + addr = host + } + + ip := net.ParseIP(addr) + if len(ip) == 0 { + return addr + } + + // Don't check any headers if the server can be reached by untrusted clients directly. + if trusted == nil || !trusted.Contains(ip) { + return addr + } + + if realIP := r.Header.Get("X-Real-IP"); realIP != "" { + if ip := net.ParseIP(realIP); len(ip) > 0 { + return realIP + } + } + + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address + forwarded := strings.Split(strings.Join(r.Header.Values("X-Forwarded-For"), ","), ",") + if len(forwarded) > 0 { + slices.Reverse(forwarded) + var lastTrusted string + for _, hop := range forwarded { + hop = strings.TrimSpace(hop) + // Make sure to remove any port. + if host, _, err := net.SplitHostPort(hop); err == nil { + hop = host + } + + ip := net.ParseIP(hop) + if len(ip) == 0 { + continue + } + + if trusted.Contains(ip) { + lastTrusted = hop + continue + } + + return hop + } + + // If all entries in the "X-Forwarded-For" list are trusted, the left-most + // will be the client IP. This can happen if a subnet is trusted and the + // client also has an IP from this subnet. + if lastTrusted != "" { + return lastTrusted + } + } + + return addr +} diff --git a/client/ip_test.go b/client/ip_test.go new file mode 100644 index 0000000..f12b62e --- /dev/null +++ b/client/ip_test.go @@ -0,0 +1,278 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 client + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/container" +) + +func TestGetRealUserIP(t *testing.T) { + t.Parallel() + testcases := []struct { + expected string + headers http.Header + trusted string + addr string + }{ + { + "192.168.1.2", + nil, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "invalid-ip", + nil, + "192.168.0.0/16", + "invalid-ip", + }, + { + "invalid-ip", + nil, + "192.168.0.0/16", + "invalid-ip:12345", + }, + { + "10.11.12.13", + nil, + "192.168.0.0/16", + "10.11.12.13:23456", + }, + { + "10.11.12.13", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"10.11.12.13"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "2002:db8::1", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"2002:db8::1"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14:1234, 192.168.30.32:2345"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "10.11.12.13", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"10.11.12.13"}, + }, + "2001:db8::/48", + "[2001:db8::1]:23456", + }, + { + "2002:db8::1", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"2002:db8::1"}, + }, + "2001:db8::/48", + "[2001:db8::1]:23456", + }, + { + "2002:db8::1", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "2002:db8::1", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 2001:db8::1"}, + }, + "192.168.0.0/16, 2001:db8::/48", + "192.168.1.2:23456", + }, + { + "2002:db8::1", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 192.168.30.32"}, + }, + "192.168.0.0/16, 2001:db8::/48", + "[2001:db8::1]:23456", + }, + { + "2002:db8::1", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 2001:db8::2"}, + }, + "2001:db8::/48", + "[2001:db8::1]:23456", + }, + // "X-Real-IP" has preference before "X-Forwarded-For" + { + "10.11.12.13", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"10.11.12.13"}, + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + // Multiple "X-Forwarded-For" headers are merged. + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14", "192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"1.2.3.4", "11.12.13.14", "192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"1.2.3.4", "2.3.4.5", "11.12.13.14", "192.168.31.32", "192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + // Headers are ignored if coming from untrusted clients. + { + "10.11.12.13", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"11.12.13.14"}, + }, + "192.168.0.0/16", + "10.11.12.13:23456", + }, + { + "10.11.12.13", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, + }, + "192.168.0.0/16", + "10.11.12.13:23456", + }, + // X-Forwarded-For is filtered for trusted proxies. + { + "1.2.3.4", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 1.2.3.4"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "1.2.3.4", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 1.2.3.4, 192.168.2.3"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "10.11.12.13", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 1.2.3.4"}, + }, + "192.168.0.0/16", + "10.11.12.13:23456", + }, + // Invalid IPs are ignored. + { + "192.168.1.2", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"this-is-not-an-ip"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"this-is-not-an-ip"}, + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "11.12.13.14", + http.Header{ + http.CanonicalHeaderKey("x-real-ip"): []string{"this-is-not-an-ip"}, + http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32, proxy1"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "192.168.1.2", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"this-is-not-an-ip"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + { + "192.168.2.3", + http.Header{ + http.CanonicalHeaderKey("x-forwarded-for"): []string{"this-is-not-an-ip, 192.168.2.3"}, + }, + "192.168.0.0/16", + "192.168.1.2:23456", + }, + } + + for _, tc := range testcases { + trustedProxies, err := container.ParseIPList(tc.trusted) + if !assert.NoError(t, err, "invalid trusted proxies in %+v", tc) { + continue + } + request := &http.Request{ + RemoteAddr: tc.addr, + Header: tc.headers, + } + assert.Equal(t, tc.expected, GetRealUserIP(request, trustedProxies), "failed for %+v", tc) + } +} diff --git a/client_stats_prometheus.go b/client/stats_prometheus.go similarity index 87% rename from client_stats_prometheus.go rename to client/stats_prometheus.go index f27248f..86ae928 100644 --- a/client_stats_prometheus.go +++ b/client/stats_prometheus.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 client import ( "github.com/prometheus/client_golang/prometheus" @@ -28,12 +28,6 @@ import ( ) var ( - statsClientCountries = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "signaling", - Subsystem: "client", - Name: "countries_total", - Help: "The total number of connections by country", - }, []string{"country"}) statsClientRTT = prometheus.NewHistogram(prometheus.HistogramOpts{ Namespace: "signaling", Subsystem: "client", @@ -55,7 +49,6 @@ var ( }, []string{"direction"}) clientStats = []prometheus.Collector{ - statsClientCountries, statsClientRTT, statsClientBytesTotal, statsClientMessagesTotal, diff --git a/clientsession.go b/clientsession.go index e19b4b5..b601f73 100644 --- a/clientsession.go +++ b/clientsession.go @@ -88,7 +88,7 @@ type ClientSession struct { asyncCh events.AsyncChannel // +checklocks:mu - client HandlerClient + client ClientWithSession room atomic.Pointer[Room] roomJoinTime atomic.Int64 federation atomic.Pointer[FederationClient] @@ -604,7 +604,7 @@ func (s *ClientSession) doUnsubscribeRoomEvents(notify bool) { s.roomSessionId = "" } -func (s *ClientSession) ClearClient(client HandlerClient) { +func (s *ClientSession) ClearClient(client ClientWithSession) { s.mu.Lock() defer s.mu.Unlock() @@ -612,7 +612,7 @@ func (s *ClientSession) ClearClient(client HandlerClient) { } // +checklocks:s.mu -func (s *ClientSession) clearClientLocked(client HandlerClient) { +func (s *ClientSession) clearClientLocked(client ClientWithSession) { if s.client == nil { return } else if client != nil && s.client != client { @@ -625,7 +625,7 @@ func (s *ClientSession) clearClientLocked(client HandlerClient) { prevClient.SetSession(nil) } -func (s *ClientSession) GetClient() HandlerClient { +func (s *ClientSession) GetClient() ClientWithSession { s.mu.Lock() defer s.mu.Unlock() @@ -633,11 +633,11 @@ func (s *ClientSession) GetClient() HandlerClient { } // +checklocks:s.mu -func (s *ClientSession) getClientUnlocked() HandlerClient { +func (s *ClientSession) getClientUnlocked() ClientWithSession { return s.client } -func (s *ClientSession) SetClient(client HandlerClient) HandlerClient { +func (s *ClientSession) SetClient(client ClientWithSession) ClientWithSession { if client == nil { panic("Use ClearClient to set the client to nil") } @@ -1551,7 +1551,7 @@ func (s *ClientSession) filterAsyncMessage(msg *events.AsyncMessage) *api.Server } } -func (s *ClientSession) NotifySessionResumed(client HandlerClient) { +func (s *ClientSession) NotifySessionResumed(client ClientWithSession) { s.mu.Lock() if len(s.pendingClientMessages) == 0 { s.mu.Unlock() diff --git a/cmd/proxy/proxy_client.go b/cmd/proxy/proxy_client.go index 935a2b9..aec1f03 100644 --- a/cmd/proxy/proxy_client.go +++ b/cmd/proxy/proxy_client.go @@ -27,25 +27,35 @@ import ( "time" "github.com/gorilla/websocket" - signaling "github.com/strukturag/nextcloud-spreed-signaling" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/client" ) type ProxyClient struct { - signaling.Client + client.Client proxy *ProxyServer session atomic.Pointer[ProxySession] } -func NewProxyClient(ctx context.Context, proxy *ProxyServer, conn *websocket.Conn, addr string) (*ProxyClient, error) { +func NewProxyClient(ctx context.Context, proxy *ProxyServer, conn *websocket.Conn, addr string, agent string) (*ProxyClient, error) { client := &ProxyClient{ proxy: proxy, } - client.SetConn(ctx, conn, addr, client) + client.SetConn(ctx, conn, addr, agent, false, client) return client, nil } +func (c *ProxyClient) GetSessionId() api.PublicSessionId { + if session := c.GetSession(); session != nil { + return session.PublicId() + } + + return "" +} + func (c *ProxyClient) GetSession() *ProxySession { return c.session.Load() } @@ -54,18 +64,18 @@ func (c *ProxyClient) SetSession(session *ProxySession) { c.session.Store(session) } -func (c *ProxyClient) OnClosed(client signaling.HandlerClient) { - if session := c.GetSession(); session != nil { +func (c *ProxyClient) OnClosed() { + if session := c.session.Swap(nil); session != nil { session.MarkUsed() } - c.proxy.clientClosed(&c.Client) + c.proxy.clientClosed(c) } -func (c *ProxyClient) OnMessageReceived(client signaling.HandlerClient, data []byte) { +func (c *ProxyClient) OnMessageReceived(data []byte) { c.proxy.processMessage(c, data) } -func (c *ProxyClient) OnRTTReceived(client signaling.HandlerClient, rtt time.Duration) { +func (c *ProxyClient) OnRTTReceived(rtt time.Duration) { if session := c.GetSession(); session != nil { session.MarkUsed() } diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index 6817cf7..8d4de6c 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -51,6 +51,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" + "github.com/strukturag/nextcloud-spreed-signaling/client" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" @@ -87,6 +88,8 @@ const ( ) var ( + InvalidFormat = client.InvalidFormat + defaultProxyFeatures = []string{ ProxyFeatureRemoteStreams, } @@ -279,7 +282,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * if !trustedProxiesIps.Empty() { logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { - trustedProxiesIps = signaling.DefaultTrustedProxies + trustedProxiesIps = client.DefaultTrustedProxies logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } @@ -640,7 +643,7 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { if !trustedProxiesIps.Empty() { s.logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { - trustedProxiesIps = signaling.DefaultTrustedProxies + trustedProxiesIps = client.DefaultTrustedProxies s.logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } s.trustedProxies.Store(trustedProxiesIps) @@ -675,7 +678,7 @@ func (s *ProxyServer) welcomeHandler(w http.ResponseWriter, r *http.Request) { } func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { - addr := signaling.GetRealUserIP(r, s.trustedProxies.Load()) + addr := client.GetRealUserIP(r, s.trustedProxies.Load()) header := http.Header{} header.Set("Server", "nextcloud-spreed-signaling-proxy/"+s.version) header.Set("X-Spreed-Signaling-Features", strings.Join(s.welcomeMsg.Features, ", ")) @@ -685,14 +688,14 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { return } + agent := r.Header.Get("User-Agent") ctx := log.NewLoggerContext(r.Context(), s.logger) if conn.Subprotocol() == janus.EventsSubprotocol { - agent := r.Header.Get("User-Agent") janus.RunEventsHandler(ctx, s.mcu, conn, addr, agent) return } - client, err := NewProxyClient(ctx, s, conn, addr) + client, err := NewProxyClient(ctx, s, conn, addr, agent) if err != nil { s.logger.Printf("Could not create client for %s: %s", addr, err) return @@ -702,7 +705,7 @@ func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) { client.ReadPump() } -func (s *ProxyServer) clientClosed(client *signaling.Client) { +func (s *ProxyServer) clientClosed(client *ProxyClient) { s.logger.Printf("Connection from %s closed", client.RemoteAddr()) } @@ -766,7 +769,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { } else { s.logger.Printf("Error decoding message from %s: %v", client.RemoteAddr(), err) } - client.SendError(signaling.InvalidFormat) + client.SendError(InvalidFormat) return } @@ -776,7 +779,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { } else { s.logger.Printf("Invalid message %+v from %s: %v", message, client.RemoteAddr(), err) } - client.SendMessage(message.NewErrorServerMessage(signaling.InvalidFormat)) + client.SendMessage(message.NewErrorServerMessage(InvalidFormat)) return } @@ -1654,7 +1657,7 @@ func (s *ProxyServer) getStats() api.StringMap { } func (s *ProxyServer) allowStatsAccess(r *http.Request) bool { - addr := signaling.GetRealUserIP(r, s.trustedProxies.Load()) + addr := client.GetRealUserIP(r, s.trustedProxies.Load()) ip := net.ParseIP(addr) if len(ip) == 0 { return false diff --git a/federation.go b/federation.go index a0f6296..1e04de5 100644 --- a/federation.go +++ b/federation.go @@ -43,6 +43,18 @@ import ( ) const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 64 * 1024 + initialFederationReconnectInterval = 100 * time.Millisecond maxFederationReconnectInterval = 8 * time.Second ) diff --git a/grpc_remote_client.go b/grpc_remote_client.go index 4c34137..0456d1a 100644 --- a/grpc_remote_client.go +++ b/grpc_remote_client.go @@ -34,6 +34,7 @@ import ( "google.golang.org/grpc/status" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/client" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -57,7 +58,7 @@ type remoteGrpcClient struct { hub *Hub client grpc.RpcSessions_ProxySessionServer - sessionId string + sessionId api.PublicSessionId remoteAddr string country geoip.Country userAgent string @@ -66,7 +67,7 @@ type remoteGrpcClient struct { closeFunc context.CancelCauseFunc session atomic.Pointer[Session] - messages chan WritableClientMessage + messages chan client.WritableClientMessage } func newRemoteGrpcClient(hub *Hub, request grpc.RpcSessions_ProxySessionServer) (*remoteGrpcClient, error) { @@ -82,7 +83,7 @@ func newRemoteGrpcClient(hub *Hub, request grpc.RpcSessions_ProxySessionServer) hub: hub, client: request, - sessionId: getMD(md, "sessionId"), + sessionId: api.PublicSessionId(getMD(md, "sessionId")), remoteAddr: getMD(md, "remoteAddr"), country: geoip.Country(getMD(md, "country")), userAgent: getMD(md, "userAgent"), @@ -90,7 +91,7 @@ func newRemoteGrpcClient(hub *Hub, request grpc.RpcSessions_ProxySessionServer) closeCtx: closeCtx, closeFunc: closeFunc, - messages: make(chan WritableClientMessage, grpcRemoteClientMessageQueue), + messages: make(chan client.WritableClientMessage, grpcRemoteClientMessageQueue), } return result, nil } @@ -99,7 +100,7 @@ func (c *remoteGrpcClient) readPump() { var closeError error defer func() { c.closeFunc(closeError) - c.hub.OnClosed(c) + c.hub.processUnregister(c) }() for { @@ -117,7 +118,7 @@ func (c *remoteGrpcClient) readPump() { break } - c.hub.OnMessageReceived(c, msg.Message) + c.hub.processMessage(c, msg.Message) } } @@ -145,6 +146,10 @@ func (c *remoteGrpcClient) IsAuthenticated() bool { return c.GetSession() != nil } +func (c *remoteGrpcClient) GetSessionId() api.PublicSessionId { + return c.sessionId +} + func (c *remoteGrpcClient) GetSession() Session { session := c.session.Load() if session == nil { @@ -190,7 +195,7 @@ func (c *remoteGrpcClient) SendByeResponseWithReason(message *api.ClientMessage, return c.SendMessage(response) } -func (c *remoteGrpcClient) SendMessage(message WritableClientMessage) bool { +func (c *remoteGrpcClient) SendMessage(message client.WritableClientMessage) bool { if c.closeCtx.Err() != nil { return false } diff --git a/hub.go b/hub.go index 622197c..61d43e8 100644 --- a/hub.go +++ b/hub.go @@ -54,6 +54,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/client" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/etcd" @@ -139,14 +140,20 @@ var ( // Allow time differences of up to one minute between server and proxy. tokenLeeway = time.Minute - - DefaultTrustedProxies = container.DefaultPrivateIPs() ) func init() { RegisterHubStats() } +type ClientWithSession interface { + client.HandlerClient + + IsAuthenticated() bool + GetSession() Session + SetSession(session Session) +} + type Hub struct { version string logger log.Logger @@ -174,7 +181,7 @@ type Hub struct { sid atomic.Uint64 // +checklocks:mu - clients map[uint64]HandlerClient + clients map[uint64]ClientWithSession // +checklocks:mu sessions map[uint64]Session // +checklocks:ru @@ -198,7 +205,7 @@ type Hub struct { // +checklocks:mu anonymousSessions map[*ClientSession]time.Time // +checklocks:mu - expectHelloClients map[HandlerClient]time.Time + expectHelloClients map[ClientWithSession]time.Time // +checklocks:mu dialoutSessions map[*ClientSession]bool // +checklocks:mu @@ -309,7 +316,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven if !trustedProxiesIps.Empty() { logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { - trustedProxiesIps = DefaultTrustedProxies + trustedProxiesIps = client.DefaultTrustedProxies logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } @@ -388,7 +395,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven roomInCall: make(chan *talk.BackendServerRoomRequest), roomParticipants: make(chan *talk.BackendServerRoomRequest), - clients: make(map[uint64]HandlerClient), + clients: make(map[uint64]ClientWithSession), sessions: make(map[uint64]Session), rooms: make(map[string]*Room), @@ -405,7 +412,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven expiredSessions: make(map[Session]time.Time), anonymousSessions: make(map[*ClientSession]time.Time), - expectHelloClients: make(map[HandlerClient]time.Time), + expectHelloClients: make(map[ClientWithSession]time.Time), dialoutSessions: make(map[*ClientSession]bool), remoteSessions: make(map[*RemoteSession]bool), federatedSessions: make(map[*ClientSession]bool), @@ -584,7 +591,7 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { if !trustedProxiesIps.Empty() { h.logger.Printf("Trusted proxies: %s", trustedProxiesIps) } else { - trustedProxiesIps = DefaultTrustedProxies + trustedProxiesIps = client.DefaultTrustedProxies h.logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } h.trustedProxies.Store(trustedProxiesIps) @@ -894,7 +901,7 @@ func (h *Hub) startWaitAnonymousSessionRoomLocked(session *ClientSession) { h.anonymousSessions[session] = now.Add(anonmyousJoinRoomTimeout) } -func (h *Hub) startExpectHello(client HandlerClient) { +func (h *Hub) startExpectHello(client ClientWithSession) { h.mu.Lock() defer h.mu.Unlock() if !client.IsConnected() { @@ -910,16 +917,16 @@ func (h *Hub) startExpectHello(client HandlerClient) { h.expectHelloClients[client] = now.Add(initialHelloTimeout) } -func (h *Hub) processNewClient(client HandlerClient) { +func (h *Hub) processNewClient(client ClientWithSession) { h.startExpectHello(client) h.sendWelcome(client) } -func (h *Hub) sendWelcome(client HandlerClient) { +func (h *Hub) sendWelcome(client ClientWithSession) { client.SendMessage(h.getWelcomeMessage()) } -func (h *Hub) registerClient(client HandlerClient) uint64 { +func (h *Hub) registerClient(client ClientWithSession) uint64 { sid := h.sid.Add(1) for sid == 0 { sid = h.sid.Add(1) @@ -956,24 +963,17 @@ func (h *Hub) newSessionIdData(backend *talk.Backend) *SessionIdData { return sessionIdData } -func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backend *talk.Backend, auth *talk.BackendClientResponse) { - if !c.IsConnected() { +func (h *Hub) processRegister(client ClientWithSession, message *api.ClientMessage, backend *talk.Backend, auth *talk.BackendClientResponse) { + if !client.IsConnected() { // Client disconnected while waiting for "hello" response. return } if auth.Type == "error" { - c.SendMessage(message.NewErrorServerMessage(auth.Error)) + client.SendMessage(message.NewErrorServerMessage(auth.Error)) return } else if auth.Type != "auth" { - c.SendMessage(message.NewErrorServerMessage(UserAuthFailed)) - return - } - - client, ok := c.(*Client) - if !ok { - h.logger.Printf("Can't register non-client %T", c) - client.SendMessage(message.NewWrappedErrorServerMessage(errors.New("can't register non-client"))) + client.SendMessage(message.NewErrorServerMessage(UserAuthFailed)) return } @@ -1077,7 +1077,7 @@ func (h *Hub) processRegister(c HandlerClient, message *api.ClientMessage, backe h.sendHelloResponse(session, message) } -func (h *Hub) processUnregister(client HandlerClient) Session { +func (h *Hub) processUnregister(client ClientWithSession) Session { session := client.GetSession() h.mu.Lock() @@ -1090,10 +1090,8 @@ func (h *Hub) processUnregister(client HandlerClient) Session { h.mu.Unlock() if session != nil { h.logger.Printf("Unregister %s (private=%s)", session.PublicId(), session.PrivateId()) - if c, ok := client.(*Client); ok { - if cs, ok := session.(*ClientSession); ok { - cs.ClearClient(c) - } + if cs, ok := session.(*ClientSession); ok { + cs.ClearClient(client) } } @@ -1101,7 +1099,7 @@ func (h *Hub) processUnregister(client HandlerClient) Session { return session } -func (h *Hub) processMessage(client HandlerClient, data []byte) { +func (h *Hub) processMessage(client ClientWithSession, data []byte) { var message api.ClientMessage if err := message.UnmarshalJSON(data); err != nil { if session := client.GetSession(); session != nil { @@ -1198,8 +1196,8 @@ type remoteClientInfo struct { response *grpc.LookupResumeIdReply } -func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, message *api.ClientMessage) bool { - client, ok := c.(*Client) +func (h *Hub) tryProxyResume(c ClientWithSession, resumeId api.PrivateSessionId, message *api.ClientMessage) bool { + client, ok := c.(*HubClient) if !ok { return false } @@ -1212,7 +1210,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, mes return false } - rpcCtx, rpcCancel := context.WithTimeout(c.Context(), 5*time.Second) + rpcCtx, rpcCancel := context.WithTimeout(client.Context(), 5*time.Second) defer rpcCancel() var wg sync.WaitGroup @@ -1274,7 +1272,7 @@ func (h *Hub) tryProxyResume(c HandlerClient, resumeId api.PrivateSessionId, mes return true } -func (h *Hub) processHello(client HandlerClient, message *api.ClientMessage) { +func (h *Hub) processHello(client ClientWithSession, message *api.ClientMessage) { ctx := log.NewLoggerContext(client.Context(), h.logger) resumeId := message.Hello.ResumeId if resumeId != "" { @@ -1366,7 +1364,7 @@ func (h *Hub) processHello(client HandlerClient, message *api.ClientMessage) { } } -func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) { +func (h *Hub) processHelloV1(ctx context.Context, client ClientWithSession, message *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) { url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { @@ -1390,7 +1388,7 @@ func (h *Hub) processHelloV1(ctx context.Context, client HandlerClient, message return backend, &auth, nil } -func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) { +func (h *Hub) processHelloV2(ctx context.Context, client ClientWithSession, message *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) { url := message.Hello.Auth.ParsedUrl backend := h.backend.GetBackend(url) if backend == nil { @@ -1543,11 +1541,11 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return backend, auth, nil } -func (h *Hub) processHelloClient(client HandlerClient, message *api.ClientMessage) { +func (h *Hub) processHelloClient(client ClientWithSession, message *api.ClientMessage) { // Make sure the client must send another "hello" in case of errors. defer h.startExpectHello(client) - var authFunc func(context.Context, HandlerClient, *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) + var authFunc func(context.Context, ClientWithSession, *api.ClientMessage) (*talk.Backend, *talk.BackendClientResponse, error) switch message.Hello.Version { case api.HelloVersionV1: // Auth information contains a ticket that must be validated against the @@ -1574,7 +1572,7 @@ func (h *Hub) processHelloClient(client HandlerClient, message *api.ClientMessag h.processRegister(client, message, backend, auth) } -func (h *Hub) processHelloInternal(client HandlerClient, message *api.ClientMessage) { +func (h *Hub) processHelloInternal(client ClientWithSession, message *api.ClientMessage) { defer h.startExpectHello(client) if len(h.internalClientsSecret) == 0 { client.SendMessage(message.NewErrorServerMessage(InvalidClientType)) @@ -2957,7 +2955,7 @@ func (h *Hub) sendMcuMessageResponse(session *ClientSession, mcuClient sfu.Clien session.SendMessage(response_message) } -func (h *Hub) processByeMsg(client HandlerClient, message *api.ClientMessage) { +func (h *Hub) processByeMsg(client ClientWithSession, message *api.ClientMessage) { client.SendByeResponse(message) if session := h.processUnregister(client); session != nil { session.Close() @@ -3076,66 +3074,8 @@ func (h *Hub) GetServerInfoDialout() (result []talk.BackendServerInfoDialout) { return } -func GetRealUserIP(r *http.Request, trusted *container.IPList) string { - addr := r.RemoteAddr - if host, _, err := net.SplitHostPort(addr); err == nil { - addr = host - } - - ip := net.ParseIP(addr) - if len(ip) == 0 { - return addr - } - - // Don't check any headers if the server can be reached by untrusted clients directly. - if trusted == nil || !trusted.Contains(ip) { - return addr - } - - if realIP := r.Header.Get("X-Real-IP"); realIP != "" { - if ip := net.ParseIP(realIP); len(ip) > 0 { - return realIP - } - } - - // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address - forwarded := strings.Split(strings.Join(r.Header.Values("X-Forwarded-For"), ","), ",") - if len(forwarded) > 0 { - slices.Reverse(forwarded) - var lastTrusted string - for _, hop := range forwarded { - hop = strings.TrimSpace(hop) - // Make sure to remove any port. - if host, _, err := net.SplitHostPort(hop); err == nil { - hop = host - } - - ip := net.ParseIP(hop) - if len(ip) == 0 { - continue - } - - if trusted.Contains(ip) { - lastTrusted = hop - continue - } - - return hop - } - - // If all entries in the "X-Forwarded-For" list are trusted, the left-most - // will be the client IP. This can happen if a subnet is trusted and the - // client also has an IP from this subnet. - if lastTrusted != "" { - return lastTrusted - } - } - - return addr -} - func (h *Hub) getRealUserIP(r *http.Request) string { - return GetRealUserIP(r, h.trustedProxies.Load()) + return client.GetRealUserIP(r, h.trustedProxies.Load()) } func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { @@ -3158,7 +3098,7 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { return } - client, err := NewClient(ctx, conn, addr, agent, h) + client, err := NewHubClient(ctx, conn, addr, agent, h) if err != nil { h.logger.Printf("Could not create client for %s: %s", addr, err) return @@ -3176,8 +3116,8 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { client.ReadPump() } -func (h *Hub) OnLookupCountry(client HandlerClient) geoip.Country { - ip := net.ParseIP(client.RemoteAddr()) +func (h *Hub) LookupCountry(addr string) geoip.Country { + ip := net.ParseIP(addr) if ip == nil { return geoip.NoCountry } @@ -3206,18 +3146,6 @@ func (h *Hub) OnLookupCountry(client HandlerClient) geoip.Country { return country } -func (h *Hub) OnClosed(client HandlerClient) { - h.processUnregister(client) -} - -func (h *Hub) OnMessageReceived(client HandlerClient, data []byte) { - h.processMessage(client, data) -} - -func (h *Hub) OnRTTReceived(client HandlerClient, rtt time.Duration) { - // Ignore -} - func (h *Hub) ShutdownChannel() <-chan struct{} { return h.shutdown.C } diff --git a/hub_client.go b/hub_client.go new file mode 100644 index 0000000..ad32ac9 --- /dev/null +++ b/hub_client.go @@ -0,0 +1,128 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2017 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" + "strings" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/client" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" +) + +var ( + InvalidFormat = client.InvalidFormat +) + +func init() { + RegisterClientStats() +} + +type HubClient struct { + client.Client + + hub *Hub + session atomic.Pointer[Session] +} + +func NewHubClient(ctx context.Context, conn *websocket.Conn, remoteAddress string, agent string, hub *Hub) (*HubClient, error) { + remoteAddress = strings.TrimSpace(remoteAddress) + if remoteAddress == "" { + remoteAddress = "unknown remote address" + } + agent = strings.TrimSpace(agent) + if agent == "" { + agent = "unknown user agent" + } + + client := &HubClient{ + hub: hub, + } + client.SetConn(ctx, conn, remoteAddress, agent, true, client) + return client, nil +} + +func (c *HubClient) OnLookupCountry(addr string) geoip.Country { + return c.hub.LookupCountry(addr) +} + +func (c *HubClient) OnClosed() { + c.hub.processUnregister(c) +} + +func (c *HubClient) OnMessageReceived(data []byte) { + c.hub.processMessage(c, data) +} + +func (c *HubClient) OnRTTReceived(rtt time.Duration) { + // Ignore +} + +func (c *HubClient) CloseSession() { + if session := c.GetSession(); session != nil { + session.Close() + } +} + +func (c *HubClient) IsAuthenticated() bool { + return c.GetSession() != nil +} + +func (c *HubClient) GetSession() Session { + session := c.session.Load() + if session == nil { + return nil + } + + return *session +} + +func (c *HubClient) SetSession(session Session) { + if session == nil { + c.session.Store(nil) + } else { + c.session.Store(&session) + } +} + +func (c *HubClient) GetSessionId() api.PublicSessionId { + session := c.GetSession() + if session == nil { + return "" + } + + return session.PublicId() +} + +func (c *HubClient) IsInRoom(id string) bool { + session := c.GetSession() + if session == nil { + return false + } + + return session.IsInRoom(id) +} diff --git a/client_test.go b/hub_client_stats_prometheus.go similarity index 62% rename from client_test.go rename to hub_client_stats_prometheus.go index 9b90e54..034d9a4 100644 --- a/client_test.go +++ b/hub_client_stats_prometheus.go @@ -1,6 +1,6 @@ /** * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2025 struktur AG + * Copyright (C) 2021 struktur AG * * @author Joachim Bauch * @@ -22,26 +22,24 @@ package signaling import ( - "bytes" - "testing" + "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/assert" + "github.com/strukturag/nextcloud-spreed-signaling/metrics" ) -func TestCounterWriter(t *testing.T) { - t.Parallel() - assert := assert.New(t) +var ( + statsClientCountries = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "client", + Name: "countries_total", + Help: "The total number of connections by country", + }, []string{"country"}) - var b bytes.Buffer - var written int - w := &counterWriter{ - w: &b, - counter: &written, - } - if count, err := w.Write(nil); assert.NoError(err) && assert.Equal(0, count) { - assert.Equal(0, written) - } - if count, err := w.Write([]byte("foo")); assert.NoError(err) && assert.Equal(3, count) { - assert.Equal(3, written) + clientStats = []prometheus.Collector{ + statsClientCountries, } +) + +func RegisterClientStats() { + metrics.RegisterAll(clientStats...) } diff --git a/hub_test.go b/hub_test.go index 1b1c7d1..8fce006 100644 --- a/hub_test.go +++ b/hub_test.go @@ -3084,233 +3084,6 @@ func TestJoinRoomSwitchClient(t *testing.T) { require.Empty(roomMsg.Room.RoomId) } -func TestGetRealUserIP(t *testing.T) { - t.Parallel() - testcases := []struct { - expected string - headers http.Header - trusted string - addr string - }{ - { - "192.168.1.2", - nil, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "10.11.12.13", - nil, - "192.168.0.0/16", - "10.11.12.13:23456", - }, - { - "10.11.12.13", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"10.11.12.13"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "2002:db8::1", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"2002:db8::1"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "11.12.13.14", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "10.11.12.13", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"10.11.12.13"}, - }, - "2001:db8::/48", - "[2001:db8::1]:23456", - }, - { - "2002:db8::1", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"2002:db8::1"}, - }, - "2001:db8::/48", - "[2001:db8::1]:23456", - }, - { - "2002:db8::1", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "2002:db8::1", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 2001:db8::1"}, - }, - "192.168.0.0/16, 2001:db8::/48", - "192.168.1.2:23456", - }, - { - "2002:db8::1", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 192.168.30.32"}, - }, - "192.168.0.0/16, 2001:db8::/48", - "[2001:db8::1]:23456", - }, - { - "2002:db8::1", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"2002:db8::1, 2001:db8::2"}, - }, - "2001:db8::/48", - "[2001:db8::1]:23456", - }, - // "X-Real-IP" has preference before "X-Forwarded-For" - { - "10.11.12.13", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"10.11.12.13"}, - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - // Multiple "X-Forwarded-For" headers are merged. - { - "11.12.13.14", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14", "192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "11.12.13.14", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"1.2.3.4", "11.12.13.14", "192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "11.12.13.14", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"1.2.3.4", "2.3.4.5", "11.12.13.14", "192.168.31.32", "192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - // Headers are ignored if coming from untrusted clients. - { - "10.11.12.13", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"11.12.13.14"}, - }, - "192.168.0.0/16", - "10.11.12.13:23456", - }, - { - "10.11.12.13", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, - }, - "192.168.0.0/16", - "10.11.12.13:23456", - }, - // X-Forwarded-For is filtered for trusted proxies. - { - "1.2.3.4", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 1.2.3.4"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "1.2.3.4", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 1.2.3.4, 192.168.2.3"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "10.11.12.13", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 1.2.3.4"}, - }, - "192.168.0.0/16", - "10.11.12.13:23456", - }, - // Invalid IPs are ignored. - { - "192.168.1.2", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"this-is-not-an-ip"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "11.12.13.14", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"this-is-not-an-ip"}, - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "11.12.13.14", - http.Header{ - http.CanonicalHeaderKey("x-real-ip"): []string{"this-is-not-an-ip"}, - http.CanonicalHeaderKey("x-forwarded-for"): []string{"11.12.13.14, 192.168.30.32, proxy1"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "192.168.1.2", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"this-is-not-an-ip"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - { - "192.168.2.3", - http.Header{ - http.CanonicalHeaderKey("x-forwarded-for"): []string{"this-is-not-an-ip, 192.168.2.3"}, - }, - "192.168.0.0/16", - "192.168.1.2:23456", - }, - } - - for _, tc := range testcases { - trustedProxies, err := container.ParseIPList(tc.trusted) - if !assert.NoError(t, err, "invalid trusted proxies in %+v", tc) { - continue - } - request := &http.Request{ - RemoteAddr: tc.addr, - Header: tc.headers, - } - assert.Equal(t, tc.expected, GetRealUserIP(request, trustedProxies), "failed for %+v", tc) - } -} - func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) { t.Parallel() require := require.New(t) @@ -5254,11 +5027,11 @@ func TestGeoipOverrides(t *testing.T) { return conf, err }) - assert.Equal(geoip.Loopback, hub.OnLookupCountry(&Client{addr: "127.0.0.1"})) - assert.Equal(geoip.UnknownCountry, hub.OnLookupCountry(&Client{addr: "8.8.8.8"})) - assert.EqualValues(country1, hub.OnLookupCountry(&Client{addr: "10.1.1.2"})) - assert.EqualValues(country2, hub.OnLookupCountry(&Client{addr: "10.2.1.2"})) - assert.EqualValues(strings.ToUpper(country3), hub.OnLookupCountry(&Client{addr: "192.168.10.20"})) + assert.Equal(geoip.Loopback, hub.LookupCountry("127.0.0.1")) + assert.Equal(geoip.UnknownCountry, hub.LookupCountry("8.8.8.8")) + assert.EqualValues(country1, hub.LookupCountry("10.1.1.2")) + assert.EqualValues(country2, hub.LookupCountry("10.2.1.2")) + assert.EqualValues(strings.ToUpper(country3), hub.LookupCountry("192.168.10.20")) } func TestDialoutStatus(t *testing.T) { diff --git a/remotesession.go b/remotesession.go index 2b56333..e1a40ac 100644 --- a/remotesession.go +++ b/remotesession.go @@ -29,6 +29,7 @@ import ( "time" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/client" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -37,14 +38,14 @@ import ( type RemoteSession struct { logger log.Logger hub *Hub - client *Client + client *HubClient remoteClient *grpc.Client sessionId api.PublicSessionId proxy atomic.Pointer[grpc.SessionProxy] } -func NewRemoteSession(hub *Hub, client *Client, remoteClient *grpc.Client, sessionId api.PublicSessionId) (*RemoteSession, error) { +func NewRemoteSession(hub *Hub, client *HubClient, remoteClient *grpc.Client, sessionId api.PublicSessionId) (*RemoteSession, error) { remoteSession := &RemoteSession{ logger: hub.logger, hub: hub, @@ -67,6 +68,10 @@ func NewRemoteSession(hub *Hub, client *Client, remoteClient *grpc.Client, sessi return remoteSession, nil } +func (s *RemoteSession) GetSessionId() api.PublicSessionId { + return s.sessionId +} + func (s *RemoteSession) Country() geoip.Country { return s.client.Country() } @@ -107,7 +112,7 @@ func (s *RemoteSession) OnProxyClose(err error) { s.Close() } -func (s *RemoteSession) SendMessage(message WritableClientMessage) bool { +func (s *RemoteSession) SendMessage(message client.WritableClientMessage) bool { return s.sendMessage(message) == nil } @@ -140,20 +145,25 @@ func (s *RemoteSession) Close() { s.client.Close() } -func (s *RemoteSession) OnLookupCountry(client HandlerClient) geoip.Country { - return s.hub.OnLookupCountry(client) +func (s *RemoteSession) OnLookupCountry(addr string) geoip.Country { + return s.hub.LookupCountry(addr) } -func (s *RemoteSession) OnClosed(client HandlerClient) { +func (s *RemoteSession) OnClosed() { s.Close() } -func (s *RemoteSession) OnMessageReceived(client HandlerClient, message []byte) { +func (s *RemoteSession) OnMessageReceived(message []byte) { if err := s.sendProxyMessage(message); err != nil { s.logger.Printf("Error sending %s to the proxy for session %s: %s", string(message), s.sessionId, err) s.Close() } } -func (s *RemoteSession) OnRTTReceived(client HandlerClient, rtt time.Duration) { +func (s *RemoteSession) OnRTTReceived(rtt time.Duration) { + // Ignore +} + +func (s *RemoteSession) IsInRoom(id string) bool { + return s.client.IsInRoom(id) } diff --git a/testclient_test.go b/testclient_test.go index c6fffd2..7fe62ef 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -319,10 +319,8 @@ func (c *TestClient) WaitForClientRemoved(ctx context.Context) error { for { found := false for _, client := range c.hub.clients { - if cc, ok := client.(*Client); ok { - cc.mu.Lock() - conn := cc.conn - cc.mu.Unlock() + if cc, ok := client.(*HubClient); ok { + conn := cc.GetConn() if conn != nil && conn.RemoteAddr().String() == c.localAddr.String() { found = true break @@ -736,7 +734,7 @@ func (c *TestClient) RunUntilMessage(ctx context.Context) (*api.ServerMessage, b func (c *TestClient) RunUntilMessageOrClosed(ctx context.Context) (*api.ServerMessage, bool) { select { case err := <-c.readErrorChan: - if c.assert.Error(err) && websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { + if c.assert.Error(err) && websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { return nil, true } From 7dfd82c8df39a8b06fc353e3da927c413ad4bf8b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 14:12:20 +0100 Subject: [PATCH 450/549] Don't import errors for proxy from signaling package. --- cmd/proxy/proxy_server.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index 8d4de6c..e242ca0 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -100,6 +100,11 @@ type ContextKey string var ( ContextKeySession = ContextKey("session") + // HelloExpected is returned if a client sends a message before the "hello" request. + HelloExpected = api.NewError("hello_expected", "Expected Hello request.") + // NoSuchSession is returned if the session to be resumed is unknown or expired. + NoSuchSession = api.NewError("no_such_session", "The session to resume does not exist.") + TimeoutCreatingPublisher = api.NewError("timeout", "Timeout creating publisher.") TimeoutCreatingSubscriber = api.NewError("timeout", "Timeout creating subscriber.") TokenAuthFailed = api.NewError("auth_failed", "The token could not be authenticated.") @@ -786,7 +791,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { session := client.GetSession() if session == nil { if message.Type != "hello" { - client.SendMessage(message.NewErrorServerMessage(signaling.HelloExpected)) + client.SendMessage(message.NewErrorServerMessage(HelloExpected)) return } @@ -797,7 +802,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) { } if session == nil || resumeId != session.PublicId() { - client.SendMessage(message.NewErrorServerMessage(signaling.NoSuchSession)) + client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) return } From daaf16bbf8670121fd85bac5fa873fcb33884cf5 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 14:19:34 +0100 Subject: [PATCH 451/549] Move session id codec to separate package. --- .codecov.yml | 4 ++ clientsession.go | 7 ++- cmd/proxy/proxy_server.go | 8 +-- hub.go | 27 ++++----- hub_test.go | 17 +++--- roomsessions_test.go | 3 +- session.go | 3 +- session.pb.go => session/session.pb.go | 58 +++++++++---------- session.proto => session/session.proto | 4 +- .../sessionid_codec.go | 4 +- .../sessionid_codec_test.go | 2 +- testclient_test.go | 3 +- virtualsession.go | 7 ++- 13 files changed, 79 insertions(+), 68 deletions(-) rename session.pb.go => session/session.pb.go (67%) rename session.proto => session/session.proto (96%) rename sessionid_codec.go => session/sessionid_codec.go (99%) rename sessionid_codec_test.go => session/sessionid_codec_test.go (99%) diff --git a/.codecov.yml b/.codecov.yml index 7960edb..1e6a59b 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -100,6 +100,10 @@ component_management: name: security paths: - security/** + - component_id: module_session + name: session + paths: + - session/** - component_id: module_sfu name: sfu paths: diff --git a/clientsession.go b/clientsession.go index b601f73..75696fe 100644 --- a/clientsession.go +++ b/clientsession.go @@ -41,6 +41,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/session" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -63,7 +64,7 @@ type ClientSession struct { events events.AsyncEvents privateId api.PrivateSessionId publicId api.PublicSessionId - data *SessionIdData + data *session.SessionIdData ctx context.Context closeFunc context.CancelFunc @@ -125,7 +126,7 @@ type ClientSession struct { responseHandlers map[string]ResponseHandlerFunc } -func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, backend *talk.Backend, hello *api.HelloClientMessage, auth *talk.BackendClientAuthResponse) (*ClientSession, error) { +func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *session.SessionIdData, backend *talk.Backend, hello *api.HelloClientMessage, auth *talk.BackendClientAuthResponse) (*ClientSession, error) { ctx := log.NewLoggerContext(context.Background(), hub.logger) ctx, closeFunc := context.WithCancel(ctx) s := &ClientSession{ @@ -183,7 +184,7 @@ func (s *ClientSession) RoomSessionId() api.RoomSessionId { return s.roomSessionId } -func (s *ClientSession) Data() *SessionIdData { +func (s *ClientSession) Data() *session.SessionIdData { return s.data } diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index e242ca0..ec11ba7 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -48,7 +48,6 @@ import ( "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus/promhttp" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/client" @@ -57,6 +56,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/session" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" janusapi "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" @@ -147,7 +147,7 @@ type ProxyServer struct { trustedProxies atomic.Pointer[container.IPList] sid atomic.Uint64 - cookie *signaling.SessionIdCodec + cookie *session.SessionIdCodec sessionsLock sync.RWMutex // +checklocks:sessionsLock sessions map[uint64]*ProxySession @@ -242,7 +242,7 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * return nil, fmt.Errorf("could not generate random block key: %s", err) } - sessionIds, err := signaling.NewSessionIdCodec(hashKey, blockKey) + sessionIds, err := session.NewSessionIdCodec(hashKey, blockKey) if err != nil { return nil, fmt.Errorf("error creating session id codec: %w", err) } @@ -1507,7 +1507,7 @@ func (s *ProxyServer) NewSession(hello *proxy.HelloClientMessage) (*ProxySession sid = s.sid.Add(1) } - sessionIdData := &signaling.SessionIdData{ + sessionIdData := &session.SessionIdData{ Sid: sid, Created: time.Now().UnixMicro(), } diff --git a/hub.go b/hub.go index 61d43e8..090af33 100644 --- a/hub.go +++ b/hub.go @@ -62,6 +62,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/session" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -159,7 +160,7 @@ type Hub struct { logger log.Logger events events.AsyncEvents upgrader websocket.Upgrader - sessionIds *SessionIdCodec + sessionIds *session.SessionIdCodec info *api.WelcomeServerMessage infoInternal *api.WelcomeServerMessage welcome atomic.Value // *api.ServerMessage @@ -192,7 +193,7 @@ type Hub struct { // +checklocks:mu virtualSessions map[api.PublicSessionId]uint64 - decodeCaches []*container.LruCache[*SessionIdData] + decodeCaches []*container.LruCache[*session.SessionIdData] mcu sfu.SFU mcuTimeout time.Duration @@ -258,7 +259,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven return nil, fmt.Errorf("the sessions block key must be 16, 24 or 32 bytes but is %d bytes", len(blockKey)) } - sessionIds, err := NewSessionIdCodec([]byte(hashKey), blockBytes) + sessionIds, err := session.NewSessionIdCodec([]byte(hashKey), blockBytes) if err != nil { return nil, fmt.Errorf("error creating session id codec: %w", err) } @@ -320,9 +321,9 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } - decodeCaches := make([]*container.LruCache[*SessionIdData], 0, numDecodeCaches) + decodeCaches := make([]*container.LruCache[*session.SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) + decodeCaches = append(decodeCaches, container.NewLruCache[*session.SessionIdData](decodeCacheSize)) } roomSessions, err := NewBuiltinRoomSessions(rpcClients) @@ -632,7 +633,7 @@ func (h *Hub) Reload(ctx context.Context, config *goconf.ConfigFile) { h.rpcClients.Reload(config) } -func (h *Hub) getDecodeCache(cache_key string) *container.LruCache[*SessionIdData] { +func (h *Hub) getDecodeCache(cache_key string) *container.LruCache[*session.SessionIdData] { hash := fnv.New32a() // Make sure we don't have a temporary allocation for the string -> []byte conversion. hash.Write(unsafe.Slice(unsafe.StringData(cache_key), len(cache_key))) // nolint @@ -657,15 +658,15 @@ func (h *Hub) invalidateSessionId(id string) { cache.Remove(id) } -func (h *Hub) setDecodedPublicSessionId(id api.PublicSessionId, data *SessionIdData) { +func (h *Hub) setDecodedPublicSessionId(id api.PublicSessionId, data *session.SessionIdData) { h.setDecodedSessionId(string(id), data) } -func (h *Hub) setDecodedPrivateSessionId(id api.PrivateSessionId, data *SessionIdData) { +func (h *Hub) setDecodedPrivateSessionId(id api.PrivateSessionId, data *session.SessionIdData) { h.setDecodedSessionId(string(id), data) } -func (h *Hub) setDecodedSessionId(id string, data *SessionIdData) { +func (h *Hub) setDecodedSessionId(id string, data *session.SessionIdData) { if len(id) == 0 { return } @@ -674,7 +675,7 @@ func (h *Hub) setDecodedSessionId(id string, data *SessionIdData) { cache.Set(id, data) } -func (h *Hub) decodePrivateSessionId(id api.PrivateSessionId) *SessionIdData { +func (h *Hub) decodePrivateSessionId(id api.PrivateSessionId) *session.SessionIdData { if len(id) == 0 { return nil } @@ -694,7 +695,7 @@ func (h *Hub) decodePrivateSessionId(id api.PrivateSessionId) *SessionIdData { return data } -func (h *Hub) decodePublicSessionId(id api.PublicSessionId) *SessionIdData { +func (h *Hub) decodePublicSessionId(id api.PublicSessionId) *session.SessionIdData { if len(id) == 0 { return nil } @@ -950,12 +951,12 @@ func (h *Hub) unregisterRemoteSession(session *RemoteSession) { delete(h.remoteSessions, session) } -func (h *Hub) newSessionIdData(backend *talk.Backend) *SessionIdData { +func (h *Hub) newSessionIdData(backend *talk.Backend) *session.SessionIdData { sid := h.sid.Add(1) for sid == 0 { sid = h.sid.Add(1) } - sessionIdData := &SessionIdData{ + sessionIdData := &session.SessionIdData{ Sid: sid, Created: time.Now().UnixMicro(), BackendId: backend.Id(), diff --git a/hub_test.go b/hub_test.go index 8fce006..e9de42a 100644 --- a/hub_test.go +++ b/hub_test.go @@ -63,6 +63,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/session" sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -823,17 +824,17 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { require := require.New(b) - decodeCaches := make([]*container.LruCache[*SessionIdData], 0, numDecodeCaches) + decodeCaches := make([]*container.LruCache[*session.SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) + decodeCaches = append(decodeCaches, container.NewLruCache[*session.SessionIdData](decodeCacheSize)) } backend := talk.NewCompatBackend(nil) - data := &SessionIdData{ + data := &session.SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), BackendId: backend.Id(), } - codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + codec, err := session.NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) require.NoError(err) sid, err := codec.EncodePrivate(data) require.NoError(err, "could not create session id") @@ -850,17 +851,17 @@ func Benchmark_DecodePrivateSessionIdCached(b *testing.B) { func Benchmark_DecodePublicSessionIdCached(b *testing.B) { require := require.New(b) - decodeCaches := make([]*container.LruCache[*SessionIdData], 0, numDecodeCaches) + decodeCaches := make([]*container.LruCache[*session.SessionIdData], 0, numDecodeCaches) for range numDecodeCaches { - decodeCaches = append(decodeCaches, container.NewLruCache[*SessionIdData](decodeCacheSize)) + decodeCaches = append(decodeCaches, container.NewLruCache[*session.SessionIdData](decodeCacheSize)) } backend := talk.NewCompatBackend(nil) - data := &SessionIdData{ + data := &session.SessionIdData{ Sid: 1, Created: time.Now().UnixMicro(), BackendId: backend.Id(), } - codec, err := NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) + codec, err := session.NewSessionIdCodec([]byte("12345678901234567890123456789012"), []byte("09876543210987654321098765432109")) require.NoError(err) sid, err := codec.EncodePublic(data) require.NoError(err, "could not create session id") diff --git a/roomsessions_test.go b/roomsessions_test.go index 0a8d5b5..20d2122 100644 --- a/roomsessions_test.go +++ b/roomsessions_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/session" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -55,7 +56,7 @@ func (s *DummySession) ClientType() api.ClientType { return "" } -func (s *DummySession) Data() *SessionIdData { +func (s *DummySession) Data() *session.SessionIdData { return nil } diff --git a/session.go b/session.go index 606cccf..cf35bde 100644 --- a/session.go +++ b/session.go @@ -29,6 +29,7 @@ import ( "time" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/session" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -37,7 +38,7 @@ type Session interface { PrivateId() api.PrivateSessionId PublicId() api.PublicSessionId ClientType() api.ClientType - Data() *SessionIdData + Data() *session.SessionIdData UserId() string UserData() json.RawMessage diff --git a/session.pb.go b/session/session.pb.go similarity index 67% rename from session.pb.go rename to session/session.pb.go index a998ae3..0bf63ee 100644 --- a/session.pb.go +++ b/session/session.pb.go @@ -20,9 +20,9 @@ // along with this program. If not, see . // Code generated by protoc-gen-go. DO NOT EDIT. -// source: session.proto +// source: session/session.proto -package signaling +package session import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -50,7 +50,7 @@ type SessionIdData struct { func (x *SessionIdData) Reset() { *x = SessionIdData{} - mi := &file_session_proto_msgTypes[0] + mi := &file_session_session_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -62,7 +62,7 @@ func (x *SessionIdData) String() string { func (*SessionIdData) ProtoMessage() {} func (x *SessionIdData) ProtoReflect() protoreflect.Message { - mi := &file_session_proto_msgTypes[0] + mi := &file_session_session_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -75,7 +75,7 @@ func (x *SessionIdData) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionIdData.ProtoReflect.Descriptor instead. func (*SessionIdData) Descriptor() ([]byte, []int) { - return file_session_proto_rawDescGZIP(), []int{0} + return file_session_session_proto_rawDescGZIP(), []int{0} } func (x *SessionIdData) GetSid() uint64 { @@ -99,33 +99,33 @@ func (x *SessionIdData) GetBackendId() string { return "" } -var File_session_proto protoreflect.FileDescriptor +var File_session_session_proto protoreflect.FileDescriptor -const file_session_proto_rawDesc = "" + +const file_session_session_proto_rawDesc = "" + "\n" + - "\rsession.proto\x12\tsignaling\"Y\n" + + "\x15session/session.proto\x12\asession\"Y\n" + "\rSessionIdData\x12\x10\n" + "\x03Sid\x18\x01 \x01(\x04R\x03Sid\x12\x18\n" + "\aCreated\x18\x02 \x01(\x03R\aCreated\x12\x1c\n" + - "\tBackendId\x18\x03 \x01(\tR\tBackendIdB. */ -package signaling +package session import ( "crypto/aes" @@ -86,7 +86,7 @@ func (p *bytesPool) Put(b []byte) { // SessionIdCodec encodes and decodes session ids. // // Inspired by https://github.com/gorilla/securecookie -type SessionIdCodec struct { +type SessionIdCodec struct { // nolint hashKey []byte cipher cipher.Block diff --git a/sessionid_codec_test.go b/session/sessionid_codec_test.go similarity index 99% rename from sessionid_codec_test.go rename to session/sessionid_codec_test.go index 7504c27..f932bcf 100644 --- a/sessionid_codec_test.go +++ b/session/sessionid_codec_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 session import ( "testing" diff --git a/testclient_test.go b/testclient_test.go index 7fe62ef..9931c02 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -44,6 +44,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/session" ) var ( @@ -71,7 +72,7 @@ func getWebsocketUrl(url string) string { } } -func getPubliceSessionIdData(h *Hub, publicId api.PublicSessionId) *SessionIdData { +func getPubliceSessionIdData(h *Hub, publicId api.PublicSessionId) *session.SessionIdData { decodedPublic := h.decodePublicSessionId(publicId) if decodedPublic == nil { panic("invalid public session id") diff --git a/virtualsession.go b/virtualsession.go index 4a34810..817cb5c 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -34,6 +34,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/session" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -49,7 +50,7 @@ type VirtualSession struct { session *ClientSession privateId api.PrivateSessionId publicId api.PublicSessionId - data *SessionIdData + data *session.SessionIdData ctx context.Context closeFunc context.CancelFunc room atomic.Pointer[Room] @@ -70,7 +71,7 @@ func GetVirtualSessionId(session Session, sessionId api.PublicSessionId) api.Pub return session.PublicId() + "|" + sessionId } -func NewVirtualSession(session *ClientSession, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *SessionIdData, msg *api.AddSessionInternalClientMessage) (*VirtualSession, error) { +func NewVirtualSession(session *ClientSession, privateId api.PrivateSessionId, publicId api.PublicSessionId, data *session.SessionIdData, msg *api.AddSessionInternalClientMessage) (*VirtualSession, error) { ctx := log.NewLoggerContext(session.Context(), session.hub.logger) ctx, closeFunc := context.WithCancel(ctx) @@ -138,7 +139,7 @@ func (s *VirtualSession) SetInCall(inCall int) bool { return s.inCall.Set(uint32(inCall)) } -func (s *VirtualSession) Data() *SessionIdData { +func (s *VirtualSession) Data() *session.SessionIdData { return s.data } From 5eb0571d310428f339eef527f5e077d4e6543769 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 14:42:15 +0100 Subject: [PATCH 452/549] Move backend client to "talk" package. --- hub.go | 19 +++--- room_ping.go | 19 +++--- room_ping_test.go | 4 +- backend_client.go => talk/backend_client.go | 64 ++++++++++++++----- .../backend_client_stats_prometheus.go | 2 +- .../backend_client_test.go | 11 ++-- talk/capabilities.go | 4 -- 7 files changed, 76 insertions(+), 47 deletions(-) rename backend_client.go => talk/backend_client.go (81%) rename backend_client_stats_prometheus.go => talk/backend_client_stats_prometheus.go (99%) rename backend_client_test.go => talk/backend_client_test.go (97%) diff --git a/hub.go b/hub.go index 090af33..b490868 100644 --- a/hub.go +++ b/hub.go @@ -217,7 +217,7 @@ type Hub struct { federationClients map[*FederationClient]bool backendTimeout time.Duration - backend *BackendClient + backend *talk.BackendClient trustedProxies atomic.Pointer[container.IPList] geoip *geoip.Lookup @@ -274,7 +274,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven maxConcurrentRequestsPerHost = defaultMaxConcurrentRequestsPerHost } - backend, err := NewBackendClient(ctx, cfg, maxConcurrentRequestsPerHost, version, etcdClient) + backend, err := talk.NewBackendClient(ctx, cfg, maxConcurrentRequestsPerHost, version, etcdClient) if err != nil { return nil, err } @@ -331,7 +331,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven return nil, err } - roomPing, err := NewRoomPing(backend, backend.capabilities) + roomPing, err := NewRoomPing(backend) if err != nil { return nil, err } @@ -463,7 +463,10 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven Type: "welcome", Welcome: api.NewWelcomeServerMessage(version, api.DefaultWelcomeFeatures...), }) - backend.hub = hub + backend.SetFeaturesFunc(func() []string { + return hub.info.Features + }) + roomPing.hub = hub if rpcServer != nil { rpcServer.hub = hub } @@ -1403,7 +1406,7 @@ func (h *Hub) processHelloV2(ctx context.Context, client ClientWithSession, mess tokenString = message.Hello.Auth.HelloV2Params.Token tokenClaims = &api.HelloV2TokenClaims{} case api.HelloClientTypeFederation: - if !h.backend.capabilities.HasCapabilityFeature(ctx, url, talk.FeatureFederationV2) { + if !h.backend.HasCapabilityFeature(ctx, url, talk.FeatureFederationV2) { return nil, nil, ErrFederationNotSupported } @@ -1455,13 +1458,13 @@ func (h *Hub) processHelloV2(ctx context.Context, client ClientWithSession, mess backendCtx, cancel := context.WithTimeout(ctx, h.backendTimeout) defer cancel() - keyData, cached, found := h.backend.capabilities.GetStringConfig(backendCtx, url, talk.ConfigGroupSignaling, talk.ConfigKeyHelloV2TokenKey) + keyData, cached, found := h.backend.GetStringConfig(backendCtx, url, talk.ConfigGroupSignaling, talk.ConfigKeyHelloV2TokenKey) if !found { if cached { // The Nextcloud instance might just have enabled JWT but we probably use // the cached capabilities without the public key. Make sure to re-fetch. - h.backend.capabilities.InvalidateCapabilities(url) - keyData, _, found = h.backend.capabilities.GetStringConfig(backendCtx, url, talk.ConfigGroupSignaling, talk.ConfigKeyHelloV2TokenKey) + h.backend.InvalidateCapabilities(url) + keyData, _, found = h.backend.GetStringConfig(backendCtx, url, talk.ConfigGroupSignaling, talk.ConfigKeyHelloV2TokenKey) } if !found { return nil, errors.New("no key found for issuer") diff --git a/room_ping.go b/room_ping.go index 3b94c12..993fe0e 100644 --- a/room_ping.go +++ b/room_ping.go @@ -70,18 +70,17 @@ type RoomPing struct { mu sync.Mutex closer *internal.Closer - backend *BackendClient - capabilities *talk.Capabilities + hub *Hub + backend *talk.BackendClient // +checklocks:mu entries map[string]*pingEntries } -func NewRoomPing(backend *BackendClient, capabilities *talk.Capabilities) (*RoomPing, error) { +func NewRoomPing(backend *talk.BackendClient) (*RoomPing, error) { result := &RoomPing{ - closer: internal.NewCloser(), - backend: backend, - capabilities: capabilities, + closer: internal.NewCloser(), + backend: backend, } return result, nil @@ -121,7 +120,7 @@ func (p *RoomPing) publishEntries(ctx context.Context, entries *pingEntries, tim ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - limit, _, found := p.capabilities.GetIntegerConfig(ctx, entries.url, talk.ConfigGroupSignaling, talk.ConfigKeySessionPingLimit) + limit, _, found := p.backend.GetIntegerConfig(ctx, entries.url, talk.ConfigGroupSignaling, talk.ConfigKeySessionPingLimit) if !found || limit <= 0 { // Limit disabled while waiting for the next iteration, fallback to sending // one request per room. @@ -146,8 +145,8 @@ func (p *RoomPing) publishEntries(ctx context.Context, entries *pingEntries, tim func (p *RoomPing) publishActiveSessions(ctx context.Context) { var timeout time.Duration - if p.backend.hub != nil { - timeout = p.backend.hub.backendTimeout + if p.hub != nil { + timeout = p.hub.backendTimeout } else { // Running from tests. timeout = time.Second * time.Duration(defaultBackendTimeoutSeconds) @@ -185,7 +184,7 @@ func (p *RoomPing) sendPingsCombined(ctx context.Context, url *url.URL, entries } func (p *RoomPing) SendPings(ctx context.Context, roomId string, url *url.URL, entries []talk.BackendPingEntry) error { - limit, _, found := p.capabilities.GetIntegerConfig(ctx, url, talk.ConfigGroupSignaling, talk.ConfigKeySessionPingLimit) + limit, _, found := p.backend.GetIntegerConfig(ctx, url, talk.ConfigGroupSignaling, talk.ConfigKeySessionPingLimit) if !found || limit <= 0 { // Old-style Nextcloud or session limit not configured. Perform one request // per room. Don't queue to avoid sending all ping requests to old-style diff --git a/room_ping_test.go b/room_ping_test.go index e647d21..ae7adad 100644 --- a/room_ping_test.go +++ b/room_ping_test.go @@ -48,10 +48,10 @@ func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) config, err := getTestConfig(server) require.NoError(err) - backend, err := NewBackendClient(ctx, config, 1, "0.0", nil) + backend, err := talk.NewBackendClient(ctx, config, 1, "0.0", nil) require.NoError(err) - p, err := NewRoomPing(backend, backend.capabilities) + p, err := NewRoomPing(backend) require.NoError(err) u, err := url.Parse(server.URL + "/" + PathToOcsSignalingBackend) diff --git a/backend_client.go b/talk/backend_client.go similarity index 81% rename from backend_client.go rename to talk/backend_client.go index 8f33d3f..8ca63da 100644 --- a/backend_client.go +++ b/talk/backend_client.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" @@ -36,7 +36,6 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" - "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -50,19 +49,27 @@ func init() { RegisterBackendClientStats() } +type FeaturesFunc = func() []string + +var ( + emptyFeaturesFunc = func() []string { + return nil + } +) + type BackendClient struct { - hub *Hub version string - backends *talk.BackendConfiguration + features FeaturesFunc + backends *BackendConfiguration pool *pool.HttpClientPool - capabilities *talk.Capabilities + capabilities *Capabilities buffers pool.BufferPool } func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient etcd.Client) (*BackendClient, error) { logger := log.LoggerFromContext(ctx) - backends, err := talk.NewBackendConfiguration(logger, config, etcdClient) + backends, err := NewBackendConfiguration(logger, config, etcdClient) if err != nil { return nil, err } @@ -77,13 +84,14 @@ func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurr return nil, err } - capabilities, err := talk.NewCapabilities(version, pool) + capabilities, err := NewCapabilities(version, pool) if err != nil { return nil, err } return &BackendClient{ version: version, + features: emptyFeaturesFunc, backends: backends, pool: pool, @@ -99,15 +107,15 @@ func (b *BackendClient) Reload(config *goconf.ConfigFile) { b.backends.Reload(config) } -func (b *BackendClient) GetCompatBackend() *talk.Backend { +func (b *BackendClient) GetCompatBackend() *Backend { return b.backends.GetCompatBackend() } -func (b *BackendClient) GetBackend(u *url.URL) *talk.Backend { +func (b *BackendClient) GetBackend(u *url.URL) *Backend { return b.backends.GetBackend(u) } -func (b *BackendClient) GetBackends() []*talk.Backend { +func (b *BackendClient) GetBackends() []*Backend { return b.backends.GetBackends() } @@ -115,6 +123,30 @@ func (b *BackendClient) IsUrlAllowed(u *url.URL) bool { return b.backends.IsUrlAllowed(u) } +func (b *BackendClient) HasCapabilityFeature(ctx context.Context, u *url.URL, feature string) bool { + return b.capabilities.HasCapabilityFeature(ctx, u, feature) +} + +func (b *BackendClient) GetStringConfig(ctx context.Context, u *url.URL, group, key string) (string, bool, bool) { + return b.capabilities.GetStringConfig(ctx, u, group, key) +} + +func (b *BackendClient) GetIntegerConfig(ctx context.Context, u *url.URL, group, key string) (int, bool, bool) { + return b.capabilities.GetIntegerConfig(ctx, u, group, key) +} + +func (b *BackendClient) InvalidateCapabilities(u *url.URL) { + b.capabilities.InvalidateCapabilities(u) +} + +func (b *BackendClient) SetFeaturesFunc(f func() []string) { + if f == nil { + f = emptyFeaturesFunc + } + + b.features = f +} + // 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 { @@ -129,7 +161,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } var requestUrl *url.URL - if b.capabilities.HasCapabilityFeature(ctx, u, talk.FeatureSignalingV3Api) { + if b.capabilities.HasCapabilityFeature(ctx, u, 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/") @@ -161,12 +193,12 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ req.Header.Set("Accept", "application/json") req.Header.Set("OCS-APIRequest", "true") req.Header.Set("User-Agent", "nextcloud-spreed-signaling/"+b.version) - if b.hub != nil { - req.Header.Set("X-Spreed-Signaling-Features", strings.Join(b.hub.info.Features, ", ")) + if features := b.features(); len(features) > 0 { + req.Header.Set("X-Spreed-Signaling-Features", strings.Join(features, ", ")) } // Add checksum so the backend can validate the request. - talk.AddBackendChecksum(req, data.Bytes(), backend.Secret()) + AddBackendChecksum(req, data.Bytes(), backend.Secret()) start := time.Now() resp, err := c.Do(req) @@ -203,7 +235,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ defer b.buffers.Put(body) - if talk.IsOcsRequest(u) || req.Header.Get("OCS-APIRequest") != "" { + if 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: // { @@ -212,7 +244,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ // "data": { ... } // } // } - var ocs talk.OcsResponse + var ocs 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_stats_prometheus.go b/talk/backend_client_stats_prometheus.go similarity index 99% rename from backend_client_stats_prometheus.go rename to talk/backend_client_stats_prometheus.go index 26a6188..cab22c5 100644 --- a/backend_client_stats_prometheus.go +++ b/talk/backend_client_stats_prometheus.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 ( "github.com/prometheus/client_golang/prometheus" diff --git a/backend_client_test.go b/talk/backend_client_test.go similarity index 97% rename from backend_client_test.go rename to talk/backend_client_test.go index 25625cb..accf94c 100644 --- a/backend_client_test.go +++ b/talk/backend_client_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 ( "encoding/json" @@ -37,13 +37,12 @@ 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 := talk.OcsResponse{ - Ocs: &talk.OcsBody{ - Meta: talk.OcsMeta{ + response := OcsResponse{ + Ocs: &OcsBody{ + Meta: OcsMeta{ Status: "OK", StatusCode: http.StatusOK, Message: "OK", @@ -52,7 +51,7 @@ func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { }, } if strings.Contains(t.Name(), "Throttled") { - response.Ocs.Meta = talk.OcsMeta{ + response.Ocs.Meta = OcsMeta{ Status: "failure", StatusCode: 429, Message: "Reached maximum delay", diff --git a/talk/capabilities.go b/talk/capabilities.go index d629d8f..6a60493 100644 --- a/talk/capabilities.go +++ b/talk/capabilities.go @@ -59,10 +59,6 @@ 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 { From 9d321bb3aba50771dcc819349565494f23b830f7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 8 Jan 2026 14:49:49 +0100 Subject: [PATCH 453/549] Move server-related code to "server" package. --- .codecov.yml | 4 ++++ cmd/server/main.go | 10 +++++----- backend_server.go => server/backend_server.go | 2 +- .../backend_server_test.go | 2 +- clientsession.go => server/clientsession.go | 2 +- clientsession_test.go => server/clientsession_test.go | 2 +- federation.go => server/federation.go | 2 +- federation_test.go => server/federation_test.go | 2 +- grpc_client_test.go => server/grpc_client_test.go | 2 +- grpc_remote_client.go => server/grpc_remote_client.go | 2 +- grpc_server.go => server/grpc_server.go | 2 +- grpc_server_test.go => server/grpc_server_test.go | 2 +- .../grpc_stats_prometheus.go | 2 +- hub.go => server/hub.go | 2 +- hub_client.go => server/hub_client.go | 2 +- .../hub_client_stats_prometheus.go | 2 +- hub_sfu_janus_test.go => server/hub_sfu_janus_test.go | 2 +- hub_sfu_proxy_test.go => server/hub_sfu_proxy_test.go | 2 +- .../hub_stats_prometheus.go | 2 +- hub_test.go => server/hub_test.go | 2 +- .../hub_transient_data_test.go | 2 +- remotesession.go => server/remotesession.go | 2 +- room.go => server/room.go | 2 +- room_ping.go => server/room_ping.go | 2 +- room_ping_test.go => server/room_ping_test.go | 2 +- .../room_stats_prometheus.go | 2 +- room_test.go => server/room_test.go | 2 +- roomsessions.go => server/roomsessions.go | 2 +- .../roomsessions_builtin.go | 2 +- .../roomsessions_builtin_test.go | 2 +- roomsessions_test.go => server/roomsessions_test.go | 2 +- session.go => server/session.go | 2 +- session_test.go => server/session_test.go | 2 +- stats_prometheus.go => server/stats_prometheus.go | 2 +- testclient_test.go => server/testclient_test.go | 2 +- testutils_test.go => server/testutils_test.go | 2 +- virtualsession.go => server/virtualsession.go | 2 +- .../virtualsession_test.go | 2 +- 38 files changed, 45 insertions(+), 41 deletions(-) rename backend_server.go => server/backend_server.go (99%) rename backend_server_test.go => server/backend_server_test.go (99%) rename clientsession.go => server/clientsession.go (99%) rename clientsession_test.go => server/clientsession_test.go (99%) rename federation.go => server/federation.go (99%) rename federation_test.go => server/federation_test.go (99%) rename grpc_client_test.go => server/grpc_client_test.go (99%) rename grpc_remote_client.go => server/grpc_remote_client.go (99%) rename grpc_server.go => server/grpc_server.go (99%) rename grpc_server_test.go => server/grpc_server_test.go (99%) rename grpc_stats_prometheus.go => server/grpc_stats_prometheus.go (98%) rename hub.go => server/hub.go (99%) rename hub_client.go => server/hub_client.go (99%) rename hub_client_stats_prometheus.go => server/hub_client_stats_prometheus.go (98%) rename hub_sfu_janus_test.go => server/hub_sfu_janus_test.go (99%) rename hub_sfu_proxy_test.go => server/hub_sfu_proxy_test.go (99%) rename hub_stats_prometheus.go => server/hub_stats_prometheus.go (99%) rename hub_test.go => server/hub_test.go (99%) rename hub_transient_data_test.go => server/hub_transient_data_test.go (99%) rename remotesession.go => server/remotesession.go (99%) rename room.go => server/room.go (99%) rename room_ping.go => server/room_ping.go (99%) rename room_ping_test.go => server/room_ping_test.go (99%) rename room_stats_prometheus.go => server/room_stats_prometheus.go (98%) rename room_test.go => server/room_test.go (99%) rename roomsessions.go => server/roomsessions.go (98%) rename roomsessions_builtin.go => server/roomsessions_builtin.go (99%) rename roomsessions_builtin_test.go => server/roomsessions_builtin_test.go (98%) rename roomsessions_test.go => server/roomsessions_test.go (99%) rename session.go => server/session.go (99%) rename session_test.go => server/session_test.go (99%) rename stats_prometheus.go => server/stats_prometheus.go (98%) rename testclient_test.go => server/testclient_test.go (99%) rename testutils_test.go => server/testutils_test.go (99%) rename virtualsession.go => server/virtualsession.go (99%) rename virtualsession_test.go => server/virtualsession_test.go (99%) diff --git a/.codecov.yml b/.codecov.yml index 1e6a59b..0ab358c 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -100,6 +100,10 @@ component_management: name: security paths: - security/** + - component_id: module_server + name: server + paths: + - server/** - component_id: module_session name: session paths: diff --git a/cmd/server/main.go b/cmd/server/main.go index 70cafcb..8075bff 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -41,7 +41,6 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" - signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/dns" @@ -50,6 +49,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/server" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy" @@ -183,7 +183,7 @@ func main() { logger.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0)) - signaling.RegisterStats() + server.RegisterStats() natsUrl, _ := config.GetStringOptionWithEnv(cfg, "nats", "url") if natsUrl == "" { @@ -221,7 +221,7 @@ func main() { } }() - rpcServer, err := signaling.NewGrpcServer(stopCtx, cfg, version) + rpcServer, err := server.NewGrpcServer(stopCtx, cfg, version) if err != nil { logger.Fatalf("Could not create RPC server: %s", err) } @@ -239,7 +239,7 @@ func main() { defer rpcClients.Close() r := mux.NewRouter() - hub, err := signaling.NewHub(stopCtx, cfg, events, rpcServer, rpcClients, etcdClient, r, version) + hub, err := server.NewHub(stopCtx, cfg, events, rpcServer, rpcClients, etcdClient, r, version) if err != nil { logger.Fatal("Could not create hub: ", err) } @@ -324,7 +324,7 @@ func main() { go hub.Run() defer hub.Stop() - server, err := signaling.NewBackendServer(stopCtx, cfg, hub, version) + server, err := server.NewBackendServer(stopCtx, cfg, hub, version) if err != nil { logger.Fatal("Could not create backend server: ", err) } diff --git a/backend_server.go b/server/backend_server.go similarity index 99% rename from backend_server.go rename to server/backend_server.go index c550231..303cd39 100644 --- a/backend_server.go +++ b/server/backend_server.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 server import ( "context" diff --git a/backend_server_test.go b/server/backend_server_test.go similarity index 99% rename from backend_server_test.go rename to server/backend_server_test.go index c15c239..15582a3 100644 --- a/backend_server_test.go +++ b/server/backend_server_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 server import ( "bytes" diff --git a/clientsession.go b/server/clientsession.go similarity index 99% rename from clientsession.go rename to server/clientsession.go index 75696fe..276c48e 100644 --- a/clientsession.go +++ b/server/clientsession.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 server import ( "context" diff --git a/clientsession_test.go b/server/clientsession_test.go similarity index 99% rename from clientsession_test.go rename to server/clientsession_test.go index 49554b7..208cc5d 100644 --- a/clientsession_test.go +++ b/server/clientsession_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 server import ( "context" diff --git a/federation.go b/server/federation.go similarity index 99% rename from federation.go rename to server/federation.go index 1e04de5..c9be1e4 100644 --- a/federation.go +++ b/server/federation.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 server import ( "context" diff --git a/federation_test.go b/server/federation_test.go similarity index 99% rename from federation_test.go rename to server/federation_test.go index 515f49a..7861480 100644 --- a/federation_test.go +++ b/server/federation_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 server import ( "context" diff --git a/grpc_client_test.go b/server/grpc_client_test.go similarity index 99% rename from grpc_client_test.go rename to server/grpc_client_test.go index 34a3146..1e116f6 100644 --- a/grpc_client_test.go +++ b/server/grpc_client_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 server import ( "context" diff --git a/grpc_remote_client.go b/server/grpc_remote_client.go similarity index 99% rename from grpc_remote_client.go rename to server/grpc_remote_client.go index 0456d1a..94281e0 100644 --- a/grpc_remote_client.go +++ b/server/grpc_remote_client.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 server import ( "context" diff --git a/grpc_server.go b/server/grpc_server.go similarity index 99% rename from grpc_server.go rename to server/grpc_server.go index 1675193..d2012d8 100644 --- a/grpc_server.go +++ b/server/grpc_server.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 server import ( "context" diff --git a/grpc_server_test.go b/server/grpc_server_test.go similarity index 99% rename from grpc_server_test.go rename to server/grpc_server_test.go index 05bfb72..232cdf7 100644 --- a/grpc_server_test.go +++ b/server/grpc_server_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 server import ( "context" diff --git a/grpc_stats_prometheus.go b/server/grpc_stats_prometheus.go similarity index 98% rename from grpc_stats_prometheus.go rename to server/grpc_stats_prometheus.go index ff3e04c..c6897f6 100644 --- a/grpc_stats_prometheus.go +++ b/server/grpc_stats_prometheus.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 server import ( "github.com/prometheus/client_golang/prometheus" diff --git a/hub.go b/server/hub.go similarity index 99% rename from hub.go rename to server/hub.go index b490868..830a86b 100644 --- a/hub.go +++ b/server/hub.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 server import ( "bytes" diff --git a/hub_client.go b/server/hub_client.go similarity index 99% rename from hub_client.go rename to server/hub_client.go index ad32ac9..6cf596a 100644 --- a/hub_client.go +++ b/server/hub_client.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 server import ( "context" diff --git a/hub_client_stats_prometheus.go b/server/hub_client_stats_prometheus.go similarity index 98% rename from hub_client_stats_prometheus.go rename to server/hub_client_stats_prometheus.go index 034d9a4..85fec5c 100644 --- a/hub_client_stats_prometheus.go +++ b/server/hub_client_stats_prometheus.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 server import ( "github.com/prometheus/client_golang/prometheus" diff --git a/hub_sfu_janus_test.go b/server/hub_sfu_janus_test.go similarity index 99% rename from hub_sfu_janus_test.go rename to server/hub_sfu_janus_test.go index 803daa7..7bdc0aa 100644 --- a/hub_sfu_janus_test.go +++ b/server/hub_sfu_janus_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 server import ( "context" diff --git a/hub_sfu_proxy_test.go b/server/hub_sfu_proxy_test.go similarity index 99% rename from hub_sfu_proxy_test.go rename to server/hub_sfu_proxy_test.go index f21e78d..1ca36ed 100644 --- a/hub_sfu_proxy_test.go +++ b/server/hub_sfu_proxy_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 server import ( "context" diff --git a/hub_stats_prometheus.go b/server/hub_stats_prometheus.go similarity index 99% rename from hub_stats_prometheus.go rename to server/hub_stats_prometheus.go index 571ac2f..2297a10 100644 --- a/hub_stats_prometheus.go +++ b/server/hub_stats_prometheus.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 server import ( "github.com/prometheus/client_golang/prometheus" diff --git a/hub_test.go b/server/hub_test.go similarity index 99% rename from hub_test.go rename to server/hub_test.go index e9de42a..ba1f0a8 100644 --- a/hub_test.go +++ b/server/hub_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 server import ( "context" diff --git a/hub_transient_data_test.go b/server/hub_transient_data_test.go similarity index 99% rename from hub_transient_data_test.go rename to server/hub_transient_data_test.go index c350dc6..f029e2a 100644 --- a/hub_transient_data_test.go +++ b/server/hub_transient_data_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 server import ( "context" diff --git a/remotesession.go b/server/remotesession.go similarity index 99% rename from remotesession.go rename to server/remotesession.go index e1a40ac..92cbcdc 100644 --- a/remotesession.go +++ b/server/remotesession.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 server import ( "context" diff --git a/room.go b/server/room.go similarity index 99% rename from room.go rename to server/room.go index 268fbfa..8d39537 100644 --- a/room.go +++ b/server/room.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 server import ( "bytes" diff --git a/room_ping.go b/server/room_ping.go similarity index 99% rename from room_ping.go rename to server/room_ping.go index 993fe0e..536054b 100644 --- a/room_ping.go +++ b/server/room_ping.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 server import ( "context" diff --git a/room_ping_test.go b/server/room_ping_test.go similarity index 99% rename from room_ping_test.go rename to server/room_ping_test.go index ae7adad..a0b011b 100644 --- a/room_ping_test.go +++ b/server/room_ping_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 server import ( "context" diff --git a/room_stats_prometheus.go b/server/room_stats_prometheus.go similarity index 98% rename from room_stats_prometheus.go rename to server/room_stats_prometheus.go index 0e312d5..db96c3d 100644 --- a/room_stats_prometheus.go +++ b/server/room_stats_prometheus.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 server import ( "github.com/prometheus/client_golang/prometheus" diff --git a/room_test.go b/server/room_test.go similarity index 99% rename from room_test.go rename to server/room_test.go index 68a94f9..f88f94d 100644 --- a/room_test.go +++ b/server/room_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 server import ( "bytes" diff --git a/roomsessions.go b/server/roomsessions.go similarity index 98% rename from roomsessions.go rename to server/roomsessions.go index de7e76f..3211a35 100644 --- a/roomsessions.go +++ b/server/roomsessions.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 server import ( "context" diff --git a/roomsessions_builtin.go b/server/roomsessions_builtin.go similarity index 99% rename from roomsessions_builtin.go rename to server/roomsessions_builtin.go index 2a416e7..f69d9cf 100644 --- a/roomsessions_builtin.go +++ b/server/roomsessions_builtin.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 server import ( "context" diff --git a/roomsessions_builtin_test.go b/server/roomsessions_builtin_test.go similarity index 98% rename from roomsessions_builtin_test.go rename to server/roomsessions_builtin_test.go index 02a6162..1d84bf6 100644 --- a/roomsessions_builtin_test.go +++ b/server/roomsessions_builtin_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 server import ( "testing" diff --git a/roomsessions_test.go b/server/roomsessions_test.go similarity index 99% rename from roomsessions_test.go rename to server/roomsessions_test.go index 20d2122..b7f2b5c 100644 --- a/roomsessions_test.go +++ b/server/roomsessions_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 server import ( "context" diff --git a/session.go b/server/session.go similarity index 99% rename from session.go rename to server/session.go index cf35bde..5cc7eda 100644 --- a/session.go +++ b/server/session.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 server import ( "context" diff --git a/session_test.go b/server/session_test.go similarity index 99% rename from session_test.go rename to server/session_test.go index 046a86d..7f4aae7 100644 --- a/session_test.go +++ b/server/session_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 server import ( "testing" diff --git a/stats_prometheus.go b/server/stats_prometheus.go similarity index 98% rename from stats_prometheus.go rename to server/stats_prometheus.go index 7a8885a..e5e41e8 100644 --- a/stats_prometheus.go +++ b/server/stats_prometheus.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 server import ( "github.com/prometheus/client_golang/prometheus" diff --git a/testclient_test.go b/server/testclient_test.go similarity index 99% rename from testclient_test.go rename to server/testclient_test.go index 9931c02..453d8e7 100644 --- a/testclient_test.go +++ b/server/testclient_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 server import ( "context" diff --git a/testutils_test.go b/server/testutils_test.go similarity index 99% rename from testutils_test.go rename to server/testutils_test.go index 03cc519..62379f5 100644 --- a/testutils_test.go +++ b/server/testutils_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 server import ( "context" diff --git a/virtualsession.go b/server/virtualsession.go similarity index 99% rename from virtualsession.go rename to server/virtualsession.go index 817cb5c..776d4f7 100644 --- a/virtualsession.go +++ b/server/virtualsession.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 server import ( "context" diff --git a/virtualsession_test.go b/server/virtualsession_test.go similarity index 99% rename from virtualsession_test.go rename to server/virtualsession_test.go index de2c7a1..bb70a90 100644 --- a/virtualsession_test.go +++ b/server/virtualsession_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 server import ( "context" From be8353a54bbe13e1c27afc78076607bfcab06d7e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 9 Jan 2026 09:34:42 +0100 Subject: [PATCH 454/549] Move grpc server code to "grpc" package. --- cmd/server/main.go | 2 +- grpc/client_test.go | 109 +++ server/grpc_server.go => grpc/server.go | 214 ++--- .../server_stats_prometheus.go | 4 +- grpc/server_test.go | 853 ++++++++++++++++++ grpc/test/client_test.go | 56 ++ grpc/test/server.go | 72 ++ grpc/test/server_test.go | 51 ++ server/backend_server_test.go | 4 +- server/grpc_client_test.go | 157 ---- server/grpc_server_test.go | 314 ------- server/hub.go | 141 ++- server/hub_sfu_proxy_test.go | 125 ++- server/hub_test.go | 6 +- 14 files changed, 1444 insertions(+), 664 deletions(-) rename server/grpc_server.go => grpc/server.go (52%) rename server/grpc_stats_prometheus.go => grpc/server_stats_prometheus.go (96%) create mode 100644 grpc/server_test.go create mode 100644 grpc/test/client_test.go create mode 100644 grpc/test/server.go create mode 100644 grpc/test/server_test.go delete mode 100644 server/grpc_client_test.go delete mode 100644 server/grpc_server_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 8075bff..24a226b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -221,7 +221,7 @@ func main() { } }() - rpcServer, err := server.NewGrpcServer(stopCtx, cfg, version) + rpcServer, err := grpc.NewServer(stopCtx, cfg, version) if err != nil { logger.Fatalf("Could not create RPC server: %s", err) } diff --git a/grpc/client_test.go b/grpc/client_test.go index 331a2af..59ddcbe 100644 --- a/grpc/client_test.go +++ b/grpc/client_test.go @@ -172,3 +172,112 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { assert.True(clients[0].ip.Equal(ip1), "Expected IP %s, got %s", ip1, clients[0].ip) } } + +func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { + _, addr1 := NewServerForTest(t) + _, addr2 := NewServerForTest(t) + + embedEtcd := etcdtest.NewServerForTest(t) + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + client, _ := NewClientsWithEtcdForTest(t, embedEtcd, nil) + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + require.NoError(t, client.WaitForInitialized(ctx)) + + clients := client.GetClients() + assert.Len(t, clients, 2, "Expected two clients, got %+v", clients) + }) +} + +func Test_GrpcClients_EtcdUpdate(t *testing.T) { + t.Parallel() + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + client, _ := NewClientsWithEtcdForTest(t, embedEtcd, nil) + ch := client.GetWakeupChannelForTesting() + + ctx, cancel := context.WithTimeout(ctx, testTimeout) + defer cancel() + + assert.Empty(client.GetClients()) + + test.DrainWakeupChannel(ch) + _, addr1 := NewServerForTest(t) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + waitForEvent(ctx, t, ch) + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(addr1, clients[0].Target()) + } + + test.DrainWakeupChannel(ch) + _, addr2 := NewServerForTest(t) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + waitForEvent(ctx, t, ch) + if clients := client.GetClients(); assert.Len(clients, 2) { + assert.Equal(addr1, clients[0].Target()) + assert.Equal(addr2, clients[1].Target()) + } + + test.DrainWakeupChannel(ch) + embedEtcd.DeleteValue("/grpctargets/one") + waitForEvent(ctx, t, ch) + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(addr2, clients[0].Target()) + } + + test.DrainWakeupChannel(ch) + _, addr3 := NewServerForTest(t) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr3+"\"}")) + waitForEvent(ctx, t, ch) + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(addr3, clients[0].Target()) + } +} + +func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { + t.Parallel() + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + client, _ := NewClientsWithEtcdForTest(t, embedEtcd, nil) + ch := client.GetWakeupChannelForTesting() + + ctx, cancel := context.WithTimeout(ctx, testTimeout) + defer cancel() + + assert.Empty(client.GetClients()) + + test.DrainWakeupChannel(ch) + _, addr1 := NewServerForTest(t) + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + waitForEvent(ctx, t, ch) + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(addr1, clients[0].Target()) + } + + test.DrainWakeupChannel(ch) + server2, addr2 := NewServerForTest(t) + server2.serverId = ServerId + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + waitForEvent(ctx, t, ch) + client.WaitForSelfCheck() + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(addr1, clients[0].Target()) + } + + test.DrainWakeupChannel(ch) + embedEtcd.DeleteValue("/grpctargets/two") + waitForEvent(ctx, t, ch) + if clients := client.GetClients(); assert.Len(clients, 1) { + assert.Equal(addr1, clients[0].Target()) + } +} diff --git a/server/grpc_server.go b/grpc/server.go similarity index 52% rename from server/grpc_server.go rename to grpc/server.go index d2012d8..f1b2863 100644 --- a/server/grpc_server.go +++ b/grpc/server.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 server +package grpc import ( "context" @@ -37,35 +37,35 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/config" - rpc "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) -var ( - ErrNoProxyMcu = errors.New("no proxy mcu") -) - func init() { - RegisterGrpcServerStats() + RegisterServerStats() } -type GrpcServerHub interface { - GetSessionByResumeId(resumeId api.PrivateSessionId) Session - GetSessionByPublicId(sessionId api.PublicSessionId) Session +type ServerHub interface { + GetSessionIdByResumeId(resumeId api.PrivateSessionId) api.PublicSessionId GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) - GetRoomForBackend(roomId string, backend *talk.Backend) *Room + IsSessionIdInCall(sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, bool) + + DisconnectSessionByRoomSessionId(sessionId api.PublicSessionId, roomSessionId api.RoomSessionId, reason string) GetBackend(u *url.URL) *talk.Backend - CreateProxyToken(publisherId string) (string, error) + GetInternalSessions(roomId string, backend *talk.Backend) ([]*InternalSessionData, []*VirtualSessionData, bool) + GetTransientEntries(roomId string, backend *talk.Backend) (api.TransientDataEntries, bool) + GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*GetPublisherIdReply, error) + + ProxySession(request RpcSessions_ProxySessionServer) error } -type GrpcServer struct { - rpc.UnimplementedRpcBackendServer - rpc.UnimplementedRpcInternalServer - rpc.UnimplementedRpcMcuServer - rpc.UnimplementedRpcSessionsServer +type Server struct { + UnimplementedRpcBackendServer + UnimplementedRpcInternalServer + UnimplementedRpcMcuServer + UnimplementedRpcSessionsServer logger log.Logger version string @@ -74,10 +74,10 @@ type GrpcServer struct { listener net.Listener serverId string // can be overwritten from tests - hub GrpcServerHub + hub ServerHub } -func NewGrpcServer(ctx context.Context, cfg *goconf.ConfigFile, version string) (*GrpcServer, error) { +func NewServer(ctx context.Context, cfg *goconf.ConfigFile, version string) (*Server, error) { var listener net.Listener if addr, _ := config.GetStringOptionWithEnv(cfg, "grpc", "listen"); addr != "" { var err error @@ -88,28 +88,36 @@ func NewGrpcServer(ctx context.Context, cfg *goconf.ConfigFile, version string) } logger := log.LoggerFromContext(ctx) - creds, err := rpc.NewReloadableCredentials(logger, cfg, true) + creds, err := NewReloadableCredentials(logger, cfg, true) if err != nil { return nil, err } conn := grpc.NewServer(grpc.Creds(creds)) - result := &GrpcServer{ + result := &Server{ logger: logger, version: version, creds: creds, conn: conn, listener: listener, - serverId: rpc.ServerId, + serverId: ServerId, } - rpc.RegisterRpcBackendServer(conn, result) - rpc.RegisterRpcInternalServer(conn, result) - rpc.RegisterRpcSessionsServer(conn, result) - rpc.RegisterRpcMcuServer(conn, result) + RegisterRpcBackendServer(conn, result) + RegisterRpcInternalServer(conn, result) + RegisterRpcSessionsServer(conn, result) + RegisterRpcMcuServer(conn, result) return result, nil } -func (s *GrpcServer) Run() error { +func (s *Server) SetHub(hub ServerHub) { + s.hub = hub +} + +func (s *Server) SetServerId(serverId string) { + s.serverId = serverId +} + +func (s *Server) Run() error { if s.listener == nil { return nil } @@ -121,28 +129,32 @@ type SimpleCloser interface { Close() } -func (s *GrpcServer) Close() { +func (s *Server) Close() { s.conn.GracefulStop() if cr, ok := s.creds.(SimpleCloser); ok { cr.Close() } } -func (s *GrpcServer) LookupResumeId(ctx context.Context, request *rpc.LookupResumeIdRequest) (*rpc.LookupResumeIdReply, error) { +func (s *Server) CloseUnclean() { + s.conn.Stop() +} + +func (s *Server) LookupResumeId(ctx context.Context, request *LookupResumeIdRequest) (*LookupResumeIdReply, error) { statsGrpcServerCalls.WithLabelValues("LookupResumeId").Inc() // TODO: Remove debug logging s.logger.Printf("Lookup session for resume id %s", request.ResumeId) - session := s.hub.GetSessionByResumeId(api.PrivateSessionId(request.ResumeId)) - if session == nil { + sessionId := s.hub.GetSessionIdByResumeId(api.PrivateSessionId(request.ResumeId)) + if sessionId == "" { return nil, status.Error(codes.NotFound, "no such room session id") } - return &rpc.LookupResumeIdReply{ - SessionId: string(session.PublicId()), + return &LookupResumeIdReply{ + SessionId: string(sessionId), }, nil } -func (s *GrpcServer) LookupSessionId(ctx context.Context, request *rpc.LookupSessionIdRequest) (*rpc.LookupSessionIdReply, error) { +func (s *Server) LookupSessionId(ctx context.Context, request *LookupSessionIdRequest) (*LookupSessionIdReply, error) { statsGrpcServerCalls.WithLabelValues("LookupSessionId").Inc() // TODO: Remove debug logging s.logger.Printf("Lookup session id for room session id %s", request.RoomSessionId) @@ -154,45 +166,30 @@ func (s *GrpcServer) LookupSessionId(ctx context.Context, request *rpc.LookupSes } if sid != "" && request.DisconnectReason != "" { - if session := s.hub.GetSessionByPublicId(api.PublicSessionId(sid)); session != nil { - s.logger.Printf("Closing session %s because same room session %s connected", session.PublicId(), request.RoomSessionId) - session.LeaveRoom(false) - switch sess := session.(type) { - case *ClientSession: - if client := sess.GetClient(); client != nil { - client.SendByeResponseWithReason(nil, "room_session_reconnected") - } - } - session.Close() - } + s.hub.DisconnectSessionByRoomSessionId(sid, api.RoomSessionId(request.RoomSessionId), request.DisconnectReason) } - return &rpc.LookupSessionIdReply{ + return &LookupSessionIdReply{ SessionId: string(sid), }, nil } -func (s *GrpcServer) IsSessionInCall(ctx context.Context, request *rpc.IsSessionInCallRequest) (*rpc.IsSessionInCallReply, error) { +func (s *Server) IsSessionInCall(ctx context.Context, request *IsSessionInCallRequest) (*IsSessionInCallReply, error) { statsGrpcServerCalls.WithLabelValues("IsSessionInCall").Inc() // TODO: Remove debug logging s.logger.Printf("Check if session %s is in call %s on %s", request.SessionId, request.RoomId, request.BackendUrl) - session := s.hub.GetSessionByPublicId(api.PublicSessionId(request.SessionId)) - if session == nil { + + found, inCall := s.hub.IsSessionIdInCall(api.PublicSessionId(request.SessionId), request.GetRoomId(), request.GetBackendUrl()) + if !found { return nil, status.Error(codes.NotFound, "no such session id") } - result := &rpc.IsSessionInCallReply{} - room := session.GetRoom() - if room == nil || room.Id() != request.GetRoomId() || !room.Backend().HasUrl(request.GetBackendUrl()) || - (session.ClientType() != api.HelloClientTypeInternal && !room.IsSessionInCall(session)) { - // Recipient is not in a room, a different room or not in the call. - result.InCall = false - } else { - result.InCall = true + result := &IsSessionInCallReply{ + InCall: inCall, } return result, nil } -func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *rpc.GetInternalSessionsRequest) (*rpc.GetInternalSessionsReply, error) { +func (s *Server) GetInternalSessions(ctx context.Context, request *GetInternalSessionsRequest) (*GetInternalSessionsReply, error) { statsGrpcServerCalls.WithLabelValues("GetInternalSessions").Inc() // TODO: Remove debug logging s.logger.Printf("Get internal sessions from %s on %v (fallback %s)", request.RoomId, request.BackendUrls, request.BackendUrl) // nolint @@ -207,7 +204,7 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *rpc.GetIn backendUrls = []string{""} } - result := &rpc.GetInternalSessionsReply{} + result := &GetInternalSessionsReply{} processed := make(map[string]bool) for _, bu := range backendUrls { var parsed *url.URL @@ -230,81 +227,35 @@ func (s *GrpcServer) GetInternalSessions(ctx context.Context, request *rpc.GetIn } processed[backend.Id()] = true - room := s.hub.GetRoomForBackend(request.RoomId, backend) - if room == nil { + internalSessions, virtualSessions, found := s.hub.GetInternalSessions(request.RoomId, backend) + if !found { return nil, status.Error(codes.NotFound, "no such room") } - room.mu.RLock() - defer room.mu.RUnlock() - - for session := range room.internalSessions { - result.InternalSessions = append(result.InternalSessions, &rpc.InternalSessionData{ - SessionId: string(session.PublicId()), - InCall: uint32(session.GetInCall()), - Features: session.GetFeatures(), - }) - } - - for session := range room.virtualSessions { - result.VirtualSessions = append(result.VirtualSessions, &rpc.VirtualSessionData{ - SessionId: string(session.PublicId()), - InCall: uint32(session.GetInCall()), - }) - } + result.InternalSessions = append(result.InternalSessions, internalSessions...) + result.VirtualSessions = append(result.VirtualSessions, virtualSessions...) } return result, nil } -func (s *GrpcServer) GetPublisherId(ctx context.Context, request *rpc.GetPublisherIdRequest) (*rpc.GetPublisherIdReply, error) { +func (s *Server) GetPublisherId(ctx context.Context, request *GetPublisherIdRequest) (*GetPublisherIdReply, error) { statsGrpcServerCalls.WithLabelValues("GetPublisherId").Inc() // TODO: Remove debug logging s.logger.Printf("Get %s publisher id for session %s", request.StreamType, request.SessionId) - session := s.hub.GetSessionByPublicId(api.PublicSessionId(request.SessionId)) - if session == nil { - return nil, status.Error(codes.NotFound, "no such session") - } - clientSession, ok := session.(*ClientSession) - if !ok { - return nil, status.Error(codes.NotFound, "no such session") - } - - publisher := clientSession.GetOrWaitForPublisher(ctx, sfu.StreamType(request.StreamType)) - if publisher, ok := publisher.(sfu.PublisherWithConnectionUrlAndIP); ok { - connUrl, ip := publisher.GetConnectionURL() - reply := &rpc.GetPublisherIdReply{ - PublisherId: publisher.Id(), - ProxyUrl: connUrl, - } - if len(ip) > 0 { - reply.Ip = ip.String() - } - var err error - if reply.ConnectToken, err = s.hub.CreateProxyToken(""); err != nil && !errors.Is(err, ErrNoProxyMcu) { - s.logger.Printf("Error creating proxy token for connection: %s", err) - return nil, status.Error(codes.Internal, "error creating proxy connect token") - } - if reply.PublisherToken, err = s.hub.CreateProxyToken(publisher.Id()); err != nil && !errors.Is(err, ErrNoProxyMcu) { - s.logger.Printf("Error creating proxy token for publisher %s: %s", publisher.Id(), err) - return nil, status.Error(codes.Internal, "error creating proxy publisher token") - } - return reply, nil - } - - return nil, status.Error(codes.NotFound, "no such publisher") + return s.hub.GetPublisherIdForSessionId(ctx, api.PublicSessionId(request.SessionId), sfu.StreamType(request.StreamType)) } -func (s *GrpcServer) GetServerId(ctx context.Context, request *rpc.GetServerIdRequest) (*rpc.GetServerIdReply, error) { +func (s *Server) GetServerId(ctx context.Context, request *GetServerIdRequest) (*GetServerIdReply, error) { statsGrpcServerCalls.WithLabelValues("GetServerId").Inc() - return &rpc.GetServerIdReply{ + return &GetServerIdReply{ ServerId: s.serverId, Version: s.version, }, nil } -func (s *GrpcServer) GetTransientData(ctx context.Context, request *rpc.GetTransientDataRequest) (*rpc.GetTransientDataReply, error) { +func (s *Server) GetTransientData(ctx context.Context, request *GetTransientDataRequest) (*GetTransientDataReply, error) { statsGrpcServerCalls.WithLabelValues("GetTransientData").Inc() backendUrls := request.BackendUrls @@ -313,7 +264,7 @@ func (s *GrpcServer) GetTransientData(ctx context.Context, request *rpc.GetTrans backendUrls = []string{""} } - result := &rpc.GetTransientDataReply{} + result := &GetTransientDataReply{} processed := make(map[string]bool) for _, bu := range backendUrls { var parsed *url.URL @@ -336,21 +287,18 @@ func (s *GrpcServer) GetTransientData(ctx context.Context, request *rpc.GetTrans } processed[backend.Id()] = true - room := s.hub.GetRoomForBackend(request.RoomId, backend) - if room == nil { + entries, found := s.hub.GetTransientEntries(request.RoomId, backend) + if !found { return nil, status.Error(codes.NotFound, "no such room") - } - - entries := room.transientData.GetEntries() - if len(entries) == 0 { + } else if len(entries) == 0 { return nil, status.Error(codes.NotFound, "room has no transient data") } if result.Entries == nil { - result.Entries = make(map[string]*rpc.GrpcTransientDataEntry) + result.Entries = make(map[string]*GrpcTransientDataEntry) } for k, v := range entries { - e := &rpc.GrpcTransientDataEntry{} + e := &GrpcTransientDataEntry{} var err error if e.Value, err = json.Marshal(v.Value); err != nil { return nil, status.Errorf(codes.Internal, "error marshalling data: %s", err) @@ -365,7 +313,7 @@ func (s *GrpcServer) GetTransientData(ctx context.Context, request *rpc.GetTrans return result, nil } -func (s *GrpcServer) GetSessionCount(ctx context.Context, request *rpc.GetSessionCountRequest) (*rpc.GetSessionCountReply, error) { +func (s *Server) GetSessionCount(ctx context.Context, request *GetSessionCountRequest) (*GetSessionCountReply, error) { statsGrpcServerCalls.WithLabelValues("SessionCount").Inc() u, err := url.Parse(request.Url) @@ -378,25 +326,13 @@ func (s *GrpcServer) GetSessionCount(ctx context.Context, request *rpc.GetSessio return nil, status.Error(codes.NotFound, "no such backend") } - return &rpc.GetSessionCountReply{ + return &GetSessionCountReply{ Count: uint32(backend.Len()), }, nil } -func (s *GrpcServer) ProxySession(request rpc.RpcSessions_ProxySessionServer) error { +func (s *Server) ProxySession(request RpcSessions_ProxySessionServer) error { statsGrpcServerCalls.WithLabelValues("ProxySession").Inc() - hub, ok := s.hub.(*Hub) - if !ok { - return status.Error(codes.Internal, "invalid hub type") - } - client, err := newRemoteGrpcClient(hub, request) - if err != nil { - return err - } - - sid := hub.registerClient(client) - defer hub.unregisterClient(sid) - - return client.run() + return s.hub.ProxySession(request) } diff --git a/server/grpc_stats_prometheus.go b/grpc/server_stats_prometheus.go similarity index 96% rename from server/grpc_stats_prometheus.go rename to grpc/server_stats_prometheus.go index c6897f6..e04b0e6 100644 --- a/server/grpc_stats_prometheus.go +++ b/grpc/server_stats_prometheus.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 server +package grpc import ( "github.com/prometheus/client_golang/prometheus" @@ -40,6 +40,6 @@ var ( } ) -func RegisterGrpcServerStats() { +func RegisterServerStats() { metrics.RegisterAll(grpcServerStats...) } diff --git a/grpc/server_test.go b/grpc/server_test.go new file mode 100644 index 0000000..aa27e52 --- /dev/null +++ b/grpc/server_test.go @@ -0,0 +1,853 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2022 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 grpc + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "net" + "net/url" + "path" + "strconv" + "sync/atomic" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + status "google.golang.org/grpc/status" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/test" +) + +type CertificateReloadWaiter interface { + WaitForCertificateReload(ctx context.Context, counter uint64) error +} + +func (s *Server) WaitForCertificateReload(ctx context.Context, counter uint64) error { + c, ok := s.creds.(CertificateReloadWaiter) + if !ok { + return errors.New("no reloadable credentials found") + } + + return c.WaitForCertificateReload(ctx, counter) +} + +type CertPoolReloadWaiter interface { + WaitForCertPoolReload(ctx context.Context, counter uint64) error +} + +func (s *Server) WaitForCertPoolReload(ctx context.Context, counter uint64) error { + c, ok := s.creds.(CertPoolReloadWaiter) + if !ok { + return errors.New("no reloadable credentials found") + } + + return c.WaitForCertPoolReload(ctx, counter) +} + +func NewServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *Server, addr string) { + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + for port := 50000; port < 50100; port++ { + addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + config.AddOption("grpc", "listen", addr) + var err error + server, err = NewServer(ctx, config, "0.0.0") + if test.IsErrorAddressAlreadyInUse(err) { + continue + } + + require.NoError(t, err) + break + } + + require.NotNil(t, server, "could not find free port") + + // Don't match with own server id by default. + server.SetServerId("dont-match") + + go func() { + assert.NoError(t, server.Run(), "could not start GRPC server") + }() + + t.Cleanup(func() { + server.Close() + }) + return server, addr +} + +func NewServerForTest(t *testing.T) (server *Server, addr string) { + config := goconf.NewConfigFile() + return NewServerForTestWithConfig(t, config) +} + +func TestServer_ReloadCerts(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + key, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + org1 := "Testing certificate" + cert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, key) + + dir := t.TempDir() + privkeyFile := path.Join(dir, "privkey.pem") + pubkeyFile := path.Join(dir, "pubkey.pem") + certFile := path.Join(dir, "cert.pem") + require.NoError(internal.WritePrivateKey(key, privkeyFile)) + require.NoError(internal.WritePublicKey(&key.PublicKey, pubkeyFile)) + require.NoError(internal.WriteCertificate(cert1, certFile)) + + config := goconf.NewConfigFile() + config.AddOption("grpc", "servercertificate", certFile) + config.AddOption("grpc", "serverkey", privkeyFile) + + server, addr := NewServerForTestWithConfig(t, config) + + cp1 := x509.NewCertPool() + cp1.AddCert(cert1) + + cfg1 := &tls.Config{ + RootCAs: cp1, + } + conn1, err := tls.Dial("tcp", addr, cfg1) + require.NoError(err) + defer conn1.Close() // nolint + state1 := conn1.ConnectionState() + if certs := state1.PeerCertificates; assert.NotEmpty(certs) { + if assert.NotEmpty(certs[0].Subject.Organization) { + assert.Equal(org1, certs[0].Subject.Organization[0]) + } + } + + org2 := "Updated certificate" + cert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, key) + internal.ReplaceCertificate(t, certFile, cert2) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(server.WaitForCertificateReload(ctx, 0)) + + cp2 := x509.NewCertPool() + cp2.AddCert(cert2) + + cfg2 := &tls.Config{ + RootCAs: cp2, + } + conn2, err := tls.Dial("tcp", addr, cfg2) + require.NoError(err) + defer conn2.Close() // nolint + state2 := conn2.ConnectionState() + if certs := state2.PeerCertificates; assert.NotEmpty(certs) { + if assert.NotEmpty(certs[0].Subject.Organization) { + assert.Equal(org2, certs[0].Subject.Organization[0]) + } + } +} + +func TestServer_ReloadCA(t *testing.T) { + t.Parallel() + logger := log.NewLoggerForTest(t) + require := require.New(t) + serverKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + clientKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) + org1 := "Testing client" + clientCert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, clientKey) + + dir := t.TempDir() + privkeyFile := path.Join(dir, "privkey.pem") + pubkeyFile := path.Join(dir, "pubkey.pem") + certFile := path.Join(dir, "cert.pem") + caFile := path.Join(dir, "ca.pem") + require.NoError(internal.WritePrivateKey(serverKey, privkeyFile)) + require.NoError(internal.WritePublicKey(&serverKey.PublicKey, pubkeyFile)) + require.NoError(internal.WriteCertificate(serverCert, certFile)) + require.NoError(internal.WriteCertificate(clientCert1, caFile)) + + config := goconf.NewConfigFile() + config.AddOption("grpc", "servercertificate", certFile) + config.AddOption("grpc", "serverkey", privkeyFile) + config.AddOption("grpc", "clientca", caFile) + + server, addr := NewServerForTestWithConfig(t, config) + + pool := x509.NewCertPool() + pool.AddCert(serverCert) + + pair1, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCert1.Raw, + }), pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(clientKey), + })) + require.NoError(err) + + cfg1 := &tls.Config{ + RootCAs: pool, + Certificates: []tls.Certificate{pair1}, + } + client1, err := NewClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg1))) + require.NoError(err) + defer client1.Close() // nolint + + ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second) + defer cancel1() + + _, _, err = client1.GetServerId(ctx1) + require.NoError(err) + + org2 := "Updated client" + clientCert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, clientKey) + internal.ReplaceCertificate(t, caFile, clientCert2) + + require.NoError(server.WaitForCertPoolReload(ctx1, 0)) + + pair2, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCert2.Raw, + }), pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(clientKey), + })) + require.NoError(err) + + cfg2 := &tls.Config{ + RootCAs: pool, + Certificates: []tls.Certificate{pair2}, + } + client2, err := NewClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg2))) + require.NoError(err) + defer client2.Close() // nolint + + ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second) + defer cancel2() + + // This will fail if the CA certificate has not been reloaded by the server. + _, _, err = client2.GetServerId(ctx2) + require.NoError(err) +} + +func TestClients_Encryption(t *testing.T) { // nolint:paralleltest + test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { + require := require.New(t) + serverKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + clientKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) + clientCert := internal.GenerateSelfSignedCertificateForTesting(t, "Testing client", clientKey) + + dir := t.TempDir() + serverPrivkeyFile := path.Join(dir, "server-privkey.pem") + serverPubkeyFile := path.Join(dir, "server-pubkey.pem") + serverCertFile := path.Join(dir, "server-cert.pem") + require.NoError(internal.WritePrivateKey(serverKey, serverPrivkeyFile)) + require.NoError(internal.WritePublicKey(&serverKey.PublicKey, serverPubkeyFile)) + require.NoError(internal.WriteCertificate(serverCert, serverCertFile)) + clientPrivkeyFile := path.Join(dir, "client-privkey.pem") + clientPubkeyFile := path.Join(dir, "client-pubkey.pem") + clientCertFile := path.Join(dir, "client-cert.pem") + require.NoError(internal.WritePrivateKey(clientKey, clientPrivkeyFile)) + require.NoError(internal.WritePublicKey(&clientKey.PublicKey, clientPubkeyFile)) + require.NoError(internal.WriteCertificate(clientCert, clientCertFile)) + + serverConfig := goconf.NewConfigFile() + serverConfig.AddOption("grpc", "servercertificate", serverCertFile) + serverConfig.AddOption("grpc", "serverkey", serverPrivkeyFile) + serverConfig.AddOption("grpc", "clientca", clientCertFile) + _, addr := NewServerForTestWithConfig(t, serverConfig) + + clientConfig := goconf.NewConfigFile() + clientConfig.AddOption("grpc", "targets", addr) + clientConfig.AddOption("grpc", "clientcertificate", clientCertFile) + clientConfig.AddOption("grpc", "clientkey", clientPrivkeyFile) + clientConfig.AddOption("grpc", "serverca", serverCertFile) + clients, _ := NewClientsForTestWithConfig(t, clientConfig, nil, nil) + + ctx, cancel1 := context.WithTimeout(context.Background(), time.Second) + defer cancel1() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + _, _, err := client.GetServerId(ctx) + require.NoError(err) + } + }) +} + +type disconnectInfo struct { + sessionId api.PublicSessionId + roomSessionId api.RoomSessionId + reason string +} + +type testServerHub struct { + t *testing.T + backend *talk.Backend + + disconnected atomic.Pointer[disconnectInfo] +} + +func newTestServerHub(t *testing.T) *testServerHub { + t.Helper() + logger := log.NewLoggerForTest(t) + + cfg := goconf.NewConfigFile() + cfg.AddOption(testBackendId, "secret", "not-so-secret") + cfg.AddOption(testBackendId, "sessionlimit", "10") + backend, err := talk.NewBackendFromConfig(logger, testBackendId, cfg, "foo") + require.NoError(t, err) + + u, err := url.Parse(testBackendUrl) + require.NoError(t, err) + backend.AddUrl(u) + + return &testServerHub{ + t: t, + backend: backend, + } +} + +const ( + testResumeId = "test-resume-id" + testSessionId = "test-session-id" + testRoomSessionId = "test-room-session-id" + testInternalSessionId = "test-internal-session-id" + testVirtualSessionId = "test-virtual-session-id" + testInternalInCallFlags = 2 + testVirtualInCallFlags = 3 + testBackendId = "backend-1" + testBackendUrl = "https://server.domain.invalid" + testRoomId = "test-room-id" + testStreamType = sfu.StreamTypeVideo + testProxyUrl = "https://proxy.domain.invalid" + testIp = "1.2.3.4" + testConnectToken = "test-connection-token" + testPublisherToken = "test-publisher-token" + testAddr = "2.3.4.5" + testCountry = geoip.Country("DE") + testAgent = "test-agent" +) + +var ( + testFeatures = []string{"bar", "foo"} + testExpires = time.Now().Add(time.Minute).Truncate(time.Millisecond) + testMessage = []byte("hello world!") +) + +func (h *testServerHub) GetSessionIdByResumeId(resumeId api.PrivateSessionId) api.PublicSessionId { + if resumeId == testResumeId { + return testSessionId + } + + return "" +} + +func (h *testServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { + if roomSessionId == testRoomSessionId { + return testSessionId, nil + } + + return "", ErrNoSuchRoomSession +} + +func (h *testServerHub) IsSessionIdInCall(sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, bool) { + if roomId == testRoomId && backendUrl == testBackendUrl { + return sessionId == testSessionId, true + } + + return false, false +} + +func (h *testServerHub) DisconnectSessionByRoomSessionId(sessionId api.PublicSessionId, roomSessionId api.RoomSessionId, reason string) { + h.t.Helper() + prev := h.disconnected.Swap(&disconnectInfo{ + sessionId: sessionId, + roomSessionId: roomSessionId, + reason: reason, + }) + assert.Nil(h.t, prev, "duplicate call") +} + +func (h *testServerHub) GetBackend(u *url.URL) *talk.Backend { + if u == nil { + // No compat backend. + return nil + } else if u.String() == testBackendUrl { + return h.backend + } + + return nil +} + +func (h *testServerHub) GetInternalSessions(roomId string, backend *talk.Backend) ([]*InternalSessionData, []*VirtualSessionData, bool) { + if roomId == testRoomId && backend == h.backend { + return []*InternalSessionData{ + { + SessionId: testInternalSessionId, + InCall: testInternalInCallFlags, + Features: testFeatures, + }, + }, []*VirtualSessionData{ + { + SessionId: testVirtualSessionId, + InCall: testVirtualInCallFlags, + }, + }, true + } + + return nil, nil, false +} + +func (h *testServerHub) GetTransientEntries(roomId string, backend *talk.Backend) (api.TransientDataEntries, bool) { + if roomId == testRoomId && backend == h.backend { + return api.TransientDataEntries{ + "foo": api.NewTransientDataEntryWithExpires("bar", testExpires), + "bar": api.NewTransientDataEntry(123, 0), + }, true + } + + return nil, false +} + +func (h *testServerHub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*GetPublisherIdReply, error) { + if sessionId == testSessionId { + if streamType != testStreamType { + return nil, status.Error(codes.NotFound, "no such publisher") + } + + return &GetPublisherIdReply{ + PublisherId: testSessionId, + ProxyUrl: testProxyUrl, + Ip: testIp, + ConnectToken: testConnectToken, + PublisherToken: testPublisherToken, + }, nil + } + + return nil, status.Error(codes.NotFound, "no such session") +} + +func getMetadata(t *testing.T, md metadata.MD, key string) string { + t.Helper() + if values := md.Get(key); len(values) > 0 { + return values[0] + } + + return "" +} + +func (h *testServerHub) ProxySession(request RpcSessions_ProxySessionServer) error { + h.t.Helper() + if md, found := metadata.FromIncomingContext(request.Context()); assert.True(h.t, found) { + if getMetadata(h.t, md, "sessionId") != testSessionId { + return status.Error(codes.InvalidArgument, "unknown session id") + } + + assert.Equal(h.t, testSessionId, getMetadata(h.t, md, "sessionId")) + assert.Equal(h.t, testAddr, getMetadata(h.t, md, "remoteAddr")) + assert.EqualValues(h.t, testCountry, getMetadata(h.t, md, "country")) + assert.Equal(h.t, testAgent, getMetadata(h.t, md, "userAgent")) + } + + assert.NoError(h.t, request.Send(&ServerSessionMessage{ + Message: testMessage, + })) + + return nil +} + +func TestServer_GetSessionIdByResumeId(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + reply, err := client.LookupResumeId(ctx, "") + assert.ErrorIs(err, ErrNoSuchResumeId, "expected unknown resume id, got %s", reply.GetSessionId()) + + reply, err = client.LookupResumeId(ctx, testResumeId+"1") + assert.ErrorIs(err, ErrNoSuchResumeId, "expected unknown resume id, got %s", reply.GetSessionId()) + + if reply, err := client.LookupResumeId(ctx, testResumeId); assert.NoError(err) { + assert.Equal(testSessionId, reply.SessionId) + } + } +} + +func TestServer_LookupSessionId(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + sessionId, err := client.LookupSessionId(ctx, "", "") + assert.ErrorIs(err, ErrNoSuchRoomSession, "expected unknown room session id, got %s", sessionId) + + sessionId, err = client.LookupSessionId(ctx, testRoomSessionId+"1", "") + assert.ErrorIs(err, ErrNoSuchRoomSession, "expected unknown room session id, got %s", sessionId) + + if sessionId, err := client.LookupSessionId(ctx, testRoomSessionId, "test-reason"); assert.NoError(err) { + assert.EqualValues(testSessionId, sessionId) + } + } + + if disconnected := hub.disconnected.Load(); assert.NotNil(disconnected, "session was not disconnected") { + assert.EqualValues(testSessionId, disconnected.sessionId) + assert.EqualValues(testRoomSessionId, disconnected.roomSessionId) + assert.Equal("test-reason", disconnected.reason) + } +} + +func TestServer_IsSessionInCall(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + if inCall, err := client.IsSessionInCall(ctx, testSessionId, testRoomId+"1", testBackendUrl); assert.NoError(err) { + assert.False(inCall) + } + if inCall, err := client.IsSessionInCall(ctx, testSessionId, testRoomId, testBackendUrl+"1"); assert.NoError(err) { + assert.False(inCall) + } + + if inCall, err := client.IsSessionInCall(ctx, testSessionId+"1", testRoomId, testBackendUrl); assert.NoError(err) { + assert.False(inCall, "should not be in call") + } + if inCall, err := client.IsSessionInCall(ctx, testSessionId, testRoomId, testBackendUrl); assert.NoError(err) { + assert.True(inCall, "should be in call") + } + } +} + +func TestServer_GetInternalSessions(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + if internal, virtual, err := client.GetInternalSessions(ctx, testRoomId+"1", []string{testBackendUrl}); assert.NoError(err) { + assert.Empty(internal) + assert.Empty(virtual) + } + if internal, virtual, err := client.GetInternalSessions(ctx, testRoomId, nil); assert.NoError(err) { + assert.Empty(internal) + assert.Empty(virtual) + } + if internal, virtual, err := client.GetInternalSessions(ctx, testRoomId, []string{testBackendUrl}); assert.NoError(err) { + if assert.Len(internal, 1) && assert.NotNil(internal[testInternalSessionId], "did not find %s in %+v", testInternalSessionId, internal) { + assert.Equal(testInternalSessionId, internal[testInternalSessionId].SessionId) + assert.EqualValues(testInternalInCallFlags, internal[testInternalSessionId].InCall) + assert.Equal(testFeatures, internal[testInternalSessionId].Features) + } + if assert.Len(virtual, 1) && assert.NotNil(virtual[testVirtualSessionId], "did not find %s in %+v", testVirtualSessionId, virtual) { + assert.Equal(testVirtualSessionId, virtual[testVirtualSessionId].SessionId) + assert.EqualValues(testVirtualInCallFlags, virtual[testVirtualSessionId].InCall) + } + } + } +} + +func TestServer_GetPublisherId(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + if publisherId, proxyUrl, ip, connToken, publisherToken, err := client.GetPublisherId(ctx, testSessionId, sfu.StreamTypeVideo); assert.NoError(err) { + assert.EqualValues(testSessionId, publisherId) + assert.Equal(testProxyUrl, proxyUrl) + assert.True(net.ParseIP(testIp).Equal(ip), "expected IP %s, got %s", testIp, ip.String()) + assert.Equal(testConnectToken, connToken) + assert.Equal(testPublisherToken, publisherToken) + } + } +} + +func TestServer_GetTransientData(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + if entries, err := client.GetTransientData(ctx, testRoomId+"1", hub.backend); assert.NoError(err) { + assert.Empty(entries) + } + if entries, err := client.GetTransientData(ctx, testRoomId, hub.backend); assert.NoError(err) && assert.Len(entries, 2) { + if e := entries["foo"]; assert.NotNil(e, "did not find foo in %+v", entries) { + assert.Equal("bar", e.Value) + assert.Equal(testExpires, e.Expires) + } + + if e := entries["bar"]; assert.NotNil(e, "did not find bar in %+v", entries) { + assert.EqualValues(123, e.Value) + assert.True(e.Expires.IsZero(), "should have no expiration, got %s", e.Expires) + } + } + } +} + +type testReceiver struct { + t *testing.T + received atomic.Bool + closed chan struct{} +} + +func (r *testReceiver) RemoteAddr() string { + return testAddr +} + +func (r *testReceiver) Country() geoip.Country { + return testCountry +} + +func (r *testReceiver) UserAgent() string { + return testAgent +} + +func (r *testReceiver) OnProxyMessage(message *ServerSessionMessage) error { + assert.Equal(r.t, testMessage, message.Message) + assert.False(r.t, r.received.Swap(true), "received additional message %v", message) + return nil +} + +func (r *testReceiver) OnProxyClose(err error) { + if err != nil { + if s := status.Convert(err); assert.NotNil(r.t, s, "expected status, got %+v", err) { + assert.Equal(r.t, codes.InvalidArgument, s.Code()) + assert.Equal(r.t, "unknown session id", s.Message()) + } + } + + close(r.closed) +} + +func TestServer_ProxySession(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + receiver := &testReceiver{ + t: t, + closed: make(chan struct{}), + } + if proxy, err := client.ProxySession(ctx, testSessionId, receiver); assert.NoError(err) { + t.Cleanup(func() { + assert.NoError(proxy.Close()) + }) + + assert.NotNil(proxy) + <-receiver.closed + assert.True(receiver.received.Load(), "should have received message") + } + } +} + +func TestServer_ProxySessionError(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + receiver := &testReceiver{ + t: t, + closed: make(chan struct{}), + } + if proxy, err := client.ProxySession(ctx, testSessionId+"1", receiver); assert.NoError(err) { + t.Cleanup(func() { + assert.NoError(proxy.Close()) + }) + + assert.NotNil(proxy) + <-receiver.closed + } + } +} + +type testSession struct{} + +func (s *testSession) PublicId() api.PublicSessionId { + return testSessionId +} + +func (s *testSession) ClientType() api.ClientType { + return api.HelloClientTypeClient +} + +func TestServer_GetSessionCount(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + hub := newTestServerHub(t) + + server, addr := NewServerForTest(t) + server.SetHub(hub) + clients, _ := NewClientsForTest(t, addr, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + require.NoError(clients.WaitForInitialized(ctx)) + + for _, client := range clients.GetClients() { + if count, err := client.GetSessionCount(ctx, testBackendUrl+"1"); assert.NoError(err) { + assert.EqualValues(0, count) + } + if count, err := client.GetSessionCount(ctx, testBackendUrl); assert.NoError(err) { + assert.EqualValues(0, count) + } + assert.NoError(hub.backend.AddSession(&testSession{})) + if count, err := client.GetSessionCount(ctx, testBackendUrl); assert.NoError(err) { + assert.EqualValues(1, count) + } + hub.backend.RemoveSession(&testSession{}) + if count, err := client.GetSessionCount(ctx, testBackendUrl); assert.NoError(err) { + assert.EqualValues(0, count) + } + } +} diff --git a/grpc/test/client_test.go b/grpc/test/client_test.go new file mode 100644 index 0000000..ccb09cd --- /dev/null +++ b/grpc/test/client_test.go @@ -0,0 +1,56 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" +) + +func TestClientsWithEtcd(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + serverId := "the-test-server-id" + server, addr := NewServerForTest(t) + server.SetServerId(serverId) + + etcd := etcdtest.NewServerForTest(t) + etcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr+"\"}")) + + clients, _ := NewClientsWithEtcdForTest(t, etcd, nil) + + require.NoError(clients.WaitForInitialized(t.Context())) + + for _, client := range clients.GetClients() { + if id, version, err := client.GetServerId(t.Context()); assert.NoError(err) { + assert.Equal(serverId, id) + assert.NotEmpty(version) + } + } +} diff --git a/grpc/test/server.go b/grpc/test/server.go new file mode 100644 index 0000000..9ad8b98 --- /dev/null +++ b/grpc/test/server.go @@ -0,0 +1,72 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "net" + "strconv" + "testing" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" +) + +func NewServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *grpc.Server, addr string) { + logger := log.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + for port := 50000; port < 50100; port++ { + addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + config.AddOption("grpc", "listen", addr) + var err error + server, err = grpc.NewServer(ctx, config, "0.0.0") + if test.IsErrorAddressAlreadyInUse(err) { + continue + } + + require.NoError(t, err) + break + } + + require.NotNil(t, server, "could not find free port") + + // Don't match with own server id by default. + server.SetServerId("dont-match") + + go func() { + assert.NoError(t, server.Run(), "could not start GRPC server") + }() + + t.Cleanup(func() { + server.Close() + }) + return server, addr +} + +func NewServerForTest(t *testing.T) (server *grpc.Server, addr string) { + config := goconf.NewConfigFile() + return NewServerForTestWithConfig(t, config) +} diff --git a/grpc/test/server_test.go b/grpc/test/server_test.go new file mode 100644 index 0000000..bd5e871 --- /dev/null +++ b/grpc/test/server_test.go @@ -0,0 +1,51 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestServer(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + serverId := "the-test-server-id" + server, addr := NewServerForTest(t) + server.SetServerId(serverId) + + clients, _ := NewClientsForTest(t, addr, nil) + + require.NoError(clients.WaitForInitialized(t.Context())) + + for _, client := range clients.GetClients() { + if id, version, err := client.GetServerId(t.Context()); assert.NoError(err) { + assert.Equal(serverId, id) + assert.NotEmpty(version) + } + } +} diff --git a/server/backend_server_test.go b/server/backend_server_test.go index 15582a3..c521d99 100644 --- a/server/backend_server_test.go +++ b/server/backend_server_test.go @@ -150,8 +150,8 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g }) nats, _ := nats.StartLocalServer(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) if config1 == nil { config1 = goconf.NewConfigFile() diff --git a/server/grpc_client_test.go b/server/grpc_client_test.go deleted file mode 100644 index 1e116f6..0000000 --- a/server/grpc_client_test.go +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2022 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 server - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/test" -) - -func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { - t.Helper() - - select { - case <-ch: - return - case <-ctx.Done(): - assert.Fail(t, "timeout waiting for event") - } -} - -func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - _, addr1 := NewGrpcServerForTest(t) - _, addr2 := NewGrpcServerForTest(t) - - embedEtcd := etcdtest.NewServerForTest(t) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - client, _ := grpctest.NewClientsWithEtcdForTest(t, embedEtcd, nil) - ctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - require.NoError(t, client.WaitForInitialized(ctx)) - - clients := client.GetClients() - assert.Len(t, clients, 2, "Expected two clients, got %+v", clients) - }) -} - -func Test_GrpcClients_EtcdUpdate(t *testing.T) { - t.Parallel() - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - assert := assert.New(t) - embedEtcd := etcdtest.NewServerForTest(t) - client, _ := grpctest.NewClientsWithEtcdForTest(t, embedEtcd, nil) - ch := client.GetWakeupChannelForTesting() - - ctx, cancel := context.WithTimeout(ctx, testTimeout) - defer cancel() - - assert.Empty(client.GetClients()) - - test.DrainWakeupChannel(ch) - _, addr1 := NewGrpcServerForTest(t) - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - waitForEvent(ctx, t, ch) - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(addr1, clients[0].Target()) - } - - test.DrainWakeupChannel(ch) - _, addr2 := NewGrpcServerForTest(t) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - waitForEvent(ctx, t, ch) - if clients := client.GetClients(); assert.Len(clients, 2) { - assert.Equal(addr1, clients[0].Target()) - assert.Equal(addr2, clients[1].Target()) - } - - test.DrainWakeupChannel(ch) - embedEtcd.DeleteValue("/grpctargets/one") - waitForEvent(ctx, t, ch) - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(addr2, clients[0].Target()) - } - - test.DrainWakeupChannel(ch) - _, addr3 := NewGrpcServerForTest(t) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr3+"\"}")) - waitForEvent(ctx, t, ch) - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(addr3, clients[0].Target()) - } -} - -func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { - t.Parallel() - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - assert := assert.New(t) - embedEtcd := etcdtest.NewServerForTest(t) - client, _ := grpctest.NewClientsWithEtcdForTest(t, embedEtcd, nil) - ch := client.GetWakeupChannelForTesting() - - ctx, cancel := context.WithTimeout(ctx, testTimeout) - defer cancel() - - assert.Empty(client.GetClients()) - - test.DrainWakeupChannel(ch) - _, addr1 := NewGrpcServerForTest(t) - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - waitForEvent(ctx, t, ch) - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(addr1, clients[0].Target()) - } - - test.DrainWakeupChannel(ch) - server2, addr2 := NewGrpcServerForTest(t) - server2.serverId = grpc.ServerId - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - waitForEvent(ctx, t, ch) - client.WaitForSelfCheck() - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(addr1, clients[0].Target()) - } - - test.DrainWakeupChannel(ch) - embedEtcd.DeleteValue("/grpctargets/two") - waitForEvent(ctx, t, ch) - if clients := client.GetClients(); assert.Len(clients, 1) { - assert.Equal(addr1, clients[0].Target()) - } -} diff --git a/server/grpc_server_test.go b/server/grpc_server_test.go deleted file mode 100644 index 232cdf7..0000000 --- a/server/grpc_server_test.go +++ /dev/null @@ -1,314 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2022 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 server - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "net" - "path" - "strconv" - "testing" - "time" - - "github.com/dlintw/goconf" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - - rpc "github.com/strukturag/nextcloud-spreed-signaling/grpc" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/test" -) - -type CertificateReloadWaiter interface { - WaitForCertificateReload(ctx context.Context, counter uint64) error -} - -func (s *GrpcServer) WaitForCertificateReload(ctx context.Context, counter uint64) error { - c, ok := s.creds.(CertificateReloadWaiter) - if !ok { - return errors.New("no reloadable credentials found") - } - - return c.WaitForCertificateReload(ctx, counter) -} - -type CertPoolReloadWaiter interface { - WaitForCertPoolReload(ctx context.Context, counter uint64) error -} - -func (s *GrpcServer) WaitForCertPoolReload(ctx context.Context, counter uint64) error { - c, ok := s.creds.(CertPoolReloadWaiter) - if !ok { - return errors.New("no reloadable credentials found") - } - - return c.WaitForCertPoolReload(ctx, counter) -} - -func NewGrpcServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *GrpcServer, addr string) { - logger := log.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - for port := 50000; port < 50100; port++ { - addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) - config.AddOption("grpc", "listen", addr) - var err error - server, err = NewGrpcServer(ctx, config, "0.0.0") - if test.IsErrorAddressAlreadyInUse(err) { - continue - } - - require.NoError(t, err) - break - } - - require.NotNil(t, server, "could not find free port") - - // Don't match with own server id by default. - server.serverId = "dont-match" - - go func() { - assert.NoError(t, server.Run(), "could not start GRPC server") - }() - - t.Cleanup(func() { - server.Close() - }) - return server, addr -} - -func NewGrpcServerForTest(t *testing.T) (server *GrpcServer, addr string) { - config := goconf.NewConfigFile() - return NewGrpcServerForTestWithConfig(t, config) -} - -func Test_GrpcServer_ReloadCerts(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - key, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - - org1 := "Testing certificate" - cert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, key) - - dir := t.TempDir() - privkeyFile := path.Join(dir, "privkey.pem") - pubkeyFile := path.Join(dir, "pubkey.pem") - certFile := path.Join(dir, "cert.pem") - require.NoError(internal.WritePrivateKey(key, privkeyFile)) - require.NoError(internal.WritePublicKey(&key.PublicKey, pubkeyFile)) - require.NoError(internal.WriteCertificate(cert1, certFile)) - - config := goconf.NewConfigFile() - config.AddOption("grpc", "servercertificate", certFile) - config.AddOption("grpc", "serverkey", privkeyFile) - - server, addr := NewGrpcServerForTestWithConfig(t, config) - - cp1 := x509.NewCertPool() - cp1.AddCert(cert1) - - cfg1 := &tls.Config{ - RootCAs: cp1, - } - conn1, err := tls.Dial("tcp", addr, cfg1) - require.NoError(err) - defer conn1.Close() // nolint - state1 := conn1.ConnectionState() - if certs := state1.PeerCertificates; assert.NotEmpty(certs) { - if assert.NotEmpty(certs[0].Subject.Organization) { - assert.Equal(org1, certs[0].Subject.Organization[0]) - } - } - - org2 := "Updated certificate" - cert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, key) - internal.ReplaceCertificate(t, certFile, cert2) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - require.NoError(server.WaitForCertificateReload(ctx, 0)) - - cp2 := x509.NewCertPool() - cp2.AddCert(cert2) - - cfg2 := &tls.Config{ - RootCAs: cp2, - } - conn2, err := tls.Dial("tcp", addr, cfg2) - require.NoError(err) - defer conn2.Close() // nolint - state2 := conn2.ConnectionState() - if certs := state2.PeerCertificates; assert.NotEmpty(certs) { - if assert.NotEmpty(certs[0].Subject.Organization) { - assert.Equal(org2, certs[0].Subject.Organization[0]) - } - } -} - -func Test_GrpcServer_ReloadCA(t *testing.T) { - t.Parallel() - logger := log.NewLoggerForTest(t) - require := require.New(t) - serverKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - clientKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - - serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) - org1 := "Testing client" - clientCert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, clientKey) - - dir := t.TempDir() - privkeyFile := path.Join(dir, "privkey.pem") - pubkeyFile := path.Join(dir, "pubkey.pem") - certFile := path.Join(dir, "cert.pem") - caFile := path.Join(dir, "ca.pem") - require.NoError(internal.WritePrivateKey(serverKey, privkeyFile)) - require.NoError(internal.WritePublicKey(&serverKey.PublicKey, pubkeyFile)) - require.NoError(internal.WriteCertificate(serverCert, certFile)) - require.NoError(internal.WriteCertificate(clientCert1, caFile)) - - config := goconf.NewConfigFile() - config.AddOption("grpc", "servercertificate", certFile) - config.AddOption("grpc", "serverkey", privkeyFile) - config.AddOption("grpc", "clientca", caFile) - - server, addr := NewGrpcServerForTestWithConfig(t, config) - - pool := x509.NewCertPool() - pool.AddCert(serverCert) - - pair1, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: clientCert1.Raw, - }), pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(clientKey), - })) - require.NoError(err) - - cfg1 := &tls.Config{ - RootCAs: pool, - Certificates: []tls.Certificate{pair1}, - } - client1, err := rpc.NewClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg1))) - require.NoError(err) - defer client1.Close() // nolint - - ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second) - defer cancel1() - - _, _, err = client1.GetServerId(ctx1) - require.NoError(err) - - org2 := "Updated client" - clientCert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, clientKey) - internal.ReplaceCertificate(t, caFile, clientCert2) - - require.NoError(server.WaitForCertPoolReload(ctx1, 0)) - - pair2, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: clientCert2.Raw, - }), pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(clientKey), - })) - require.NoError(err) - - cfg2 := &tls.Config{ - RootCAs: pool, - Certificates: []tls.Certificate{pair2}, - } - client2, err := rpc.NewClient(logger, addr, nil, grpc.WithTransportCredentials(credentials.NewTLS(cfg2))) - require.NoError(err) - defer client2.Close() // nolint - - ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second) - defer cancel2() - - // This will fail if the CA certificate has not been reloaded by the server. - _, _, err = client2.GetServerId(ctx2) - require.NoError(err) -} - -func Test_GrpcClients_Encryption(t *testing.T) { // nolint:paralleltest - test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { - require := require.New(t) - serverKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - clientKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(err) - - serverCert := internal.GenerateSelfSignedCertificateForTesting(t, "Server cert", serverKey) - clientCert := internal.GenerateSelfSignedCertificateForTesting(t, "Testing client", clientKey) - - dir := t.TempDir() - serverPrivkeyFile := path.Join(dir, "server-privkey.pem") - serverPubkeyFile := path.Join(dir, "server-pubkey.pem") - serverCertFile := path.Join(dir, "server-cert.pem") - require.NoError(internal.WritePrivateKey(serverKey, serverPrivkeyFile)) - require.NoError(internal.WritePublicKey(&serverKey.PublicKey, serverPubkeyFile)) - require.NoError(internal.WriteCertificate(serverCert, serverCertFile)) - clientPrivkeyFile := path.Join(dir, "client-privkey.pem") - clientPubkeyFile := path.Join(dir, "client-pubkey.pem") - clientCertFile := path.Join(dir, "client-cert.pem") - require.NoError(internal.WritePrivateKey(clientKey, clientPrivkeyFile)) - require.NoError(internal.WritePublicKey(&clientKey.PublicKey, clientPubkeyFile)) - require.NoError(internal.WriteCertificate(clientCert, clientCertFile)) - - serverConfig := goconf.NewConfigFile() - serverConfig.AddOption("grpc", "servercertificate", serverCertFile) - serverConfig.AddOption("grpc", "serverkey", serverPrivkeyFile) - serverConfig.AddOption("grpc", "clientca", clientCertFile) - _, addr := NewGrpcServerForTestWithConfig(t, serverConfig) - - clientConfig := goconf.NewConfigFile() - clientConfig.AddOption("grpc", "targets", addr) - clientConfig.AddOption("grpc", "clientcertificate", clientCertFile) - clientConfig.AddOption("grpc", "clientkey", clientPrivkeyFile) - clientConfig.AddOption("grpc", "serverca", serverCertFile) - clients, _ := grpctest.NewClientsForTestWithConfig(t, clientConfig, nil, nil) - - ctx, cancel1 := context.WithTimeout(context.Background(), time.Second) - defer cancel1() - - require.NoError(clients.WaitForInitialized(ctx)) - - for _, client := range clients.GetClients() { - _, _, err := client.GetServerId(ctx) - require.NoError(err) - } - }) -} diff --git a/server/hub.go b/server/hub.go index 830a86b..dd5e024 100644 --- a/server/hub.go +++ b/server/hub.go @@ -50,6 +50,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/gorilla/mux" "github.com/gorilla/websocket" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" @@ -93,6 +95,8 @@ var ( // TooManyRequests is returned if brute force detection reports too many failed "hello" requests. TooManyRequests = api.NewError("too_many_requests", "Too many requests.") + ErrNoProxyTokenSupported = errors.New("proxy token generation not supported") + // Maximum number of concurrent requests to a backend. defaultMaxConcurrentRequestsPerHost = 8 @@ -225,7 +229,7 @@ type Hub struct { geoipUpdating atomic.Bool etcdClient etcd.Client - rpcServer *GrpcServer + rpcServer *grpc.Server rpcClients *grpc.Clients throttler async.Throttler @@ -237,7 +241,7 @@ type Hub struct { blockedCandidates atomic.Pointer[container.IPList] } -func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEvents, rpcServer *GrpcServer, rpcClients *grpc.Clients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { +func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEvents, rpcServer *grpc.Server, rpcClients *grpc.Clients, etcdClient etcd.Client, r *mux.Router, version string) (*Hub, error) { logger := log.LoggerFromContext(ctx) hashKey, _ := config.GetStringOptionWithEnv(cfg, "sessions", "hashkey") switch len(hashKey) { @@ -468,7 +472,7 @@ func NewHub(ctx context.Context, cfg *goconf.ConfigFile, events events.AsyncEven }) roomPing.hub = hub if rpcServer != nil { - rpcServer.hub = hub + rpcServer.SetHub(hub) } hub.upgrader.CheckOrigin = hub.checkOrigin r.HandleFunc("/spreed", func(w http.ResponseWriter, r *http.Request) { @@ -750,10 +754,72 @@ func (h *Hub) GetSessionByResumeId(resumeId api.PrivateSessionId) Session { return session } +func (h *Hub) GetSessionIdByResumeId(resumeId api.PrivateSessionId) api.PublicSessionId { + session := h.GetSessionByResumeId(resumeId) + if session == nil { + return "" + } + + return session.PublicId() +} + func (h *Hub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { return h.roomSessions.GetSessionId(roomSessionId) } +func (h *Hub) IsSessionIdInCall(sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, bool) { + session := h.GetSessionByPublicId(sessionId) + if session == nil { + return false, false + } + + inCall := true + room := session.GetRoom() + if room == nil || room.Id() != roomId || !room.Backend().HasUrl(backendUrl) || + (session.ClientType() != api.HelloClientTypeInternal && !room.IsSessionInCall(session)) { + // Recipient is not in a room, a different room or not in the call. + inCall = false + } + + return inCall, true +} + +func (h *Hub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*grpc.GetPublisherIdReply, error) { + session := h.GetSessionByPublicId(sessionId) + if session == nil { + return nil, status.Error(codes.NotFound, "no such session") + } + + clientSession, ok := session.(*ClientSession) + if !ok { + return nil, status.Error(codes.NotFound, "no such session") + } + + publisher := clientSession.GetOrWaitForPublisher(ctx, streamType) + if publisher, ok := publisher.(sfu.PublisherWithConnectionUrlAndIP); ok { + connUrl, ip := publisher.GetConnectionURL() + reply := &grpc.GetPublisherIdReply{ + PublisherId: publisher.Id(), + ProxyUrl: connUrl, + } + if len(ip) > 0 { + reply.Ip = ip.String() + } + var err error + if reply.ConnectToken, err = h.CreateProxyToken(""); err != nil && !errors.Is(err, ErrNoProxyTokenSupported) { + h.logger.Printf("Error creating proxy token for connection: %s", err) + return nil, status.Error(codes.Internal, "error creating proxy connect token") + } + if reply.PublisherToken, err = h.CreateProxyToken(publisher.Id()); err != nil && !errors.Is(err, ErrNoProxyTokenSupported) { + h.logger.Printf("Error creating proxy token for publisher %s: %s", publisher.Id(), err) + return nil, status.Error(codes.Internal, "error creating proxy publisher token") + } + return reply, nil + } + + return nil, status.Error(codes.NotFound, "no such publisher") +} + func (h *Hub) GetDialoutSessions(roomId string, backend *talk.Backend) (result []*ClientSession) { h.mu.RLock() defer h.mu.RUnlock() @@ -780,7 +846,7 @@ func (h *Hub) GetBackend(u *url.URL) *talk.Backend { func (h *Hub) CreateProxyToken(publisherId string) (string, error) { withToken, ok := h.mcu.(sfu.WithToken) if !ok { - return "", ErrNoProxyMcu + return "", ErrNoProxyTokenSupported } return withToken.CreateToken(publisherId) @@ -1648,11 +1714,25 @@ func (h *Hub) disconnectByRoomSessionId(ctx context.Context, roomSessionId api.R } h.logger.Printf("Closing session %s because same room session %s connected", session.PublicId(), roomSessionId) + h.disconnectSessionWithReason(session, "room_session_reconnected") +} + +func (h *Hub) DisconnectSessionByRoomSessionId(sessionId api.PublicSessionId, roomSessionId api.RoomSessionId, reason string) { + session := h.GetSessionByPublicId(sessionId) + if session == nil { + return + } + + h.logger.Printf("Closing session %s because same room session %s connected", session.PublicId(), roomSessionId) + h.disconnectSessionWithReason(session, reason) +} + +func (h *Hub) disconnectSessionWithReason(session Session, reason string) { session.LeaveRoom(false) switch sess := session.(type) { case *ClientSession: if client := sess.GetClient(); client != nil { - client.SendByeResponseWithReason(nil, "room_session_reconnected") + client.SendByeResponseWithReason(nil, reason) } } session.Close() @@ -1981,6 +2061,45 @@ func (h *Hub) GetRoomForBackend(id string, backend *talk.Backend) *Room { return h.rooms[internalRoomId] } +func (h *Hub) GetInternalSessions(roomId string, backend *talk.Backend) ([]*grpc.InternalSessionData, []*grpc.VirtualSessionData, bool) { + room := h.GetRoomForBackend(roomId, backend) + if room == nil { + return nil, nil, false + } + + room.mu.RLock() + defer room.mu.RUnlock() + + var internalSessions []*grpc.InternalSessionData + var virtualSessions []*grpc.VirtualSessionData + for session := range room.internalSessions { + internalSessions = append(internalSessions, &grpc.InternalSessionData{ + SessionId: string(session.PublicId()), + InCall: uint32(session.GetInCall()), + Features: session.GetFeatures(), + }) + } + + for session := range room.virtualSessions { + virtualSessions = append(virtualSessions, &grpc.VirtualSessionData{ + SessionId: string(session.PublicId()), + InCall: uint32(session.GetInCall()), + }) + } + + return internalSessions, virtualSessions, true +} + +func (h *Hub) GetTransientEntries(roomId string, backend *talk.Backend) (api.TransientDataEntries, bool) { + room := h.GetRoomForBackend(roomId, backend) + if room == nil { + return nil, false + } + + entries := room.transientData.GetEntries() + return entries, true +} + func (h *Hub) removeRoom(room *Room) { internalRoomId := getRoomIdForBackend(room.Id(), room.Backend()) h.ru.Lock() @@ -3120,6 +3239,18 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) { client.ReadPump() } +func (h *Hub) ProxySession(request grpc.RpcSessions_ProxySessionServer) error { + client, err := newRemoteGrpcClient(h, request) + if err != nil { + return err + } + + sid := h.registerClient(client) + defer h.unregisterClient(sid) + + return client.run() +} + func (h *Hub) LookupCountry(addr string) geoip.Country { ip := net.ParseIP(addr) if ip == nil { diff --git a/server/hub_sfu_proxy_test.go b/server/hub_sfu_proxy_test.go index 1ca36ed..28e0721 100644 --- a/server/hub_sfu_proxy_test.go +++ b/server/hub_sfu_proxy_test.go @@ -34,11 +34,15 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/sfu/mock" proxytest "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/test" "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" "github.com/strukturag/nextcloud-spreed-signaling/talk" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type mockGrpcServerHub struct { @@ -56,6 +60,13 @@ func (h *mockGrpcServerHub) setProxy(t *testing.T, proxy sfu.SFU) { h.proxy.Store(&wt) } +func (h *mockGrpcServerHub) getSession(sessionId api.PublicSessionId) Session { + h.sessionsLock.Lock() + defer h.sessionsLock.Unlock() + + return h.sessionByPublicId[sessionId] +} + func (h *mockGrpcServerHub) addSession(session *ClientSession) { h.sessionsLock.Lock() defer h.sessionsLock.Unlock() @@ -71,35 +82,67 @@ func (h *mockGrpcServerHub) removeSession(session *ClientSession) { delete(h.sessionByPublicId, session.PublicId()) } -func (h *mockGrpcServerHub) GetSessionByResumeId(resumeId api.PrivateSessionId) Session { - return nil -} - -func (h *mockGrpcServerHub) GetSessionByPublicId(sessionId api.PublicSessionId) Session { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - return h.sessionByPublicId[sessionId] +func (h *mockGrpcServerHub) GetSessionIdByResumeId(resumeId api.PrivateSessionId) api.PublicSessionId { + return "" } func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { return "", nil } +func (h *mockGrpcServerHub) IsSessionIdInCall(sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, bool) { + return false, false +} + +func (h *mockGrpcServerHub) DisconnectSessionByRoomSessionId(sessionId api.PublicSessionId, roomSessionId api.RoomSessionId, reason string) { +} + func (h *mockGrpcServerHub) GetBackend(u *url.URL) *talk.Backend { return nil } -func (h *mockGrpcServerHub) GetRoomForBackend(roomId string, backend *talk.Backend) *Room { - return nil +func (h *mockGrpcServerHub) GetInternalSessions(roomId string, backend *talk.Backend) ([]*grpc.InternalSessionData, []*grpc.VirtualSessionData, bool) { + return nil, nil, false } -func (h *mockGrpcServerHub) CreateProxyToken(publisherId string) (string, error) { - proxy := h.proxy.Load() - if proxy == nil { - return "", errors.New("not a proxy mcu") +func (h *mockGrpcServerHub) GetTransientEntries(roomId string, backend *talk.Backend) (api.TransientDataEntries, bool) { + return nil, false +} + +func (h *mockGrpcServerHub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*grpc.GetPublisherIdReply, error) { + session := h.getSession(sessionId) + if session == nil { + return nil, status.Error(codes.NotFound, "no such session") } - return (*proxy).CreateToken(publisherId) + clientSession, ok := session.(*ClientSession) + if !ok { + return nil, status.Error(codes.NotFound, "no such session") + } + + publisher := clientSession.GetOrWaitForPublisher(ctx, streamType) + if publisher, ok := publisher.(sfu.PublisherWithConnectionUrlAndIP); ok { + connUrl, ip := publisher.GetConnectionURL() + reply := &grpc.GetPublisherIdReply{ + PublisherId: publisher.Id(), + ProxyUrl: connUrl, + } + if len(ip) > 0 { + reply.Ip = ip.String() + } + + if proxy := h.proxy.Load(); proxy != nil { + reply.ConnectToken, _ = (*proxy).CreateToken("") + reply.PublisherToken, _ = (*proxy).CreateToken(publisher.Id()) + } + return reply, nil + } + + return nil, status.Error(codes.NotFound, "no such publisher") +} + +func (h *mockGrpcServerHub) ProxySession(request grpc.RpcSessions_ProxySessionServer) error { + return errors.New("not implemented") } func Test_ProxyRemotePublisher(t *testing.T) { @@ -107,13 +150,13 @@ func Test_ProxyRemotePublisher(t *testing.T) { embedEtcd := etcdtest.NewServerForTest(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) hub1 := &mockGrpcServerHub{} hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) @@ -178,16 +221,16 @@ func Test_ProxyMultipleRemotePublisher(t *testing.T) { embedEtcd := etcdtest.NewServerForTest(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) - grpcServer3, addr3 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) + grpcServer3, addr3 := grpctest.NewServerForTest(t) hub1 := &mockGrpcServerHub{} hub2 := &mockGrpcServerHub{} hub3 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 - grpcServer3.hub = hub3 + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) + grpcServer3.SetHub(hub3) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) @@ -272,13 +315,13 @@ func Test_ProxyRemotePublisherWait(t *testing.T) { embedEtcd := etcdtest.NewServerForTest(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) hub1 := &mockGrpcServerHub{} hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) @@ -360,13 +403,13 @@ func Test_ProxyRemotePublisherTemporary(t *testing.T) { assert := assert.New(t) embedEtcd := etcdtest.NewServerForTest(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) hub1 := &mockGrpcServerHub{} hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) @@ -465,13 +508,13 @@ func Test_ProxyConnectToken(t *testing.T) { embedEtcd := etcdtest.NewServerForTest(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) hub1 := &mockGrpcServerHub{} hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) @@ -537,13 +580,13 @@ func Test_ProxyPublisherToken(t *testing.T) { embedEtcd := etcdtest.NewServerForTest(t) - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) hub1 := &mockGrpcServerHub{} hub2 := &mockGrpcServerHub{} - grpcServer1.hub = hub1 - grpcServer2.hub = hub2 + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) diff --git a/server/hub_test.go b/server/hub_test.go index ba1f0a8..d3e148e 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -242,8 +242,8 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http } else { nats2 = nats1 } - grpcServer1, addr1 := NewGrpcServerForTest(t) - grpcServer2, addr2 := NewGrpcServerForTest(t) + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) if strings.Contains(t.Name(), "Federation") { // Signaling servers should not form a cluster in federation tests. @@ -1905,7 +1905,7 @@ func TestClientHelloResumeProxy_Disconnect(t *testing.T) { // nolint:paralleltes assert.Equal(hello.Hello.ResumeId, hello2.Hello.ResumeId, "%+v", hello2.Hello) // Simulate unclean shutdown of second instance. - hub2.rpcServer.conn.Stop() + hub2.rpcServer.CloseUnclean() assert.NoError(client2.WaitForClientRemoved(ctx)) }) From c162b8bbebe47d7a813c6e0ca595206044e8242c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 12:29:56 +0100 Subject: [PATCH 455/549] Evaluate expiration when setting initial transient data. --- api/transient_data.go | 9 ++++ api/transient_data_test.go | 93 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/api/transient_data.go b/api/transient_data.go index 520e86e..6cc55c2 100644 --- a/api/transient_data.go +++ b/api/transient_data.go @@ -370,6 +370,7 @@ func (t *TransientData) SetInitial(data TransientDataEntries) { t.data = make(TransientDataEntries) } + now := time.Now() msgData := make(StringMap, len(data)) for k, v := range data { if _, found := t.data[k]; found { @@ -377,6 +378,14 @@ func (t *TransientData) SetInitial(data TransientDataEntries) { continue } + if e := v.Expires; !e.IsZero() { + if now.After(e) { + // Already expired + continue + } + + t.removeAfterTTL(k, v.Value, e.Sub(now)) + } msgData[k] = v.Value t.data[k] = v } diff --git a/api/transient_data_test.go b/api/transient_data_test.go index a584eab..bbb5920 100644 --- a/api/transient_data_test.go +++ b/api/transient_data_test.go @@ -23,6 +23,7 @@ package api import ( "sync" + "sync/atomic" "testing" "testing/synctest" "time" @@ -141,3 +142,95 @@ func Test_TransientDataDeadlock(t *testing.T) { data.Set("foo", "bar") <-listener.done } + +type initialDataListener struct { + t *testing.T + + expected StringMap + sent atomic.Int32 +} + +func (l *initialDataListener) SendMessage(message *ServerMessage) bool { + switch l.sent.Add(1) { + case 1: + if assert.Equal(l.t, "transient", message.Type) && + assert.NotNil(l.t, message.TransientData) && + assert.Equal(l.t, "initial", message.TransientData.Type) { + assert.Equal(l.t, l.expected, message.TransientData.Data) + } + case 2: + if assert.Equal(l.t, "transient", message.Type) && + assert.NotNil(l.t, message.TransientData) && + assert.Equal(l.t, "remove", message.TransientData.Type) { + assert.Equal(l.t, "foo", message.TransientData.Key) + assert.Equal(l.t, "bar", message.TransientData.OldValue) + } + default: + assert.Fail(l.t, "unexpected message", "received %+v", message) + } + return true +} + +func Test_TransientDataNotifyInitial(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + data := NewTransientData() + assert.True(data.Set("foo", "bar")) + + listener := &initialDataListener{ + t: t, + expected: StringMap{ + "foo": "bar", + }, + } + data.AddListener(listener) + assert.EqualValues(1, listener.sent.Load()) +} + +func Test_TransientDataSetInitial(t *testing.T) { + t.Parallel() + test.SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + + now := time.Now() + data := NewTransientData() + listener1 := &initialDataListener{ + t: t, + expected: StringMap{ + "foo": "bar", + "bar": 1234, + }, + } + data.AddListener(listener1) + assert.EqualValues(0, listener1.sent.Load()) + + data.SetInitial(TransientDataEntries{ + "foo": NewTransientDataEntryWithExpires("bar", now.Add(time.Minute)), + "bar": NewTransientDataEntry(1234, 0), + "expired": NewTransientDataEntryWithExpires(1234, now.Add(-time.Second)), + }) + + entries := data.GetEntries() + assert.Equal(TransientDataEntries{ + "foo": NewTransientDataEntryWithExpires("bar", now.Add(time.Minute)), + "bar": NewTransientDataEntry(1234, 0), + }, entries) + + listener2 := &initialDataListener{ + t: t, + expected: StringMap{ + "foo": "bar", + "bar": 1234, + }, + } + data.AddListener(listener2) + assert.EqualValues(1, listener1.sent.Load()) + assert.EqualValues(1, listener2.sent.Load()) + + time.Sleep(time.Minute) + synctest.Wait() + assert.EqualValues(2, listener1.sent.Load()) + assert.EqualValues(2, listener2.sent.Load()) + }) +} From 3c6d3c0b7aa51968b7100a6d9d08b82b76096a62 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 13:37:31 +0100 Subject: [PATCH 456/549] Move NATS testing code to separate module. --- async/events/async_events_test.go | 3 +- async/eventstest/eventstest.go | 5 +- nats/loopback.go | 7 ++ nats/native_test.go | 19 ++++++ nats/{test_helpers.go => test/server.go} | 18 +++-- nats/test/server_test.go | 87 ++++++++++++++++++++++++ server/backend_server_test.go | 3 +- server/hub_test.go | 6 +- 8 files changed, 131 insertions(+), 17 deletions(-) rename nats/{test_helpers.go => test/server.go} (82%) create mode 100644 nats/test/server_test.go diff --git a/async/events/async_events_test.go b/async/events/async_events_test.go index 23beb5b..9acffe9 100644 --- a/async/events/async_events_test.go +++ b/async/events/async_events_test.go @@ -32,6 +32,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -159,7 +160,7 @@ func TestAsyncEvents_Loopback(t *testing.T) { func TestAsyncEvents_NATS(t *testing.T) { t.Parallel() - server, _ := nats.StartLocalServer(t) + server, _ := natstest.StartLocalServer(t) logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) events, err := NewAsyncEvents(ctx, server.ClientURL()) diff --git a/async/eventstest/eventstest.go b/async/eventstest/eventstest.go index 5f551cd..e972f0f 100644 --- a/async/eventstest/eventstest.go +++ b/async/eventstest/eventstest.go @@ -33,6 +33,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" ) var ( @@ -62,7 +63,7 @@ func GetAsyncEventsForTest(t *testing.T) events.AsyncEvents { func getRealAsyncEventsForTest(t *testing.T) events.AsyncEvents { logger := log.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) - server, _ := nats.StartLocalServer(t) + server, _ := natstest.StartLocalServer(t) events, err := events.NewAsyncEvents(ctx, server.ClientURL()) if err != nil { require.NoError(t, err) @@ -92,7 +93,7 @@ func getLoopbackAsyncEventsForTest(t *testing.T) events.AsyncEvents { return } - nats.WaitForSubscriptionsEmpty(ctx, t, e.GetNatsClient()) + natstest.WaitForSubscriptionsEmpty(ctx, t, e.GetNatsClient()) }) return events } diff --git a/nats/loopback.go b/nats/loopback.go index dba2955..ad0fa7a 100644 --- a/nats/loopback.go +++ b/nats/loopback.go @@ -60,6 +60,13 @@ func NewLoopbackClient(logger log.Logger) (Client, error) { return client, nil } +func (c *LoopbackClient) SubscriptionCount() int { + c.mu.Lock() + defer c.mu.Unlock() + + return len(c.subscriptions) +} + func (c *LoopbackClient) processMessages() { defer close(c.closed) diff --git a/nats/native_test.go b/nats/native_test.go index fe7a56c..e5d7c20 100644 --- a/nats/native_test.go +++ b/nats/native_test.go @@ -31,12 +31,31 @@ import ( "github.com/stretchr/testify/require" "github.com/nats-io/nats-server/v2/server" + natsservertest "github.com/nats-io/nats-server/v2/test" "github.com/nats-io/nats.go" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) +func StartLocalServer(t *testing.T) (*server.Server, int) { + t.Helper() + return StartLocalServerPort(t, server.RANDOM_PORT) +} + +func StartLocalServerPort(t *testing.T, port int) (*server.Server, int) { + t.Helper() + opts := natsservertest.DefaultTestOptions + opts.Port = port + opts.Cluster.Name = "testing" + srv := natsservertest.RunServer(&opts) + t.Cleanup(func() { + srv.Shutdown() + srv.WaitForShutdown() + }) + return srv, opts.Port +} + func CreateLocalClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, Client) { t.Helper() server, port := StartLocalServer(t) diff --git a/nats/test_helpers.go b/nats/test/server.go similarity index 82% rename from nats/test_helpers.go rename to nats/test/server.go index 3dc7d9f..54faea4 100644 --- a/nats/test_helpers.go +++ b/nats/test/server.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 nats +package test import ( "context" @@ -29,6 +29,8 @@ import ( "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats-server/v2/test" "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/nats" ) func StartLocalServer(t *testing.T) (*server.Server, int) { @@ -49,22 +51,18 @@ func StartLocalServerPort(t *testing.T, port int) (*server.Server, int) { return srv, opts.Port } -func WaitForSubscriptionsEmpty(ctx context.Context, t *testing.T, client Client) { +func WaitForSubscriptionsEmpty(ctx context.Context, t *testing.T, client nats.Client) { t.Helper() - if c, ok := client.(*LoopbackClient); assert.True(t, ok, "expected LoopbackNatsClient, got %T", client) { + if c, ok := client.(*nats.LoopbackClient); assert.True(t, ok, "expected LoopbackNatsClient, got %T", client) { for { - c.mu.Lock() - count := len(c.subscriptions) - c.mu.Unlock() - if count == 0 { + remaining := c.SubscriptionCount() + if remaining == 0 { break } select { case <-ctx.Done(): - c.mu.Lock() - assert.NoError(t, ctx.Err(), "Error waiting for subscriptions %+v to terminate", c.subscriptions) - c.mu.Unlock() + assert.NoError(t, ctx.Err(), "Error waiting for %d subscriptions to terminate", remaining) return default: time.Sleep(time.Millisecond) diff --git a/nats/test/server_test.go b/nats/test/server_test.go new file mode 100644 index 0000000..9cd9746 --- /dev/null +++ b/nats/test/server_test.go @@ -0,0 +1,87 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/nats" +) + +func TestLocalServer(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + server, port := StartLocalServer(t) + assert.NotEqual(0, port) + + ctx := log.NewLoggerContext(t.Context(), log.NewLoggerForTest(t)) + + client, err := nats.NewClient(ctx, server.ClientURL()) + require.NoError(err) + + assert.NoError(client.Close(context.Background())) +} + +func TestWaitForSubscriptionsEmpty(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + ctx := log.NewLoggerContext(t.Context(), log.NewLoggerForTest(t)) + + client, err := nats.NewClient(ctx, nats.LoopbackUrl) + require.NoError(err) + defer func() { + assert.NoError(client.Close(context.Background())) + }() + + ch := make(chan *nats.Msg) + sub, err := client.Subscribe("foo", ch) + require.NoError(err) + + ready := make(chan struct{}) + done := make(chan struct{}) + go func() { + defer close(done) + + ctx, cancel := context.WithTimeout(t.Context(), time.Second) + defer cancel() + + close(ready) + WaitForSubscriptionsEmpty(ctx, t, client) + }() + + <-ready + + require.NoError(sub.Unsubscribe()) + <-done +} diff --git a/server/backend_server_test.go b/server/backend_server_test.go index c521d99..94962ed 100644 --- a/server/backend_server_test.go +++ b/server/backend_server_test.go @@ -53,6 +53,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -149,7 +150,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g server2.Close() }) - nats, _ := nats.StartLocalServer(t) + nats, _ := natstest.StartLocalServer(t) grpcServer1, addr1 := grpctest.NewServerForTest(t) grpcServer2, addr2 := grpctest.NewServerForTest(t) diff --git a/server/hub_test.go b/server/hub_test.go index d3e148e..39fd8c0 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -62,7 +62,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/session" sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -235,10 +235,10 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http server2.Close() }) - nats1, _ := nats.StartLocalServer(t) + nats1, _ := natstest.StartLocalServer(t) var nats2 *server.Server if strings.Contains(t.Name(), "Federation") { - nats2, _ = nats.StartLocalServer(t) + nats2, _ = natstest.StartLocalServer(t) } else { nats2 = nats1 } From ee6f026bbbd10bfd0f1c7ed726a8c25f5c783de1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 13:58:33 +0100 Subject: [PATCH 457/549] Move log test helpers to separate package. --- async/deferred_executor_test.go | 14 ++++---- async/events/async_events_test.go | 5 +-- async/eventstest/eventstest.go | 5 +-- async/throttle_test.go | 15 +++++---- client/client_test.go | 3 +- cmd/proxy/proxy_server_test.go | 3 +- cmd/proxy/proxy_tokens_etcd_test.go | 4 +-- dns/test_helpers.go | 4 +-- etcd/client_test.go | 13 ++++---- etcd/etcdtest/etcdtest.go | 4 +-- geoip/maxmind_test.go | 10 +++--- geoip/overrides_test.go | 11 +++--- grpc/client_test.go | 13 ++++---- grpc/server_test.go | 7 ++-- grpc/test/client.go | 5 +-- grpc/test/server.go | 3 +- log/logging_test.go | 23 ++++++++++++- log/{test_helpers.go => test/log.go} | 13 ++++---- log/{test_helpers_24.go => test/log_24.go} | 2 +- log/{test_helpers_25.go => test/log_25.go} | 2 +- log/test/log_test.go | 39 ++++++++++++++++++++++ nats/loopback_test.go | 4 +-- nats/native_test.go | 3 +- nats/test/server_test.go | 5 +-- security/internal/file_watcher_test.go | 20 +++++------ server/backend_server_test.go | 35 +++++++++---------- server/hub_sfu_janus_test.go | 3 +- server/hub_test.go | 7 ++-- server/room_ping_test.go | 9 ++--- server/room_test.go | 11 +++--- sfu/janus/events_handler_test.go | 3 +- sfu/janus/janus_test.go | 3 +- sfu/janus/mcu_test.go | 4 +-- sfu/proxy/config_etcd_test.go | 4 +-- sfu/proxy/config_static_test.go | 4 +-- sfu/proxy/proxy_test.go | 3 +- sfu/proxy/test/proxy.go | 3 +- sfu/test/sfu.go | 4 +-- talk/backend_client_test.go | 9 ++--- talk/backend_configuration_test.go | 32 +++++++++--------- talk/backend_storage_etcd_test.go | 4 +-- talk/capabilities_test.go | 19 ++++++----- 42 files changed, 236 insertions(+), 151 deletions(-) rename log/{test_helpers.go => test/log.go} (82%) rename log/{test_helpers_24.go => test/log_24.go} (98%) rename log/{test_helpers_25.go => test/log_25.go} (98%) create mode 100644 log/test/log_test.go diff --git a/async/deferred_executor_test.go b/async/deferred_executor_test.go index 6dfc312..ccd78f2 100644 --- a/async/deferred_executor_test.go +++ b/async/deferred_executor_test.go @@ -27,13 +27,13 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestDeferredExecutor_MultiClose(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() @@ -44,7 +44,7 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { func TestDeferredExecutor_QueueSize(t *testing.T) { t.Parallel() test.SynctestTest(t, func(t *testing.T) { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() defer e.Close() @@ -67,7 +67,7 @@ func TestDeferredExecutor_QueueSize(t *testing.T) { func TestDeferredExecutor_Order(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() defer e.Close() @@ -96,7 +96,7 @@ func TestDeferredExecutor_Order(t *testing.T) { func TestDeferredExecutor_CloseFromFunc(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -111,7 +111,7 @@ func TestDeferredExecutor_CloseFromFunc(t *testing.T) { func TestDeferredExecutor_DeferAfterClose(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() @@ -124,7 +124,7 @@ func TestDeferredExecutor_DeferAfterClose(t *testing.T) { func TestDeferredExecutor_WaitForStopTwice(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 64) defer e.waitForStop() diff --git a/async/events/async_events_test.go b/async/events/async_events_test.go index 9acffe9..2b2f1a9 100644 --- a/async/events/async_events_test.go +++ b/async/events/async_events_test.go @@ -31,6 +31,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/nats" natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -150,7 +151,7 @@ func testAsyncEvents(t *testing.T, events AsyncEvents) { func TestAsyncEvents_Loopback(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) events, err := NewAsyncEvents(ctx, nats.LoopbackUrl) require.NoError(t, err) @@ -161,7 +162,7 @@ func TestAsyncEvents_NATS(t *testing.T) { t.Parallel() server, _ := natstest.StartLocalServer(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) events, err := NewAsyncEvents(ctx, server.ClientURL()) require.NoError(t, err) diff --git a/async/eventstest/eventstest.go b/async/eventstest/eventstest.go index e972f0f..b7b52d2 100644 --- a/async/eventstest/eventstest.go +++ b/async/eventstest/eventstest.go @@ -32,6 +32,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/async/events" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/nats" natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" ) @@ -61,7 +62,7 @@ func GetAsyncEventsForTest(t *testing.T) events.AsyncEvents { } func getRealAsyncEventsForTest(t *testing.T) events.AsyncEvents { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) server, _ := natstest.StartLocalServer(t) events, err := events.NewAsyncEvents(ctx, server.ClientURL()) @@ -76,7 +77,7 @@ type natsEvents interface { } func getLoopbackAsyncEventsForTest(t *testing.T) events.AsyncEvents { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) events, err := events.NewAsyncEvents(ctx, nats.LoopbackUrl) if err != nil { diff --git a/async/throttle_test.go b/async/throttle_test.go index 9f7474e..fbbf729 100644 --- a/async/throttle_test.go +++ b/async/throttle_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -58,7 +59,7 @@ func TestThrottler(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") @@ -93,7 +94,7 @@ func TestThrottlerIPv6(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) // Make sure full /64 subnets are throttled for IPv6. @@ -131,7 +132,7 @@ func TestThrottler_Bruteforce(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) delay := 100 * time.Millisecond @@ -160,7 +161,7 @@ func TestThrottler_Cleanup(t *testing.T) { th, ok := throttler.(*memoryThrottler) require.True(t, ok, "required memoryThrottler, got %T", throttler) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") @@ -215,7 +216,7 @@ func TestThrottler_ExpirePartial(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") @@ -248,7 +249,7 @@ func TestThrottler_ExpireAll(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1") @@ -281,7 +282,7 @@ func TestThrottler_Negative(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) delay := 100 * time.Millisecond diff --git a/client/client_test.go b/client/client_test.go index 31e8c46..bac0f70 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -44,6 +44,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) func TestCounterWriter(t *testing.T) { @@ -87,7 +88,7 @@ func newTestClient(h *testHandler, r *http.Request, conn *websocket.Conn, id uin addr = host } - logger := log.NewLoggerForTest(h.t) + logger := logtest.NewLoggerForTest(h.t) ctx := log.NewLoggerContext(r.Context(), logger) result.SetConn(ctx, conn, addr, r.Header.Get("User-Agent"), false, result) return result diff --git a/cmd/proxy/proxy_server_test.go b/cmd/proxy/proxy_server_test.go index ed71a54..f04e55c 100644 --- a/cmd/proxy/proxy_server_test.go +++ b/cmd/proxy/proxy_server_test.go @@ -47,6 +47,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/proxy" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -142,7 +143,7 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httpte config := goconf.NewConfigFile() config.AddOption("tokens", TokenIdForTest, pubkey.Name()) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) proxy, err = NewProxyServer(ctx, r, "0.0", config) require.NoError(err) diff --git a/cmd/proxy/proxy_tokens_etcd_test.go b/cmd/proxy/proxy_tokens_etcd_test.go index 308fd86..32a44e8 100644 --- a/cmd/proxy/proxy_tokens_etcd_test.go +++ b/cmd/proxy/proxy_tokens_etcd_test.go @@ -41,7 +41,7 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -97,7 +97,7 @@ func newTokensEtcdForTesting(t *testing.T) (*tokensEtcd, *embed.Etcd) { cfg.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) cfg.AddOption("tokens", "keyformat", "/%s, /testing/%s/key") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) tokens, err := NewProxyTokensEtcd(logger, cfg) require.NoError(t, err) t.Cleanup(func() { diff --git a/dns/test_helpers.go b/dns/test_helpers.go index 030bb49..998ff5e 100644 --- a/dns/test_helpers.go +++ b/dns/test_helpers.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) type MockLookup struct { @@ -81,7 +81,7 @@ func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *MockLookup) t.Helper() require := require.New(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) var lookupFunc MonitorLookupFunc if lookup != nil { lookupFunc = lookup.lookup diff --git a/etcd/client_test.go b/etcd/client_test.go index b789125..7a6862a 100644 --- a/etcd/client_test.go +++ b/etcd/client_test.go @@ -45,6 +45,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -140,7 +141,7 @@ func NewClientForTest(t *testing.T) (*embed.Etcd, Client) { config.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) config.AddOption("etcd", "loglevel", "error") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) client, err := NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { @@ -159,7 +160,7 @@ func NewEtcdClientWithTLSForTest(t *testing.T) (*embed.Etcd, Client) { config.AddOption("etcd", "clientcert", certfile) config.AddOption("etcd", "cacert", certfile) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) client, err := NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { @@ -184,7 +185,7 @@ func DeleteValue(etcd *embed.Etcd, key string) { func Test_EtcdClient_Get(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) @@ -229,7 +230,7 @@ func Test_EtcdClient_Get(t *testing.T) { func Test_EtcdClientTLS_Get(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) require := require.New(t) @@ -274,7 +275,7 @@ func Test_EtcdClientTLS_Get(t *testing.T) { func Test_EtcdClient_GetPrefix(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd, client := NewClientForTest(t) @@ -386,7 +387,7 @@ func (l *EtcdClientTestListener) EtcdKeyDeleted(client Client, key string, prevV func Test_EtcdClient_Watch(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) etcd, client := NewClientForTest(t) diff --git a/etcd/etcdtest/etcdtest.go b/etcd/etcdtest/etcdtest.go index 4a8603d..cd4f7c7 100644 --- a/etcd/etcdtest/etcdtest.go +++ b/etcd/etcdtest/etcdtest.go @@ -46,7 +46,7 @@ import ( "go.uber.org/zap/zaptest" "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -129,7 +129,7 @@ func NewServerForTest(t *testing.T) *Server { func NewEtcdClientForTest(t *testing.T, server *Server) etcd.Client { t.Helper() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", server.URL().String()) diff --git a/geoip/maxmind_test.go b/geoip/maxmind_test.go index 82d4657..c73c450 100644 --- a/geoip/maxmind_test.go +++ b/geoip/maxmind_test.go @@ -36,7 +36,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) func testLookupReader(t *testing.T, reader *Lookup) { @@ -79,7 +79,7 @@ func GetIpUrlForTest(t *testing.T) string { func TestLookup(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) reader, err := NewLookupFromUrl(logger, GetIpUrlForTest(t)) require.NoError(err) @@ -92,7 +92,7 @@ func TestLookup(t *testing.T) { func TestLookupCaching(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) reader, err := NewLookupFromUrl(logger, GetIpUrlForTest(t)) require.NoError(err) @@ -133,7 +133,7 @@ func TestLookupContinent(t *testing.T) { func TestLookupCloseEmpty(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) reader, err := NewLookupFromUrl(logger, "ignore-url") require.NoError(t, err) reader.Close() @@ -141,7 +141,7 @@ func TestLookupCloseEmpty(t *testing.T) { func TestLookupFromFile(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) geoIpUrl := GetIpUrlForTest(t) diff --git a/geoip/overrides_test.go b/geoip/overrides_test.go index 68845c2..bb893a5 100644 --- a/geoip/overrides_test.go +++ b/geoip/overrides_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) func mustSucceed1[T any, A1 any](t *testing.T, f func(a1 A1) (T, bool), a1 A1) T { @@ -48,7 +49,7 @@ func TestOverridesEmpty(t *testing.T) { assert := assert.New(t) config := goconf.NewConfigFile() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) overrides, err := LoadOverrides(ctx, config, true) @@ -69,7 +70,7 @@ func TestOverrides(t *testing.T) { config.AddOption("geoip-overrides", "10.4.5.6", "loopback") config.AddOption("geoip-overrides", "192.168.1.0", "") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) overrides, err := LoadOverrides(ctx, config, true) @@ -101,7 +102,7 @@ func TestOverridesInvalidIgnoreErrors(t *testing.T) { config.AddOption("geoip-overrides", "300.1.2.3/8", "DE") config.AddOption("geoip-overrides", "10.2.0.0/16", "FR") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) overrides, err := LoadOverrides(ctx, config, true) @@ -127,7 +128,7 @@ func TestOverridesInvalidIPReturnErrors(t *testing.T) { config.AddOption("geoip-overrides", "invalid-ip", "DE") config.AddOption("geoip-overrides", "10.2.0.0/16", "FR") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) overrides, err := LoadOverrides(ctx, config, false) @@ -143,7 +144,7 @@ func TestOverridesInvalidCIDRReturnErrors(t *testing.T) { config.AddOption("geoip-overrides", "300.1.2.3/8", "DE") config.AddOption("geoip-overrides", "10.2.0.0/16", "FR") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) overrides, err := LoadOverrides(ctx, config, false) diff --git a/grpc/client_test.go b/grpc/client_test.go index 59ddcbe..7ab9e47 100644 --- a/grpc/client_test.go +++ b/grpc/client_test.go @@ -36,12 +36,13 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) client, err := NewClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") require.NoError(t, err) @@ -67,7 +68,7 @@ func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup config.AddOption("grpc", "targettype", "etcd") config.AddOption("grpc", "targetprefix", "/grpctargets") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) etcdClient, err := etcd.NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { @@ -89,7 +90,7 @@ func waitForEvent(ctx context.Context, t *testing.T, ch <-chan struct{}) { } func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) @@ -174,7 +175,7 @@ func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { } func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { _, addr1 := NewServerForTest(t) @@ -197,7 +198,7 @@ func Test_GrpcClients_EtcdInitial(t *testing.T) { // nolint:paralleltest func Test_GrpcClients_EtcdUpdate(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) embedEtcd := etcdtest.NewServerForTest(t) @@ -244,7 +245,7 @@ func Test_GrpcClients_EtcdUpdate(t *testing.T) { func Test_GrpcClients_EtcdIgnoreSelf(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) embedEtcd := etcdtest.NewServerForTest(t) diff --git a/grpc/server_test.go b/grpc/server_test.go index aa27e52..273c261 100644 --- a/grpc/server_test.go +++ b/grpc/server_test.go @@ -50,6 +50,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -82,7 +83,7 @@ func (s *Server) WaitForCertPoolReload(ctx context.Context, counter uint64) erro } func NewServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *Server, addr string) { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) for port := 50000; port < 50100; port++ { addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) @@ -185,7 +186,7 @@ func TestServer_ReloadCerts(t *testing.T) { func TestServer_ReloadCA(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) serverKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(err) @@ -335,7 +336,7 @@ type testServerHub struct { func newTestServerHub(t *testing.T) *testServerHub { t.Helper() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) cfg := goconf.NewConfigFile() cfg.AddOption(testBackendId, "secret", "not-so-secret") diff --git a/grpc/test/client.go b/grpc/test/client.go index 52a8f86..b58d735 100644 --- a/grpc/test/client.go +++ b/grpc/test/client.go @@ -34,11 +34,12 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) client, err := grpc.NewClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") require.NoError(t, err) @@ -64,7 +65,7 @@ func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup config.AddOption("grpc", "targettype", "etcd") config.AddOption("grpc", "targetprefix", "/grpctargets") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) etcdClient, err := etcd.NewClient(logger, config, "") require.NoError(t, err) t.Cleanup(func() { diff --git a/grpc/test/server.go b/grpc/test/server.go index 9ad8b98..fd1ba21 100644 --- a/grpc/test/server.go +++ b/grpc/test/server.go @@ -32,11 +32,12 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) func NewServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *grpc.Server, addr string) { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) for port := 50000; port < 50100; port++ { addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) diff --git a/log/logging_test.go b/log/logging_test.go index c3bb64f..c07e27c 100644 --- a/log/logging_test.go +++ b/log/logging_test.go @@ -22,6 +22,9 @@ package log import ( + "bytes" + "log" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -41,11 +44,29 @@ func TestGlobalLogger(t *testing.T) { assert.Fail("should have paniced", "got logger %+v", logger) } +type testLogWriter struct { + mu sync.Mutex + t testing.TB +} + +func (w *testLogWriter) Write(b []byte) (int, error) { + w.t.Helper() + if !bytes.HasSuffix(b, []byte("\n")) { + b = append(b, '\n') + } + w.mu.Lock() + defer w.mu.Unlock() + w.t.Logf("%s", string(b)) + return len(b), nil +} + func TestLoggerContext(t *testing.T) { t.Parallel() assert := assert.New(t) - testLogger := NewLoggerForTest(t) + testLogger := log.New(&testLogWriter{ + t: t, + }, t.Name()+": ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) testLogger.Printf("Hello %s!", "world") ctx := NewLoggerContext(t.Context(), testLogger) diff --git a/log/test_helpers.go b/log/test/log.go similarity index 82% rename from log/test_helpers.go rename to log/test/log.go index 8eee5d6..b1acc6d 100644 --- a/log/test_helpers.go +++ b/log/test/log.go @@ -19,15 +19,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package log +package test import ( "bytes" - "log" + stdlog "log" "sync" "testing" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/log" ) type testLogWriter struct { @@ -46,17 +47,17 @@ func (w *testLogWriter) Write(b []byte) (int, error) { } var ( - testLoggers internal.TestStorage[Logger] + testLoggers internal.TestStorage[log.Logger] ) -func NewLoggerForTest(t testing.TB) Logger { +func NewLoggerForTest(t testing.TB) log.Logger { t.Helper() logger, found := testLoggers.Get(t) if !found { - logger = log.New(&testLogWriter{ + logger = stdlog.New(&testLogWriter{ t: t, - }, t.Name()+": ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) + }, t.Name()+": ", stdlog.LstdFlags|stdlog.Lmicroseconds|stdlog.Lshortfile) testLoggers.Set(t, logger) } diff --git a/log/test_helpers_24.go b/log/test/log_24.go similarity index 98% rename from log/test_helpers_24.go rename to log/test/log_24.go index c55c540..29ed970 100644 --- a/log/test_helpers_24.go +++ b/log/test/log_24.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package log +package test import ( "testing" diff --git a/log/test_helpers_25.go b/log/test/log_25.go similarity index 98% rename from log/test_helpers_25.go rename to log/test/log_25.go index 74a0769..8fad2b3 100644 --- a/log/test_helpers_25.go +++ b/log/test/log_25.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package log +package test import ( "testing" diff --git a/log/test/log_test.go b/log/test/log_test.go new file mode 100644 index 0000000..174bff3 --- /dev/null +++ b/log/test/log_test.go @@ -0,0 +1,39 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoggerForTest(t *testing.T) { + t.Parallel() + + log1 := NewLoggerForTest(t) + log2 := NewLoggerForTest(t) + assert.Equal(t, log1, log2) + + log1.Printf("Test output") + log1.Println("Test output") +} diff --git a/nats/loopback_test.go b/nats/loopback_test.go index 01357f0..968a29a 100644 --- a/nats/loopback_test.go +++ b/nats/loopback_test.go @@ -29,11 +29,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) func CreateLoopbackClientForTest(t *testing.T) Client { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) result, err := NewLoopbackClient(logger) require.NoError(t, err) t.Cleanup(func() { diff --git a/nats/native_test.go b/nats/native_test.go index e5d7c20..6d3b0d4 100644 --- a/nats/native_test.go +++ b/nats/native_test.go @@ -35,6 +35,7 @@ import ( "github.com/nats-io/nats.go" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -59,7 +60,7 @@ func StartLocalServerPort(t *testing.T, port int) (*server.Server, int) { func CreateLocalClientForTest(t *testing.T, options ...nats.Option) (*server.Server, int, Client) { t.Helper() server, port := StartLocalServer(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) result, err := NewClient(ctx, server.ClientURL(), options...) require.NoError(t, err) diff --git a/nats/test/server_test.go b/nats/test/server_test.go index 9cd9746..5855307 100644 --- a/nats/test/server_test.go +++ b/nats/test/server_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/nats" ) @@ -42,7 +43,7 @@ func TestLocalServer(t *testing.T) { server, port := StartLocalServer(t) assert.NotEqual(0, port) - ctx := log.NewLoggerContext(t.Context(), log.NewLoggerForTest(t)) + ctx := log.NewLoggerContext(t.Context(), logtest.NewLoggerForTest(t)) client, err := nats.NewClient(ctx, server.ClientURL()) require.NoError(err) @@ -56,7 +57,7 @@ func TestWaitForSubscriptionsEmpty(t *testing.T) { require := require.New(t) assert := assert.New(t) - ctx := log.NewLoggerContext(t.Context(), log.NewLoggerForTest(t)) + ctx := log.NewLoggerContext(t.Context(), logtest.NewLoggerForTest(t)) client, err := nats.NewClient(ctx, nats.LoopbackUrl) require.NoError(err) diff --git a/security/internal/file_watcher_test.go b/security/internal/file_watcher_test.go index 948099b..614ca4a 100644 --- a/security/internal/file_watcher_test.go +++ b/security/internal/file_watcher_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -42,7 +42,7 @@ func TestFileWatcher_NotExist(t *testing.T) { t.Parallel() assert := assert.New(t) tmpdir := t.TempDir() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) if w, err := NewFileWatcher(logger, path.Join(tmpdir, "test.txt"), func(filename string) {}, DefaultDeduplicateWatchEvents); !assert.ErrorIs(err, os.ErrNotExist) { if w != nil { assert.NoError(w.Close()) @@ -58,7 +58,7 @@ func TestFileWatcher_File(t *testing.T) { // nolint:paralleltest filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -101,7 +101,7 @@ func TestFileWatcher_CurrentDir(t *testing.T) { // nolint:paralleltest filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, "./"+path.Base(filename), func(filename string) { modified <- struct{}{} @@ -143,7 +143,7 @@ func TestFileWatcher_Rename(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.WriteFile(filename, []byte("Hello world!"), 0644)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -187,7 +187,7 @@ func TestFileWatcher_Symlink(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename, filename)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -222,7 +222,7 @@ func TestFileWatcher_ChangeSymlinkTarget(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename1, filename)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -259,7 +259,7 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { filename := path.Join(tmpdir, "symlink.txt") require.NoError(os.Symlink(sourceFilename1, filename)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -290,7 +290,7 @@ func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.Symlink(sourceFilename1, filename)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} @@ -344,7 +344,7 @@ func TestFileWatcher_UpdateSymlinkFolder(t *testing.T) { filename := path.Join(tmpdir, "test.txt") require.NoError(os.Symlink("data/test.txt", filename)) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) modified := make(chan struct{}) w, err := NewFileWatcher(logger, filename, func(filename string) { modified <- struct{}{} diff --git a/server/backend_server_test.go b/server/backend_server_test.go index 94962ed..213e3bc 100644 --- a/server/backend_server_test.go +++ b/server/backend_server_test.go @@ -52,6 +52,7 @@ import ( grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/nats" natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -107,7 +108,7 @@ func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFil config.AddOption("clients", "internalsecret", string(testInternalSecret)) config.AddOption("geoip", "url", "none") events := eventstest.GetAsyncEventsForTest(t) - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) hub, err := NewHub(ctx, config, events, nil, nil, nil, r, "no-version") require.NoError(err) @@ -169,7 +170,7 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g config1.AddOption("clients", "internalsecret", string(testInternalSecret)) config1.AddOption("geoip", "url", "none") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) events1, err := events.NewAsyncEvents(ctx, nats.ClientURL()) @@ -404,7 +405,7 @@ func TestBackendServer_RoomInvite(t *testing.T) { for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomInvite(ctx, t) }) @@ -474,7 +475,7 @@ func TestBackendServer_RoomDisinvite(t *testing.T) { for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomDisinvite(ctx, t) }) @@ -555,7 +556,7 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) { func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -638,7 +639,7 @@ func TestBackendServer_RoomUpdate(t *testing.T) { for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomUpdate(ctx, t) }) @@ -710,7 +711,7 @@ func TestBackendServer_RoomDelete(t *testing.T) { for _, backend := range eventstest.EventBackendsForTest { t.Run(backend, func(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) RunTestBackendServer_RoomDelete(ctx, t) }) @@ -779,7 +780,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -877,7 +878,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -942,7 +943,7 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1105,7 +1106,7 @@ func TestBackendServer_InCallAll(t *testing.T) { for _, subtest := range clusteredTests { t.Run(subtest, func(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1277,7 +1278,7 @@ func TestBackendServer_InCallAll(t *testing.T) { func TestBackendServer_RoomMessage(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1447,7 +1448,7 @@ func Test_IsNumeric(t *testing.T) { func TestBackendServer_DialoutNoSipBridge(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1491,7 +1492,7 @@ func TestBackendServer_DialoutNoSipBridge(t *testing.T) { func TestBackendServer_DialoutAccepted(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1578,7 +1579,7 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1665,7 +1666,7 @@ func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { func TestBackendServer_DialoutRejected(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -1750,7 +1751,7 @@ func TestBackendServer_DialoutRejected(t *testing.T) { func TestBackendServer_DialoutFirstFailed(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) diff --git a/server/hub_sfu_janus_test.go b/server/hub_sfu_janus_test.go index 7bdc0aa..5d30758 100644 --- a/server/hub_sfu_janus_test.go +++ b/server/hub_sfu_janus_test.go @@ -35,6 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/sfu" sfujanus "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" @@ -56,7 +57,7 @@ func newMcuJanusForTesting(t *testing.T) (JanusSFU, *janustest.JanusGateway) { if strings.Contains(t.Name(), "Filter") { config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") } - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) mcu, err := sfujanus.NewJanusSFUWithGateway(ctx, gateway, config) require.NoError(t, err) diff --git a/server/hub_test.go b/server/hub_test.go index 39fd8c0..b5b9388 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -61,6 +61,7 @@ import ( grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/mock" natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/session" @@ -164,7 +165,7 @@ func getTestConfigWithMultipleUrls(server *httptest.Server) (*goconf.ConfigFile, } func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, events.AsyncEvents, *mux.Router, *httptest.Server) { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() @@ -215,7 +216,7 @@ func CreateHubWithMultipleUrlsForTest(t *testing.T) (*Hub, events.AsyncEvents, * } func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, *Hub, *mux.Router, *mux.Router, *httptest.Server, *httptest.Server) { - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -5037,7 +5038,7 @@ func TestGeoipOverrides(t *testing.T) { func TestDialoutStatus(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) diff --git a/server/room_ping_test.go b/server/room_ping_test.go index a0b011b..5667e2c 100644 --- a/server/room_ping_test.go +++ b/server/room_ping_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -62,7 +63,7 @@ func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) func TestSingleRoomPing(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) @@ -106,7 +107,7 @@ func TestSingleRoomPing(t *testing.T) { func TestMultiRoomPing(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) @@ -146,7 +147,7 @@ func TestMultiRoomPing(t *testing.T) { func TestMultiRoomPing_Separate(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) @@ -182,7 +183,7 @@ func TestMultiRoomPing_Separate(t *testing.T) { func TestMultiRoomPing_DeleteRoom(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) u, ping := NewRoomPingForTest(ctx, t) diff --git a/server/room_test.go b/server/room_test.go index f88f94d..6da0685 100644 --- a/server/room_test.go +++ b/server/room_test.go @@ -36,6 +36,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) @@ -80,7 +81,7 @@ func TestRoom_InCall(t *testing.T) { func TestRoom_Update(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -175,7 +176,7 @@ loop: func TestRoom_Delete(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -273,7 +274,7 @@ loop: func TestRoom_RoomJoinFeatures(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -311,7 +312,7 @@ func TestRoom_RoomJoinFeatures(t *testing.T) { func TestRoom_RoomSessionData(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -355,7 +356,7 @@ func TestRoom_RoomSessionData(t *testing.T) { func TestRoom_InCallAll(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) diff --git a/sfu/janus/events_handler_test.go b/sfu/janus/events_handler_test.go index 66cf697..b38bb6e 100644 --- a/sfu/janus/events_handler_test.go +++ b/sfu/janus/events_handler_test.go @@ -40,6 +40,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" "github.com/strukturag/nextcloud-spreed-signaling/sfu" sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" @@ -70,7 +71,7 @@ func (h *TestJanusEventsServerHandler) ServeHTTP(w http.ResponseWriter, r *http. if host, _, err := net.SplitHostPort(addr); err == nil { addr = host } - logger := log.NewLoggerForTest(h.t) + logger := logtest.NewLoggerForTest(h.t) ctx := log.NewLoggerContext(r.Context(), logger) RunEventsHandler(ctx, h.mcu, conn, addr, r.Header.Get("User-Agent")) return diff --git a/sfu/janus/janus_test.go b/sfu/janus/janus_test.go index 6f58b26..5906b50 100644 --- a/sfu/janus/janus_test.go +++ b/sfu/janus/janus_test.go @@ -36,6 +36,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/sfu" @@ -59,7 +60,7 @@ func newMcuJanusForTesting(t *testing.T) (*janusSFU, *janustest.JanusGateway) { if strings.Contains(t.Name(), "Filter") { config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") } - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) mcu, err := NewJanusSFU(ctx, "", config) require.NoError(t, err) diff --git a/sfu/janus/mcu_test.go b/sfu/janus/mcu_test.go index 1f1714a..971ea87 100644 --- a/sfu/janus/mcu_test.go +++ b/sfu/janus/mcu_test.go @@ -34,7 +34,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -193,7 +193,7 @@ func (c *TestMCUClient) MaxBitrate() api.Bandwidth { func (c *TestMCUClient) Close(ctx context.Context) { if c.closed.CompareAndSwap(false, true) { - logger := log.NewLoggerForTest(c.t) + logger := logtest.NewLoggerForTest(c.t) logger.Printf("Close MCU client %s", c.id) } } diff --git a/sfu/proxy/config_etcd_test.go b/sfu/proxy/config_etcd_test.go index a95741d..9bc1561 100644 --- a/sfu/proxy/config_etcd_test.go +++ b/sfu/proxy/config_etcd_test.go @@ -31,7 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) type TestProxyInformationEtcd struct { @@ -45,7 +45,7 @@ func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.TestServer, Con embedEtcd, client := etcdtest.NewClientForTest(t) cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "keyprefix", "proxies/") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) p, err := NewConfigEtcd(logger, cfg, client, proxy) require.NoError(t, err) t.Cleanup(func() { diff --git a/sfu/proxy/config_static_test.go b/sfu/proxy/config_static_test.go index 9fb093d..e5ad95b 100644 --- a/sfu/proxy/config_static_test.go +++ b/sfu/proxy/config_static_test.go @@ -31,7 +31,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dns.MockLookup, urls ...string) (Config, *dns.Monitor) { @@ -41,7 +41,7 @@ func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, looku cfg.AddOption("mcu", "dnsdiscovery", "true") } dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) p, err := NewConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) t.Cleanup(func() { diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go index 5ecfcd5..81864b3 100644 --- a/sfu/proxy/proxy_test.go +++ b/sfu/proxy/proxy_test.go @@ -47,6 +47,7 @@ import ( grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" "github.com/strukturag/nextcloud-spreed-signaling/proxy" "github.com/strukturag/nextcloud-spreed-signaling/sfu" @@ -241,7 +242,7 @@ func newMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOpt etcdConfig.AddOption("etcd", "endpoints", options.Etcd.URL().String()) etcdConfig.AddOption("etcd", "loglevel", "error") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) etcdClient, err := etcd.NewClient(logger, etcdConfig, "") require.NoError(err) diff --git a/sfu/proxy/test/proxy.go b/sfu/proxy/test/proxy.go index e6e9262..64bf5e9 100644 --- a/sfu/proxy/test/proxy.go +++ b/sfu/proxy/test/proxy.go @@ -42,6 +42,7 @@ import ( grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy" "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" @@ -96,7 +97,7 @@ func NewMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOpt etcdConfig.AddOption("etcd", "endpoints", options.Etcd.URL().String()) etcdConfig.AddOption("etcd", "loglevel", "error") - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) etcdClient, err := etcd.NewClient(logger, etcdConfig, "") require.NoError(err) diff --git a/sfu/test/sfu.go b/sfu/test/sfu.go index 130f92f..bd3db0a 100644 --- a/sfu/test/sfu.go +++ b/sfu/test/sfu.go @@ -34,7 +34,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" @@ -193,7 +193,7 @@ func (c *SFUClient) MaxBitrate() api.Bandwidth { func (c *SFUClient) Close(ctx context.Context) { if c.closed.CompareAndSwap(false, true) { - logger := log.NewLoggerForTest(c.t) + logger := logtest.NewLoggerForTest(c.t) logger.Printf("Close SFU client %s", c.id) } } diff --git a/talk/backend_client_test.go b/talk/backend_client_test.go index accf94c..643be3f 100644 --- a/talk/backend_client_test.go +++ b/talk/backend_client_test.go @@ -36,6 +36,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/pool" ) @@ -69,7 +70,7 @@ func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { func TestPostOnRedirect(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() @@ -117,7 +118,7 @@ func TestPostOnRedirect(t *testing.T) { func TestPostOnRedirectDifferentHost(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) r := mux.NewRouter() @@ -154,7 +155,7 @@ func TestPostOnRedirectDifferentHost(t *testing.T) { func TestPostOnRedirectStatusFound(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) @@ -197,7 +198,7 @@ func TestPostOnRedirectStatusFound(t *testing.T) { func TestHandleThrottled(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) require := require.New(t) assert := assert.New(t) diff --git a/talk/backend_configuration_test.go b/talk/backend_configuration_test.go index 47b8fb9..bb3d7e8 100644 --- a/talk/backend_configuration_test.go +++ b/talk/backend_configuration_test.go @@ -34,7 +34,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -114,7 +114,7 @@ func (s *mockBackendStats) DecBackends() { func TestIsUrlAllowed_Compat(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) // Old-style configuration valid_urls := []string{ "http://domain.invalid", @@ -136,7 +136,7 @@ func TestIsUrlAllowed_Compat(t *testing.T) { func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) // Old-style configuration, force HTTPS valid_urls := []string{ "https://domain.invalid", @@ -157,7 +157,7 @@ func TestIsUrlAllowed_CompatForceHttps(t *testing.T) { func TestIsUrlAllowed(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) valid_urls := [][]string{ {"https://domain.invalid/foo", string(testBackendSecret) + "-foo"}, {"https://domain.invalid/foo/", string(testBackendSecret) + "-foo"}, @@ -202,7 +202,7 @@ func TestIsUrlAllowed(t *testing.T) { func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) valid_urls := []string{} invalid_urls := []string{ "http://domain.invalid", @@ -219,7 +219,7 @@ func TestIsUrlAllowed_EmptyAllowlist(t *testing.T) { func TestIsUrlAllowed_AllowAll(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) valid_urls := []string{ "http://domain.invalid", "https://domain.invalid", @@ -265,7 +265,7 @@ func TestBackendReloadNoChange(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -301,7 +301,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -342,7 +342,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -379,7 +379,7 @@ func TestBackendReloadAddBackend(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -420,7 +420,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -458,7 +458,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) original_config := goconf.NewConfigFile() @@ -512,7 +512,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) embedEtcd, client := etcdtest.NewClientForTest(t) @@ -629,7 +629,7 @@ func TestBackendConfiguration_EtcdCompat(t *testing.T) { func TestBackendCommonSecret(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) u1, err := url.Parse("http://domain1.invalid") @@ -672,7 +672,7 @@ func TestBackendChangeUrls(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) u1, err := url.Parse("http://domain1.invalid/") @@ -762,7 +762,7 @@ func TestBackendConfiguration_EtcdChangeUrls(t *testing.T) { t.Parallel() stats := &mockBackendStats{} - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) require := require.New(t) assert := assert.New(t) embedEtcd, client := etcdtest.NewClientForTest(t) diff --git a/talk/backend_storage_etcd_test.go b/talk/backend_storage_etcd_test.go index de8f310..6adca58 100644 --- a/talk/backend_storage_etcd_test.go +++ b/talk/backend_storage_etcd_test.go @@ -29,7 +29,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" - "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -56,7 +56,7 @@ func (tl *testListener) EtcdClientCreated(client etcd.Client) { } func Test_BackendStorageEtcdNoLeak(t *testing.T) { // nolint:paralleltest - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { embedEtcd, client := etcdtest.NewClientForTest(t) tl := &testListener{ diff --git a/talk/capabilities_test.go b/talk/capabilities_test.go index 2f64aaa..d2ceacd 100644 --- a/talk/capabilities_test.go +++ b/talk/capabilities_test.go @@ -43,6 +43,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/pool" ) @@ -182,7 +183,7 @@ func SetCapabilitiesGetNow(t *testing.T, capabilities *Capabilities, f func() ti func TestCapabilities(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) url, capabilities := NewCapabilitiesForTest(t) @@ -226,7 +227,7 @@ func TestCapabilities(t *testing.T) { func TestInvalidateCapabilities(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -287,7 +288,7 @@ func TestInvalidateCapabilities(t *testing.T) { func TestCapabilitiesNoCache(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -332,7 +333,7 @@ func TestCapabilitiesNoCache(t *testing.T) { func TestCapabilitiesShortCache(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -387,7 +388,7 @@ func TestCapabilitiesShortCache(t *testing.T) { func TestCapabilitiesNoCacheETag(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -429,7 +430,7 @@ func TestCapabilitiesNoCacheETag(t *testing.T) { func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -470,7 +471,7 @@ func TestCapabilitiesCacheNoMustRevalidate(t *testing.T) { func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -511,7 +512,7 @@ func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) { func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 @@ -550,7 +551,7 @@ func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) { func TestConcurrentExpired(t *testing.T) { t.Parallel() - logger := log.NewLoggerForTest(t) + logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) assert := assert.New(t) var called atomic.Uint32 From a4631a19cfe482b0fb2d0b74af447a975316f17d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 14:01:29 +0100 Subject: [PATCH 458/549] Move test storage to "test" package. --- log/test/log.go | 4 ++-- server/hub_test.go | 6 +++--- internal/test_storage.go => test/storage.go | 12 ++++++------ .../test_storage_test.go => test/storage_test.go | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) rename internal/test_storage.go => test/storage.go (86%) rename internal/test_storage_test.go => test/storage_test.go (97%) diff --git a/log/test/log.go b/log/test/log.go index b1acc6d..2524a31 100644 --- a/log/test/log.go +++ b/log/test/log.go @@ -27,8 +27,8 @@ import ( "sync" "testing" - "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) type testLogWriter struct { @@ -47,7 +47,7 @@ func (w *testLogWriter) Write(b []byte) (int, error) { } var ( - testLoggers internal.TestStorage[log.Logger] + testLoggers test.Storage[log.Logger] ) func NewLoggerForTest(t testing.TB) log.Logger { diff --git a/server/hub_test.go b/server/hub_test.go index b5b9388..680eb59 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -553,7 +553,7 @@ func processSessionRequest(t *testing.T, w http.ResponseWriter, r *http.Request, } var ( - pingRequests internal.TestStorage[[]*talk.BackendClientRequest] + pingRequests test.Storage[[]*talk.BackendClientRequest] ) func getPingRequests(t *testing.T) []*talk.BackendClientRequest { @@ -599,7 +599,7 @@ type testAuthToken struct { } var ( - authTokens internal.TestStorage[testAuthToken] + authTokens test.Storage[testAuthToken] ) func ensureAuthTokens(t *testing.T) (string, string) { @@ -708,7 +708,7 @@ func registerBackendHandler(t *testing.T, router *mux.Router) { } var ( - skipV2Capabilities internal.TestStorage[bool] + skipV2Capabilities test.Storage[bool] ) func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { diff --git a/internal/test_storage.go b/test/storage.go similarity index 86% rename from internal/test_storage.go rename to test/storage.go index b0007e3..7d1f694 100644 --- a/internal/test_storage.go +++ b/test/storage.go @@ -19,20 +19,20 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package internal +package test import ( "sync" "testing" ) -type TestStorage[T any] struct { +type Storage[T any] struct { mu sync.Mutex // +checklocks:mu entries map[string]T // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 } -func (s *TestStorage[T]) cleanup(key string) { +func (s *Storage[T]) cleanup(key string) { s.mu.Lock() defer s.mu.Unlock() @@ -42,7 +42,7 @@ func (s *TestStorage[T]) cleanup(key string) { } } -func (s *TestStorage[T]) Set(tb testing.TB, value T) { +func (s *Storage[T]) Set(tb testing.TB, value T) { s.mu.Lock() defer s.mu.Unlock() @@ -59,7 +59,7 @@ func (s *TestStorage[T]) Set(tb testing.TB, value T) { s.entries[key] = value } -func (s *TestStorage[T]) Get(tb testing.TB) (T, bool) { +func (s *Storage[T]) Get(tb testing.TB) (T, bool) { s.mu.Lock() defer s.mu.Unlock() @@ -72,7 +72,7 @@ func (s *TestStorage[T]) Get(tb testing.TB) (T, bool) { return defaultValue, false } -func (s *TestStorage[T]) Del(tb testing.TB) { +func (s *Storage[T]) Del(tb testing.TB) { key := tb.Name() s.cleanup(key) } diff --git a/internal/test_storage_test.go b/test/storage_test.go similarity index 97% rename from internal/test_storage_test.go rename to test/storage_test.go index 0e63cca..32d6729 100644 --- a/internal/test_storage_test.go +++ b/test/storage_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 internal +package test import ( "testing" @@ -30,7 +30,7 @@ import ( func Test_TestStorage(t *testing.T) { t.Parallel() assert := assert.New(t) - var storage TestStorage[int] + var storage Storage[int] t.Cleanup(func() { storage.mu.Lock() From 69d63ffd11f6c9ad198ddf7c6d572e0bfd8c5cc8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 14:34:44 +0100 Subject: [PATCH 459/549] Move dns test helpers to separate package. --- .../mock_lookup.go} | 33 +-------- dns/internal/mock_lookup_test.go | 58 ++++++++++++++++ dns/monitor_test.go | 26 ++++++- dns/test/dns.go | 59 ++++++++++++++++ dns/test/dns_test.go | 68 +++++++++++++++++++ grpc/client_test.go | 13 ++-- grpc/test/client.go | 9 +-- sfu/proxy/config_static_test.go | 7 +- sfu/proxy/proxy_test.go | 10 +-- sfu/proxy/test/proxy.go | 4 +- 10 files changed, 235 insertions(+), 52 deletions(-) rename dns/{test_helpers.go => internal/mock_lookup.go} (68%) create mode 100644 dns/internal/mock_lookup_test.go create mode 100644 dns/test/dns.go create mode 100644 dns/test/dns_test.go diff --git a/dns/test_helpers.go b/dns/internal/mock_lookup.go similarity index 68% rename from dns/test_helpers.go rename to dns/internal/mock_lookup.go index 998ff5e..be7fcdf 100644 --- a/dns/test_helpers.go +++ b/dns/internal/mock_lookup.go @@ -19,17 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package dns +package internal import ( "net" "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" - - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) type MockLookup struct { @@ -39,8 +33,7 @@ type MockLookup struct { ips map[string][]net.IP } -func NewMockLookupForTest(t *testing.T) *MockLookup { - t.Helper() +func NewMockLookup() *MockLookup { mock := &MockLookup{ ips: make(map[string][]net.IP), } @@ -61,7 +54,7 @@ func (m *MockLookup) Get(host string) []net.IP { return m.ips[host] } -func (m *MockLookup) lookup(host string) ([]net.IP, error) { +func (m *MockLookup) Lookup(host string) ([]net.IP, error) { m.RLock() defer m.RUnlock() @@ -76,23 +69,3 @@ func (m *MockLookup) lookup(host string) ([]net.IP, error) { return append([]net.IP{}, ips...), nil } - -func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *MockLookup) *Monitor { - t.Helper() - require := require.New(t) - - logger := logtest.NewLoggerForTest(t) - var lookupFunc MonitorLookupFunc - if lookup != nil { - lookupFunc = lookup.lookup - } - monitor, err := NewMonitor(logger, interval, lookupFunc) - require.NoError(err) - - t.Cleanup(func() { - monitor.Stop() - }) - - require.NoError(monitor.Start()) - return monitor -} diff --git a/dns/internal/mock_lookup_test.go b/dns/internal/mock_lookup_test.go new file mode 100644 index 0000000..9bcf7c8 --- /dev/null +++ b/dns/internal/mock_lookup_test.go @@ -0,0 +1,58 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 internal + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMockLookup(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + host1 := "domain1.invalid" + host2 := "domain2.invalid" + + lookup := NewMockLookup() + assert.Empty(lookup.Get(host1)) + assert.Empty(lookup.Get(host2)) + + ips := []net.IP{ + net.ParseIP("1.2.3.4"), + } + lookup.Set(host1, ips) + assert.Equal(ips, lookup.Get(host1)) + assert.Empty(lookup.Get(host2)) + + if resolved, err := lookup.Lookup(host1); assert.NoError(err) { + assert.Equal(ips, resolved) + } + var de *net.DNSError + if resolved, err := lookup.Lookup(host2); assert.ErrorAs(err, &de, "expected error, got %+v", resolved) { + assert.True(de.IsNotFound) + assert.Equal(host2, de.Name) + } +} diff --git a/dns/monitor_test.go b/dns/monitor_test.go index f8d8546..f8316b1 100644 --- a/dns/monitor_test.go +++ b/dns/monitor_test.go @@ -33,8 +33,30 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/dns/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) +func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *internal.MockLookup) *Monitor { + t.Helper() + require := require.New(t) + + logger := logtest.NewLoggerForTest(t) + var lookupFunc MonitorLookupFunc + if lookup != nil { + lookupFunc = lookup.Lookup + } + monitor, err := NewMonitor(logger, interval, lookupFunc) + require.NoError(err) + + t.Cleanup(func() { + monitor.Stop() + }) + + require.NoError(monitor.Start()) + return monitor +} + type monitorReceiverRecord struct { all []net.IP add []net.IP @@ -158,7 +180,7 @@ func (r *monitorReceiver) ExpectNone() { func TestMonitor(t *testing.T) { t.Parallel() - lookup := NewMockLookupForTest(t) + lookup := internal.NewMockLookup() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -339,7 +361,7 @@ func (r *deadlockMonitorReceiver) Close() { func TestMonitorDeadlock(t *testing.T) { t.Parallel() - lookup := NewMockLookupForTest(t) + lookup := internal.NewMockLookup() ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") lookup.Set("foo", []net.IP{ip1}) diff --git a/dns/test/dns.go b/dns/test/dns.go new file mode 100644 index 0000000..a6aa729 --- /dev/null +++ b/dns/test/dns.go @@ -0,0 +1,59 @@ +/** + * 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 test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/dns" + "github.com/strukturag/nextcloud-spreed-signaling/dns/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" +) + +type MockLookup = internal.MockLookup + +func NewMockLookup() *MockLookup { + return internal.NewMockLookup() +} + +func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *MockLookup) *dns.Monitor { + t.Helper() + require := require.New(t) + + logger := logtest.NewLoggerForTest(t) + var lookupFunc dns.MonitorLookupFunc + if lookup != nil { + lookupFunc = lookup.Lookup + } + monitor, err := dns.NewMonitor(logger, interval, lookupFunc) + require.NoError(err) + + t.Cleanup(func() { + monitor.Stop() + }) + + require.NoError(monitor.Start()) + return monitor +} diff --git a/dns/test/dns_test.go b/dns/test/dns_test.go new file mode 100644 index 0000000..e67c734 --- /dev/null +++ b/dns/test/dns_test.go @@ -0,0 +1,68 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/dns" +) + +func TestDnsMonitor(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + lookup := NewMockLookup() + ips := []net.IP{ + net.ParseIP("1.2.3.4"), + } + lookup.Set("domain.invalid", ips) + monitor := NewMonitorForTest(t, time.Second, lookup) + + called := make(chan struct{}) + callback1 := func(entry *dns.MonitorEntry, all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) { + defer func() { + called <- struct{}{} + }() + + assert.Equal(ips, all) + assert.Equal(ips, add) + assert.Empty(keep) + assert.Empty(remove) + } + + entry1, err := monitor.Add("domain.invalid", callback1) + require.NoError(err) + + t.Cleanup(func() { + monitor.Remove(entry1) + }) + + <-called +} diff --git a/grpc/client_test.go b/grpc/client_test.go index 7ab9e47..db5c444 100644 --- a/grpc/client_test.go +++ b/grpc/client_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -40,8 +41,8 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/test" ) -func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { - dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually +func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dnstest.MockLookup) (*Clients, *dns.Monitor) { + dnsMonitor := dnstest.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) client, err := NewClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") @@ -53,7 +54,7 @@ func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdCl return client, dnsMonitor } -func NewClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { +func NewClientsForTest(t *testing.T, addr string, lookup *dnstest.MockLookup) (*Clients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("grpc", "targets", addr) config.AddOption("grpc", "dnsdiscovery", "true") @@ -61,7 +62,7 @@ func NewClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*Clie return NewClientsForTestWithConfig(t, config, nil, lookup) } -func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dns.MockLookup) (*Clients, *dns.Monitor) { +func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dnstest.MockLookup) (*Clients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) @@ -95,7 +96,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest test.EnsureNoGoroutinesLeak(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) - lookup := dns.NewMockLookupForTest(t) + lookup := dnstest.NewMockLookup() target := "testgrpc:12345" ip1 := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") @@ -147,7 +148,7 @@ func Test_GrpcClients_DnsDiscovery(t *testing.T) { // nolint:paralleltest func Test_GrpcClients_DnsDiscoveryInitialFailed(t *testing.T) { t.Parallel() assert := assert.New(t) - lookup := dns.NewMockLookupForTest(t) + lookup := dnstest.NewMockLookup() target := "testgrpc:12345" ip1 := net.ParseIP("192.168.0.1") targetWithIp1 := fmt.Sprintf("%s (%s)", target, ip1) diff --git a/grpc/test/client.go b/grpc/test/client.go index b58d735..a6580cb 100644 --- a/grpc/test/client.go +++ b/grpc/test/client.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/grpc" @@ -37,8 +38,8 @@ import ( logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) -func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { - dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually +func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dnstest.MockLookup) (*grpc.Clients, *dns.Monitor) { + dnsMonitor := dnstest.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) client, err := grpc.NewClients(ctx, config, etcdClient, dnsMonitor, "0.0.0") @@ -50,7 +51,7 @@ func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdCl return client, dnsMonitor } -func NewClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { +func NewClientsForTest(t *testing.T, addr string, lookup *dnstest.MockLookup) (*grpc.Clients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("grpc", "targets", addr) config.AddOption("grpc", "dnsdiscovery", "true") @@ -58,7 +59,7 @@ func NewClientsForTest(t *testing.T, addr string, lookup *dns.MockLookup) (*grpc return NewClientsForTestWithConfig(t, config, nil, lookup) } -func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dns.MockLookup) (*grpc.Clients, *dns.Monitor) { +func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dnstest.MockLookup) (*grpc.Clients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) diff --git a/sfu/proxy/config_static_test.go b/sfu/proxy/config_static_test.go index e5ad95b..4895e82 100644 --- a/sfu/proxy/config_static_test.go +++ b/sfu/proxy/config_static_test.go @@ -31,16 +31,17 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) -func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dns.MockLookup, urls ...string) (Config, *dns.Monitor) { +func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dnstest.MockLookup, urls ...string) (Config, *dns.Monitor) { cfg := goconf.NewConfigFile() cfg.AddOption("mcu", "url", strings.Join(urls, " ")) if dnsDiscovery { cfg.AddOption("mcu", "dnsdiscovery", "true") } - dnsMonitor := dns.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually + dnsMonitor := dnstest.NewMonitorForTest(t, time.Hour, lookup) // will be updated manually logger := logtest.NewLoggerForTest(t) p, err := NewConfigStatic(logger, cfg, proxy, dnsMonitor) require.NoError(t, err) @@ -78,7 +79,7 @@ func TestProxyConfigStaticSimple(t *testing.T) { func TestProxyConfigStaticDNS(t *testing.T) { t.Parallel() - lookup := dns.NewMockLookupForTest(t) + lookup := dnstest.NewMockLookup() proxy := newMcuProxyForConfig(t) config, dnsMonitor := newProxyConfigStatic(t, proxy, true, lookup, "https://foo/") require.NoError(t, config.Start()) diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go index 81864b3..c4f15d7 100644 --- a/sfu/proxy/proxy_test.go +++ b/sfu/proxy/proxy_test.go @@ -40,7 +40,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" "github.com/strukturag/nextcloud-spreed-signaling/geoip" @@ -198,7 +198,7 @@ func Test_sortConnectionsForCountryWithOverride(t *testing.T) { } } -func newMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOptions, idx int, lookup *dns.MockLookup) (*proxySFU, *goconf.ConfigFile) { +func newMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOptions, idx int, lookup *dnstest.MockLookup) (*proxySFU, *goconf.ConfigFile) { t.Helper() require := require.New(t) if options.Etcd == nil { @@ -286,7 +286,7 @@ func newMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOpt return proxy, cfg } -func newMcuProxyForTestWithServers(t *testing.T, servers []testserver.ProxyTestServer, idx int, lookup *dns.MockLookup) *proxySFU { +func newMcuProxyForTestWithServers(t *testing.T, servers []testserver.ProxyTestServer, idx int, lookup *dnstest.MockLookup) *proxySFU { t.Helper() proxy, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ @@ -295,7 +295,7 @@ func newMcuProxyForTestWithServers(t *testing.T, servers []testserver.ProxyTestS return proxy } -func newMcuProxyForTest(t *testing.T, idx int, lookup *dns.MockLookup) *proxySFU { +func newMcuProxyForTest(t *testing.T, idx int, lookup *dnstest.MockLookup) *proxySFU { t.Helper() server := testserver.NewProxyServerForTest(t, "DE") @@ -381,7 +381,7 @@ func Test_ProxyAddRemoveConnectionsDnsDiscovery(t *testing.T) { assert := assert.New(t) require := require.New(t) - lookup := dns.NewMockLookupForTest(t) + lookup := dnstest.NewMockLookup() server1 := testserver.NewProxyServerForTest(t, "DE") server1.Start() diff --git a/sfu/proxy/test/proxy.go b/sfu/proxy/test/proxy.go index 64bf5e9..a288125 100644 --- a/sfu/proxy/test/proxy.go +++ b/sfu/proxy/test/proxy.go @@ -36,7 +36,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" @@ -57,7 +57,7 @@ type ConnectionWaiter interface { WaitForConnectionsEstablished(ctx context.Context, waitMap map[string]bool) error } -func NewMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOptions, idx int, lookup *dns.MockLookup) (sfu.SFU, *goconf.ConfigFile) { +func NewMcuProxyForTestWithOptions(t *testing.T, options testserver.ProxyTestOptions, idx int, lookup *dnstest.MockLookup) (sfu.SFU, *goconf.ConfigFile) { t.Helper() require := require.New(t) require.NotEmpty(options.Servers) From 3083c392156cc732acd2b292e54d380ff4ab9839 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 14:38:49 +0100 Subject: [PATCH 460/549] Rename package of etcd test helpers. --- etcd/{etcdtest/etcdtest.go => test/etcd.go} | 40 +++++++++---------- .../etcdtest_test.go => test/etcd_test.go} | 2 +- grpc/client_test.go | 4 +- grpc/test/client.go | 4 +- grpc/test/client_test.go | 2 +- server/hub_sfu_proxy_test.go | 2 +- sfu/proxy/config_etcd_test.go | 6 +-- sfu/proxy/proxy_test.go | 2 +- sfu/proxy/test/proxy.go | 2 +- sfu/proxy/testserver/server.go | 4 +- talk/backend_configuration_test.go | 2 +- talk/backend_storage_etcd_test.go | 4 +- 12 files changed, 37 insertions(+), 37 deletions(-) rename etcd/{etcdtest/etcdtest.go => test/etcd.go} (92%) rename etcd/{etcdtest/etcdtest_test.go => test/etcd_test.go} (99%) diff --git a/etcd/etcdtest/etcdtest.go b/etcd/test/etcd.go similarity index 92% rename from etcd/etcdtest/etcdtest.go rename to etcd/test/etcd.go index cd4f7c7..1cb8f52 100644 --- a/etcd/etcdtest/etcdtest.go +++ b/etcd/test/etcd.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 etcdtest +package test import ( "bytes" @@ -54,29 +54,29 @@ var ( etcdListenUrl = "http://localhost:8080" ) -type Server struct { +type EtcdServer struct { embed *embed.Etcd } -func (s *Server) URL() *url.URL { +func (s *EtcdServer) URL() *url.URL { return &s.embed.Config().ListenClientUrls[0] } -func (s *Server) SetValue(key string, value []byte) { +func (s *EtcdServer) SetValue(key string, value []byte) { if kv := s.embed.Server.KV(); kv != nil { kv.Put([]byte(key), value, lease.NoLease) kv.Commit() } } -func (s *Server) DeleteValue(key string) { +func (s *EtcdServer) DeleteValue(key string) { if kv := s.embed.Server.KV(); kv != nil { kv.DeleteRange([]byte(key), nil) kv.Commit() } } -func NewServerForTest(t *testing.T) *Server { +func NewServerForTest(t *testing.T) *EtcdServer { t.Helper() require := require.New(t) cfg := embed.NewConfig() @@ -120,13 +120,13 @@ func NewServerForTest(t *testing.T) *Server { // Wait for server to be ready. <-etcd.Server.ReadyNotify() - server := &Server{ + server := &EtcdServer{ embed: etcd, } return server } -func NewEtcdClientForTest(t *testing.T, server *Server) etcd.Client { +func NewEtcdClientForTest(t *testing.T, server *EtcdServer) etcd.Client { t.Helper() logger := logtest.NewLoggerForTest(t) @@ -154,7 +154,7 @@ type testWatch struct { type testClient struct { mu sync.Mutex - server *TestServer + server *Server // +checklocks:mu closed bool @@ -166,7 +166,7 @@ type testClient struct { watchers []*testWatch } -func newTestClient(server *TestServer) *testClient { +func newTestClient(server *Server) *testClient { client := &testClient{ server: server, closeCh: make(chan struct{}), @@ -340,7 +340,7 @@ type testServerValue struct { revision int64 } -type TestServer struct { +type Server struct { t *testing.T mu sync.Mutex // +checklocks:mu @@ -351,20 +351,20 @@ type TestServer struct { revision int64 } -func (s *TestServer) newClient() *testClient { +func (s *Server) newClient() *testClient { client := newTestClient(s) s.addClient(client) return client } -func (s *TestServer) addClient(client *testClient) { +func (s *Server) addClient(client *testClient) { s.mu.Lock() defer s.mu.Unlock() s.clients = append(s.clients, client) } -func (s *TestServer) removeClient(client *testClient) { +func (s *Server) removeClient(client *testClient) { s.mu.Lock() defer s.mu.Unlock() @@ -373,14 +373,14 @@ func (s *TestServer) removeClient(client *testClient) { }) } -func (s *TestServer) getRevision() int64 { +func (s *Server) getRevision() int64 { s.mu.Lock() defer s.mu.Unlock() return s.revision } -func (s *TestServer) getValues(key string, minRevision int64, opts ...clientv3.OpOption) (keys []string, values [][]byte, revision int64) { +func (s *Server) getValues(key string, minRevision int64, opts ...clientv3.OpOption) (keys []string, values [][]byte, revision int64) { s.mu.Lock() defer s.mu.Unlock() @@ -409,7 +409,7 @@ func (s *TestServer) getValues(key string, minRevision int64, opts ...clientv3.O return } -func (s *TestServer) SetValue(key string, value []byte) { +func (s *Server) SetValue(key string, value []byte) { s.mu.Lock() defer s.mu.Unlock() @@ -435,7 +435,7 @@ func (s *TestServer) SetValue(key string, value []byte) { } } -func (s *TestServer) DeleteValue(key string) { +func (s *Server) DeleteValue(key string) { s.mu.Lock() defer s.mu.Unlock() @@ -452,9 +452,9 @@ func (s *TestServer) DeleteValue(key string) { } } -func NewClientForTest(t *testing.T) (*TestServer, etcd.Client) { +func NewClientForTest(t *testing.T) (*Server, etcd.Client) { t.Helper() - server := &TestServer{ + server := &Server{ t: t, revision: 1, } diff --git a/etcd/etcdtest/etcdtest_test.go b/etcd/test/etcd_test.go similarity index 99% rename from etcd/etcdtest/etcdtest_test.go rename to etcd/test/etcd_test.go index 2302c73..2278d68 100644 --- a/etcd/etcdtest/etcdtest_test.go +++ b/etcd/test/etcd_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 etcdtest +package test import ( "context" diff --git a/grpc/client_test.go b/grpc/client_test.go index db5c444..c17c972 100644 --- a/grpc/client_test.go +++ b/grpc/client_test.go @@ -35,7 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/dns" dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" "github.com/strukturag/nextcloud-spreed-signaling/log" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -62,7 +62,7 @@ func NewClientsForTest(t *testing.T, addr string, lookup *dnstest.MockLookup) (* return NewClientsForTestWithConfig(t, config, nil, lookup) } -func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dnstest.MockLookup) (*Clients, *dns.Monitor) { +func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.EtcdServer, lookup *dnstest.MockLookup) (*Clients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) diff --git a/grpc/test/client.go b/grpc/test/client.go index a6580cb..69c5bfc 100644 --- a/grpc/test/client.go +++ b/grpc/test/client.go @@ -32,7 +32,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/dns" dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" @@ -59,7 +59,7 @@ func NewClientsForTest(t *testing.T, addr string, lookup *dnstest.MockLookup) (* return NewClientsForTestWithConfig(t, config, nil, lookup) } -func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.Server, lookup *dnstest.MockLookup) (*grpc.Clients, *dns.Monitor) { +func NewClientsWithEtcdForTest(t *testing.T, embedEtcd *etcdtest.EtcdServer, lookup *dnstest.MockLookup) (*grpc.Clients, *dns.Monitor) { config := goconf.NewConfigFile() config.AddOption("etcd", "endpoints", embedEtcd.URL().String()) diff --git a/grpc/test/client_test.go b/grpc/test/client_test.go index ccb09cd..cc9391c 100644 --- a/grpc/test/client_test.go +++ b/grpc/test/client_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" ) func TestClientsWithEtcd(t *testing.T) { diff --git a/server/hub_sfu_proxy_test.go b/server/hub_sfu_proxy_test.go index 28e0721..278f3be 100644 --- a/server/hub_sfu_proxy_test.go +++ b/server/hub_sfu_proxy_test.go @@ -33,7 +33,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" "github.com/strukturag/nextcloud-spreed-signaling/grpc" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/sfu" diff --git a/sfu/proxy/config_etcd_test.go b/sfu/proxy/config_etcd_test.go index 9bc1561..0eea860 100644 --- a/sfu/proxy/config_etcd_test.go +++ b/sfu/proxy/config_etcd_test.go @@ -30,7 +30,7 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" ) @@ -40,7 +40,7 @@ type TestProxyInformationEtcd struct { OtherData string `json:"otherdata,omitempty"` } -func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.TestServer, Config) { +func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.Server, Config) { t.Helper() embedEtcd, client := etcdtest.NewClientForTest(t) cfg := goconf.NewConfigFile() @@ -54,7 +54,7 @@ func newProxyConfigEtcd(t *testing.T, proxy McuProxy) (*etcdtest.TestServer, Con return embedEtcd, p } -func SetEtcdProxy(t *testing.T, server *etcdtest.TestServer, path string, proxy *TestProxyInformationEtcd) { +func SetEtcdProxy(t *testing.T, server *etcdtest.Server, path string, proxy *TestProxyInformationEtcd) { t.Helper() data, _ := json.Marshal(proxy) server.SetValue(path, data) diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go index c4f15d7..41049fd 100644 --- a/sfu/proxy/proxy_test.go +++ b/sfu/proxy/proxy_test.go @@ -42,7 +42,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" "github.com/strukturag/nextcloud-spreed-signaling/geoip" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" diff --git a/sfu/proxy/test/proxy.go b/sfu/proxy/test/proxy.go index a288125..809c673 100644 --- a/sfu/proxy/test/proxy.go +++ b/sfu/proxy/test/proxy.go @@ -38,7 +38,7 @@ import ( dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" diff --git a/sfu/proxy/testserver/server.go b/sfu/proxy/testserver/server.go index 08ad5eb..7b2d6bd 100644 --- a/sfu/proxy/testserver/server.go +++ b/sfu/proxy/testserver/server.go @@ -42,7 +42,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" "github.com/strukturag/nextcloud-spreed-signaling/geoip" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/proxy" @@ -62,7 +62,7 @@ type ProxyTestServer interface { } type ProxyTestOptions struct { - Etcd *etcdtest.Server + Etcd *etcdtest.EtcdServer Servers []ProxyTestServer } diff --git a/talk/backend_configuration_test.go b/talk/backend_configuration_test.go index bb3d7e8..c2c8b41 100644 --- a/talk/backend_configuration_test.go +++ b/talk/backend_configuration_test.go @@ -33,7 +33,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) diff --git a/talk/backend_storage_etcd_test.go b/talk/backend_storage_etcd_test.go index 6adca58..3f5a2ab 100644 --- a/talk/backend_storage_etcd_test.go +++ b/talk/backend_storage_etcd_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/etcd/etcdtest" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -47,7 +47,7 @@ func (s *backendStorageEtcd) getWakeupChannelForTesting() <-chan struct{} { } type testListener struct { - etcd *etcdtest.TestServer + etcd *etcdtest.Server closed chan struct{} } From 8293de75b1859dd287f4a7af1b25463256bd8ab8 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 12 Jan 2026 14:58:48 +0100 Subject: [PATCH 461/549] Add metrics tests. --- metrics/test/metrics.go | 30 ++---------------- metrics/test/metrics_test.go | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 metrics/test/metrics_test.go diff --git a/metrics/test/metrics.go b/metrics/test/metrics.go index 98ca16a..f7f126c 100644 --- a/metrics/test/metrics.go +++ b/metrics/test/metrics.go @@ -22,9 +22,6 @@ package test import ( - "fmt" - "runtime" - "strings" "testing" "github.com/prometheus/client_golang/prometheus" @@ -58,7 +55,7 @@ func AssertCollectorChangeBy(t *testing.T, collector prometheus.Collector, delta }) } -func CheckStatsValue(t *testing.T, collector prometheus.Collector, value float64) { // nolint:unused +func CheckStatsValue(t *testing.T, collector prometheus.Collector, value float64) { // Make sure test is not executed with "t.Parallel()" t.Setenv("PARALLEL_CHECK", "1") @@ -66,30 +63,7 @@ func CheckStatsValue(t *testing.T, collector prometheus.Collector, value float64 collector.Describe(ch) desc := <-ch v := testutil.ToFloat64(collector) - if v != value { - assert := assert.New(t) - pc := make([]uintptr, 10) - n := runtime.Callers(2, pc) - if n == 0 { - assert.InDelta(value, v, 0.0001, "failed for %s", desc) - return - } - - pc = pc[:n] - frames := runtime.CallersFrames(pc) - var stack strings.Builder - for { - frame, more := frames.Next() - if !strings.Contains(frame.File, "nextcloud-spreed-signaling") { - break - } - fmt.Fprintf(&stack, "%s:%d\n", frame.File, frame.Line) - if !more { - break - } - } - assert.InDelta(value, v, 0.0001, "Unexpected value for %s at\n%s", desc, stack.String()) - } + assert.InDelta(t, value, v, 0.0001, "unexpected value for %s", desc) } func CollectAndLint(t *testing.T, collectors ...prometheus.Collector) { diff --git a/metrics/test/metrics_test.go b/metrics/test/metrics_test.go new file mode 100644 index 0000000..d6c4a65 --- /dev/null +++ b/metrics/test/metrics_test.go @@ -0,0 +1,61 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" +) + +func TestMetrics(t *testing.T) { // nolint:paralleltest + gauge := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "metrics_tests", + Name: "value", + Help: "Current value for the metrics tests", + }) + + CollectAndLint(t, gauge) + + CheckStatsValue(t, gauge, 0) + + gauge.Inc() + CheckStatsValue(t, gauge, 1) + + ResetStatsValue(t, gauge) + CheckStatsValue(t, gauge, 0) +} + +func TestAssertCollectorChangeBy(t *testing.T) { + t.Parallel() + + gauge := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "metrics_tests", + Name: "value", + Help: "Current value for the metrics tests", + }) + + AssertCollectorChangeBy(t, gauge, 1) + gauge.Inc() +} From 4eb2576e429ab0d4ad1e41a7f0499a81e5a92742 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 13 Jan 2026 08:44:37 +0100 Subject: [PATCH 462/549] Rename package of async events test helpers. --- .../eventstest.go => events/test/events.go} | 10 +- async/events/test/events_test.go | 94 +++++++++++++++++++ server/backend_server_test.go | 2 +- server/hub_test.go | 2 +- 4 files changed, 99 insertions(+), 9 deletions(-) rename async/{eventstest/eventstest.go => events/test/events.go} (96%) create mode 100644 async/events/test/events_test.go diff --git a/async/eventstest/eventstest.go b/async/events/test/events.go similarity index 96% rename from async/eventstest/eventstest.go rename to async/events/test/events.go index b7b52d2..0695478 100644 --- a/async/eventstest/eventstest.go +++ b/async/events/test/events.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 eventstest +package test import ( "context" @@ -66,9 +66,7 @@ func getRealAsyncEventsForTest(t *testing.T) events.AsyncEvents { ctx := log.NewLoggerContext(t.Context(), logger) server, _ := natstest.StartLocalServer(t) events, err := events.NewAsyncEvents(ctx, server.ClientURL()) - if err != nil { - require.NoError(t, err) - } + require.NoError(t, err) return events } @@ -80,9 +78,7 @@ func getLoopbackAsyncEventsForTest(t *testing.T) events.AsyncEvents { logger := logtest.NewLoggerForTest(t) ctx := log.NewLoggerContext(t.Context(), logger) events, err := events.NewAsyncEvents(ctx, nats.LoopbackUrl) - if err != nil { - require.NoError(t, err) - } + require.NoError(t, err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) diff --git a/async/events/test/events_test.go b/async/events/test/events_test.go new file mode 100644 index 0000000..658d72c --- /dev/null +++ b/async/events/test/events_test.go @@ -0,0 +1,94 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" +) + +type testListener struct { + ch events.AsyncChannel +} + +func (l *testListener) AsyncChannel() events.AsyncChannel { + return l.ch +} + +func TestAsyncEventsTest(t *testing.T) { + t.Parallel() + + for _, backend := range EventBackendsForTest { + t.Run(backend, func(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + eventsHandler := GetAsyncEventsForTest(t) + + listener := &testListener{ + ch: make(events.AsyncChannel, 1), + } + sessionId := api.PublicSessionId("foo") + backend := talk.NewCompatBackend(nil) + require.NoError(eventsHandler.RegisterSessionListener(sessionId, backend, listener)) + defer func() { + assert.NoError(eventsHandler.UnregisterSessionListener(sessionId, backend, listener)) + }() + + msg := events.AsyncMessage{ + Type: "message", + Message: &api.ServerMessage{ + Type: "error", + Error: api.NewError("test_error", "This is a test error."), + }, + } + if err := eventsHandler.PublishSessionMessage(sessionId, backend, &msg); assert.NoError(err) { + select { + case natsMsg := <-listener.ch: + var received events.AsyncMessage + if err := nats.Decode(natsMsg, &received); assert.NoError(err) { + assert.True(msg.SendTime.Equal(received.SendTime), "send times don't match, expected %s, got %s", msg.SendTime, received.SendTime) + received.SendTime = msg.SendTime + assert.Equal(msg, received) + } + case <-ctx.Done(): + require.NoError(ctx.Err()) + } + } + + WaitForAsyncEventsFlushed(ctx, t, eventsHandler) + }) + } +} diff --git a/server/backend_server_test.go b/server/backend_server_test.go index 213e3bc..f133bd7 100644 --- a/server/backend_server_test.go +++ b/server/backend_server_test.go @@ -48,7 +48,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/async/eventstest" + eventstest "github.com/strukturag/nextcloud-spreed-signaling/async/events/test" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" diff --git a/server/hub_test.go b/server/hub_test.go index 680eb59..2bd62a1 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -55,7 +55,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/async" "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/async/eventstest" + eventstest "github.com/strukturag/nextcloud-spreed-signaling/async/events/test" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" From 88be60c0a6a94cf0d8d9294cffb721f309710691 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 13 Jan 2026 15:21:11 +0100 Subject: [PATCH 463/549] Remove unused testing code. Was moved to "sfu/test/sfu.go" in #1151 but not deleted. --- sfu/janus/mcu_test.go | 308 ------------------------------------------ 1 file changed, 308 deletions(-) delete mode 100644 sfu/janus/mcu_test.go diff --git a/sfu/janus/mcu_test.go b/sfu/janus/mcu_test.go deleted file mode 100644 index 971ea87..0000000 --- a/sfu/janus/mcu_test.go +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2019 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 janus - -import ( - "context" - "errors" - "fmt" - "maps" - "sync" - "sync/atomic" - "testing" - - "github.com/dlintw/goconf" - - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" -) - -var ( - TestMaxBitrateScreen = api.BandwidthFromBits(12345678) - TestMaxBitrateVideo = api.BandwidthFromBits(23456789) -) - -type TestMCU struct { - t *testing.T - mu sync.Mutex - // +checklocks:mu - publishers map[api.PublicSessionId]*TestMCUPublisher - // +checklocks:mu - subscribers map[string]*TestMCUSubscriber - - maxStreamBitrate api.AtomicBandwidth - maxScreenBitrate api.AtomicBandwidth -} - -func NewTestMCU(t *testing.T) *TestMCU { - return &TestMCU{ - t: t, - - publishers: make(map[api.PublicSessionId]*TestMCUPublisher), - subscribers: make(map[string]*TestMCUSubscriber), - } -} - -func (m *TestMCU) GetBandwidthLimits() (api.Bandwidth, api.Bandwidth) { - return m.maxStreamBitrate.Load(), m.maxScreenBitrate.Load() -} - -func (m *TestMCU) SetBandwidthLimits(maxStreamBitrate api.Bandwidth, maxScreenBitrate api.Bandwidth) { - m.maxStreamBitrate.Store(maxStreamBitrate) - m.maxScreenBitrate.Store(maxScreenBitrate) -} - -func (m *TestMCU) Start(ctx context.Context) error { - return nil -} - -func (m *TestMCU) Stop() { -} - -func (m *TestMCU) Reload(config *goconf.ConfigFile) { -} - -func (m *TestMCU) SetOnConnected(f func()) { -} - -func (m *TestMCU) SetOnDisconnected(f func()) { -} - -func (m *TestMCU) GetStats() any { - return nil -} - -func (m *TestMCU) GetServerInfoSfu() *talk.BackendServerInfoSfu { - return nil -} - -func (m *TestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { - var maxBitrate api.Bandwidth - if streamType == sfu.StreamTypeScreen { - maxBitrate = TestMaxBitrateScreen - } else { - maxBitrate = TestMaxBitrateVideo - } - publisherSettings := settings - bitrate := publisherSettings.Bitrate - if bitrate <= 0 || bitrate > maxBitrate { - publisherSettings.Bitrate = maxBitrate - } - pub := &TestMCUPublisher{ - TestMCUClient: TestMCUClient{ - t: m.t, - id: string(id), - sid: sid, - streamType: streamType, - }, - - settings: publisherSettings, - } - - m.mu.Lock() - defer m.mu.Unlock() - - m.publishers[id] = pub - return pub, nil -} - -func (m *TestMCU) GetPublishers() map[api.PublicSessionId]*TestMCUPublisher { - m.mu.Lock() - defer m.mu.Unlock() - - result := maps.Clone(m.publishers) - return result -} - -func (m *TestMCU) GetPublisher(id api.PublicSessionId) *TestMCUPublisher { - m.mu.Lock() - defer m.mu.Unlock() - - return m.publishers[id] -} - -func (m *TestMCU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { - m.mu.Lock() - defer m.mu.Unlock() - - pub := m.publishers[publisher] - if pub == nil { - return nil, errors.New("Waiting for publisher not implemented yet") - } - - id := internal.RandomString(8) - sub := &TestMCUSubscriber{ - TestMCUClient: TestMCUClient{ - t: m.t, - id: id, - streamType: streamType, - }, - - publisher: pub, - } - return sub, nil -} - -type TestMCUClient struct { - t *testing.T - closed atomic.Bool - - id string - sid string - streamType sfu.StreamType -} - -func (c *TestMCUClient) Id() string { - return c.id -} - -func (c *TestMCUClient) Sid() string { - return c.sid -} - -func (c *TestMCUClient) StreamType() sfu.StreamType { - return c.streamType -} - -func (c *TestMCUClient) MaxBitrate() api.Bandwidth { - return 0 -} - -func (c *TestMCUClient) Close(ctx context.Context) { - if c.closed.CompareAndSwap(false, true) { - logger := logtest.NewLoggerForTest(c.t) - logger.Printf("Close MCU client %s", c.id) - } -} - -func (c *TestMCUClient) isClosed() bool { - return c.closed.Load() -} - -type TestMCUPublisher struct { - TestMCUClient - - settings sfu.NewPublisherSettings - - sdp string -} - -func (p *TestMCUPublisher) PublisherId() api.PublicSessionId { - return api.PublicSessionId(p.id) -} - -func (p *TestMCUPublisher) HasMedia(mt sfu.MediaType) bool { - return (p.settings.MediaTypes & mt) == mt -} - -func (p *TestMCUPublisher) SetMedia(mt sfu.MediaType) { - p.settings.MediaTypes = mt -} - -func (p *TestMCUPublisher) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { - go func() { - if p.isClosed() { - callback(errors.New("Already closed"), nil) - return - } - - switch data.Type { - case "offer": - sdp := data.Payload["sdp"] - if sdp, ok := sdp.(string); ok { - p.sdp = sdp - switch sdp { - case mock.MockSdpOfferAudioOnly: - callback(nil, api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioOnly, - }) - return - case mock.MockSdpOfferAudioAndVideo: - callback(nil, api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }) - return - } - } - callback(fmt.Errorf("Offer payload %+v is not implemented", data.Payload), nil) - default: - callback(fmt.Errorf("Message type %s is not implemented", data.Type), nil) - } - }() -} - -func (p *TestMCUPublisher) GetStreams(ctx context.Context) ([]sfu.PublisherStream, error) { - return nil, errors.New("not implemented") -} - -func (p *TestMCUPublisher) PublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { - return errors.New("remote publishing not supported") -} - -func (p *TestMCUPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicSessionId, hostname string, port int, rtcpPort int) error { - return errors.New("remote publishing not supported") -} - -type TestMCUSubscriber struct { - TestMCUClient - - publisher *TestMCUPublisher -} - -func (s *TestMCUSubscriber) Publisher() api.PublicSessionId { - return s.publisher.PublisherId() -} - -func (s *TestMCUSubscriber) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { - go func() { - if s.isClosed() { - callback(errors.New("Already closed"), nil) - return - } - - switch data.Type { - case "requestoffer": - fallthrough - case "sendoffer": - sdp := s.publisher.sdp - if sdp == "" { - callback(errors.New("Publisher not sending (no SDP)"), nil) - return - } - - callback(nil, api.StringMap{ - "type": "offer", - "sdp": sdp, - }) - case "answer": - callback(nil, nil) - default: - callback(fmt.Errorf("Message type %s is not implemented", data.Type), nil) - } - }() -} From bc1ecd1f32b882c5172713c6a3a306d9dadb2198 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:43:05 +0000 Subject: [PATCH 464/549] Bump markdown from 3.10 to 3.10.1 in /docs Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.10 to 3.10.1. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.10.0...3.10.1) --- updated-dependencies: - dependency-name: markdown dependency-version: 3.10.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index dd6e550..58ca5bc 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ jinja2==3.1.6 -markdown==3.10 +markdown==3.10.1 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==9.1.0 From 9a525f86ccaeae89c14d8265d35d9794f009de47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 20:42:42 +0000 Subject: [PATCH 465/549] Bump github.com/nats-io/nats-server/v2 from 2.12.3 to 2.12.4 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.3 to 2.12.4. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Commits](https://github.com/nats-io/nats-server/compare/v2.12.3...v2.12.4) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 3e75147..3297563 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 - github.com/nats-io/nats-server/v2 v2.12.3 + github.com/nats-io/nats-server/v2 v2.12.4 github.com/nats-io/nats.go v1.48.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -44,13 +44,13 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-tpm v0.9.7 // indirect + github.com/google/go-tpm v0.9.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/compress v1.18.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -88,10 +88,10 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect diff --git a/go.sum b/go.sum index 38a7e88..dd9fb25 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA= -github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= +github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -58,8 +58,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -74,8 +74,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.3 h1:KRv+1n7lddMVgkJPQer+pt36TcO0ENxjilBmeWdjcHs= -github.com/nats-io/nats-server/v2 v2.12.3/go.mod h1:MQXjG9WjyXKz9koWzUc3jYUMKD8x3CLmTNy91IQQz3Y= +github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts= +github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg= github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc= @@ -184,8 +184,8 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -193,8 +193,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -205,12 +205,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 989100330490daccdd5e12003c39742ccd1f1ce1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:42:24 +0000 Subject: [PATCH 466/549] Bump github.com/golang-jwt/jwt/v5 from 5.3.0 to 5.3.1 Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.3.0 to 5.3.1. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.3.0...v5.3.1) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-version: 5.3.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3297563..c911fef 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 github.com/fsnotify/fsnotify v1.9.0 - github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 diff --git a/go.sum b/go.sum index dd9fb25..8e89633 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= From 4c4abb16ce0087c89c767fd938537bba6d033f57 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 09:18:27 +0100 Subject: [PATCH 467/549] Add more metrics about sessions in calls. --- server/room.go | 105 +++++++++++++++++++++++++------- server/room_stats_prometheus.go | 21 +++++++ 2 files changed, 103 insertions(+), 23 deletions(-) diff --git a/server/room.go b/server/room.go index 8d39537..74894a9 100644 --- a/server/room.go +++ b/server/room.go @@ -94,6 +94,12 @@ type Room struct { // +checklocks:mu statsRoomSessionsCurrent *prometheus.GaugeVec + // +checklocks:mu + statsCallSessionsCurrent *prometheus.GaugeVec + // +checklocks:mu + statsCallSessionsTotal *prometheus.CounterVec + // +checklocks:mu + statsCallRoomsTotal prometheus.Counter // Users currently in the room users []api.StringMap @@ -136,6 +142,14 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, asyncEvents ev "backend": backend.Id(), "room": roomId, }), + statsCallSessionsCurrent: statsCallSessionsCurrent.MustCurryWith(prometheus.Labels{ + "backend": backend.Id(), + "room": roomId, + }), + statsCallSessionsTotal: statsCallSessionsTotal.MustCurryWith(prometheus.Labels{ + "backend": backend.Id(), + }), + statsCallRoomsTotal: statsCallRoomsTotal.WithLabelValues(backend.Id()), lastRoomRequests: make(map[string]int64), @@ -223,6 +237,7 @@ func (r *Room) Close() []Session { r.hub.removeRoom(r) r.doClose() r.mu.Lock() + defer r.mu.Unlock() r.unsubscribeBackend() result := make([]Session, 0, len(r.sessions)) for _, s := range r.sessions { @@ -230,9 +245,10 @@ func (r *Room) Close() []Session { } r.sessions = nil r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeClient)}) + r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeFederation)}) r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeInternal)}) r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeVirtual)}) - r.mu.Unlock() + r.clearInCallStats() return result } @@ -480,16 +496,69 @@ func (r *Room) notifySessionJoined(sessionId api.PublicSessionId) { func (r *Room) HasSession(session Session) bool { r.mu.RLock() + defer r.mu.RUnlock() + _, result := r.sessions[session.PublicId()] - r.mu.RUnlock() return result } func (r *Room) IsSessionInCall(session Session) bool { r.mu.RLock() - _, result := r.inCallSessions[session] - r.mu.RUnlock() - return result + defer r.mu.RUnlock() + + return r.inCallSessions[session] +} + +// +checklocks:r.mu +func (r *Room) clearInCallStats() { + r.statsCallSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeClient)}) + r.statsCallSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeFederation)}) + r.statsCallSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeInternal)}) + r.statsCallSessionsCurrent.Delete(prometheus.Labels{"clienttype": string(api.HelloClientTypeVirtual)}) +} + +func (r *Room) addSessionToCall(session Session) bool { + r.mu.Lock() + defer r.mu.Unlock() + + return r.addSessionToCallLocked(session) +} + +// +checklocks:r.mu +func (r *Room) addSessionToCallLocked(session Session) bool { + if r.inCallSessions[session] { + return false + } + + if len(r.inCallSessions) == 0 { + r.statsCallRoomsTotal.Inc() + } + r.inCallSessions[session] = true + r.statsCallSessionsCurrent.WithLabelValues(string(session.ClientType())).Inc() + r.statsCallSessionsTotal.WithLabelValues(string(session.ClientType())).Inc() + return true +} + +func (r *Room) removeSessionFromCall(session Session) bool { + r.mu.Lock() + defer r.mu.Unlock() + + return r.removeSessionFromCallLocked(session) +} + +// +checklocks:r.mu +func (r *Room) removeSessionFromCallLocked(session Session) bool { + if !r.inCallSessions[session] { + return false + } + + delete(r.inCallSessions, session) + if len(r.inCallSessions) == 0 { + r.clearInCallStats() + } else { + r.statsCallSessionsCurrent.WithLabelValues(string(session.ClientType())).Dec() + } + return true } // Returns "true" if there are still clients in the room. @@ -520,7 +589,7 @@ func (r *Room) RemoveSession(session Session) bool { delete(r.internalSessions, clientSession) r.transientData.RemoveListener(clientSession) } - delete(r.inCallSessions, session) + r.removeSessionFromCallLocked(session) delete(r.roomSessionData, sid) if len(r.sessions) > 0 { r.mu.Unlock() @@ -829,16 +898,11 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St } if inCall { - r.mu.Lock() - if !r.inCallSessions[session] { - r.inCallSessions[session] = true + if r.addSessionToCall(session) { r.logger.Printf("Session %s joined call %s", session.PublicId(), r.id) } - r.mu.Unlock() } else { - r.mu.Lock() - delete(r.inCallSessions, session) - r.mu.Unlock() + r.removeSessionFromCall(session) if clientSession, ok := session.(*ClientSession); ok { clientSession.LeaveCall() } @@ -884,8 +948,7 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { continue } - if !r.inCallSessions[session] { - r.inCallSessions[session] = true + if r.addSessionToCallLocked(session) { joined = append(joined, session.PublicId()) } notify = append(notify, clientSession) @@ -925,7 +988,8 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { } } close(ch) - r.inCallSessions = make(map[Session]bool) + clear(r.inCallSessions) + r.clearInCallStats() } else { // All sessions already left the call, no need to notify. return @@ -1021,16 +1085,11 @@ func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) { if joinLeave != 0 { switch joinLeave { case 1: - r.mu.Lock() - if !r.inCallSessions[session] { - r.inCallSessions[session] = true + if r.addSessionToCall(session) { r.logger.Printf("Session %s joined call %s", session.PublicId(), r.id) } - r.mu.Unlock() case 2: - r.mu.Lock() - delete(r.inCallSessions, session) - r.mu.Unlock() + r.removeSessionFromCall(session) if clientSession, ok := session.(*ClientSession); ok { clientSession.LeaveCall() } diff --git a/server/room_stats_prometheus.go b/server/room_stats_prometheus.go index db96c3d..973bdc1 100644 --- a/server/room_stats_prometheus.go +++ b/server/room_stats_prometheus.go @@ -34,9 +34,30 @@ var ( Name: "sessions", Help: "The current number of sessions in a room", }, []string{"backend", "room", "clienttype"}) + statsCallSessionsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "call", + Name: "sessions", + Help: "The current number of sessions in a call", + }, []string{"backend", "room", "clienttype"}) + statsCallSessionsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "call", + Name: "sessions_total", + Help: "The total number of sessions in a call", + }, []string{"backend", "clienttype"}) + statsCallRoomsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "call", + Name: "rooms_total", + Help: "The total number of rooms with an active call", + }, []string{"backend"}) roomStats = []prometheus.Collector{ statsRoomSessionsCurrent, + statsCallSessionsCurrent, + statsCallSessionsTotal, + statsCallRoomsTotal, } ) From 3b667ffcf6abbf7db840c3dfeea09f35775b5fd5 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 09:21:38 +0100 Subject: [PATCH 468/549] Document new call metrics. --- docs/prometheus-metrics.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 9a5f365..2979b16 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -74,3 +74,6 @@ The following metrics are available: | `signaling_mcu_media_lost_total` | Counter | 2.0.5 | The total number of lost media packets | `media`, `origin` | | `signaling_client_bytes_total` | Counter | 2.0.5 | The total number of bytes sent to or received by clients | `direction` | | `signaling_client_messages_total` | Counter | 2.0.5 | The total number of messages sent to or received by clients | `direction` | +| `signaling_call_sessions` | Gauge | 2.0.5 | The current number of sessions in a call | `backend`, `room`, `clienttype` | +| `signaling_call_sessions_total` | Counter | 2.0.5 | The total number of sessions in a call | `backend`, `clienttype` | +| `signaling_call_rooms_total` | Counter | 2.0.5 | The total number of rooms with an active call | `backend` | From 7ea46914049fe1b0bfa20b034769d032ae7c8fe3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 10:05:28 +0100 Subject: [PATCH 469/549] Improve incall tests with leaving sessions. --- server/room_test.go | 150 +++++++++++++++++++++++++++++++++- server/virtualsession_test.go | 24 ++++++ 2 files changed, 173 insertions(+), 1 deletion(-) diff --git a/server/room_test.go b/server/room_test.go index 6da0685..9f18fc4 100644 --- a/server/room_test.go +++ b/server/room_test.go @@ -35,12 +35,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/log" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" ) -func TestRoom_InCall(t *testing.T) { +func TestRoom_InCallFlag(t *testing.T) { t.Parallel() type Testcase struct { Value any @@ -354,6 +355,153 @@ func TestRoom_RoomSessionData(t *testing.T) { wg.Wait() } +func TestRoom_InCall(t *testing.T) { + t.Parallel() + logger := logtest.NewLoggerForTest(t) + ctx := log.NewLoggerContext(t.Context(), logger) + require := require.New(t) + assert := assert.New(t) + hub, _, router, server := CreateHubForTest(t) + + config, err := getTestConfig(server) + require.NoError(err) + b, err := NewBackendServer(ctx, config, hub, "no-version") + require.NoError(err) + require.NoError(b.Start(router)) + + ctx, cancel := context.WithTimeout(ctx, testTimeout) + defer cancel() + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + + // Join room by id. + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client1.RunUntilJoined(ctx, hello1.Hello) + + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello) + + client1.RunUntilJoined(ctx, hello2.Hello) + + msg1 := &talk.BackendServerRoomRequest{ + Type: "incall", + InCall: &talk.BackendRoomInCallRequest{ + InCall: json.RawMessage(strconv.FormatInt(FlagInCall, 10)), + Changed: []api.StringMap{ + { + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), + "inCall": json.RawMessage(strconv.FormatInt(FlagInCall, 10)), + }, + }, + Users: []api.StringMap{ + { + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), + "inCall": json.RawMessage(strconv.FormatInt(FlagInCall, 10)), + }, + { + "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), + "inCall": json.RawMessage(strconv.FormatInt(0, 10)), + }, + }, + }, + } + + data1, err := json.Marshal(msg1) + require.NoError(err) + res1, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data1) + require.NoError(err) + defer res1.Body.Close() + body1, err := io.ReadAll(res1.Body) + assert.NoError(err) + assert.Equal(http.StatusOK, res1.StatusCode, "Expected successful request, got %s", string(body1)) + + if msg, ok := client1.RunUntilMessage(ctx); ok { + if message, ok := checkMessageParticipantsInCall(t, msg); ok { + assert.Equal(roomId, message.RoomId) + if assert.Len(message.Users, 2) { + assert.EqualValues(hello1.Hello.SessionId, message.Users[0]["sessionId"]) + assert.EqualValues(FlagInCall, message.Users[0]["inCall"]) + assert.EqualValues(hello2.Hello.SessionId, message.Users[1]["sessionId"]) + assert.EqualValues(0, message.Users[1]["inCall"]) + } + } + } + + if msg, ok := client2.RunUntilMessage(ctx); ok { + if message, ok := checkMessageParticipantsInCall(t, msg); ok { + assert.Equal(roomId, message.RoomId) + if assert.Len(message.Users, 2) { + assert.EqualValues(hello1.Hello.SessionId, message.Users[0]["sessionId"]) + assert.EqualValues(FlagInCall, message.Users[0]["inCall"]) + assert.EqualValues(hello2.Hello.SessionId, message.Users[1]["sessionId"]) + assert.EqualValues(0, message.Users[1]["inCall"]) + } + } + } + + msg2 := &talk.BackendServerRoomRequest{ + Type: "incall", + InCall: &talk.BackendRoomInCallRequest{ + InCall: json.RawMessage(strconv.FormatInt(0, 10)), + Changed: []api.StringMap{ + { + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), + "inCall": json.RawMessage(strconv.FormatInt(0, 10)), + }, + }, + Users: []api.StringMap{ + { + "sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId), + "inCall": json.RawMessage(strconv.FormatInt(0, 10)), + }, + { + "sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId), + "inCall": json.RawMessage(strconv.FormatInt(0, 10)), + }, + }, + }, + } + + data2, err := json.Marshal(msg2) + require.NoError(err) + res2, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data2) + require.NoError(err) + defer res2.Body.Close() + body2, err := io.ReadAll(res2.Body) + assert.NoError(err) + assert.Equal(http.StatusOK, res2.StatusCode, "Expected successful request, got %s", string(body2)) + + if msg, ok := client1.RunUntilMessage(ctx); ok { + if message, ok := checkMessageParticipantsInCall(t, msg); ok { + assert.Equal(roomId, message.RoomId) + if assert.Len(message.Users, 2) { + assert.EqualValues(hello1.Hello.SessionId, message.Users[0]["sessionId"]) + assert.EqualValues(0, message.Users[0]["inCall"]) + assert.EqualValues(hello2.Hello.SessionId, message.Users[1]["sessionId"]) + assert.EqualValues(0, message.Users[1]["inCall"]) + } + } + } + + if msg, ok := client2.RunUntilMessage(ctx); ok { + if message, ok := checkMessageParticipantsInCall(t, msg); ok { + assert.Equal(roomId, message.RoomId) + if assert.Len(message.Users, 2) { + assert.EqualValues(hello1.Hello.SessionId, message.Users[0]["sessionId"]) + assert.EqualValues(0, message.Users[0]["inCall"]) + assert.EqualValues(hello2.Hello.SessionId, message.Users[1]["sessionId"]) + assert.EqualValues(0, message.Users[1]["inCall"]) + } + } + } +} + func TestRoom_InCallAll(t *testing.T) { t.Parallel() logger := logtest.NewLoggerForTest(t) diff --git a/server/virtualsession_test.go b/server/virtualsession_test.go index bb70a90..cc943a2 100644 --- a/server/virtualsession_test.go +++ b/server/virtualsession_test.go @@ -589,6 +589,30 @@ func TestVirtualSessionCustomInCall(t *testing.T) { checkHasEntryWithInCall(t, updateMsg, sessionId, "virtual", newInCall) checkHasEntryWithInCall(t, updateMsg, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio) } + + newInCall2 := FlagDisconnected + msgInCall3 := &api.ClientMessage{ + Type: "internal", + Internal: &api.InternalClientMessage{ + Type: "updatesession", + UpdateSession: &api.UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: api.CommonSessionInternalClientMessage{ + SessionId: internalSessionId, + RoomId: roomId, + }, + InCall: &newInCall2, + }, + }, + } + require.NoError(clientInternal.WriteJSON(msgInCall3)) + + msg6 := MustSucceed1(t, client.RunUntilMessage, ctx) + if updateMsg, ok := checkMessageParticipantsInCall(t, msg6); ok { + assert.Equal(roomId, updateMsg.RoomId) + assert.Len(updateMsg.Users, 2) + checkHasEntryWithInCall(t, updateMsg, sessionId, "virtual", newInCall2) + checkHasEntryWithInCall(t, updateMsg, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio) + } } func TestVirtualSessionCleanup(t *testing.T) { From d80143af4c3b028348e2cd9cbe5ff80f6aacee14 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 10:26:48 +0100 Subject: [PATCH 470/549] checklocks: Remove ignore since generics are supported now. --- container/concurrentmap.go | 2 +- test/storage.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/container/concurrentmap.go b/container/concurrentmap.go index b9aa3db..8170446 100644 --- a/container/concurrentmap.go +++ b/container/concurrentmap.go @@ -28,7 +28,7 @@ import ( type ConcurrentMap[K comparable, V any] struct { mu sync.RWMutex // +checklocks:mu - d map[K]V // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 + d map[K]V } func (m *ConcurrentMap[K, V]) Set(key K, value V) { diff --git a/test/storage.go b/test/storage.go index 7d1f694..9b23ad3 100644 --- a/test/storage.go +++ b/test/storage.go @@ -29,7 +29,7 @@ import ( type Storage[T any] struct { mu sync.Mutex // +checklocks:mu - entries map[string]T // +checklocksignore: Not supported yet, see https://github.com/google/gvisor/issues/11671 + entries map[string]T } func (s *Storage[T]) cleanup(key string) { From eafa39a1c5c0d089896cd1dcc4c2c926281be523 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 11:31:08 +0100 Subject: [PATCH 471/549] Support receiving and forwarding multiple chat messages from Talk. --- api/signaling.go | 8 ++ server/clientsession.go | 134 ++++++++++++++++++++--------- server/clientsession_test.go | 159 +++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 41 deletions(-) diff --git a/api/signaling.go b/api/signaling.go index 56b4c61..27ee638 100644 --- a/api/signaling.go +++ b/api/signaling.go @@ -1215,6 +1215,14 @@ type RoomEventMessageDataChat struct { // Comment will be included if the client supports the "chat-relay" feature. Comment json.RawMessage `json:"comment,omitempty"` + // Comments will be included if the client supports the "chat-relay" feature. + Comments []json.RawMessage `json:"comments,omitempty"` +} + +func (m *RoomEventMessageDataChat) HasComment() bool { + return len(m.Comment) > 0 || slices.ContainsFunc(m.Comments, func(comment json.RawMessage) bool { + return len(comment) > 0 + }) } type RoomEventMessageData struct { diff --git a/server/clientsession.go b/server/clientsession.go index 276c48e..10f1145 100644 --- a/server/clientsession.go +++ b/server/clientsession.go @@ -721,15 +721,14 @@ func (s *ClientSession) sendCandidate(client sfu.Client, sender api.PublicSessio } // +checklocks:s.mu -func (s *ClientSession) sendMessageUnlocked(message *api.ServerMessage) bool { +func (s *ClientSession) sendMessageUnlocked(message *api.ServerMessage) { if c := s.getClientUnlocked(); c != nil { if c.SendMessage(message) { - return true + return } } s.storePendingMessage(message) - return true } func (s *ClientSession) SendError(e *api.Error) bool { @@ -741,15 +740,21 @@ func (s *ClientSession) SendError(e *api.Error) bool { } func (s *ClientSession) SendMessage(message *api.ServerMessage) bool { - message = s.filterMessage(message) - if message == nil { + message, messages := s.filterMessage(message) + if message == nil && len(messages) == 0 { return true } s.mu.Lock() defer s.mu.Unlock() - return s.sendMessageUnlocked(message) + if message != nil { + s.sendMessageUnlocked(message) + } + for _, msg := range messages { + s.sendMessageUnlocked(msg) + } + return true } func (s *ClientSession) SendMessages(messages []*api.ServerMessage) bool { @@ -1333,7 +1338,7 @@ func (s *ClientSession) filterDuplicateFlags(message *api.RoomFlagsServerMessage return false } -func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMessage { +func (s *ClientSession) filterMessage(message *api.ServerMessage) (*api.ServerMessage, []*api.ServerMessage) { switch message.Type { case "event": switch message.Event.Target { @@ -1356,7 +1361,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes m.Changed = nil case "flags": if s.filterDuplicateFlags(message.Event.Flags) { - return nil + return nil, nil } } case "room": @@ -1364,7 +1369,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes case "join": join := s.filterDuplicateJoin(message.Event.Join) if len(join) == 0 { - return nil + return nil, nil } copied := false if len(join) != len(message.Event.Join) { @@ -1402,7 +1407,7 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes leave := s.filterUnknownLeave(message.Event.Leave) if len(leave) == 0 { - return nil + return nil, nil } for _, e := range message.Event.Leave { @@ -1423,57 +1428,104 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes } case "message": if message.Event.Message == nil || len(message.Event.Message.Data) == 0 { - return message + return message, nil } data, err := message.Event.Message.GetData() if data == nil || err != nil { - return message + return message, nil } if data.Type == "chat" && data.Chat != nil { update := false - if data.Chat.Refresh && len(data.Chat.Comment) > 0 { + if data.Chat.Refresh && data.Chat.HasComment() { // New-style chat event, check what the client supports. if s.HasFeature(api.ClientFeatureChatRelay) { data.Chat.Refresh = false } else { data.Chat.Comment = nil + data.Chat.Comments = nil } update = true } - if len(data.Chat.Comment) > 0 && s.HasPermission(api.PERMISSION_HIDE_DISPLAYNAMES) { - var comment api.ChatComment - if err := json.Unmarshal(data.Chat.Comment, &comment); err != nil { - return message - } - - if displayName, found := comment["actorDisplayName"]; found && displayName != "" { - comment["actorDisplayName"] = "" - var err error - if data.Chat.Comment, err = json.Marshal(comment); err != nil { - return message + if data.Chat.HasComment() { + data.Chat.Comments = slices.DeleteFunc(data.Chat.Comments, func(comment json.RawMessage) bool { + return len(comment) == 0 + }) + if len(data.Chat.Comment) > 0 { + if len(data.Chat.Comments) == 0 { + data.Chat.Comments = []json.RawMessage{data.Chat.Comment} + } else { + data.Chat.Comments = append([]json.RawMessage{data.Chat.Comment}, data.Chat.Comments...) + } + data.Chat.Comment = nil + } + if len(data.Chat.Comments) > 0 && s.HasPermission(api.PERMISSION_HIDE_DISPLAYNAMES) { + for i, commentData := range data.Chat.Comments { + var comment api.ChatComment + if err := json.Unmarshal(commentData, &comment); err != nil { + continue + } + + if displayName, found := comment["actorDisplayName"]; found && displayName != "" { + comment["actorDisplayName"] = "" + var err error + if commentData, err = json.Marshal(comment); err != nil { + continue + } + data.Chat.Comments[i] = commentData + update = true + } } - update = true } } - if update { - if encoded, err := json.Marshal(data); err == nil { - // Create unique copy of message for only this client. - message = &api.ServerMessage{ - Id: message.Id, - Type: message.Type, - Event: &api.EventServerMessage{ - Type: message.Event.Type, - Target: message.Event.Target, - Message: &api.RoomEventMessage{ - RoomId: message.Event.Message.RoomId, - Data: encoded, + if update || len(data.Chat.Comments) > 0 { + if len(data.Chat.Comment) == 0 && len(data.Chat.Comments) == 0 { + if encoded, err := json.Marshal(data); err == nil { + // Create unique copy of message for only this client. + message = &api.ServerMessage{ + Id: message.Id, + Type: message.Type, + Event: &api.EventServerMessage{ + Type: message.Event.Type, + Target: message.Event.Target, + Message: &api.RoomEventMessage{ + RoomId: message.Event.Message.RoomId, + Data: encoded, + }, }, - }, + } } + } else { + // Forward different chat comments individually. + var result []*api.ServerMessage + for _, comment := range data.Chat.Comments { + commentData := api.RoomEventMessageData{ + Type: data.Type, + Chat: &api.RoomEventMessageDataChat{ + Refresh: data.Chat.Refresh, + Comment: comment, + }, + } + if encoded, err := json.Marshal(commentData); err == nil { + // Create unique copy of message for only this client. + result = append(result, &api.ServerMessage{ + Id: message.Id, + Type: message.Type, + Event: &api.EventServerMessage{ + Type: message.Event.Type, + Target: message.Event.Target, + Message: &api.RoomEventMessage{ + RoomId: message.Event.Message.RoomId, + Data: encoded, + }, + }, + }) + } + } + return nil, result } } } @@ -1483,16 +1535,16 @@ func (s *ClientSession) filterMessage(message *api.ServerMessage) *api.ServerMes if message.Message != nil && len(message.Message.Data) > 0 && s.HasPermission(api.PERMISSION_HIDE_DISPLAYNAMES) { var data api.MessageServerMessageData if err := json.Unmarshal(message.Message.Data, &data); err != nil { - return message + return message, nil } if data.Type == "nickChanged" { - return nil + return nil, nil } } } - return message + return message, nil } func (s *ClientSession) filterAsyncMessage(msg *events.AsyncMessage) *api.ServerMessage { diff --git a/server/clientsession_test.go b/server/clientsession_test.go index 208cc5d..5466618 100644 --- a/server/clientsession_test.go +++ b/server/clientsession_test.go @@ -249,6 +249,72 @@ func TestFeatureChatRelay(t *testing.T) { } } } + + chatComment2 := api.StringMap{ + "hello": "world", + } + message2 := api.StringMap{ + "type": "chat", + "chat": api.StringMap{ + "refresh": true, + "comments": []api.StringMap{ + chatComment, + chatComment2, + }, + }, + } + data2, err := json.Marshal(message2) + require.NoError(err) + + // Simulate request from the backend. + room.processAsyncMessage(&events.AsyncMessage{ + Type: "room", + Room: &talk.BackendServerRoomRequest{ + Type: "message", + Message: &talk.BackendRoomMessageRequest{ + Data: data2, + }, + }, + }) + + if msg, ok := client.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + if feature { + assert.EqualValues(chatComment, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + + // A second message with the second comment will be sent + if msg, ok := client.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + assert.EqualValues(chatComment2, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } + } + } + } else { + // Only a single refresh will be sent + assert.Equal(true, chat["refresh"]) + _, found := chat["comment"] + assert.False(found, "the comment should not be included") + + ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel2() + + client.RunUntilErrorIs(ctx2, context.DeadlineExceeded) + } + } + } + } } } @@ -461,6 +527,99 @@ func TestFeatureChatRelayFederation(t *testing.T) { } } } + + chatComment2 := api.StringMap{ + "hello": "world", + } + message2 := api.StringMap{ + "type": "chat", + "chat": api.StringMap{ + "refresh": true, + "comments": []api.StringMap{ + chatComment, + chatComment2, + }, + }, + } + data2, err := json.Marshal(message2) + require.NoError(err) + + // Simulate request from the backend. + room.processAsyncMessage(&events.AsyncMessage{ + Type: "room", + Room: &talk.BackendServerRoomRequest{ + Type: "message", + Message: &talk.BackendRoomMessageRequest{ + Data: data2, + }, + }, + }) + + // The first client will receive the message for the local room (always including the actual message). + if msg, ok := client1.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + AssertEqualSerialized(t, chatComment, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } + } + } + // A second message with the second comment will be sent + if msg, ok := client1.RunUntilRoomMessage(ctx); ok { + assert.Equal(roomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + assert.EqualValues(chatComment2, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } + } + } + + // The second client will receive the message from the federated room (either as refresh or with the message). + if msg, ok := client2.RunUntilRoomMessage(ctx); ok { + assert.Equal(federatedRoomId, msg.RoomId) + var data api.StringMap + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + if feature { + AssertEqualSerialized(t, federatedChatComment, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + + // A second message with the second comment will be sent + if msg, ok := client2.RunUntilRoomMessage(ctx); ok { + assert.Equal(federatedRoomId, msg.RoomId) + if err := json.Unmarshal(msg.Data, &data); assert.NoError(err) { + assert.Equal("chat", data["type"], "invalid type entry in %+v", data) + if chat, found := api.GetStringMapEntry[map[string]any](data, "chat"); assert.True(found, "chat entry is missing in %+v", data) { + assert.EqualValues(chatComment2, chat["comment"]) + _, found := chat["refresh"] + assert.False(found, "refresh should not be included") + } + } + } + } else { + // Only a single refresh will be sent + assert.Equal(true, chat["refresh"]) + _, found := chat["comment"] + assert.False(found, "the comment should not be included") + + ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel2() + + client2.RunUntilErrorIs(ctx2, context.DeadlineExceeded) + } + } + } + } } } From 77f06726821825404b49f6549e940e304fc7c136 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 11:31:41 +0100 Subject: [PATCH 472/549] Update generated files. --- api/signaling_easyjson.go | 270 ++++++++++++++++++++++---------------- 1 file changed, 159 insertions(+), 111 deletions(-) diff --git a/api/signaling_easyjson.go b/api/signaling_easyjson.go index 4a4ce0e..c385b66 100644 --- a/api/signaling_easyjson.go +++ b/api/signaling_easyjson.go @@ -1396,6 +1396,35 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(in *jl in.AddError((out.Comment).UnmarshalJSON(data)) } } + case "comments": + if in.IsNull() { + in.Skip() + out.Comments = nil + } else { + in.Delim('[') + if out.Comments == nil { + if !in.IsDelim(']') { + out.Comments = make([]json.RawMessage, 0, 2) + } else { + out.Comments = []json.RawMessage{} + } + } else { + out.Comments = (out.Comments)[:0] + } + for !in.IsDelim(']') { + var v16 json.RawMessage + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((v16).UnmarshalJSON(data)) + } + } + out.Comments = append(out.Comments, v16) + in.WantComma() + } + in.Delim(']') + } default: in.SkipRecursive() } @@ -1426,6 +1455,25 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(out *j } out.Raw((in.Comment).MarshalJSON()) } + if len(in.Comments) != 0 { + const prefix string = ",\"comments\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + { + out.RawByte('[') + for v17, v18 := range in.Comments { + if v17 > 0 { + out.RawByte(',') + } + out.Raw((v18).MarshalJSON()) + } + out.RawByte(']') + } + } out.RawByte('}') } @@ -1749,33 +1797,33 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *j out.Changed = (out.Changed)[:0] } for !in.IsDelim(']') { - var v16 StringMap + var v19 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v16 = make(StringMap) + v19 = make(StringMap) } else { - v16 = nil + v19 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v17 interface{} - if m, ok := v17.(easyjson.Unmarshaler); ok { + var v20 interface{} + if m, ok := v20.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v17.(json.Unmarshaler); ok { + } else if m, ok := v20.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v17 = in.Interface() + v20 = in.Interface() } - (v16)[key] = v17 + (v19)[key] = v20 in.WantComma() } in.Delim('}') } - out.Changed = append(out.Changed, v16) + out.Changed = append(out.Changed, v19) in.WantComma() } in.Delim(']') @@ -1796,33 +1844,33 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *j out.Users = (out.Users)[:0] } for !in.IsDelim(']') { - var v18 StringMap + var v21 StringMap if in.IsNull() { in.Skip() } else { in.Delim('{') if !in.IsDelim('}') { - v18 = make(StringMap) + v21 = make(StringMap) } else { - v18 = nil + v21 = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v19 interface{} - if m, ok := v19.(easyjson.Unmarshaler); ok { + var v22 interface{} + if m, ok := v22.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v19.(json.Unmarshaler); ok { + } else if m, ok := v22.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v19 = in.Interface() + v22 = in.Interface() } - (v18)[key] = v19 + (v21)[key] = v22 in.WantComma() } in.Delim('}') } - out.Users = append(out.Users, v18) + out.Users = append(out.Users, v21) in.WantComma() } in.Delim(']') @@ -1872,43 +1920,7 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(out * out.RawString(prefix) { out.RawByte('[') - for v20, v21 := range in.Changed { - if v20 > 0 { - out.RawByte(',') - } - if v21 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { - out.RawString(`null`) - } else { - out.RawByte('{') - v22First := true - for v22Name, v22Value := range v21 { - if v22First { - v22First = false - } else { - out.RawByte(',') - } - out.String(string(v22Name)) - out.RawByte(':') - if m, ok := v22Value.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := v22Value.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(v22Value)) - } - } - out.RawByte('}') - } - } - out.RawByte(']') - } - } - if len(in.Users) != 0 { - const prefix string = ",\"users\":" - out.RawString(prefix) - { - out.RawByte('[') - for v23, v24 := range in.Users { + for v23, v24 := range in.Changed { if v23 > 0 { out.RawByte(',') } @@ -1939,6 +1951,42 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(out * out.RawByte(']') } } + if len(in.Users) != 0 { + const prefix string = ",\"users\":" + out.RawString(prefix) + { + out.RawByte('[') + for v26, v27 := range in.Users { + if v26 > 0 { + out.RawByte(',') + } + if v27 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + out.RawString(`null`) + } else { + out.RawByte('{') + v28First := true + for v28Name, v28Value := range v27 { + if v28First { + v28First = false + } else { + out.RawByte(',') + } + out.String(string(v28Name)) + out.RawByte(':') + if m, ok := v28Value.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := v28Value.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(v28Value)) + } + } + out.RawByte('}') + } + } + out.RawByte(']') + } + } if in.All { const prefix string = ",\"all\":" out.RawString(prefix) @@ -2623,15 +2671,15 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(in *j for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v26 interface{} - if m, ok := v26.(easyjson.Unmarshaler); ok { + var v29 interface{} + if m, ok := v29.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v26.(json.Unmarshaler); ok { + } else if m, ok := v29.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v26 = in.Interface() + v29 = in.Interface() } - (out.Payload)[key] = v26 + (out.Payload)[key] = v29 in.WantComma() } in.Delim('}') @@ -2702,21 +2750,21 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(out * out.RawString(`null`) } else { out.RawByte('{') - v27First := true - for v27Name, v27Value := range in.Payload { - if v27First { - v27First = false + v30First := true + for v30Name, v30Value := range in.Payload { + if v30First { + v30First = false } else { out.RawByte(',') } - out.String(string(v27Name)) + out.String(string(v30Name)) out.RawByte(':') - if m, ok := v27Value.(easyjson.Marshaler); ok { + if m, ok := v30Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v27Value.(json.Marshaler); ok { + } else if m, ok := v30Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v27Value)) + out.Raw(json.Marshal(v30Value)) } } out.RawByte('}') @@ -3868,13 +3916,13 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(in *j out.Features = (out.Features)[:0] } for !in.IsDelim(']') { - var v28 string + var v31 string if in.IsNull() { in.Skip() } else { - v28 = string(in.String()) + v31 = string(in.String()) } - out.Features = append(out.Features, v28) + out.Features = append(out.Features, v31) in.WantComma() } in.Delim(']') @@ -3922,11 +3970,11 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(out * out.RawString(prefix) { out.RawByte('[') - for v29, v30 := range in.Features { - if v29 > 0 { + for v32, v33 := range in.Features { + if v32 > 0 { out.RawByte(',') } - out.String(string(v30)) + out.String(string(v33)) } out.RawByte(']') } @@ -4359,13 +4407,13 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(in *j out.Features = (out.Features)[:0] } for !in.IsDelim(']') { - var v31 string + var v34 string if in.IsNull() { in.Skip() } else { - v31 = string(in.String()) + v34 = string(in.String()) } - out.Features = append(out.Features, v31) + out.Features = append(out.Features, v34) in.WantComma() } in.Delim(']') @@ -4419,11 +4467,11 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(out * out.RawString(prefix) { out.RawByte('[') - for v32, v33 := range in.Features { - if v32 > 0 { + for v35, v36 := range in.Features { + if v35 > 0 { out.RawByte(',') } - out.String(string(v33)) + out.String(string(v36)) } out.RawByte(']') } @@ -4511,13 +4559,13 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(in *j out.Join = (out.Join)[:0] } for !in.IsDelim(']') { - var v34 EventServerMessageSessionEntry + var v37 EventServerMessageSessionEntry if in.IsNull() { in.Skip() } else { - (v34).UnmarshalEasyJSON(in) + (v37).UnmarshalEasyJSON(in) } - out.Join = append(out.Join, v34) + out.Join = append(out.Join, v37) in.WantComma() } in.Delim(']') @@ -4538,13 +4586,13 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(in *j out.Leave = (out.Leave)[:0] } for !in.IsDelim(']') { - var v35 PublicSessionId + var v38 PublicSessionId if in.IsNull() { in.Skip() } else { - v35 = PublicSessionId(in.String()) + v38 = PublicSessionId(in.String()) } - out.Leave = append(out.Leave, v35) + out.Leave = append(out.Leave, v38) in.WantComma() } in.Delim(']') @@ -4565,13 +4613,13 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(in *j out.Change = (out.Change)[:0] } for !in.IsDelim(']') { - var v36 EventServerMessageSessionEntry + var v39 EventServerMessageSessionEntry if in.IsNull() { in.Skip() } else { - (v36).UnmarshalEasyJSON(in) + (v39).UnmarshalEasyJSON(in) } - out.Change = append(out.Change, v36) + out.Change = append(out.Change, v39) in.WantComma() } in.Delim(']') @@ -4703,11 +4751,11 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(out * out.RawString(prefix) { out.RawByte('[') - for v37, v38 := range in.Join { - if v37 > 0 { + for v40, v41 := range in.Join { + if v40 > 0 { out.RawByte(',') } - (v38).MarshalEasyJSON(out) + (v41).MarshalEasyJSON(out) } out.RawByte(']') } @@ -4717,11 +4765,11 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(out * out.RawString(prefix) { out.RawByte('[') - for v39, v40 := range in.Leave { - if v39 > 0 { + for v42, v43 := range in.Leave { + if v42 > 0 { out.RawByte(',') } - out.String(string(v40)) + out.String(string(v43)) } out.RawByte(']') } @@ -4731,11 +4779,11 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(out * out.RawString(prefix) { out.RawByte('[') - for v41, v42 := range in.Change { - if v41 > 0 { + for v44, v45 := range in.Change { + if v44 > 0 { out.RawByte(',') } - (v42).MarshalEasyJSON(out) + (v45).MarshalEasyJSON(out) } out.RawByte(']') } @@ -5844,15 +5892,15 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(in *j for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v43 interface{} - if m, ok := v43.(easyjson.Unmarshaler); ok { + var v46 interface{} + if m, ok := v46.(easyjson.Unmarshaler); ok { m.UnmarshalEasyJSON(in) - } else if m, ok := v43.(json.Unmarshaler); ok { + } else if m, ok := v46.(json.Unmarshaler); ok { _ = m.UnmarshalJSON(in.Raw()) } else { - v43 = in.Interface() + v46 = in.Interface() } - (out.Payload)[key] = v43 + (out.Payload)[key] = v46 in.WantComma() } in.Delim('}') @@ -5904,21 +5952,21 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(out * out.RawString(`null`) } else { out.RawByte('{') - v44First := true - for v44Name, v44Value := range in.Payload { - if v44First { - v44First = false + v47First := true + for v47Name, v47Value := range in.Payload { + if v47First { + v47First = false } else { out.RawByte(',') } - out.String(string(v44Name)) + out.String(string(v47Name)) out.RawByte(':') - if m, ok := v44Value.(easyjson.Marshaler); ok { + if m, ok := v47Value.(easyjson.Marshaler); ok { m.MarshalEasyJSON(out) - } else if m, ok := v44Value.(json.Marshaler); ok { + } else if m, ok := v47Value.(json.Marshaler); ok { out.Raw(m.MarshalJSON()) } else { - out.Raw(json.Marshal(v44Value)) + out.Raw(json.Marshal(v47Value)) } } out.RawByte('}') From 15d734d77b0c7534e0330ceb22f838a0ee2b5854 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 11:31:56 +0100 Subject: [PATCH 473/549] Document sending multiple chat messages from Talk. --- docs/standalone-signaling-api-v1.md | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index e961dfb..e6c5d17 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -1867,6 +1867,36 @@ Message format (Backend -> Server) } +The signaling server also supports combining multiple chat comments into one +request which will then be sent out individually to clients. + +Message format (Backend -> Server) + + { + "type": "message" + "message" { + "data": { + "type": "chat", + "chat": { + "refresh": true, + "comments": [ + { + ...properties of the first comment written in the chat... + }, + { + ...properties of the second comment written in the chat... + } + ] + } + } + } + } + +In this case, clients will either receive a single `"refresh": true` message +(if they don't support `chat-relay`) or multiple messages with the different +comments. + + ### Notify sessions to switch to a different room This can be used to let sessions in a room know that they switch to a different From cde3ee53a9aac32f0ea108879018dc9f640c7e36 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 16:31:03 +0100 Subject: [PATCH 474/549] Add mock ServerHub implementation. --- grpc/test/server.go | 49 +++++++++++++++++++++++++ grpc/test/server_test.go | 79 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/grpc/test/server.go b/grpc/test/server.go index fd1ba21..ef67170 100644 --- a/grpc/test/server.go +++ b/grpc/test/server.go @@ -22,7 +22,10 @@ package test import ( + "context" + "errors" "net" + "net/url" "strconv" "testing" @@ -30,9 +33,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/grpc" "github.com/strukturag/nextcloud-spreed-signaling/log" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -71,3 +77,46 @@ func NewServerForTest(t *testing.T) (server *grpc.Server, addr string) { config := goconf.NewConfigFile() return NewServerForTestWithConfig(t, config) } + +type MockHub struct { +} + +func (h *MockHub) GetSessionIdByResumeId(resumeId api.PrivateSessionId) api.PublicSessionId { + return "" +} + +func (h *MockHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { + return "", errors.New("not implemented") +} + +func (h *MockHub) IsSessionIdInCall(sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, bool) { + return false, false +} + +func (h *MockHub) DisconnectSessionByRoomSessionId(sessionId api.PublicSessionId, roomSessionId api.RoomSessionId, reason string) { +} + +func (h *MockHub) GetBackend(u *url.URL) *talk.Backend { + return nil +} + +func (h *MockHub) GetInternalSessions(roomId string, backend *talk.Backend) ([]*grpc.InternalSessionData, []*grpc.VirtualSessionData, bool) { + return nil, nil, false +} + +func (h *MockHub) GetTransientEntries(roomId string, backend *talk.Backend) (api.TransientDataEntries, bool) { + return nil, false +} + +func (h *MockHub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*grpc.GetPublisherIdReply, error) { + return nil, errors.New("not implemented") +} + +func (h *MockHub) ProxySession(request grpc.RpcSessions_ProxySessionServer) error { + return errors.New("not implemented") +} + +var ( + // Compile-time check that MockHub implements the interface. + _ grpc.ServerHub = &MockHub{} +) diff --git a/grpc/test/server_test.go b/grpc/test/server_test.go index bd5e871..323334e 100644 --- a/grpc/test/server_test.go +++ b/grpc/test/server_test.go @@ -22,12 +22,43 @@ package test import ( + "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) +type emptyReceiver struct { +} + +func (r *emptyReceiver) RemoteAddr() string { + return "127.0.0.1" +} + +func (r *emptyReceiver) Country() geoip.Country { + return "DE" +} + +func (r *emptyReceiver) UserAgent() string { + return "testing" +} + +func (r *emptyReceiver) OnProxyMessage(message *grpc.ServerSessionMessage) error { + return errors.New("not implemented") +} + +func (r *emptyReceiver) OnProxyClose(err error) { + // Ignore +} + func TestServer(t *testing.T) { t.Parallel() @@ -37,15 +68,63 @@ func TestServer(t *testing.T) { serverId := "the-test-server-id" server, addr := NewServerForTest(t) server.SetServerId(serverId) + hub := &MockHub{} + server.SetHub(hub) clients, _ := NewClientsForTest(t, addr, nil) require.NoError(clients.WaitForInitialized(t.Context())) + backend := talk.NewCompatBackend(nil) + for _, client := range clients.GetClients() { if id, version, err := client.GetServerId(t.Context()); assert.NoError(err) { assert.Equal(serverId, id) assert.NotEmpty(version) } + + reply, err := client.LookupResumeId(t.Context(), "resume-id") + assert.ErrorIs(err, grpc.ErrNoSuchResumeId) + assert.Nil(reply) + + id, err := client.LookupSessionId(t.Context(), "session-id", "") + if s, ok := status.FromError(err); assert.True(ok) { + assert.Equal(codes.Unknown, s.Code()) + assert.Equal("not implemented", s.Message()) + } + assert.Empty(id) + + if incall, err := client.IsSessionInCall(t.Context(), "session-id", "room-id", ""); assert.NoError(err) { + assert.False(incall) + } + + if internal, virtual, err := client.GetInternalSessions(t.Context(), "room-id", nil); assert.NoError(err) { + assert.Empty(internal) + assert.Empty(virtual) + } + + publisherId, proxyUrl, ip, connToken, publisherToken, err := client.GetPublisherId(t.Context(), "session-id", sfu.StreamTypeVideo) + if s, ok := status.FromError(err); assert.True(ok) { + assert.Equal(codes.Unknown, s.Code()) + assert.Equal("not implemented", s.Message()) + } + assert.Empty(publisherId) + assert.Empty(proxyUrl) + assert.Empty(ip) + assert.Empty(connToken) + assert.Empty(publisherToken) + + if count, err := client.GetSessionCount(t.Context(), ""); assert.NoError(err) { + assert.EqualValues(0, count) + } + + if data, err := client.GetTransientData(t.Context(), "room-id", backend); assert.NoError(err) { + assert.Empty(data) + } + + receiver := &emptyReceiver{} + proxy, err := client.ProxySession(t.Context(), "session-id", receiver) + assert.NoError(err) + assert.NotNil(proxy) } } From 710264e3667a82b13b90135c6912e5348ce683f4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 16:35:50 +0100 Subject: [PATCH 475/549] Move SFU proxy tests closer to the checked code. --- server/hub_sfu_proxy_test.go | 653 ----------------------------------- sfu/proxy/proxy_test.go | 510 +++++++++++++++++++++++++++ 2 files changed, 510 insertions(+), 653 deletions(-) delete mode 100644 server/hub_sfu_proxy_test.go diff --git a/server/hub_sfu_proxy_test.go b/server/hub_sfu_proxy_test.go deleted file mode 100644 index 278f3be..0000000 --- a/server/hub_sfu_proxy_test.go +++ /dev/null @@ -1,653 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2026 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 server - -import ( - "context" - "errors" - "net/url" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/mock" - proxytest "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/test" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" - "github.com/strukturag/nextcloud-spreed-signaling/talk" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type mockGrpcServerHub struct { - proxy atomic.Pointer[sfu.WithToken] - sessionsLock sync.Mutex - // +checklocks:sessionsLock - sessionByPublicId map[api.PublicSessionId]Session -} - -func (h *mockGrpcServerHub) setProxy(t *testing.T, proxy sfu.SFU) { - t.Helper() - - wt, ok := proxy.(sfu.WithToken) - require.True(t, ok, "need a sfu with token support") - h.proxy.Store(&wt) -} - -func (h *mockGrpcServerHub) getSession(sessionId api.PublicSessionId) Session { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - - return h.sessionByPublicId[sessionId] -} - -func (h *mockGrpcServerHub) addSession(session *ClientSession) { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - if h.sessionByPublicId == nil { - h.sessionByPublicId = make(map[api.PublicSessionId]Session) - } - h.sessionByPublicId[session.PublicId()] = session -} - -func (h *mockGrpcServerHub) removeSession(session *ClientSession) { - h.sessionsLock.Lock() - defer h.sessionsLock.Unlock() - delete(h.sessionByPublicId, session.PublicId()) -} - -func (h *mockGrpcServerHub) GetSessionIdByResumeId(resumeId api.PrivateSessionId) api.PublicSessionId { - return "" -} - -func (h *mockGrpcServerHub) GetSessionIdByRoomSessionId(roomSessionId api.RoomSessionId) (api.PublicSessionId, error) { - return "", nil -} - -func (h *mockGrpcServerHub) IsSessionIdInCall(sessionId api.PublicSessionId, roomId string, backendUrl string) (bool, bool) { - return false, false -} - -func (h *mockGrpcServerHub) DisconnectSessionByRoomSessionId(sessionId api.PublicSessionId, roomSessionId api.RoomSessionId, reason string) { -} - -func (h *mockGrpcServerHub) GetBackend(u *url.URL) *talk.Backend { - return nil -} - -func (h *mockGrpcServerHub) GetInternalSessions(roomId string, backend *talk.Backend) ([]*grpc.InternalSessionData, []*grpc.VirtualSessionData, bool) { - return nil, nil, false -} - -func (h *mockGrpcServerHub) GetTransientEntries(roomId string, backend *talk.Backend) (api.TransientDataEntries, bool) { - return nil, false -} - -func (h *mockGrpcServerHub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*grpc.GetPublisherIdReply, error) { - session := h.getSession(sessionId) - if session == nil { - return nil, status.Error(codes.NotFound, "no such session") - } - - clientSession, ok := session.(*ClientSession) - if !ok { - return nil, status.Error(codes.NotFound, "no such session") - } - - publisher := clientSession.GetOrWaitForPublisher(ctx, streamType) - if publisher, ok := publisher.(sfu.PublisherWithConnectionUrlAndIP); ok { - connUrl, ip := publisher.GetConnectionURL() - reply := &grpc.GetPublisherIdReply{ - PublisherId: publisher.Id(), - ProxyUrl: connUrl, - } - if len(ip) > 0 { - reply.Ip = ip.String() - } - - if proxy := h.proxy.Load(); proxy != nil { - reply.ConnectToken, _ = (*proxy).CreateToken("") - reply.PublisherToken, _ = (*proxy).CreateToken(publisher.Id()) - } - return reply, nil - } - - return nil, status.Error(codes.NotFound, "no such publisher") -} - -func (h *mockGrpcServerHub) ProxySession(request grpc.RpcSessions_ProxySessionServer) error { - return errors.New("not implemented") -} - -func Test_ProxyRemotePublisher(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := grpctest.NewServerForTest(t) - grpcServer2, addr2 := grpctest.NewServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.SetHub(hub1) - grpcServer2.SetHub(hub2) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := testserver.NewProxyServerForTest(t, "DE") - server2 := testserver.NewProxyServerForTest(t, "DE") - - mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - }, - }, 1, nil) - hub1.setProxy(t, mcu1) - mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - }, - }, 2, nil) - hub2.setProxy(t, mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := mock.NewListener(pubId + "-public") - pubInitiator := mock.NewInitiator("DE") - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[sfu.StreamType]sfu.Publisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ - MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[sfu.StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - subListener := mock.NewListener("subscriber-public") - subInitiator := mock.NewInitiator("DE") - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} - -func Test_ProxyMultipleRemotePublisher(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := grpctest.NewServerForTest(t) - grpcServer2, addr2 := grpctest.NewServerForTest(t) - grpcServer3, addr3 := grpctest.NewServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - hub3 := &mockGrpcServerHub{} - grpcServer1.SetHub(hub1) - grpcServer2.SetHub(hub2) - grpcServer3.SetHub(hub3) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - embedEtcd.SetValue("/grpctargets/three", []byte("{\"address\":\""+addr3+"\"}")) - - server1 := testserver.NewProxyServerForTest(t, "DE") - server2 := testserver.NewProxyServerForTest(t, "US") - server3 := testserver.NewProxyServerForTest(t, "US") - - mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - server3, - }, - }, 1, nil) - hub1.setProxy(t, mcu1) - mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - server3, - }, - }, 2, nil) - hub2.setProxy(t, mcu2) - mcu3, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - server3, - }, - }, 3, nil) - hub3.setProxy(t, mcu3) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := mock.NewListener(pubId + "-public") - pubInitiator := mock.NewInitiator("DE") - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[sfu.StreamType]sfu.Publisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ - MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[sfu.StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - sub1Listener := mock.NewListener("subscriber-public-1") - sub1Initiator := mock.NewInitiator("US") - sub1, err := mcu2.NewSubscriber(ctx, sub1Listener, pubId, sfu.StreamTypeVideo, sub1Initiator) - require.NoError(t, err) - - defer sub1.Close(context.Background()) - - sub2Listener := mock.NewListener("subscriber-public-2") - sub2Initiator := mock.NewInitiator("US") - sub2, err := mcu3.NewSubscriber(ctx, sub2Listener, pubId, sfu.StreamTypeVideo, sub2Initiator) - require.NoError(t, err) - - defer sub2.Close(context.Background()) -} - -func Test_ProxyRemotePublisherWait(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := grpctest.NewServerForTest(t) - grpcServer2, addr2 := grpctest.NewServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.SetHub(hub1) - grpcServer2.SetHub(hub2) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := testserver.NewProxyServerForTest(t, "DE") - server2 := testserver.NewProxyServerForTest(t, "DE") - - mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - }, - }, 1, nil) - hub1.setProxy(t, mcu1) - mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - server2, - }, - }, 2, nil) - hub2.setProxy(t, mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := mock.NewListener(pubId + "-public") - pubInitiator := mock.NewInitiator("DE") - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[sfu.StreamType]sfu.Publisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - subListener := mock.NewListener("subscriber-public") - subInitiator := mock.NewInitiator("DE") - - done := make(chan struct{}) - go func() { - defer close(done) - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) - if !assert.NoError(t, err) { - return - } - - defer sub.Close(context.Background()) - }() - - // Give subscriber goroutine some time to start - time.Sleep(100 * time.Millisecond) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ - MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[sfu.StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - select { - case <-done: - case <-ctx.Done(): - assert.NoError(t, ctx.Err()) - } -} - -func Test_ProxyRemotePublisherTemporary(t *testing.T) { - t.Parallel() - - assert := assert.New(t) - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := grpctest.NewServerForTest(t) - grpcServer2, addr2 := grpctest.NewServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.SetHub(hub1) - grpcServer2.SetHub(hub2) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := testserver.NewProxyServerForTest(t, "DE") - server2 := testserver.NewProxyServerForTest(t, "DE") - - mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - }, - }, 1, nil) - hub1.setProxy(t, mcu1) - mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server2, - }, - }, 2, nil) - hub2.setProxy(t, mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := mock.NewListener(pubId + "-public") - pubInitiator := mock.NewInitiator("DE") - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[sfu.StreamType]sfu.Publisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ - MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[sfu.StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - type connectionCounter interface { - ConnectionsCount() int - } - - if counter2, ok := mcu2.(connectionCounter); assert.True(ok) { - assert.Equal(1, counter2.ConnectionsCount()) - } - - subListener := mock.NewListener("subscriber-public") - subInitiator := mock.NewInitiator("DE") - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) - - if connSub, ok := sub.(sfu.SubscriberWithConnectionUrlAndIP); assert.True(ok) { - url, ip := connSub.GetConnectionURL() - assert.Equal(server1.URL(), url) - assert.Empty(ip) - } - - // The temporary connection has been added - if counter2, ok := mcu2.(connectionCounter); assert.True(ok) { - assert.Equal(2, counter2.ConnectionsCount()) - } - - sub.Close(context.Background()) - - // Wait for temporary connection to be removed. -loop: - for { - select { - case <-ctx.Done(): - assert.NoError(ctx.Err()) - default: - if counter2, ok := mcu2.(connectionCounter); assert.True(ok) { - if counter2.ConnectionsCount() == 1 { - break loop - } - } - } - } -} - -func Test_ProxyConnectToken(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := grpctest.NewServerForTest(t) - grpcServer2, addr2 := grpctest.NewServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.SetHub(hub1) - grpcServer2.SetHub(hub2) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := testserver.NewProxyServerForTest(t, "DE") - server2 := testserver.NewProxyServerForTest(t, "DE") - - // Signaling server instances are in a cluster but don't share their proxies, - // i.e. they are only known to their local proxy, not the one of the other - // signaling server - so the connection token must be passed between them. - mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - }, - }, 1, nil) - hub1.setProxy(t, mcu1) - mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server2, - }, - }, 2, nil) - hub2.setProxy(t, mcu2) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := mock.NewListener(pubId + "-public") - pubInitiator := mock.NewInitiator("DE") - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[sfu.StreamType]sfu.Publisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ - MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[sfu.StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - subListener := mock.NewListener("subscriber-public") - subInitiator := mock.NewInitiator("DE") - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} - -func Test_ProxyPublisherToken(t *testing.T) { - t.Parallel() - - embedEtcd := etcdtest.NewServerForTest(t) - - grpcServer1, addr1 := grpctest.NewServerForTest(t) - grpcServer2, addr2 := grpctest.NewServerForTest(t) - - hub1 := &mockGrpcServerHub{} - hub2 := &mockGrpcServerHub{} - grpcServer1.SetHub(hub1) - grpcServer2.SetHub(hub2) - - embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) - embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) - - server1 := testserver.NewProxyServerForTest(t, "DE") - server2 := testserver.NewProxyServerForTest(t, "US") - - // Signaling server instances are in a cluster but don't share their proxies, - // i.e. they are only known to their local proxy, not the one of the other - // signaling server - so the connection token must be passed between them. - // Also the subscriber is connecting from a different country, so a remote - // stream will be created that needs a valid token from the remote proxy. - mcu1, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server1, - }, - }, 1, nil) - hub1.setProxy(t, mcu1) - mcu2, _ := proxytest.NewMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ - Etcd: embedEtcd, - Servers: []testserver.ProxyTestServer{ - server2, - }, - }, 2, nil) - hub2.setProxy(t, mcu2) - // Support remote subscribers for the tests. - server1.Servers = append(server1.Servers, server2) - server2.Servers = append(server2.Servers, server1) - - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - defer cancel() - - pubId := api.PublicSessionId("the-publisher") - pubSid := "1234567890" - pubListener := mock.NewListener(pubId + "-public") - pubInitiator := mock.NewInitiator("DE") - - session1 := &ClientSession{ - publicId: pubId, - publishers: make(map[sfu.StreamType]sfu.Publisher), - } - hub1.addSession(session1) - defer hub1.removeSession(session1) - - pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ - MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, - }, pubInitiator) - require.NoError(t, err) - - defer pub.Close(context.Background()) - - session1.mu.Lock() - session1.publishers[sfu.StreamTypeVideo] = pub - session1.publisherWaiters.Wakeup() - session1.mu.Unlock() - - subListener := mock.NewListener("subscriber-public") - subInitiator := mock.NewInitiator("US") - sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) - require.NoError(t, err) - - defer sub.Close(context.Background()) -} diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go index 41049fd..47c225f 100644 --- a/sfu/proxy/proxy_test.go +++ b/sfu/proxy/proxy_test.go @@ -25,6 +25,7 @@ import ( "context" "crypto/rand" "crypto/rsa" + "encoding/json" "fmt" "net" "net/url" @@ -32,6 +33,7 @@ import ( "slices" "strconv" "strings" + "sync" "testing" "time" @@ -40,10 +42,12 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/async" dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/etcd" etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/grpc" grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" @@ -1238,3 +1242,509 @@ func Test_ProxyResumeFail(t *testing.T) { assert.NotEqual(sessionId, connections[0].SessionId()) } } + +type publisherHub struct { + grpctest.MockHub + + mu sync.Mutex + // +checklocks:mu + publishers map[api.PublicSessionId]*proxyPublisher + waiter async.ChannelWaiters // +checklocksignore: Has its own locking. +} + +func newPublisherHub() *publisherHub { + return &publisherHub{ + publishers: make(map[api.PublicSessionId]*proxyPublisher), + } +} + +func (h *publisherHub) addPublisher(publisher *proxyPublisher) { + h.mu.Lock() + defer h.mu.Unlock() + + h.publishers[publisher.PublisherId()] = publisher + h.waiter.Wakeup() +} + +func (h *publisherHub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*grpc.GetPublisherIdReply, error) { + h.mu.Lock() + defer h.mu.Unlock() + + pub, found := h.publishers[sessionId] + if !found { + ch := make(chan struct{}, 1) + id := h.waiter.Add(ch) + defer h.waiter.Remove(id) + + for !found { + h.mu.Unlock() + select { + case <-ch: + h.mu.Lock() + pub, found = h.publishers[sessionId] + case <-ctx.Done(): + h.mu.Lock() + return nil, ctx.Err() + } + } + } + + connToken, err := pub.conn.proxy.CreateToken("") + if err != nil { + return nil, err + } + pubToken, err := pub.conn.proxy.CreateToken(string(pub.Id())) + if err != nil { + return nil, err + } + + reply := &grpc.GetPublisherIdReply{ + PublisherId: pub.Id(), + ProxyUrl: pub.conn.rawUrl, + ConnectToken: connToken, + PublisherToken: pubToken, + } + if ip := pub.conn.ip; len(ip) > 0 { + reply.Ip = ip.String() + } + return reply, nil +} + +func Test_ProxyRemotePublisher(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) + + hub1 := newPublisherHub() + hub2 := newPublisherHub() + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + mcu1, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 1, nil) + mcu2, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 2, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + if proxyPub, ok := pub.(*proxyPublisher); assert.True(ok) { + hub1.addPublisher(proxyPub) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} + +func Test_ProxyMultipleRemotePublisher(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) + grpcServer3, addr3 := grpctest.NewServerForTest(t) + + hub1 := newPublisherHub() + hub2 := newPublisherHub() + hub3 := newPublisherHub() + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) + grpcServer3.SetHub(hub3) + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + embedEtcd.SetValue("/grpctargets/three", []byte("{\"address\":\""+addr3+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "US") + server3 := testserver.NewProxyServerForTest(t, "US") + + mcu1, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + server3, + }, + }, 1, nil) + mcu2, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + server3, + }, + }, 2, nil) + mcu3, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + server3, + }, + }, 3, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + if proxyPub, ok := pub.(*proxyPublisher); assert.True(ok) { + hub1.addPublisher(proxyPub) + } + + sub1Listener := mock.NewListener("subscriber-public-1") + sub1Initiator := mock.NewInitiator("US") + sub1, err := mcu2.NewSubscriber(ctx, sub1Listener, pubId, sfu.StreamTypeVideo, sub1Initiator) + require.NoError(t, err) + + defer sub1.Close(context.Background()) + + sub2Listener := mock.NewListener("subscriber-public-2") + sub2Initiator := mock.NewInitiator("US") + sub2, err := mcu3.NewSubscriber(ctx, sub2Listener, pubId, sfu.StreamTypeVideo, sub2Initiator) + require.NoError(t, err) + + defer sub2.Close(context.Background()) +} + +func Test_ProxyRemotePublisherWait(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) + + hub1 := newPublisherHub() + hub2 := newPublisherHub() + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + mcu1, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 1, nil) + mcu2, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + server2, + }, + }, 2, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + + done := make(chan struct{}) + go func() { + defer close(done) + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + if !assert.NoError(err) { + return + } + + defer sub.Close(context.Background()) + }() + + // Give subscriber goroutine some time to start + time.Sleep(100 * time.Millisecond) + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + if proxyPub, ok := pub.(*proxyPublisher); assert.True(ok) { + hub1.addPublisher(proxyPub) + } + + select { + case <-done: + case <-ctx.Done(): + assert.NoError(ctx.Err()) + } +} + +func Test_ProxyRemotePublisherTemporary(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + server, addr1 := grpctest.NewServerForTest(t) + hub := newPublisherHub() + server.SetHub(hub) + + target := grpc.TargetInformationEtcd{ + Address: addr1, + } + encoded, err := json.Marshal(target) + require.NoError(err) + embedEtcd.SetValue("/grpctargets/server", encoded) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + mcu1, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 1, nil) + mcu2, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server2, + }, + }, 2, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(err) + + defer pub.Close(context.Background()) + + if proxyPub, ok := pub.(*proxyPublisher); assert.True(ok) { + hub.addPublisher(proxyPub) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(err) + + defer sub.Close(context.Background()) + + if connSub, ok := sub.(*proxySubscriber); assert.True(ok) { + assert.Equal(server1.URL(), connSub.conn.rawUrl) + assert.Empty(connSub.conn.ip) + } + + // The temporary connection has been added + assert.Equal(2, mcu2.ConnectionsCount()) + + sub.Close(context.Background()) + + // Wait for temporary connection to be removed. +loop: + for { + select { + case <-ctx.Done(): + assert.NoError(ctx.Err()) + default: + if mcu2.ConnectionsCount() == 1 { + break loop + } + } + } +} + +func Test_ProxyConnectToken(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) + + hub1 := newPublisherHub() + hub2 := newPublisherHub() + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "DE") + + // Signaling server instances are in a cluster but don't share their proxies, + // i.e. they are only known to their local proxy, not the one of the other + // signaling server - so the connection token must be passed between them. + mcu1, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 1, nil) + mcu2, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server2, + }, + }, 2, nil) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + if proxyPub, ok := pub.(*proxyPublisher); assert.True(ok) { + hub1.addPublisher(proxyPub) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("DE") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} + +func Test_ProxyPublisherToken(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + embedEtcd := etcdtest.NewServerForTest(t) + + grpcServer1, addr1 := grpctest.NewServerForTest(t) + grpcServer2, addr2 := grpctest.NewServerForTest(t) + + hub1 := newPublisherHub() + hub2 := newPublisherHub() + grpcServer1.SetHub(hub1) + grpcServer2.SetHub(hub2) + + embedEtcd.SetValue("/grpctargets/one", []byte("{\"address\":\""+addr1+"\"}")) + embedEtcd.SetValue("/grpctargets/two", []byte("{\"address\":\""+addr2+"\"}")) + + server1 := testserver.NewProxyServerForTest(t, "DE") + server2 := testserver.NewProxyServerForTest(t, "US") + + // Signaling server instances are in a cluster but don't share their proxies, + // i.e. they are only known to their local proxy, not the one of the other + // signaling server - so the connection token must be passed between them. + // Also the subscriber is connecting from a different country, so a remote + // stream will be created that needs a valid token from the remote proxy. + mcu1, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server1, + }, + }, 1, nil) + mcu2, _ := newMcuProxyForTestWithOptions(t, testserver.ProxyTestOptions{ + Etcd: embedEtcd, + Servers: []testserver.ProxyTestServer{ + server2, + }, + }, 2, nil) + // Support remote subscribers for the tests. + server1.Servers = append(server1.Servers, server2) + server2.Servers = append(server2.Servers, server1) + + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("the-publisher") + pubSid := "1234567890" + pubListener := mock.NewListener(pubId + "-public") + pubInitiator := mock.NewInitiator("DE") + + pub, err := mcu1.NewPublisher(ctx, pubListener, pubId, pubSid, sfu.StreamTypeVideo, sfu.NewPublisherSettings{ + MediaTypes: sfu.MediaTypeVideo | sfu.MediaTypeAudio, + }, pubInitiator) + require.NoError(t, err) + + defer pub.Close(context.Background()) + + if proxyPub, ok := pub.(*proxyPublisher); assert.True(ok) { + hub1.addPublisher(proxyPub) + } + + subListener := mock.NewListener("subscriber-public") + subInitiator := mock.NewInitiator("US") + sub, err := mcu2.NewSubscriber(ctx, subListener, pubId, sfu.StreamTypeVideo, subInitiator) + require.NoError(t, err) + + defer sub.Close(context.Background()) +} From b733b6fa6076536498c0a87d2f8b2b0c65c5bfea Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 16:55:09 +0100 Subject: [PATCH 476/549] Implement "GetConnectionURL" for SFUPublisher. --- sfu/test/sfu.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sfu/test/sfu.go b/sfu/test/sfu.go index bd3db0a..7f77726 100644 --- a/sfu/test/sfu.go +++ b/sfu/test/sfu.go @@ -26,6 +26,7 @@ import ( "errors" "fmt" "maps" + "net" "sync" "sync/atomic" "testing" @@ -272,6 +273,10 @@ func (p *SFUPublisher) UnpublishRemote(ctx context.Context, remoteId api.PublicS return errors.New("remote publishing not supported") } +func (p *SFUPublisher) GetConnectionURL() (string, net.IP) { + return "https://proxy.domain.invalid", net.ParseIP("10.20.30.40") +} + type SFUSubscriber struct { SFUClient From d129c5c55b3c8dcf4a9806567ed3525aff4e2e5d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 29 Jan 2026 16:55:30 +0100 Subject: [PATCH 477/549] Add test for "Hub.GetPublisherIdForSessionId". --- server/hub_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/server/hub_test.go b/server/hub_test.go index 2bd62a1..13f0af0 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -65,6 +65,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/mock" natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" "github.com/strukturag/nextcloud-spreed-signaling/session" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" @@ -5259,3 +5260,53 @@ func TestGracefulShutdownOnExpiration(t *testing.T) { assert.Fail("should have shutdown") } } + +func TestHubGetPublisherIdForSessionId(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + + mcu := sfutest.NewSFU(t) + hub.SetMcu(mcu) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + + done := make(chan struct{}) + go func() { + defer close(done) + + if reply, err := hub.GetPublisherIdForSessionId(ctx, hello.Hello.SessionId, sfu.StreamTypeVideo); assert.NoError(err) && assert.NotNil(reply) { + assert.Equal("https://proxy.domain.invalid", reply.ProxyUrl) + assert.Equal("10.20.30.40", reply.Ip) + // The test-SFU doesn't support token creation. + assert.Empty(reply.ConnectToken) + assert.Empty(reply.PublisherToken) + + if session := hub.GetSessionByPublicId(hello.Hello.SessionId); assert.NotNil(session) { + if cs, ok := session.(*ClientSession); assert.True(ok) { + if pub := cs.GetPublisher(sfu.StreamTypeVideo); assert.NotNil(pub) { + assert.EqualValues(pub.PublisherId(), reply.PublisherId) + } + } + } + } + }() + + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + Sid: "54321", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + <-done +} From 1d4ffd33fc207f234a7842782fa282a2a759eeaa Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 10:26:52 +0100 Subject: [PATCH 478/549] Move SFU Janus tests closer to the checked code. --- server/hub_sfu_janus_test.go | 758 ------------------------------- sfu/janus/janus_test.go | 857 ++++++++++++++++++++++++++++++++++- sfu/janus/subscriber.go | 4 - 3 files changed, 854 insertions(+), 765 deletions(-) delete mode 100644 server/hub_sfu_janus_test.go diff --git a/server/hub_sfu_janus_test.go b/server/hub_sfu_janus_test.go deleted file mode 100644 index 5d30758..0000000 --- a/server/hub_sfu_janus_test.go +++ /dev/null @@ -1,758 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2026 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 server - -import ( - "context" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/dlintw/goconf" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - sfujanus "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" - janustest "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/test" -) - -type JanusSFU interface { - sfu.SFU - - SetStats(stats sfujanus.Stats) - Settings() *sfujanus.Settings -} - -func newMcuJanusForTesting(t *testing.T) (JanusSFU, *janustest.JanusGateway) { - gateway := janustest.NewJanusGateway(t) - - config := goconf.NewConfigFile() - if strings.Contains(t.Name(), "Filter") { - config.AddOption("mcu", "blockedcandidates", "192.0.0.0/24, 192.168.0.0/16") - } - logger := logtest.NewLoggerForTest(t) - ctx := log.NewLoggerContext(t.Context(), logger) - mcu, err := sfujanus.NewJanusSFUWithGateway(ctx, gateway, config) - require.NoError(t, err) - t.Cleanup(func() { - mcu.Stop() - }) - - require.NoError(t, mcu.Start(ctx)) - return mcu.(JanusSFU), gateway -} - -type mockJanusStats struct { - called atomic.Bool - - mu sync.Mutex - // +checklocks:mu - value map[sfu.StreamType]int -} - -func (s *mockJanusStats) Value(streamType sfu.StreamType) int { - s.mu.Lock() - defer s.mu.Unlock() - - return s.value[streamType] -} - -func (s *mockJanusStats) IncSubscriber(streamType sfu.StreamType) { - s.called.Store(true) - - s.mu.Lock() - defer s.mu.Unlock() - - if s.value == nil { - s.value = make(map[sfu.StreamType]int) - } - s.value[streamType]++ -} - -func (s *mockJanusStats) DecSubscriber(streamType sfu.StreamType) { - s.called.Store(true) - - s.mu.Lock() - defer s.mu.Unlock() - - if s.value == nil { - s.value = make(map[sfu.StreamType]int) - } - s.value[streamType]-- -} - -func Test_JanusSubscriberNoSuchRoom(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.SetStats(stats) - gateway.RegisterHandlers(map[string]janustest.JanusHandler{ - "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.Id()) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) -} - -func test_JanusSubscriberAlreadyJoined(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.SetStats(stats) - gateway.RegisterHandlers(map[string]janustest.JanusHandler{ - "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.Id()) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - if strings.Contains(t.Name(), "AttachError") { - MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - } - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) -} - -func Test_JanusSubscriberAlreadyJoined(t *testing.T) { - t.Parallel() - test_JanusSubscriberAlreadyJoined(t) -} - -func Test_JanusSubscriberAlreadyJoinedAttachError(t *testing.T) { - t.Parallel() - test_JanusSubscriberAlreadyJoined(t) -} - -func Test_JanusSubscriberTimeout(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.SetStats(stats) - gateway.RegisterHandlers(map[string]janustest.JanusHandler{ - "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.Id()) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - oldTimeout := mcu.Settings().Timeout() - mcu.Settings().SetTimeout(100 * time.Millisecond) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - MustSucceed2(t, client2.RunUntilError, ctx, "processing_failed") // nolint - - mcu.Settings().SetTimeout(oldTimeout) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) -} - -func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.SetStats(stats) - gateway.RegisterHandlers(map[string]janustest.JanusHandler{ - "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.Id()) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) - - sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) - require.NotNil(sess2) - session2 := sess2.(*ClientSession) - - sub := session2.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo) - require.NotNil(sub) - - subscriber := sub.(sfujanus.Subscriber) - handle := subscriber.JanusHandle() - require.NotNil(handle) - - for ctx.Err() == nil { - if handle = subscriber.JanusHandle(); handle == nil { - break - } - - time.Sleep(time.Millisecond) - } - - assert.Nil(handle, "subscriber should have been closed") -} - -func Test_JanusSubscriberRoomDestroyed(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.SetStats(stats) - gateway.RegisterHandlers(map[string]janustest.JanusHandler{ - "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.Id()) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) - - sess2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) - require.NotNil(sess2) - session2 := sess2.(*ClientSession) - - sub := session2.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo) - require.NotNil(sub) - - subscriber := sub.(sfujanus.Subscriber) - handle := subscriber.JanusHandle() - require.NotNil(handle) - - for ctx.Err() == nil { - if handle = subscriber.JanusHandle(); handle == nil { - break - } - - time.Sleep(time.Millisecond) - } - - assert.Nil(handle, "subscriber should have been closed") -} - -func Test_JanusSubscriberUpdateOffer(t *testing.T) { - t.Parallel() - require := require.New(t) - assert := assert.New(t) - - stats := &mockJanusStats{} - - t.Cleanup(func() { - if !t.Failed() { - assert.True(stats.called.Load(), "stats were not called") - assert.Equal(0, stats.Value("video")) - } - }) - - mcu, gateway := newMcuJanusForTesting(t) - mcu.SetStats(stats) - gateway.RegisterHandlers(map[string]janustest.JanusHandler{ - "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { - assert.EqualValues(1, room.Id()) - return &janus.EventMsg{ - Jsep: api.StringMap{ - "type": "answer", - "sdp": mock.MockSdpAnswerAudioAndVideo, - }, - }, nil - }, - }) - - hub, _, _, server := CreateHubForTest(t) - hub.SetMcu(mcu) - - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") - client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") - require.NotEqual(hello1.Hello.SessionId, hello2.Hello.SessionId) - require.NotEqual(hello1.Hello.UserId, hello2.Hello.UserId) - - // Join room by id. - roomId := "test-room" - roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - // Give message processing some time. - time.Sleep(10 * time.Millisecond) - - roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) - require.Equal(roomId, roomMsg.Room.RoomId) - - WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) - - // Simulate request from the backend that sessions joined the call. - users1 := []api.StringMap{ - { - "sessionId": hello1.Hello.SessionId, - "inCall": 1, - }, - { - "sessionId": hello2.Hello.SessionId, - "inCall": 1, - }, - } - room := hub.getRoom(roomId) - require.NotNil(room, "Could not find room %s", roomId) - room.PublishUsersInCallChanged(users1, users1) - checkReceiveClientEvent(ctx, t, client1, "update", nil) - checkReceiveClientEvent(ctx, t, client2, "update", nil) - - require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "offer", - RoomType: "video", - Payload: api.StringMap{ - "sdp": mock.MockSdpOfferAudioAndVideo, - }, - })) - - client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo) - - require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ - Type: "session", - SessionId: hello1.Hello.SessionId, - }, api.MessageClientMessageData{ - Type: "requestoffer", - RoomType: "video", - })) - - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo) - - // Test MCU will trigger an updated offer. - client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioOnly) -} diff --git a/sfu/janus/janus_test.go b/sfu/janus/janus_test.go index 5906b50..54c7c9c 100644 --- a/sfu/janus/janus_test.go +++ b/sfu/janus/janus_test.go @@ -23,6 +23,7 @@ package janus import ( "context" + "encoding/json" "strings" "sync" "sync/atomic" @@ -77,7 +78,16 @@ func newMcuJanusForTesting(t *testing.T) (*janusSFU, *janustest.JanusGateway) { } type TestMcuListener struct { - id api.PublicSessionId + id api.PublicSessionId + closed atomic.Bool + updatedOffer chan api.StringMap +} + +func NewTestMcuListener(id api.PublicSessionId) *TestMcuListener { + return &TestMcuListener{ + id: id, + updatedOffer: make(chan api.StringMap), + } } func (t *TestMcuListener) PublicId() api.PublicSessionId { @@ -85,7 +95,7 @@ func (t *TestMcuListener) PublicId() api.PublicSessionId { } func (t *TestMcuListener) OnUpdateOffer(client sfu.Client, offer api.StringMap) { - + t.updatedOffer <- offer } func (t *TestMcuListener) OnIceCandidate(client sfu.Client, candidate any) { @@ -105,7 +115,7 @@ func (t *TestMcuListener) PublisherClosed(publisher sfu.Publisher) { } func (t *TestMcuListener) SubscriberClosed(subscriber sfu.Subscriber) { - + t.closed.Store(true) } type TestMcuController struct { @@ -880,3 +890,844 @@ func Test_JanusRemotePublisher(t *testing.T) { assert.EqualValues(1, added.Load()) assert.EqualValues(1, removed.Load()) } + +type mockJanusStats struct { + called atomic.Bool + + mu sync.Mutex + // +checklocks:mu + value map[sfu.StreamType]int +} + +func (s *mockJanusStats) Value(streamType sfu.StreamType) int { + s.mu.Lock() + defer s.mu.Unlock() + + return s.value[streamType] +} + +func (s *mockJanusStats) IncSubscriber(streamType sfu.StreamType) { + s.called.Store(true) + + s.mu.Lock() + defer s.mu.Unlock() + + if s.value == nil { + s.value = make(map[sfu.StreamType]int) + } + s.value[streamType]++ +} + +func (s *mockJanusStats) DecSubscriber(streamType sfu.StreamType) { + s.called.Store(true) + + s.mu.Lock() + defer s.mu.Unlock() + + if s.value == nil { + s.value = make(map[sfu.StreamType]int) + } + s.value[streamType]-- +} + +func Test_SubscriberNoSuchRoom(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + + defer pub.Close(context.Background()) + + msgData := &api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(msgData.CheckValid()) + payload, err := json.Marshal(msgData) + require.NoError(err) + msg := &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + ch := make(chan struct{}) + pub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, sm["sdp"]) + }) + <-ch + + listener2 := &TestMcuListener{ + id: pubId, + } + initiator2 := &TestMcuInitiator{ + country: "DE", + } + + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + msgData = &api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + } + require.NoError(msgData.CheckValid()) + payload, err = json.Marshal(msgData) + require.NoError(err) + msg = &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.ErrorContains(err, "not created yet for") + assert.Empty(sm) + }) + <-ch + + assert.True(listener2.closed.Load()) + + listener3 := &TestMcuListener{ + id: pubId, + } + + sub, err = mcu.NewSubscriber(ctx, listener3, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + }) + <-ch + + assert.False(listener3.closed.Load()) +} + +func test_JanusSubscriberAlreadyJoined(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + + defer pub.Close(context.Background()) + + msgData := &api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(msgData.CheckValid()) + payload, err := json.Marshal(msgData) + require.NoError(err) + msg := &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + ch := make(chan struct{}) + pub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, sm["sdp"]) + }) + <-ch + + listener2 := &TestMcuListener{ + id: pubId, + } + initiator2 := &TestMcuInitiator{ + country: "DE", + } + + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + msgData = &api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + } + require.NoError(msgData.CheckValid()) + payload, err = json.Marshal(msgData) + require.NoError(err) + msg = &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + if strings.Contains(t.Name(), "AttachError") { + assert.ErrorContains(err, "already connected as subscriber for") + assert.Empty(sm) + } else { + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + } + }) + <-ch + + if strings.Contains(t.Name(), "AttachError") { + assert.True(listener2.closed.Load()) + + listener3 := &TestMcuListener{ + id: pubId, + } + + sub, err := mcu.NewSubscriber(ctx, listener3, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + }) + <-ch + } +} + +func Test_SubscriberAlreadyJoined(t *testing.T) { + t.Parallel() + test_JanusSubscriberAlreadyJoined(t) +} + +func Test_SubscriberAlreadyJoinedAttachError(t *testing.T) { + t.Parallel() + test_JanusSubscriberAlreadyJoined(t) +} + +func Test_SubscriberTimeout(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + + defer pub.Close(context.Background()) + + msgData := &api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(msgData.CheckValid()) + payload, err := json.Marshal(msgData) + require.NoError(err) + msg := &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + ch := make(chan struct{}) + pub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, sm["sdp"]) + }) + <-ch + + oldTimeout := mcu.Settings().Timeout() + mcu.Settings().SetTimeout(100 * time.Millisecond) + + listener2 := &TestMcuListener{ + id: pubId, + } + initiator2 := &TestMcuInitiator{ + country: "DE", + } + + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + msgData = &api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + } + require.NoError(msgData.CheckValid()) + payload, err = json.Marshal(msgData) + require.NoError(err) + msg = &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.ErrorIs(err, context.DeadlineExceeded) + assert.Empty(sm) + }) + <-ch + + assert.True(listener2.closed.Load()) + + mcu.Settings().SetTimeout(oldTimeout) + + listener3 := &TestMcuListener{ + id: pubId, + } + + sub, err = mcu.NewSubscriber(ctx, listener3, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + }) + <-ch +} + +func Test_SubscriberCloseEmptyStreams(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + + defer pub.Close(context.Background()) + + msgData := &api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(msgData.CheckValid()) + payload, err := json.Marshal(msgData) + require.NoError(err) + msg := &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + ch := make(chan struct{}) + pub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, sm["sdp"]) + }) + <-ch + + listener2 := &TestMcuListener{ + id: pubId, + } + initiator2 := &TestMcuInitiator{ + country: "DE", + } + + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + msgData = &api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + } + require.NoError(msgData.CheckValid()) + payload, err = json.Marshal(msgData) + require.NoError(err) + msg = &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + }) + <-ch + + subscriber, ok := sub.(*janusSubscriber) + require.True(ok) + handle := subscriber.JanusHandle() + require.NotNil(handle) + + for ctx.Err() == nil { + if handle = subscriber.JanusHandle(); handle == nil && listener2.closed.Load() { + break + } + + time.Sleep(time.Millisecond) + } + + assert.Nil(handle, "subscriber should have been closed") + assert.True(listener2.closed.Load()) +} + +func Test_SubscriberRoomDestroyed(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + + defer pub.Close(context.Background()) + + msgData := &api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(msgData.CheckValid()) + payload, err := json.Marshal(msgData) + require.NoError(err) + msg := &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + ch := make(chan struct{}) + pub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, sm["sdp"]) + }) + <-ch + + listener2 := &TestMcuListener{ + id: pubId, + } + initiator2 := &TestMcuInitiator{ + country: "DE", + } + + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + msgData = &api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + } + require.NoError(msgData.CheckValid()) + payload, err = json.Marshal(msgData) + require.NoError(err) + msg = &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + }) + <-ch + + subscriber, ok := sub.(*janusSubscriber) + require.True(ok) + handle := subscriber.JanusHandle() + require.NotNil(handle) + + for ctx.Err() == nil { + if handle = subscriber.JanusHandle(); handle == nil && listener2.closed.Load() { + break + } + + time.Sleep(time.Millisecond) + } + + assert.Nil(handle, "subscriber should have been closed") + assert.True(listener2.closed.Load()) +} + +func Test_SubscriberUpdateOffer(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + + stats := &mockJanusStats{} + + t.Cleanup(func() { + if !t.Failed() { + assert.True(stats.called.Load(), "stats were not called") + assert.Equal(0, stats.Value("video")) + } + }) + + mcu, gateway := newMcuJanusForTesting(t) + mcu.SetStats(stats) + gateway.RegisterHandlers(map[string]janustest.JanusHandler{ + "configure": func(room *janustest.JanusRoom, body, jsep api.StringMap) (any, *janus.ErrorMsg) { + assert.EqualValues(1, room.Id()) + return &janus.EventMsg{ + Jsep: api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, nil + }, + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + pubId := api.PublicSessionId("publisher-id") + listener1 := &TestMcuListener{ + id: pubId, + } + + settings1 := sfu.NewPublisherSettings{} + initiator1 := &TestMcuInitiator{ + country: "DE", + } + + pub, err := mcu.NewPublisher(ctx, listener1, pubId, "sid", sfu.StreamTypeVideo, settings1, initiator1) + require.NoError(err) + + defer pub.Close(context.Background()) + + msgData := &api.MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + } + require.NoError(msgData.CheckValid()) + payload, err := json.Marshal(msgData) + require.NoError(err) + msg := &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + ch := make(chan struct{}) + pub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, sm["sdp"]) + }) + <-ch + + listener2 := NewTestMcuListener(pubId) + initiator2 := &TestMcuInitiator{ + country: "DE", + } + + sub, err := mcu.NewSubscriber(ctx, listener2, pubId, sfu.StreamTypeVideo, initiator2) + require.NoError(err) + + defer sub.Close(context.Background()) + + msgData = &api.MessageClientMessageData{ + Type: "requestoffer", + RoomType: "video", + } + require.NoError(msgData.CheckValid()) + payload, err = json.Marshal(msgData) + require.NoError(err) + msg = &api.MessageClientMessage{ + Recipient: api.MessageClientMessageRecipient{ + Type: "session", + SessionId: pubId, + }, + Data: payload, + } + + sub.SendMessage(ctx, msg, msgData, func(err error, sm api.StringMap) { + defer func() { + ch <- struct{}{} + }() + + assert.NoError(err) + assert.Equal(mock.MockSdpOfferAudioAndVideo, sm["sdp"]) + }) + <-ch + + // Test MCU will trigger an updated offer. + select { + case offer := <-listener2.updatedOffer: + assert.Equal(mock.MockSdpOfferAudioOnly, offer["sdp"]) + case <-ctx.Done(): + assert.NoError(ctx.Err()) + } +} diff --git a/sfu/janus/subscriber.go b/sfu/janus/subscriber.go index a1f99c6..d581d40 100644 --- a/sfu/janus/subscriber.go +++ b/sfu/janus/subscriber.go @@ -32,10 +32,6 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" ) -type Subscriber interface { - JanusHandle() *janus.Handle -} - type janusSubscriber struct { janusClient From 5fc709434bba3ce4e3f1ea10ec3d5c471f7ca97c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 11:42:57 +0100 Subject: [PATCH 479/549] Add tests for SFU-related session events. --- server/clientsession_test.go | 175 +++++++++++++++++++++++++++++++++++ sfu/test/sfu.go | 12 +++ 2 files changed, 187 insertions(+) diff --git a/server/clientsession_test.go b/server/clientsession_test.go index 5466618..5c2def2 100644 --- a/server/clientsession_test.go +++ b/server/clientsession_test.go @@ -742,3 +742,178 @@ func TestPermissionHideDisplayNames(t *testing.T) { t.Run("without-hide-displaynames", testFunc(false)) // nolint:paralleltest t.Run("with-hide-displaynames", testFunc(true)) // nolint:paralleltest } + +func Test_ClientSessionPublisherEvents(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mcu := test.NewSFU(t) + require.NoError(mcu.Start(ctx)) + defer mcu.Stop() + + hub.SetMcu(mcu) + + client, hello := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId) + defer client.CloseWithBye() + + roomId := "test-room" + roomMsg := MustSucceed2(t, client.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + client.RunUntilJoined(ctx, hello.Hello) + + room := hub.getRoom(roomId) + require.NotNil(room) + + session := hub.GetSessionByPublicId(hello.Hello.SessionId).(*ClientSession) + require.NotNil(session, "Session %s does not exist", hello.Hello.SessionId) + + require.NoError(client.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + Sid: "54321", + RoomType: string(sfu.StreamTypeVideo), + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + require.True(client.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) + + pub := mcu.GetPublisher(hello.Hello.SessionId) + require.NotNil(pub) + + assert.Equal(pub, session.GetPublisher(sfu.StreamTypeVideo)) + session.OnIceCandidate(pub, "test-candidate") + + if message, ok := client.RunUntilMessage(ctx); ok { + assert.Equal("message", message.Type) + if msg := message.Message; assert.NotNil(msg) { + if sender := msg.Sender; assert.NotNil(sender) { + assert.Equal("session", sender.Type) + assert.Equal(hello.Hello.SessionId, sender.SessionId) + } + var ao api.AnswerOfferMessage + if assert.NoError(json.Unmarshal(msg.Data, &ao)) { + assert.Equal(hello.Hello.SessionId, ao.From) + assert.Equal(hello.Hello.SessionId, ao.To) + assert.Equal("candidate", ao.Type) + assert.EqualValues(sfu.StreamTypeVideo, ao.RoomType) + assert.Equal("test-candidate", ao.Payload["candidate"]) + } + } + } + + // No-op + session.OnIceCompleted(pub) + + session.PublisherClosed(pub) + assert.Nil(session.GetPublisher(sfu.StreamTypeVideo)) +} + +func Test_ClientSessionSubscriberEvents(t *testing.T) { + t.Parallel() + require := require.New(t) + assert := assert.New(t) + hub, _, _, server := CreateHubForTest(t) + hub.allowSubscribeAnyStream = true + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mcu := test.NewSFU(t) + require.NoError(mcu.Start(ctx)) + defer mcu.Stop() + + hub.SetMcu(mcu) + + client1, hello1 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"1") + defer client1.CloseWithBye() + client2, hello2 := NewTestClientWithHello(ctx, t, server, hub, testDefaultUserId+"2") + defer client2.CloseWithBye() + + roomId := "test-room" + roomMsg := MustSucceed2(t, client1.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + roomMsg = MustSucceed2(t, client2.JoinRoom, ctx, roomId) + require.Equal(roomId, roomMsg.Room.RoomId) + + WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) + + room := hub.getRoom(roomId) + require.NotNil(room) + + session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId).(*ClientSession) + require.NotNil(session1, "Session %s does not exist", hello1.Hello.SessionId) + session2 := hub.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) + require.NotNil(session2, "Session %s does not exist", hello2.Hello.SessionId) + + require.NoError(client1.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "offer", + Sid: "54321", + RoomType: string(sfu.StreamTypeVideo), + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + })) + + require.True(client1.RunUntilAnswer(ctx, mock.MockSdpAnswerAudioAndVideo)) + + require.NoError(client2.SendMessage(api.MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, api.MessageClientMessageData{ + Type: "requestoffer", + Sid: "54321", + RoomType: string(sfu.StreamTypeVideo), + })) + + require.True(client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioAndVideo)) + + sub := mcu.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo) + require.NotNil(sub) + + assert.Equal(sub, session2.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo)) + session2.OnIceCandidate(sub, "test-candidate") + + if message, ok := client2.RunUntilMessage(ctx); ok { + assert.Equal("message", message.Type) + if msg := message.Message; assert.NotNil(msg) { + if sender := msg.Sender; assert.NotNil(sender) { + assert.Equal("session", sender.Type) + assert.Equal(hello1.Hello.SessionId, sender.SessionId) + } + var ao api.AnswerOfferMessage + if assert.NoError(json.Unmarshal(msg.Data, &ao)) { + assert.Equal(hello1.Hello.SessionId, ao.From) + assert.Equal(hello2.Hello.SessionId, ao.To) + assert.Equal("candidate", ao.Type) + assert.EqualValues(sfu.StreamTypeVideo, ao.RoomType) + assert.Equal("test-candidate", ao.Payload["candidate"]) + } + } + } + + // No-op + session2.OnIceCompleted(sub) + + session2.OnUpdateOffer(sub, api.StringMap{ + "type": "offer", + "sdp": mock.MockSdpOfferAudioOnly, + }) + + require.True(client2.RunUntilOffer(ctx, mock.MockSdpOfferAudioOnly)) + + session2.SubscriberClosed(sub) + assert.Nil(session2.GetSubscriber(hello1.Hello.SessionId, sfu.StreamTypeVideo)) +} diff --git a/sfu/test/sfu.go b/sfu/test/sfu.go index 7f77726..dabd659 100644 --- a/sfu/test/sfu.go +++ b/sfu/test/sfu.go @@ -32,6 +32,7 @@ import ( "testing" "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" "github.com/strukturag/nextcloud-spreed-signaling/api" "github.com/strukturag/nextcloud-spreed-signaling/internal" @@ -145,6 +146,14 @@ func (m *SFU) GetPublisher(id api.PublicSessionId) *SFUPublisher { return m.publishers[id] } +func (m *SFU) GetSubscriber(id api.PublicSessionId, streamType sfu.StreamType) *SFUSubscriber { + m.mu.Lock() + defer m.mu.Unlock() + + key := fmt.Sprintf("%s|%s", id, streamType) + return m.subscribers[key] +} + func (m *SFU) NewSubscriber(ctx context.Context, listener sfu.Listener, publisher api.PublicSessionId, streamType sfu.StreamType, initiator sfu.Initiator) (sfu.Subscriber, error) { m.mu.Lock() defer m.mu.Unlock() @@ -164,6 +173,9 @@ func (m *SFU) NewSubscriber(ctx context.Context, listener sfu.Listener, publishe publisher: pub, } + key := fmt.Sprintf("%s|%s", publisher, streamType) + assert.Empty(m.t, m.subscribers[key], "duplicate subscriber") + m.subscribers[key] = sub return sub, nil } From 9a1e4121d322e7d6f8757f2099be48d0ad133493 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 11:57:44 +0100 Subject: [PATCH 480/549] Add tests for prometheus registry. --- metrics/prometheus_test.go | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 metrics/prometheus_test.go diff --git a/metrics/prometheus_test.go b/metrics/prometheus_test.go new file mode 100644 index 0000000..801ae3f --- /dev/null +++ b/metrics/prometheus_test.go @@ -0,0 +1,67 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" +) + +func TestRegistration(t *testing.T) { + t.Parallel() + + collectors := []prometheus.Collector{ + prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: "signaling", + Subsystem: "test", + Name: "value_total", + Help: "Total value.", + }), + prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "signaling", + Subsystem: "test", + Name: "value", + Help: "Current value.", + }, []string{"foo", "bar"}), + } + // Can unregister without previous registration + UnregisterAll(collectors...) + RegisterAll(collectors...) + // Can register multiple times + RegisterAll(collectors...) + UnregisterAll(collectors...) +} + +func TestRegistrationError(t *testing.T) { + t.Parallel() + + defer func() { + value := recover() + if err, ok := value.(error); assert.True(t, ok) { + assert.ErrorContains(t, err, "is not a valid metric name") + } + }() + + RegisterAll(prometheus.NewCounter(prometheus.CounterOpts{})) +} From daa542f80aadd7d69267c8661e1c1d8b0f52245a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 12:26:11 +0100 Subject: [PATCH 481/549] Add buffer pool tests. --- pool/buffer_pool_test.go | 141 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 pool/buffer_pool_test.go diff --git a/pool/buffer_pool_test.go b/pool/buffer_pool_test.go new file mode 100644 index 0000000..f06b969 --- /dev/null +++ b/pool/buffer_pool_test.go @@ -0,0 +1,141 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 pool + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBufferPool(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + var pool BufferPool + + buf1 := pool.Get() + assert.NotNil(buf1) + buf2 := pool.Get() + assert.NotSame(buf1, buf2) + + buf1.WriteString("empty string") + pool.Put(buf1) + + buf3 := pool.Get() + assert.Equal(0, buf3.Len()) + + pool.Put(nil) +} + +func TestBufferPoolReadAll(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + s := "Hello world!" + data := bytes.NewBufferString(s) + + var pool BufferPool + + buf1 := pool.Get() + assert.NotNil(buf1) + pool.Put(buf1) + + buf2, err := pool.ReadAll(data) + require.NoError(err) + assert.Equal(s, buf2.String()) +} + +var errTest = errors.New("test error") + +type errorReader struct{} + +func (e errorReader) Read(b []byte) (int, error) { + return 0, errTest +} + +func TestBufferPoolReadAllError(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + var pool BufferPool + + buf1 := pool.Get() + assert.NotNil(buf1) + pool.Put(buf1) + + r := &errorReader{} + buf2, err := pool.ReadAll(r) + assert.ErrorIs(err, errTest) + assert.Nil(buf2) +} + +func TestBufferPoolMarshalAsJSON(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + var pool BufferPool + buf1 := pool.Get() + assert.NotNil(buf1) + pool.Put(buf1) + + s := "Hello world!" + buf2, err := pool.MarshalAsJSON(s) + require.NoError(err) + + assert.Equal(fmt.Sprintf("\"%s\"\n", s), buf2.String()) +} + +type errorMarshaler struct { + json.Marshaler +} + +func (e errorMarshaler) MarshalJSON() ([]byte, error) { + return nil, errTest +} + +func TestBufferPoolMarshalAsJSONError(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + var pool BufferPool + buf1 := pool.Get() + assert.NotNil(buf1) + pool.Put(buf1) + + var ob errorMarshaler + buf2, err := pool.MarshalAsJSON(ob) + assert.ErrorIs(err, errTest) + assert.Nil(buf2) +} From 10175d6cca37195fb6f416be016b438d2805fc17 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 13:33:34 +0100 Subject: [PATCH 482/549] Add reloader tests. --- security/certificate_reloader_test.go | 154 ++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 security/certificate_reloader_test.go diff --git a/security/certificate_reloader_test.go b/security/certificate_reloader_test.go new file mode 100644 index 0000000..f8c61b0 --- /dev/null +++ b/security/certificate_reloader_test.go @@ -0,0 +1,154 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 security + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" +) + +type withReloadCounter interface { + GetReloadCounter() uint64 +} + +func waitForReload(ctx context.Context, t *testing.T, r withReloadCounter, expected uint64) bool { + t.Helper() + + for r.GetReloadCounter() < expected { + if !assert.NoError(t, ctx.Err()) { + return false + } + + time.Sleep(time.Millisecond) + } + return true +} + +func TestCertificateReloader(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + key, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + org1 := "Testing certificate" + cert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, key) + + tmpdir := t.TempDir() + certFile := path.Join(tmpdir, "cert.pem") + privkeyFile := path.Join(tmpdir, "privkey.pem") + pubkeyFile := path.Join(tmpdir, "pubkey.pem") + + require.NoError(internal.WritePrivateKey(key, privkeyFile)) + require.NoError(internal.WritePublicKey(&key.PublicKey, pubkeyFile)) + require.NoError(internal.WriteCertificate(cert1, certFile)) + + logger := logtest.NewLoggerForTest(t) + reloader, err := NewCertificateReloader(logger, certFile, privkeyFile) + require.NoError(err) + + defer reloader.Close() + + if cert, err := reloader.GetCertificate(nil); assert.NoError(err) { + assert.True(cert1.Equal(cert.Leaf)) + assert.True(key.Equal(cert.PrivateKey)) + } + if cert, err := reloader.GetClientCertificate(nil); assert.NoError(err) { + assert.True(cert1.Equal(cert.Leaf)) + assert.True(key.Equal(cert.PrivateKey)) + } + + ctx, cancel := context.WithTimeout(t.Context(), time.Second) + defer cancel() + + org2 := "Updated certificate" + cert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, key) + internal.ReplaceCertificate(t, certFile, cert2) + + waitForReload(ctx, t, reloader, 1) + + if cert, err := reloader.GetCertificate(nil); assert.NoError(err) { + assert.True(cert2.Equal(cert.Leaf)) + assert.True(key.Equal(cert.PrivateKey)) + } + if cert, err := reloader.GetClientCertificate(nil); assert.NoError(err) { + assert.True(cert2.Equal(cert.Leaf)) + assert.True(key.Equal(cert.PrivateKey)) + } +} + +func TestCertPoolReloader(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + key, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + + org1 := "Testing certificate" + cert1 := internal.GenerateSelfSignedCertificateForTesting(t, org1, key) + + tmpdir := t.TempDir() + certFile := path.Join(tmpdir, "cert.pem") + privkeyFile := path.Join(tmpdir, "privkey.pem") + pubkeyFile := path.Join(tmpdir, "pubkey.pem") + + require.NoError(internal.WritePrivateKey(key, privkeyFile)) + require.NoError(internal.WritePublicKey(&key.PublicKey, pubkeyFile)) + require.NoError(internal.WriteCertificate(cert1, certFile)) + + logger := logtest.NewLoggerForTest(t) + reloader, err := NewCertPoolReloader(logger, certFile) + require.NoError(err) + + defer reloader.Close() + + pool1 := reloader.GetCertPool() + assert.NotNil(pool1) + + ctx, cancel := context.WithTimeout(t.Context(), time.Second) + defer cancel() + + org2 := "Updated certificate" + cert2 := internal.GenerateSelfSignedCertificateForTesting(t, org2, key) + internal.ReplaceCertificate(t, certFile, cert2) + + waitForReload(ctx, t, reloader, 1) + + pool2 := reloader.GetCertPool() + assert.NotNil(pool2) + + assert.False(pool1.Equal(pool2)) +} From a5b583910a66b1c5aeb32340f6e021a2945123f9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 14:26:40 +0100 Subject: [PATCH 483/549] Add proxy API tests. --- proxy/api_test.go | 591 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 proxy/api_test.go diff --git a/proxy/api_test.go b/proxy/api_test.go new file mode 100644 index 0000000..7d5b1ca --- /dev/null +++ b/proxy/api_test.go @@ -0,0 +1,591 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 proxy + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/sfu" +) + +func TestValidate(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + testcases := []struct { + message *ClientMessage + reason string + }{ + { + &ClientMessage{}, + "type missing", + }, + { + // Unknown types are ignored. + &ClientMessage{ + Type: "invalid", + }, + "", + }, + { + &ClientMessage{ + Type: "hello", + }, + "hello missing", + }, + { + &ClientMessage{ + Type: "hello", + Hello: &HelloClientMessage{}, + }, + "unsupported hello version", + }, + { + &ClientMessage{ + Type: "hello", + Hello: &HelloClientMessage{ + Version: "abc", + }, + }, + "unsupported hello version", + }, + { + &ClientMessage{ + Type: "hello", + Hello: &HelloClientMessage{ + Version: "1.0", + }, + }, + "token missing", + }, + { + &ClientMessage{ + Type: "hello", + Hello: &HelloClientMessage{ + Version: "1.0", + Token: "token", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "hello", + Hello: &HelloClientMessage{ + Version: "1.0", + ResumeId: "resume-id", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "bye", + }, + "", + }, + { + &ClientMessage{ + Type: "bye", + Bye: &ByeClientMessage{}, + }, + "", + }, + { + &ClientMessage{ + Type: "command", + }, + "command missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{}, + }, + "type missing", + }, + { + // Unknown types are ignored. + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "invalid", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-publisher", + }, + }, + "stream type missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-publisher", + StreamType: sfu.StreamTypeVideo, + }, + }, + "", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-subscriber", + }, + }, + "publisher id missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-subscriber", + PublisherId: "foo", + }, + }, + "stream type missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-subscriber", + PublisherId: "foo", + StreamType: sfu.StreamTypeVideo, + }, + }, + "", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-subscriber", + PublisherId: "foo", + StreamType: sfu.StreamTypeVideo, + RemoteUrl: "http://domain.invalid", + }, + }, + "remote token missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-subscriber", + PublisherId: "foo", + StreamType: sfu.StreamTypeVideo, + RemoteUrl: ":", + RemoteToken: "remote-token", + }, + }, + "invalid remote url", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "create-subscriber", + PublisherId: "foo", + StreamType: sfu.StreamTypeVideo, + RemoteUrl: "http://domain.invalid", + RemoteToken: "remote-token", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "delete-publisher", + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "delete-publisher", + ClientId: "foo", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "delete-subscriber", + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "command", + Command: &CommandClientMessage{ + Type: "delete-subscriber", + ClientId: "foo", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "payload", + }, + "payload missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{}, + }, + "type missing", + }, + { + // Unknown types are ignored. + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "invalid", + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "offer", + }, + }, + "payload missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "offer", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "offer", + Payload: api.StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + ClientId: "foo", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "answer", + }, + }, + "payload missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "answer", + Payload: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "answer", + Payload: api.StringMap{ + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + ClientId: "foo", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "candidate", + }, + }, + "payload missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "candidate", + Payload: api.StringMap{ + "candidate": "invalid-candidate", + }, + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "candidate", + Payload: api.StringMap{ + "candidate": "invalid-candidate", + }, + ClientId: "foo", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "endOfCandidates", + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "endOfCandidates", + ClientId: "foo", + }, + }, + "", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "requestoffer", + }, + }, + "client id missing", + }, + { + &ClientMessage{ + Type: "payload", + Payload: &PayloadClientMessage{ + Type: "requestoffer", + ClientId: "foo", + }, + }, + "", + }, + } + + for idx, tc := range testcases { + err := tc.message.CheckValid() + if tc.reason == "" { + assert.NoError(err, "failed for testcase %d: %+v", idx, tc.message) + } else { + assert.ErrorContains(err, tc.reason, "failed for testcase %d: %+v", idx, tc.message) + } + } +} + +func TestServerErrorMessage(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + message := &ClientMessage{ + Id: "12346", + } + err := message.NewErrorServerMessage(api.NewError("error_code", "Test error")) + assert.Equal(message.Id, err.Id) + if e := err.Error; assert.NotNil(e) { + assert.Equal("error_code", e.Code) + assert.Equal("Test error", e.Message) + assert.Empty(e.Details) + } +} + +func TestWrapperServerErrorMessage(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + message := &ClientMessage{ + Id: "12346", + } + err := message.NewWrappedErrorServerMessage(errors.New("an internal server error")) + assert.Equal(message.Id, err.Id) + if e := err.Error; assert.NotNil(e) { + assert.Equal("internal_error", e.Code) + assert.Equal("an internal server error", e.Message) + assert.Empty(e.Details) + } +} + +func TestCloseAfterSend(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + message := &ServerMessage{ + Type: "bye", + } + assert.True(message.CloseAfterSend(nil)) + + for _, msgType := range []string{ + "error", + "hello", + "command", + "payload", + "event", + } { + message = &ServerMessage{ + Type: msgType, + } + assert.False(message.CloseAfterSend(nil), "failed for %s", msgType) + } +} + +func TestAllowIncoming(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + testcases := []struct { + bw float64 + allow bool + }{ + { + 0, true, + }, + { + 99, true, + }, + { + 99.9, true, + }, + { + 100, false, + }, + { + 200, false, + }, + } + + bw := EventServerBandwidth{ + Incoming: nil, + } + assert.True(bw.AllowIncoming()) + for idx, tc := range testcases { + bw := EventServerBandwidth{ + Incoming: internal.MakePtr(tc.bw), + } + assert.Equal(tc.allow, bw.AllowIncoming(), "failed for testcase %d: %+v", idx, tc) + } +} + +func TestAllowOutgoing(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + testcases := []struct { + bw float64 + allow bool + }{ + { + 0, true, + }, + { + 99, true, + }, + { + 99.9, true, + }, + { + 100, false, + }, + { + 200, false, + }, + } + + bw := EventServerBandwidth{ + Outgoing: nil, + } + assert.True(bw.AllowOutgoing()) + for idx, tc := range testcases { + bw := EventServerBandwidth{ + Outgoing: internal.MakePtr(tc.bw), + } + assert.Equal(tc.allow, bw.AllowOutgoing(), "failed for testcase %d: %+v", idx, tc) + } +} + +func TestInformationEtcd(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + info1 := &InformationEtcd{} + assert.ErrorContains(info1.CheckValid(), "address missing") + + info2 := &InformationEtcd{ + Address: "http://domain.invalid", + } + if assert.NoError(info2.CheckValid()) { + assert.Equal("http://domain.invalid/", info2.Address) + } + + info3 := &InformationEtcd{ + Address: "http://domain.invalid/", + } + if assert.NoError(info3.CheckValid()) { + assert.Equal("http://domain.invalid/", info3.Address) + } +} From f1dc9d6c5f17575fc7468a5bade117eedc734626 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 14:37:18 +0100 Subject: [PATCH 484/549] Test validation of federation messages. --- api/signaling_test.go | 67 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/api/signaling_test.go b/api/signaling_test.go index e7103dc..5729d44 100644 --- a/api/signaling_test.go +++ b/api/signaling_test.go @@ -142,6 +142,14 @@ func TestHelloClientMessage(t *testing.T) { Version: HelloVersionV2, ResumeId: "the-resume-id", }, + &HelloClientMessage{ + Version: HelloVersionV2, + Auth: &HelloClientMessageAuth{ + Type: "federation", + Params: tokenAuthParams, + Url: "https://domain.invalid", + }, + }, } invalid_messages := []testCheckValid{ // Hello version 1 @@ -222,6 +230,28 @@ func TestHelloClientMessage(t *testing.T) { Url: "https://domain.invalid", }, }, + &HelloClientMessage{ + Version: HelloVersionV2, + Auth: &HelloClientMessageAuth{ + Type: HelloClientTypeFederation, + Params: json.RawMessage("xyz"), // Invalid JSON. + }, + }, + &HelloClientMessage{ + Version: HelloVersionV2, + Auth: &HelloClientMessageAuth{ + Type: HelloClientTypeFederation, + Params: json.RawMessage("{}"), + Url: "https://domain.invalid", + }, + }, + &HelloClientMessage{ + Version: HelloVersionV2, + Auth: &HelloClientMessageAuth{ + Type: HelloClientTypeFederation, + Params: tokenAuthParams, + }, + }, } testMessages(t, "hello", valid_messages, invalid_messages) @@ -335,11 +365,44 @@ func TestByeClientMessage(t *testing.T) { func TestRoomClientMessage(t *testing.T) { t.Parallel() - // Any "room" message is valid. + // Any regular "room" message is valid. valid_messages := []testCheckValid{ &RoomClientMessage{}, + &RoomClientMessage{ + Federation: &RoomFederationMessage{ + SignalingUrl: "http://signaling.domain.invalid/", + NextcloudUrl: "http://nextcloud.domain.invalid", + Token: "the token", + }, + }, + } + invalid_messages := []testCheckValid{ + &RoomClientMessage{ + Federation: &RoomFederationMessage{}, + }, + &RoomClientMessage{ + Federation: &RoomFederationMessage{ + SignalingUrl: ":", + }, + }, + &RoomClientMessage{ + Federation: &RoomFederationMessage{ + SignalingUrl: "http://signaling.domain.invalid", + }, + }, + &RoomClientMessage{ + Federation: &RoomFederationMessage{ + SignalingUrl: "http://signaling.domain.invalid/", + NextcloudUrl: ":", + }, + }, + &RoomClientMessage{ + Federation: &RoomFederationMessage{ + SignalingUrl: "http://signaling.domain.invalid/", + NextcloudUrl: "http://nextcloud.domain.invalid", + }, + }, } - invalid_messages := []testCheckValid{} testMessages(t, "room", valid_messages, invalid_messages) From 2a3c1660e34947885b725b327a645fe4c7aa7220 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 15:12:15 +0100 Subject: [PATCH 485/549] Test validation of more client signaling messages. --- api/signaling.go | 4 + api/signaling_test.go | 415 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 411 insertions(+), 8 deletions(-) diff --git a/api/signaling.go b/api/signaling.go index 27ee638..b9e488a 100644 --- a/api/signaling.go +++ b/api/signaling.go @@ -835,6 +835,8 @@ func (m *MessageClientMessageData) CheckValid() error { return fmt.Errorf("invalid room type: %s", m.RoomType) } switch m.Type { + case "": + return errors.New("type missing") case "offer", "answer": sdpText, ok := GetStringMapEntry[string](m.Payload, "sdp") if !ok { @@ -1333,6 +1335,8 @@ type TransientDataClientMessage struct { func (m *TransientDataClientMessage) CheckValid() error { switch m.Type { + case "": + return errors.New("type missing") case "set": if m.Key == "" { return errors.New("key missing") diff --git a/api/signaling_test.go b/api/signaling_test.go index 5729d44..9748248 100644 --- a/api/signaling_test.go +++ b/api/signaling_test.go @@ -53,6 +53,12 @@ func wrapMessage(messageType string, msg testCheckValid) *ClientMessage { wrapped.Bye = msg.(*ByeClientMessage) case "room": wrapped.Room = msg.(*RoomClientMessage) + case "control": + wrapped.Control = msg.(*ControlClientMessage) + case "internal": + wrapped.Internal = msg.(*InternalClientMessage) + case "transient": + wrapped.TransientData = msg.(*TransientDataClientMessage) default: return nil } @@ -65,19 +71,23 @@ func testMessages(t *testing.T, messageType string, valid_messages []testCheckVa for _, msg := range valid_messages { assert.NoError(msg.CheckValid(), "Message %+v should be valid", msg) - // If the inner message is valid, it should also be valid in a wrapped - // ClientMessage. - if wrapped := wrapMessage(messageType, msg); assert.NotNil(wrapped, "Unknown message type: %s", messageType) { - assert.NoError(wrapped.CheckValid(), "Message %+v should be valid", wrapped) + if messageType != "" { + // If the inner message is valid, it should also be valid in a wrapped + // ClientMessage. + if wrapped := wrapMessage(messageType, msg); assert.NotNil(wrapped, "Unknown message type: %s", messageType) { + assert.NoError(wrapped.CheckValid(), "Message %+v should be valid", wrapped) + } } } for _, msg := range invalid_messages { assert.Error(msg.CheckValid(), "Message %+v should not be valid", msg) - // If the inner message is invalid, it should also be invalid in a - // wrapped ClientMessage. - if wrapped := wrapMessage(messageType, msg); assert.NotNil(wrapped, "Unknown message type: %s", messageType) { - assert.Error(wrapped.CheckValid(), "Message %+v should not be valid", wrapped) + if messageType != "" { + // If the inner message is invalid, it should also be invalid in a + // wrapped ClientMessage. + if wrapped := wrapMessage(messageType, msg); assert.NotNil(wrapped, "Unknown message type: %s", messageType) { + assert.Error(wrapped.CheckValid(), "Message %+v should not be valid", wrapped) + } } } } @@ -345,6 +355,224 @@ func TestMessageClientMessage(t *testing.T) { assert.Error(msg.CheckValid()) } +func TestControlClientMessage(t *testing.T) { + t.Parallel() + valid_messages := []testCheckValid{ + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "session", + SessionId: "the-session-id", + }, + Data: json.RawMessage("{}"), + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "user", + UserId: "the-user-id", + }, + Data: json.RawMessage("{}"), + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "room", + }, + Data: json.RawMessage("{}"), + }, + }, + } + invalid_messages := []testCheckValid{ + &ControlClientMessage{ + MessageClientMessage{}, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "session", + SessionId: "the-session-id", + }, + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "session", + }, + Data: json.RawMessage("{}"), + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "session", + UserId: "the-user-id", + }, + Data: json.RawMessage("{}"), + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "user", + }, + Data: json.RawMessage("{}"), + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "user", + UserId: "the-user-id", + }, + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "user", + SessionId: "the-user-id", + }, + Data: json.RawMessage("{}"), + }, + }, + &ControlClientMessage{ + MessageClientMessage{ + Recipient: MessageClientMessageRecipient{ + Type: "unknown-type", + }, + Data: json.RawMessage("{}"), + }, + }, + } + testMessages(t, "control", valid_messages, invalid_messages) + + // But a "control" message must be present + msg := ClientMessage{ + Type: "control", + } + assert := assert.New(t) + assert.Error(msg.CheckValid()) +} + +func TestMessageClientMessageData(t *testing.T) { + t.Parallel() + valid_messages := []testCheckValid{ + &MessageClientMessageData{ + Type: "invalid", + RoomType: "video", + }, + &MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: StringMap{ + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + }, + &MessageClientMessageData{ + Type: "answer", + RoomType: "video", + Payload: StringMap{ + "sdp": mock.MockSdpAnswerAudioAndVideo, + }, + }, + &MessageClientMessageData{ + Type: "candidate", + RoomType: "video", + Payload: StringMap{ + "candidate": StringMap{ + "candidate": "", + }, + }, + }, + &MessageClientMessageData{ + Type: "candidate", + RoomType: "video", + Payload: StringMap{ + "candidate": StringMap{ + "candidate": "candidate:0 1 UDP 2122194687 192.0.2.4 61665 typ host", + }, + }, + }, + } + invalid_messages := []testCheckValid{ + &MessageClientMessageData{}, + &MessageClientMessageData{ + RoomType: "invalid", + }, + &MessageClientMessageData{ + Type: "offer", + RoomType: "video", + }, + &MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: StringMap{ + "sdp": 1234, + }, + }, + &MessageClientMessageData{ + Type: "offer", + RoomType: "video", + Payload: StringMap{ + "sdp": "invalid-sdp", + }, + }, + &MessageClientMessageData{ + Type: "answer", + RoomType: "video", + }, + &MessageClientMessageData{ + Type: "answer", + RoomType: "video", + Payload: StringMap{ + "sdp": 1234, + }, + }, + &MessageClientMessageData{ + Type: "answer", + RoomType: "video", + Payload: StringMap{ + "sdp": "invalid-sdp", + }, + }, + &MessageClientMessageData{ + Type: "candidate", + RoomType: "video", + }, + &MessageClientMessageData{ + Type: "candidate", + RoomType: "video", + Payload: StringMap{ + "candidate": "invalid-candidate", + }, + }, + &MessageClientMessageData{ + Type: "candidate", + RoomType: "video", + Payload: StringMap{ + "candidate": StringMap{ + "candidate": 12345, + }, + }, + }, + &MessageClientMessageData{ + Type: "candidate", + RoomType: "video", + Payload: StringMap{ + "candidate": StringMap{ + "candidate": ":", + }, + }, + }, + } + + testMessages(t, "", valid_messages, invalid_messages) +} + func TestByeClientMessage(t *testing.T) { t.Parallel() // Any "bye" message is valid. @@ -414,6 +642,177 @@ func TestRoomClientMessage(t *testing.T) { assert.Error(msg.CheckValid()) } +func TestInternalClientMessage(t *testing.T) { + t.Parallel() + valid_messages := []testCheckValid{ + &InternalClientMessage{ + Type: "invalid", + }, + &InternalClientMessage{ + Type: "addsession", + AddSession: &AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: "session id", + RoomId: "room id", + }, + }, + }, + &InternalClientMessage{ + Type: "updatesession", + UpdateSession: &UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: "session id", + RoomId: "room id", + }, + }, + }, + &InternalClientMessage{ + Type: "removesession", + RemoveSession: &RemoveSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: "session id", + RoomId: "room id", + }, + }, + }, + &InternalClientMessage{ + Type: "incall", + InCall: &InCallInternalClientMessage{}, + }, + &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{ + Type: "invalid", + }, + }, + &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{ + Type: "error", + Error: &Error{}, + }, + }, + &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{ + Type: "status", + Status: &DialoutStatusInternalClientMessage{}, + }, + }, + } + invalid_messages := []testCheckValid{ + &InternalClientMessage{}, + &InternalClientMessage{ + Type: "addsession", + }, + &InternalClientMessage{ + Type: "addsession", + AddSession: &AddSessionInternalClientMessage{}, + }, + &InternalClientMessage{ + Type: "addsession", + AddSession: &AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: "session id", + }, + }, + }, + &InternalClientMessage{ + Type: "updatesession", + }, + &InternalClientMessage{ + Type: "updatesession", + UpdateSession: &UpdateSessionInternalClientMessage{}, + }, + &InternalClientMessage{ + Type: "updatesession", + UpdateSession: &UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: "session id", + }, + }, + }, + &InternalClientMessage{ + Type: "removesession", + }, + &InternalClientMessage{ + Type: "removesession", + RemoveSession: &RemoveSessionInternalClientMessage{}, + }, + &InternalClientMessage{ + Type: "removesession", + RemoveSession: &RemoveSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: "session id", + }, + }, + }, + &InternalClientMessage{ + Type: "incall", + }, + &InternalClientMessage{ + Type: "dialout", + }, + &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{}, + }, + &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{ + Type: "error", + }, + }, + &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{ + Type: "status", + }, + }, + } + + testMessages(t, "internal", valid_messages, invalid_messages) + + // But a "internal" message must be present + msg := ClientMessage{ + Type: "internal", + } + assert := assert.New(t) + assert.Error(msg.CheckValid()) +} + +func TestTransientDataClientMessage(t *testing.T) { + t.Parallel() + valid_messages := []testCheckValid{ + &TransientDataClientMessage{ + Type: "set", + Key: "foo", + }, + &TransientDataClientMessage{ + Type: "remove", + Key: "foo", + }, + } + invalid_messages := []testCheckValid{ + &TransientDataClientMessage{}, + &TransientDataClientMessage{ + Type: "set", + }, + &TransientDataClientMessage{ + Type: "remove", + }, + } + + testMessages(t, "transient", valid_messages, invalid_messages) + + // But a "transient" message must be present + msg := ClientMessage{ + Type: "transient", + } + assert := assert.New(t) + assert.Error(msg.CheckValid()) +} + func TestErrorMessages(t *testing.T) { t.Parallel() assert := assert.New(t) From 20c0fe086b5a89fdbde2611972a29cd615565aee Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 15:14:30 +0100 Subject: [PATCH 486/549] Test federated room session id handling. --- api/signaling_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/api/signaling_test.go b/api/signaling_test.go index 9748248..ed8c54c 100644 --- a/api/signaling_test.go +++ b/api/signaling_test.go @@ -36,6 +36,20 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/mock" ) +func TestRoomSessionIds(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + var s1 RoomSessionId = "foo" + assert.False(s1.IsFederated()) + assert.EqualValues("foo", s1.WithoutFederation()) + + var s2 RoomSessionId = "federated|bar" + assert.True(s2.IsFederated()) + assert.EqualValues("bar", s2.WithoutFederation()) +} + type testCheckValid interface { CheckValid() error } From 9cc07c8a651a297043697946293d97946917ed3f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 16:01:48 +0100 Subject: [PATCH 487/549] Remove unused client method and increase test coverage. --- client/client.go | 27 --------------------------- client/client_test.go | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/client/client.go b/client/client.go index 9679526..796c9db 100644 --- a/client/client.go +++ b/client/client.go @@ -462,33 +462,6 @@ close: return false } -func (c *Client) writeError(e error) bool { // nolint - message := &api.ServerMessage{ - Type: "error", - Error: api.NewError("internal_error", e.Error()), - } - c.mu.Lock() - defer c.mu.Unlock() - if c.conn == nil { - return false - } - - if !c.writeMessageLocked(message) { - return false - } - - closeData := websocket.FormatCloseMessage(websocket.CloseInternalServerErr, e.Error()) - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint - if err := c.conn.WriteMessage(websocket.CloseMessage, closeData); err != nil { - if sessionId := c.GetSessionId(); sessionId != "" { - c.logger.Printf("Could not send close message to client %s: %v", sessionId, err) - } else { - c.logger.Printf("Could not send close message to %s: %v", c.RemoteAddr(), err) - } - } - return false -} - func (c *Client) writeMessage(message WritableClientMessage) bool { c.mu.Lock() defer c.mu.Unlock() diff --git a/client/client_test.go b/client/client_test.go index bac0f70..6ec13a2 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -111,6 +111,7 @@ func (c *serverClient) GetSessionId() api.PublicSessionId { } func (c *serverClient) OnClosed() { + c.Close() c.handler.removeClient(c) } @@ -120,6 +121,9 @@ func (c *serverClient) OnMessageReceived(message []byte) { var s string if err := json.Unmarshal(message, &s); assert.NoError(c.t, err) { assert.Equal(c.t, "Hello world!", s) + c.sendPing() + assert.EqualValues(c.t, "DE", c.Country()) + assert.False(c.t, c.Client.IsInRoom("room-id")) c.SendMessage(&api.ServerMessage{ Type: "welcome", Welcome: &api.WelcomeServerMessage{ @@ -212,8 +216,15 @@ func (h *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { id := h.id.Add(1) client := newTestClient(h, r, conn, id) h.addClient(client) + + closed := make(chan struct{}) + context.AfterFunc(client.Context(), func() { + close(closed) + }) + go client.WritePump() client.ReadPump() + <-closed } type localClient struct { @@ -246,6 +257,10 @@ func (c *localClient) WriteJSON(v any) error { return c.conn.WriteJSON(v) } +func (c *localClient) Write(v []byte) error { + return c.conn.WriteMessage(websocket.BinaryMessage, v) +} + func (c *localClient) ReadJSON(v any) error { return c.conn.ReadJSON(v) } @@ -281,6 +296,14 @@ func TestClient(t *testing.T) { assert.EqualValues(1, clients[0].received.Load()) } + require.NoError(client.Write([]byte("Hello world!"))) + if assert.NoError(client.ReadJSON(&msg)) && + assert.Equal("error", msg.Type) && + assert.NotNil(msg.Error) { + assert.Equal("invalid_format", msg.Error.Code) + assert.Equal("Invalid data format.", msg.Error.Message) + } + require.NoError(client.WriteJSON("Send error")) if assert.NoError(client.ReadJSON(&msg)) && assert.Equal("error", msg.Type) && From 806ef1f56451539caf50ac9bb6cf14e4ee387fe4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 16:31:59 +0100 Subject: [PATCH 488/549] Add tests for Janus stream selection and simplify code. --- sfu/janus/publisher_stats_counter_test.go | 1 + sfu/janus/stream_selection.go | 52 ++++++------- sfu/janus/stream_selection_test.go | 92 +++++++++++++++++++++++ 3 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 sfu/janus/stream_selection_test.go diff --git a/sfu/janus/publisher_stats_counter_test.go b/sfu/janus/publisher_stats_counter_test.go index 221b9f2..123d710 100644 --- a/sfu/janus/publisher_stats_counter_test.go +++ b/sfu/janus/publisher_stats_counter_test.go @@ -88,6 +88,7 @@ func TestPublisherStatsPrometheus(t *testing.T) { t.Parallel() RegisterStats() + UnregisterStats() } func TestPublisherStatsCounter(t *testing.T) { diff --git a/sfu/janus/stream_selection.go b/sfu/janus/stream_selection.go index 046b579..aa7ad10 100644 --- a/sfu/janus/stream_selection.go +++ b/sfu/janus/stream_selection.go @@ -22,35 +22,35 @@ package janus import ( - "database/sql" "fmt" "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) type streamSelection struct { - substream sql.NullInt16 - temporal sql.NullInt16 - audio sql.NullBool - video sql.NullBool + substream *int + temporal *int + audio *bool + video *bool } func (s *streamSelection) HasValues() bool { - return s.substream.Valid || s.temporal.Valid || s.audio.Valid || s.video.Valid + return s.substream != nil || s.temporal != nil || s.audio != nil || s.video != nil } func (s *streamSelection) AddToMessage(message api.StringMap) { - if s.substream.Valid { - message["substream"] = s.substream.Int16 + if s.substream != nil { + message["substream"] = *s.substream } - if s.temporal.Valid { - message["temporal"] = s.temporal.Int16 + if s.temporal != nil { + message["temporal"] = *s.temporal } - if s.audio.Valid { - message["audio"] = s.audio.Bool + if s.audio != nil { + message["audio"] = *s.audio } - if s.video.Valid { - message["video"] = s.video.Bool + if s.video != nil { + message["video"] = *s.video } } @@ -59,14 +59,11 @@ func parseStreamSelection(payload api.StringMap) (*streamSelection, error) { if value, found := payload["substream"]; found { switch value := value.(type) { case int: - stream.substream.Valid = true - stream.substream.Int16 = int16(value) + stream.substream = &value case float32: - stream.substream.Valid = true - stream.substream.Int16 = int16(value) + stream.substream = internal.MakePtr(int(value)) case float64: - stream.substream.Valid = true - stream.substream.Int16 = int16(value) + stream.substream = internal.MakePtr(int(value)) default: return nil, fmt.Errorf("unsupported substream value: %v", value) } @@ -75,14 +72,11 @@ func parseStreamSelection(payload api.StringMap) (*streamSelection, error) { if value, found := payload["temporal"]; found { switch value := value.(type) { case int: - stream.temporal.Valid = true - stream.temporal.Int16 = int16(value) + stream.temporal = &value case float32: - stream.temporal.Valid = true - stream.temporal.Int16 = int16(value) + stream.temporal = internal.MakePtr(int(value)) case float64: - stream.temporal.Valid = true - stream.temporal.Int16 = int16(value) + stream.temporal = internal.MakePtr(int(value)) default: return nil, fmt.Errorf("unsupported temporal value: %v", value) } @@ -91,8 +85,7 @@ func parseStreamSelection(payload api.StringMap) (*streamSelection, error) { if value, found := payload["audio"]; found { switch value := value.(type) { case bool: - stream.audio.Valid = true - stream.audio.Bool = value + stream.audio = &value default: return nil, fmt.Errorf("unsupported audio value: %v", value) } @@ -101,8 +94,7 @@ func parseStreamSelection(payload api.StringMap) (*streamSelection, error) { if value, found := payload["video"]; found { switch value := value.(type) { case bool: - stream.video.Valid = true - stream.video.Bool = value + stream.video = &value default: return nil, fmt.Errorf("unsupported video value: %v", value) } diff --git a/sfu/janus/stream_selection_test.go b/sfu/janus/stream_selection_test.go new file mode 100644 index 0000000..8d5463d --- /dev/null +++ b/sfu/janus/stream_selection_test.go @@ -0,0 +1,92 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 janus + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/strukturag/nextcloud-spreed-signaling/api" +) + +func TestStreamSelection(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + testcases := []api.StringMap{ + {}, + { + "substream": 1.0, + }, + { + "temporal": 1.0, + }, + { + "substream": float32(1.0), + }, + { + "temporal": float32(1.0), + }, + { + "substream": 1, + "temporal": 3, + }, + { + "substream": 1, + "audio": true, + "video": false, + }, + } + + for idx, tc := range testcases { + parsed, err := parseStreamSelection(tc) + if assert.NoError(err, "failed for testcase %d: %+v", idx, tc) { + assert.Equal(len(tc) > 0, parsed.HasValues(), "failed for testcase %d: %+v", idx, tc) + m := make(api.StringMap) + parsed.AddToMessage(m) + for k, v := range tc { + assert.EqualValues(v, m[k], "failed for key %s in testcase %d", k, idx) + } + } + } + + _, err := parseStreamSelection(api.StringMap{ + "substream": "foo", + }) + assert.ErrorContains(err, "unsupported substream value") + + _, err = parseStreamSelection(api.StringMap{ + "temporal": "foo", + }) + assert.ErrorContains(err, "unsupported temporal value") + + _, err = parseStreamSelection(api.StringMap{ + "audio": 1, + }) + assert.ErrorContains(err, "unsupported audio value") + + _, err = parseStreamSelection(api.StringMap{ + "video": "true", + }) + assert.ErrorContains(err, "unsupported video value") +} From 15f2d3cd5c727c2b2971a19d982a2322f5098e3a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 2 Feb 2026 16:56:11 +0100 Subject: [PATCH 489/549] Add tests for static token configuration. --- cmd/proxy/proxy_tokens_static.go | 4 +- cmd/proxy/proxy_tokens_static_test.go | 185 ++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 cmd/proxy/proxy_tokens_static_test.go diff --git a/cmd/proxy/proxy_tokens_static.go b/cmd/proxy/proxy_tokens_static.go index 2abb43c..baa9148 100644 --- a/cmd/proxy/proxy_tokens_static.go +++ b/cmd/proxy/proxy_tokens_static.go @@ -84,7 +84,7 @@ func (t *tokensStatic) load(cfg *goconf.ConfigFile, ignoreErrors bool) error { keyData, err := os.ReadFile(filename) if err != nil { if !ignoreErrors { - return fmt.Errorf("could not read public key from %s: %s", filename, err) + return fmt.Errorf("could not read public key from %s: %w", filename, err) } t.logger.Printf("Could not read public key from %s, ignoring: %s", filename, err) @@ -93,7 +93,7 @@ func (t *tokensStatic) load(cfg *goconf.ConfigFile, ignoreErrors bool) error { key, err := jwt.ParseRSAPublicKeyFromPEM(keyData) if err != nil { if !ignoreErrors { - return fmt.Errorf("could not parse public key from %s: %s", filename, err) + return fmt.Errorf("could not parse public key from %s: %w", filename, err) } t.logger.Printf("Could not parse public key from %s, ignoring: %s", filename, err) diff --git a/cmd/proxy/proxy_tokens_static_test.go b/cmd/proxy/proxy_tokens_static_test.go new file mode 100644 index 0000000..662174e --- /dev/null +++ b/cmd/proxy/proxy_tokens_static_test.go @@ -0,0 +1,185 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 main + +import ( + "crypto/rand" + "crypto/rsa" + "os" + "path" + "testing" + + "github.com/dlintw/goconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" +) + +func TestStaticTokens(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + + filename := path.Join(t.TempDir(), "token.pub") + + key1, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + require.NoError(internal.WritePublicKey(&key1.PublicKey, filename)) + + logger := logtest.NewLoggerForTest(t) + config := goconf.NewConfigFile() + config.AddOption("tokens", "foo", filename) + + tokens, err := NewProxyTokensStatic(logger, config) + require.NoError(err) + + defer tokens.Close() + + if token, err := tokens.Get("foo"); assert.NoError(err) { + assert.Equal("foo", token.id) + assert.True(key1.PublicKey.Equal(token.key)) + } + + key2, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(err) + require.NoError(internal.WritePublicKey(&key2.PublicKey, filename)) + + tokens.Reload(config) + + if token, err := tokens.Get("foo"); assert.NoError(err) { + assert.Equal("foo", token.id) + assert.True(key2.PublicKey.Equal(token.key)) + } +} + +func testStaticTokensMissing(t *testing.T, reload bool) { + require := require.New(t) + assert := assert.New(t) + + filename := path.Join(t.TempDir(), "token.pub") + + logger := logtest.NewLoggerForTest(t) + config := goconf.NewConfigFile() + if !reload { + config.AddOption("tokens", "foo", filename) + } + + tokens, err := NewProxyTokensStatic(logger, config) + if !reload { + assert.ErrorIs(err, os.ErrNotExist) + return + } + + require.NoError(err) + defer tokens.Close() + + config.AddOption("tokens", "foo", filename) + tokens.Reload(config) +} + +func TestStaticTokensMissing(t *testing.T) { + t.Parallel() + + testStaticTokensMissing(t, false) +} + +func TestStaticTokensMissingReload(t *testing.T) { + t.Parallel() + + testStaticTokensMissing(t, true) +} + +func testStaticTokensEmpty(t *testing.T, reload bool) { + require := require.New(t) + assert := assert.New(t) + + logger := logtest.NewLoggerForTest(t) + config := goconf.NewConfigFile() + if !reload { + config.AddOption("tokens", "foo", "") + } + + tokens, err := NewProxyTokensStatic(logger, config) + if !reload { + assert.ErrorContains(err, "no filename given") + return + } + + require.NoError(err) + defer tokens.Close() + + config.AddOption("tokens", "foo", "") + tokens.Reload(config) +} + +func TestStaticTokensEmpty(t *testing.T) { + t.Parallel() + + testStaticTokensEmpty(t, false) +} + +func TestStaticTokensEmptyReload(t *testing.T) { + t.Parallel() + + testStaticTokensEmpty(t, true) +} + +func testStaticTokensInvalidData(t *testing.T, reload bool) { + require := require.New(t) + assert := assert.New(t) + + filename := path.Join(t.TempDir(), "token.pub") + require.NoError(os.WriteFile(filename, []byte("invalid-key-data"), 0600)) + + logger := logtest.NewLoggerForTest(t) + config := goconf.NewConfigFile() + if !reload { + config.AddOption("tokens", "foo", filename) + } + + tokens, err := NewProxyTokensStatic(logger, config) + if !reload { + assert.ErrorContains(err, "could not parse public key") + return + } + + require.NoError(err) + defer tokens.Close() + + config.AddOption("tokens", "foo", filename) + tokens.Reload(config) +} + +func TestStaticTokensInvalidData(t *testing.T) { + t.Parallel() + + testStaticTokensInvalidData(t, false) +} + +func TestStaticTokensInvalidDataReload(t *testing.T) { + t.Parallel() + + testStaticTokensInvalidData(t, true) +} From c2c9d0725fbf260f11aa825cf07e2460ed5ca28c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 3 Feb 2026 08:31:41 +0100 Subject: [PATCH 490/549] Add test for reloading etcd token configuration. --- cmd/proxy/proxy_tokens_etcd_test.go | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cmd/proxy/proxy_tokens_etcd_test.go b/cmd/proxy/proxy_tokens_etcd_test.go index 32a44e8..aba5a5b 100644 --- a/cmd/proxy/proxy_tokens_etcd_test.go +++ b/cmd/proxy/proxy_tokens_etcd_test.go @@ -153,3 +153,34 @@ func TestProxyTokensEtcd(t *testing.T) { assert.True(key2.PublicKey.Equal(token.key)) } } + +func TestProxyTokensEtcdReload(t *testing.T) { + t.Parallel() + assert := assert.New(t) + tokens, etcd := newTokensEtcdForTesting(t) + + key1 := generateAndSaveKey(t, etcd, "/foo") + + if token, err := tokens.Get("foo"); assert.NoError(err) && assert.NotNil(token) { + assert.True(key1.PublicKey.Equal(token.key)) + } + + if token, err := tokens.Get("bar"); assert.NoError(err) { + assert.Nil(token) + } + + cfg := goconf.NewConfigFile() + cfg.AddOption("etcd", "endpoints", etcd.Config().ListenClientUrls[0].String()) + cfg.AddOption("tokens", "keyformat", "/reload/%s/key") + + tokens.Reload(cfg) + key2 := generateAndSaveKey(t, etcd, "/reload/bar/key") + + if token, err := tokens.Get("foo"); assert.NoError(err) { + assert.Nil(token) + } + + if token, err := tokens.Get("bar"); assert.NoError(err) && assert.NotNil(token) { + assert.True(key2.PublicKey.Equal(token.key)) + } +} From 99762a3ca98e7aef8519733b8ed23368518cf697 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 3 Feb 2026 09:37:07 +0100 Subject: [PATCH 491/549] Improxy proxy server test coverage. --- cmd/proxy/proxy_server_test.go | 425 +++++++++++++++++++++++++++++++++ cmd/proxy/proxy_session.go | 2 +- 2 files changed, 426 insertions(+), 1 deletion(-) diff --git a/cmd/proxy/proxy_server_test.go b/cmd/proxy/proxy_server_test.go index f04e55c..4aa212d 100644 --- a/cmd/proxy/proxy_server_test.go +++ b/cmd/proxy/proxy_server_test.go @@ -28,6 +28,7 @@ import ( "crypto/x509" "encoding/pem" "errors" + "fmt" "net" "net/http/httptest" "os" @@ -48,9 +49,11 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/mock" "github.com/strukturag/nextcloud-spreed-signaling/proxy" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/test" ) const ( @@ -461,6 +464,7 @@ type PublisherTestMCU struct { type TestPublisherWithBandwidth struct { TestMCUPublisher + t *testing.T bandwidth *sfu.ClientBandwidthInfo } @@ -468,6 +472,25 @@ func (p *TestPublisherWithBandwidth) Bandwidth() *sfu.ClientBandwidthInfo { return p.bandwidth } +func (p *TestPublisherWithBandwidth) SendMessage(ctx context.Context, message *api.MessageClientMessage, data *api.MessageClientMessageData, callback func(error, api.StringMap)) { + switch data.Type { + case "offer": + assert.Equal(p.t, mock.MockSdpOfferAudioAndVideo, data.Payload["sdp"]) + assert.NotNil(p.t, data.OfferSdp) + callback(nil, api.StringMap{ + "type": "answer", + "sdp": mock.MockSdpAnswerAudioAndVideo, + }) + case "requestoffer": + callback(nil, api.StringMap{ + "type": "offer", + "sdp": mock.MockSdpOfferAudioOnly, + }) + default: + callback(fmt.Errorf("type %s not implemented", data.Type), nil) + } +} + func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener sfu.Listener, id api.PublicSessionId, sid string, streamType sfu.StreamType, settings sfu.NewPublisherSettings, initiator sfu.Initiator) (sfu.Publisher, error) { publisher := &TestPublisherWithBandwidth{ TestMCUPublisher: TestMCUPublisher{ @@ -476,6 +499,7 @@ func (m *PublisherTestMCU) NewPublisher(ctx context.Context, listener sfu.Listen streamType: streamType, }, + t: m.t, bandwidth: &sfu.ClientBandwidthInfo{ Sent: api.BandwidthFromBytes(1000), Received: api.BandwidthFromBytes(2000), @@ -497,6 +521,9 @@ func TestProxyPublisherBandwidth(t *testing.T) { assert := assert.New(t) require := require.New(t) proxyServer, key, server := newProxyServerForTest(t) + t.Cleanup(func() { + assert.EqualValues(0, proxyServer.GetSessionsCount()) + }) proxyServer.maxIncoming.Store(api.BandwidthFromBytes(10000)) proxyServer.maxOutgoing.Store(api.BandwidthFromBytes(10000)) @@ -507,6 +534,8 @@ func TestProxyPublisherBandwidth(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() + assert.EqualValues(0, proxyServer.GetSessionsCount()) + client := NewProxyTestClient(ctx, t, server.URL) defer client.CloseWithBye() @@ -528,12 +557,19 @@ func TestProxyPublisherBandwidth(t *testing.T) { }, })) + var clientId string if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { assert.Equal("2345", message.Id) if err := checkMessageType(message, "command"); assert.NoError(err) { assert.NotEmpty(message.Command.Id) + clientId = message.Command.Id } } + require.NotEmpty(clientId) + + if publisher := proxyServer.GetPublisher(clientId); assert.NotNil(publisher) { + assert.Equal(clientId, proxyServer.GetClientId(publisher)) + } proxyServer.updateLoad() @@ -550,6 +586,66 @@ func TestProxyPublisherBandwidth(t *testing.T) { } } } + + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "3456", + Type: "payload", + Payload: &proxy.PayloadClientMessage{ + Type: "offer", + ClientId: clientId, + Payload: api.StringMap{ + "type": "offer", + "sdp": mock.MockSdpOfferAudioAndVideo, + }, + }, + })) + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("3456", message.Id) + assert.Equal("payload", message.Type) + if payload := message.Payload; assert.NotNil(payload) { + assert.Equal(clientId, payload.ClientId) + assert.Equal("offer", payload.Type) + assert.Equal("answer", payload.Payload["type"]) + assert.Equal(mock.MockSdpAnswerAudioAndVideo, payload.Payload["sdp"]) + } + } + + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "4567", + Type: "payload", + Payload: &proxy.PayloadClientMessage{ + Type: "endOfCandidates", + ClientId: clientId, + }, + })) + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("4567", message.Id) + assert.Equal("payload", message.Type) + if payload := message.Payload; assert.NotNil(payload) { + assert.Equal(clientId, payload.ClientId) + assert.Equal("endOfCandidates", payload.Type) + assert.Empty(payload.Payload) + } + } + + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "5678", + Type: "payload", + Payload: &proxy.PayloadClientMessage{ + Type: "requestoffer", + ClientId: clientId, + }, + })) + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("5678", message.Id) + assert.Equal("payload", message.Type) + if payload := message.Payload; assert.NotNil(payload) { + assert.Equal(clientId, payload.ClientId) + assert.Equal("requestoffer", payload.Type) + assert.Equal("offer", payload.Payload["type"]) + assert.Equal(mock.MockSdpOfferAudioOnly, payload.Payload["sdp"]) + } + } } type HangingTestMCU struct { @@ -1605,3 +1701,332 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { assert.Nil(publisher.getRemoteData()) } } + +func TestExpireSessions(t *testing.T) { + t.Parallel() + + test.SynctestTest(t, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + proxyServer, key, server := newProxyServerForTest(t) + server.Close() + + // No-op + proxyServer.expireSessions() + + claims := &proxy.TokenClaims{ + RegisteredClaims: jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)), + Issuer: TokenIdForTest, + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + tokenString, err := token.SignedString(key) + require.NoError(err) + + hello := &proxy.HelloClientMessage{ + Version: "1.0", + Token: tokenString, + } + session, err := proxyServer.NewSession(hello) + require.NoError(err) + t.Cleanup(func() { + proxyServer.DeleteSession(session.Sid()) + }) + assert.Same(session, proxyServer.GetSession(session.Sid())) + + proxyServer.expireSessions() + assert.Same(session, proxyServer.GetSession(session.Sid())) + + time.Sleep(sessionExpirationTime) + proxyServer.expireSessions() + + assert.Nil(proxyServer.GetSession(session.Sid())) + }) +} + +func TestScheduleShutdownEmpty(t *testing.T) { + t.Parallel() + + proxyServer, _, _ := newProxyServerForTest(t) + + proxyServer.ScheduleShutdown() + <-proxyServer.ShutdownChannel() +} + +func TestScheduleShutdownNoClients(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + proxyServer, key, server := newProxyServerForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := NewProxyTestClient(ctx, t, server.URL) + defer client.CloseWithBye() + + require.NoError(client.SendHello(key)) + + if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) + } + + _, err := client.RunUntilLoad(ctx, 0) + assert.NoError(err) + + proxyServer.ScheduleShutdown() + + if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", msg.Type) + if event := msg.Event; assert.NotNil(event) { + assert.Equal("shutdown-scheduled", event.Type) + } + } + + <-proxyServer.ShutdownChannel() +} + +func TestScheduleShutdown(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + proxyServer, key, server := newProxyServerForTest(t) + + mcu := NewPublisherTestMCU(t) + proxyServer.mcu = mcu + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := NewProxyTestClient(ctx, t, server.URL) + defer client.CloseWithBye() + + require.NoError(client.SendHello(key)) + + if hello, err := client.RunUntilHello(ctx); assert.NoError(err) { + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) + } + + _, err := client.RunUntilLoad(ctx, 0) + assert.NoError(err) + + publisherId := api.PublicSessionId("the-publisher-id") + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "2345", + Type: "command", + Command: &proxy.CommandClientMessage{ + Type: "create-publisher", + PublisherId: publisherId, + Sid: "1234-abcd", + StreamType: sfu.StreamTypeVideo, + PublisherSettings: &sfu.NewPublisherSettings{ + Bitrate: 1234567, + MediaTypes: sfu.MediaTypeAudio | sfu.MediaTypeVideo, + }, + }, + })) + + var clientId string + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("2345", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + require.NotEmpty(message.Command.Id) + clientId = message.Command.Id + } + } + + readyChan := make(chan struct{}) + var readyReceived atomic.Bool + go func() { + for { + select { + case <-proxyServer.ShutdownChannel(): + return + case <-readyChan: + readyReceived.Store(true) + case <-ctx.Done(): + assert.NoError(ctx.Err()) + return + } + } + }() + + proxyServer.ScheduleShutdown() + + if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", msg.Type) + if event := msg.Event; assert.NotNil(event) { + assert.Equal("shutdown-scheduled", event.Type) + } + } + close(readyChan) + + proxyServer.ScheduleShutdown() + + select { + case <-proxyServer.ShutdownChannel(): + assert.Fail("should only shutdown after all clients closed") + default: + } + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "4567", + Type: "command", + Command: &proxy.CommandClientMessage{ + Type: "delete-publisher", + ClientId: clientId, + }, + })) + + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("4567", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + require.NotEmpty(message.Command.Id) + } + } + + <-proxyServer.ShutdownChannel() + assert.True(readyReceived.Load()) +} + +func TestScheduleShutdownOnResume(t *testing.T) { + t.Parallel() + + require := require.New(t) + assert := assert.New(t) + proxyServer, key, server := newProxyServerForTest(t) + + mcu := NewPublisherTestMCU(t) + proxyServer.mcu = mcu + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + client := NewProxyTestClient(ctx, t, server.URL) + defer client.CloseWithBye() + + require.NoError(client.SendHello(key)) + + hello, err := client.RunUntilHello(ctx) + if assert.NoError(err) { + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) + } + + _, err = client.RunUntilLoad(ctx, 0) + assert.NoError(err) + + publisherId := api.PublicSessionId("the-publisher-id") + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "2345", + Type: "command", + Command: &proxy.CommandClientMessage{ + Type: "create-publisher", + PublisherId: publisherId, + Sid: "1234-abcd", + StreamType: sfu.StreamTypeVideo, + PublisherSettings: &sfu.NewPublisherSettings{ + Bitrate: 1234567, + MediaTypes: sfu.MediaTypeAudio | sfu.MediaTypeVideo, + }, + }, + })) + + var clientId string + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("2345", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + require.NotEmpty(message.Command.Id) + clientId = message.Command.Id + } + } + + readyChan := make(chan struct{}) + var readyReceived atomic.Bool + go func() { + for { + select { + case <-proxyServer.ShutdownChannel(): + return + case <-readyChan: + readyReceived.Store(true) + case <-ctx.Done(): + assert.NoError(ctx.Err()) + return + } + } + }() + + client.Close() + + proxyServer.ScheduleShutdown() + + client = NewProxyTestClient(ctx, t, server.URL) + defer client.CloseWithBye() + + hello2 := &proxy.ClientMessage{ + Id: "1234", + Type: "hello", + Hello: &proxy.HelloClientMessage{ + Version: "1.0", + Features: []string{}, + ResumeId: hello.Hello.SessionId, + }, + } + require.NoError(client.WriteJSON(hello2)) + + if hello3, err := client.RunUntilHello(ctx); assert.NoError(err) { + assert.Equal(hello.Hello.SessionId, hello3.Hello.SessionId) + } + + if msg, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", msg.Type) + if event := msg.Event; assert.NotNil(event) { + assert.Equal("shutdown-scheduled", event.Type) + } + } + + client2 := NewProxyTestClient(ctx, t, server.URL) + defer client2.CloseWithBye() + + require.NoError(client2.SendHello(key)) + + if hello, err := client2.RunUntilHello(ctx); assert.NoError(err) { + assert.NotEmpty(hello.Hello.SessionId, "%+v", hello) + } + + if msg, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", msg.Type) + if event := msg.Event; assert.NotNil(event) { + assert.Equal("shutdown-scheduled", event.Type) + } + } + close(readyChan) + + proxyServer.ScheduleShutdown() + + select { + case <-proxyServer.ShutdownChannel(): + assert.Fail("should only shutdown after all clients closed") + default: + } + require.NoError(client.WriteJSON(&proxy.ClientMessage{ + Id: "4567", + Type: "command", + Command: &proxy.CommandClientMessage{ + Type: "delete-publisher", + ClientId: clientId, + }, + })) + + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("4567", message.Id) + if err := checkMessageType(message, "command"); assert.NoError(err) { + require.NotEmpty(message.Command.Id) + } + } + + <-proxyServer.ShutdownChannel() + assert.True(readyReceived.Load()) +} diff --git a/cmd/proxy/proxy_session.go b/cmd/proxy/proxy_session.go index 0c12f23..7bc7133 100644 --- a/cmd/proxy/proxy_session.go +++ b/cmd/proxy/proxy_session.go @@ -117,7 +117,7 @@ func (s *ProxySession) LastUsed() time.Time { func (s *ProxySession) IsExpired() bool { expiresAt := s.LastUsed().Add(sessionExpirationTime) - return expiresAt.Before(time.Now()) + return !expiresAt.After(time.Now()) } func (s *ProxySession) MarkUsed() { From f921e4a2c0c72e9d4cfcba58b381d1b9533972a9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 3 Feb 2026 09:54:10 +0100 Subject: [PATCH 492/549] Remove duplicate code to load proxy configuration. --- cmd/proxy/proxy_server.go | 56 ++++++++++++++------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index ec11ba7..53e0051 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -265,32 +265,6 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * return nil, err } - statsAllowed, _ := config.GetString("stats", "allowed_ips") - statsAllowedIps, err := container.ParseIPList(statsAllowed) - if err != nil { - return nil, err - } - - if !statsAllowedIps.Empty() { - logger.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) - } else { - statsAllowedIps = container.DefaultAllowedIPs() - logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) - } - - trustedProxies, _ := config.GetString("app", "trustedproxies") - trustedProxiesIps, err := container.ParseIPList(trustedProxies) - if err != nil { - return nil, err - } - - if !trustedProxiesIps.Empty() { - logger.Printf("Trusted proxies: %s", trustedProxiesIps) - } else { - trustedProxiesIps = client.DefaultTrustedProxies - logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) - } - countryString, _ := config.GetString("app", "country") country := geoip.Country(strings.ToUpper(countryString)) if geoip.IsValidCountry(country) { @@ -350,8 +324,6 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * logger.Printf("No token id configured, remote streams will be disabled") } - maxIncoming, maxOutgoing := getTargetBandwidths(logger, config) - mcuTimeoutSeconds, _ := config.GetInt("mcu", "timeout") if mcuTimeoutSeconds <= 0 { mcuTimeoutSeconds = defaultMcuTimeoutSeconds @@ -397,10 +369,10 @@ func NewProxyServer(ctx context.Context, r *mux.Router, version string, config * remotePublishers: make(map[string]map[*proxyRemotePublisher]bool), } - result.maxIncoming.Store(maxIncoming) - result.maxOutgoing.Store(maxOutgoing) - result.statsAllowedIps.Store(statsAllowedIps) - result.trustedProxies.Store(trustedProxiesIps) + if err := result.loadConfig(config, false); err != nil { + return nil, err + } + result.upgrader.CheckOrigin = result.checkOrigin statsLoadCurrent.Set(0) @@ -629,7 +601,7 @@ func (s *ProxyServer) ScheduleShutdown() { } } -func (s *ProxyServer) Reload(config *goconf.ConfigFile) { +func (s *ProxyServer) loadConfig(config *goconf.ConfigFile, fromReload bool) error { statsAllowed, _ := config.GetString("stats", "allowed_ips") if statsAllowedIps, err := container.ParseIPList(statsAllowed); err == nil { if !statsAllowedIps.Empty() { @@ -639,8 +611,10 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { s.logger.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps) } s.statsAllowedIps.Store(statsAllowedIps) - } else { + } else if fromReload { s.logger.Printf("Error parsing allowed stats ips from \"%s\": %s", statsAllowedIps, err) + } else { + return fmt.Errorf("error parsing allowed stats ips from \"%s\": %w", statsAllowedIps, err) } trustedProxies, _ := config.GetString("app", "trustedproxies") @@ -652,18 +626,28 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) { s.logger.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps) } s.trustedProxies.Store(trustedProxiesIps) - } else { + } else if fromReload { s.logger.Printf("Error parsing trusted proxies from \"%s\": %s", trustedProxies, err) + } else { + return fmt.Errorf("error parsing trusted proxies ips from \"%s\": %w", trustedProxies, err) } maxIncoming, maxOutgoing := getTargetBandwidths(s.logger, config) oldIncoming := s.maxIncoming.Swap(maxIncoming) oldOutgoing := s.maxOutgoing.Swap(maxOutgoing) - if oldIncoming != maxIncoming || oldOutgoing != maxOutgoing { + if fromReload && (oldIncoming != maxIncoming || oldOutgoing != maxOutgoing) { // Notify sessions about updated load / bandwidth usage. go s.sendLoadToAll(s.load.Load(), s.currentIncoming.Load(), s.currentOutgoing.Load()) } + return nil +} + +func (s *ProxyServer) Reload(config *goconf.ConfigFile) { + if err := s.loadConfig(config, true); err != nil { + s.logger.Printf("Error reloading configuration: %s", err) + } + s.tokens.Reload(config) s.mcu.Reload(config) } From 5ddfe3dc504a48c108a16b5d57de3167a87e2217 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 3 Feb 2026 11:02:34 +0100 Subject: [PATCH 493/549] Add helper script to prepare a changelog. --- scripts/prepare-changelog.py | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 scripts/prepare-changelog.py diff --git a/scripts/prepare-changelog.py b/scripts/prepare-changelog.py new file mode 100755 index 0000000..7fa3011 --- /dev/null +++ b/scripts/prepare-changelog.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 + +import git +import os.path +import re + +ROOT = os.path.join(os.path.dirname(__file__), '..') + +FIND_PR = re.compile(r'Merge pull request #(\d+) from').findall + +ENTRY = """- %(title)s + [#%(pr)s](https://github.com/strukturag/nextcloud-spreed-signaling/pull/%(pr)s)""" + +SIMPLE_ENTRY = """- %(title)s""" + +def main(): + repo = git.Repo(ROOT) + latest = repo.tags[-1] + print('Generating changelog since %s (commit %s)' % (latest, latest.commit)) + + entries = [] + dependencies = [] + ignore = set() + for commit in repo.iter_commits('%s..HEAD' % (latest.commit, )): + if len(commit.parents) > 1: + # Merge commit. + ignore.add(commit.parents[-1]) + entries.append(commit) + else: + try: + ignore.remove(commit) + except KeyError: + # Direct commit. + entries.append(commit) + else: + # Commit is part of a merge. + ignore.add(commit.parents[0]) + + # Sort commits from old to new. + for commit in reversed(entries): + lines = [x.strip() for x in commit.message.strip().split('\n')] + title = None + for line in lines: + if not line: + title = '' + elif title == '': + title = line + break + + if not title and len(lines) == 1: + title = lines[0] + assert line, (commit.message, ) + pr = FIND_PR(commit.summary) + assert len(pr) <= 1, (commit.summary, ) + if len(pr) == 1: + entry = ENTRY % { + 'title': title, + 'pr': pr[0], + } + elif len(pr) == 0: + entry = SIMPLE_ENTRY % { + 'title': title, + } + + if title.startswith('Bump '): + dependencies.append(entry) + else: + print(entry) + + # Dependencies should be in a separate section at the end. + if dependencies: + print() + print('### Dependencies') + print('\n'.join(dependencies)) + +if __name__ == '__main__': + main() From a92819f3b2b418b7807c95c02fc551d351276093 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 3 Feb 2026 11:03:06 +0100 Subject: [PATCH 494/549] Next version will be 2.1.0 --- docs/prometheus-metrics.md | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/prometheus-metrics.md b/docs/prometheus-metrics.md index 2979b16..c75aa86 100644 --- a/docs/prometheus-metrics.md +++ b/docs/prometheus-metrics.md @@ -55,25 +55,25 @@ The following metrics are available: | `signaling_backend_client_requests_total` | Counter | 2.0.3 | The total number of backend client requests | `backend` | | `signaling_backend_client_requests_duration` | Histogram | 2.0.3 | The duration of backend client requests in seconds | `backend` | | `signaling_backend_client_requests_errors_total` | Counter | 2.0.3 | The total number of backend client requests that had an error | `backend`, `error` | -| `signaling_mcu_bandwidth` | Gauge | 2.0.5 | The current bandwidth in bytes per second | `direction` | -| `signaling_mcu_backend_usage` | Gauge | 2.0.5 | The current usage of signaling proxy backends in percent | `url`, `direction` | -| `signaling_mcu_backend_bandwidth` | Gauge | 2.0.5 | The current bandwidth of signaling proxy backends in bytes per second | `url`, `direction` | -| `signaling_proxy_load` | Gauge | 2.0.5 | The current load of the signaling proxy | | -| `signaling_client_rtt` | Histogram | 2.0.5 | The roundtrip time of WebSocket ping messages in milliseconds | | -| `signaling_mcu_selected_candidate_total` | Counter | 2.0.5 | Total number of selected candidates | `origin`, `type`, `transport`, `family` | -| `signaling_mcu_peerconnection_state_total` | Counter | 2.0.5 | Total number PeerConnection states | `state`, `reason` | -| `signaling_mcu_ice_state_total` | Counter | 2.0.5 | Total number of ICE connection states | `state` | -| `signaling_mcu_dtls_state_total` | Counter | 2.0.5 | Total number of DTLS connection states | `state` | -| `signaling_mcu_slow_link_total` | Counter | 2.0.5 | Total number of slow link events | `media`, `direction` | -| `signaling_mcu_media_rtt` | Histogram | 2.0.5 | The roundtrip time of WebRTC media in milliseconds | `media` | -| `signaling_mcu_media_jitter` | Histogram | 2.0.5 | The jitter of WebRTC media in milliseconds | `media`, `origin` | -| `signaling_mcu_media_codecs_total` | Counter | 2.0.5 | The total number of codecs | `media`, `codec` | -| `signaling_mcu_media_nacks_total` | Counter | 2.0.5 | The total number of NACKs | `media`, `direction` | -| `signaling_mcu_media_retransmissions_total` | Counter | 2.0.5 | The total number of received retransmissions | `media` | -| `signaling_mcu_media_bytes_total` | Counter | 2.0.5 | The total number of media bytes sent / received | `media`, `direction` | -| `signaling_mcu_media_lost_total` | Counter | 2.0.5 | The total number of lost media packets | `media`, `origin` | -| `signaling_client_bytes_total` | Counter | 2.0.5 | The total number of bytes sent to or received by clients | `direction` | -| `signaling_client_messages_total` | Counter | 2.0.5 | The total number of messages sent to or received by clients | `direction` | -| `signaling_call_sessions` | Gauge | 2.0.5 | The current number of sessions in a call | `backend`, `room`, `clienttype` | -| `signaling_call_sessions_total` | Counter | 2.0.5 | The total number of sessions in a call | `backend`, `clienttype` | -| `signaling_call_rooms_total` | Counter | 2.0.5 | The total number of rooms with an active call | `backend` | +| `signaling_mcu_bandwidth` | Gauge | 2.1.0 | The current bandwidth in bytes per second | `direction` | +| `signaling_mcu_backend_usage` | Gauge | 2.1.0 | The current usage of signaling proxy backends in percent | `url`, `direction` | +| `signaling_mcu_backend_bandwidth` | Gauge | 2.1.0 | The current bandwidth of signaling proxy backends in bytes per second | `url`, `direction` | +| `signaling_proxy_load` | Gauge | 2.1.0 | The current load of the signaling proxy | | +| `signaling_client_rtt` | Histogram | 2.1.0 | The roundtrip time of WebSocket ping messages in milliseconds | | +| `signaling_mcu_selected_candidate_total` | Counter | 2.1.0 | Total number of selected candidates | `origin`, `type`, `transport`, `family` | +| `signaling_mcu_peerconnection_state_total` | Counter | 2.1.0 | Total number PeerConnection states | `state`, `reason` | +| `signaling_mcu_ice_state_total` | Counter | 2.1.0 | Total number of ICE connection states | `state` | +| `signaling_mcu_dtls_state_total` | Counter | 2.1.0 | Total number of DTLS connection states | `state` | +| `signaling_mcu_slow_link_total` | Counter | 2.1.0 | Total number of slow link events | `media`, `direction` | +| `signaling_mcu_media_rtt` | Histogram | 2.1.0 | The roundtrip time of WebRTC media in milliseconds | `media` | +| `signaling_mcu_media_jitter` | Histogram | 2.1.0 | The jitter of WebRTC media in milliseconds | `media`, `origin` | +| `signaling_mcu_media_codecs_total` | Counter | 2.1.0 | The total number of codecs | `media`, `codec` | +| `signaling_mcu_media_nacks_total` | Counter | 2.1.0 | The total number of NACKs | `media`, `direction` | +| `signaling_mcu_media_retransmissions_total` | Counter | 2.1.0 | The total number of received retransmissions | `media` | +| `signaling_mcu_media_bytes_total` | Counter | 2.1.0 | The total number of media bytes sent / received | `media`, `direction` | +| `signaling_mcu_media_lost_total` | Counter | 2.1.0 | The total number of lost media packets | `media`, `origin` | +| `signaling_client_bytes_total` | Counter | 2.1.0 | The total number of bytes sent to or received by clients | `direction` | +| `signaling_client_messages_total` | Counter | 2.1.0 | The total number of messages sent to or received by clients | `direction` | +| `signaling_call_sessions` | Gauge | 2.1.0 | The current number of sessions in a call | `backend`, `room`, `clienttype` | +| `signaling_call_sessions_total` | Counter | 2.1.0 | The total number of sessions in a call | `backend`, `clienttype` | +| `signaling_call_rooms_total` | Counter | 2.1.0 | The total number of rooms with an active call | `backend` | From ecac46bf5fcece2bfd0beca9fd828eb168b6c13a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 3 Feb 2026 11:10:44 +0100 Subject: [PATCH 495/549] Update changelog for 2.1.0 --- CHANGELOG.md | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d0a08..b6a397d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,243 @@ All notable changes to this project will be documented in this file. +## 2.1.0 - 2026-02-03 + +### Added +- Introduce "internal" package + [#1082](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1082) +- Add etcd TLS tests. + [#1084](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1084) +- Add missing stats registration. + [#1086](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1086) +- Add commands to the readme on how to build Docker images locally. + [#1088](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1088) +- Use gvisor checklocks for static lock analysis. + [#1078](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1078) +- Support relaying of chat messages. + [#868](https://github.com/strukturag/nextcloud-spreed-signaling/pull/868) +- Return bandwidth information in room responses. + [#1099](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1099) +- Expose real bandwidth usage through metrics. + [#1102](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1102) +- Add type to store bandwidths. + [#1108](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1108) +- Add more WebRTC-related metrics + [#1109](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1109) +- Add metrics about client bytes/messages sent/received. + [#1134](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1134) +- Introduce transient session data. + [#1120](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1120) +- Include "version.txt" in tarball. + [#1142](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1142) +- CI: Also upload images to quay.io/strukturag/nextcloud-spreed-signaling + [#1159](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1159) +- Add more metrics about sessions in calls. + [#1183](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1183) + +### Changed +- dockerfile: create system user instead of normal user, avoid home directory + [#1058](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1058) +- Use "testing/synctest" to simplify timing-dependent tests. + [#1067](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1067) +- Add dedicated types for different session ids. + [#1066](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1066) +- Move "StringMap" class to api module. + [#1077](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1077) +- CI: Disable "stdversion" check of govet. + [#1079](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1079) +- CI: Use codecov components. + [#1080](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1080) +- Add interface for method "GetInCall". + [#1083](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1083) +- Make LruCache typed through generics. + [#1085](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1085) +- Protect access to the debug pprof handlers. + [#1094](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1094) +- Don't use environment to keep per-test properties. + [#1106](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1106) +- Add formatting to bandwidth values. + [#1114](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1114) +- Don't format zero bandwidth as "unlimited". + [#1115](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1115) +- CI: Split test jobs to speed up total actions time. + [#1118](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1118) +- Stop using global logger + [#1117](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1117) +- CI: Split tarball jobs to speed up total actions time. + [#1129](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1129) +- Update client code + [#1130](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1130) +- Don't use fmt.Sprintf where not necessary. + [#1131](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1131) +- Use test-related logger for embedded etcd. + [#1132](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1132) +- No need to use list of pointers, use objects directly. + [#1133](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1133) +- Generate shorter session ids. + [#1140](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1140) +- client: Include version, optimize JSON processing. + [#1143](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1143) +- Enable more linters + [#1145](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1145) +- Parallelize more tests. + [#1149](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1149) +- Move logging code to separate package. + [#1150](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1150) +- Close subscriber synchronously on errors. + [#1152](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1152) +- CI: Run "modernize" with Go 1.25 + [#1160](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1160) +- CI: Always use latest patch release for govuln checks. + [#1161](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1161) +- Process all NATS messages for same target from single goroutine. + [#1165](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1165) +- CI: Process files in all folders with licensecheck. + [#1169](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1169) +- CI: Run checklocks with Go 1.25 + [#1170](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1170) +- Refactor code into packages + [#1151](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1151) +- Remove unused testing code. + [#1174](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1174) +- checklocks: Remove ignore since generics are supported now. + [#1184](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1184) +- Support receiving and forwarding multiple chat messages from Talk. + [#1185](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1185) +- Move tests closer to code being checked + [#1186](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1186) + +### Fixed +- A proxy connection is only connected after a hello has been processed. + [#1071](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1071) +- Fix URL to send federated ping requests. + [#1081](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1081) +- Reconnect proxy connection even if shutdown was scheduled before. + [#1100](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1100) +- Federation cleanup fixes. + [#1105](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1105) +- Also rewrite token in comment for federated chat relay. + [#1112](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1112) +- Fix transient data for clustered setups. + [#1121](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1121) +- Fix initial transient data in clustered setups + [#1127](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1127) +- fix(docs): already_joined error response + [#1126](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1126) +- Fix storing initial data when clustered. + [#1128](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1128) +- Fix flaky tests that fail under load. + [#1153](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1153) + +### Dependencies +- Bump google.golang.org/grpc from 1.74.2 to 1.75.0 + [#1054](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1054) +- Bump github.com/nats-io/nats.go from 1.44.0 to 1.45.0 + [#1057](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1057) +- Bump google.golang.org/protobuf from 1.36.7 to 1.36.8 + [#1056](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1056) +- Bump github.com/stretchr/testify from 1.10.0 to 1.11.0 + [#1059](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1059) +- Bump github.com/stretchr/testify from 1.11.0 to 1.11.1 + [#1060](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1060) +- Bump markdown from 3.8.2 to 3.9 in /docs + [#1065](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1065) +- Bump github.com/pion/sdp/v3 from 3.0.15 to 3.0.16 + [#1061](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1061) +- Bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2 + [#1064](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1064) +- Bump actions/setup-go from 5 to 6 + [#1062](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1062) +- Bump google.golang.org/grpc from 1.75.0 to 1.75.1 + [#1070](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1070) +- Bump google.golang.org/protobuf from 1.36.8 to 1.36.9 + [#1069](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1069) +- Bump github.com/nats-io/nats-server/v2 from 2.11.8 to 2.11.9 + [#1068](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1068) +- Bump github.com/mailru/easyjson from 0.9.0 to 0.9.1 + [#1072](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1072) +- Bump the etcd group with 4 updates + [#1073](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1073) +- Bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0 + [#1076](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1076) +- Bump github.com/nats-io/nats-server/v2 from 2.11.9 to 2.12.0 + [#1075](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1075) +- Bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 + [#1087](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1087) +- Bump google.golang.org/grpc from 1.75.1 to 1.76.0 + [#1092](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1092) +- Bump github/codeql-action from 3 to 4 + [#1093](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1093) +- Bump peter-evans/create-or-update-comment from 4 to 5 + [#1090](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1090) +- Bump google.golang.org/protobuf from 1.36.9 to 1.36.10 + [#1089](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1089) +- Bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0 + [#1096](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1096) +- Bump github.com/nats-io/nats-server/v2 from 2.12.0 to 2.12.1 + [#1095](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1095) +- Bump the artifacts group with 2 updates + [#1101](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1101) +- Bump markdown from 3.9 to 3.10 in /docs + [#1104](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1104) +- Bump golangci/golangci-lint-action from 8.0.0 to 9.0.0 + [#1110](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1110) +- Bump the etcd group with 4 updates + [#1111](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1111) +- Bump github.com/nats-io/nats-server/v2 from 2.12.1 to 2.12.2 + [#1113](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1113) +- Bump google.golang.org/grpc from 1.76.0 to 1.77.0 + [#1116](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1116) +- Bump golang.org/x/crypto from 0.43.0 to 0.45.0 + [#1119](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1119) +- Bump go.uber.org/zap from 1.27.0 to 1.27.1 + [#1123](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1123) +- Bump actions/checkout from 5 to 6 + [#1124](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1124) +- Bump golangci/golangci-lint-action from 9.0.0 to 9.1.0 + [#1125](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1125) +- Bump github.com/pion/ice/v4 from 4.0.10 to 4.0.11 + [#1135](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1135) +- Bump google.golang.org/grpc/cmd/protoc-gen-go-grpc from 1.5.1 to 1.6.0 + [#1136](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1136) +- Bump github.com/pion/ice/v4 from 4.0.11 to 4.0.12 + [#1138](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1138) +- Bump golangci/golangci-lint-action from 9.1.0 to 9.2.0 + [#1144](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1144) +- Bump github.com/pion/ice/v4 from 4.0.12 to 4.0.13 + [#1146](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1146) +- Bump actions/cache from 4 to 5 + [#1157](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1157) +- Bump google.golang.org/protobuf from 1.36.10 to 1.36.11 + [#1156](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1156) +- Bump github.com/pion/ice/v4 from 4.0.13 to 4.1.0 + [#1155](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1155) +- Bump the artifacts group with 2 updates + [#1154](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1154) +- Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0 + [#1163](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1163) +- Bump the etcd group with 4 updates + [#1162](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1162) +- Bump github.com/nats-io/nats-server/v2 from 2.12.2 to 2.12.3 + [#1164](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1164) +- Bump github.com/pion/sdp/v3 from 3.0.16 to 3.0.17 + [#1166](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1166) +- Bump google.golang.org/grpc from 1.77.0 to 1.78.0 + [#1167](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1167) +- Bump github.com/pion/ice/v4 from 4.1.0 to 4.2.0 + [#1171](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1171) +- Bump sphinx-rtd-theme from 3.0.2 to 3.1.0 in /docs + [#1172](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1172) +- Bump sphinx from 8.2.3 to 9.1.0 in /docs + [#1168](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1168) +- Bump markdown from 3.10 to 3.10.1 in /docs + [#1177](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1177) +- Bump github.com/nats-io/nats-server/v2 from 2.12.3 to 2.12.4 + [#1179](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1179) +- Bump github.com/golang-jwt/jwt/v5 from 5.3.0 to 5.3.1 + [#1182](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1182) + + ## 2.0.4 - 2025-08-18 ### Added From cc876701534bb228f4ccace826c47d56873bbba0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 20:42:30 +0000 Subject: [PATCH 496/549] Bump google.golang.org/grpc/cmd/protoc-gen-go-grpc from 1.6.0 to 1.6.1 Bumps [google.golang.org/grpc/cmd/protoc-gen-go-grpc](https://github.com/grpc/grpc-go) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.6.0...cmd/protoc-gen-go-grpc/v1.6.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc/cmd/protoc-gen-go-grpc dependency-version: 1.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c911fef..11193b2 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.etcd.io/etcd/server/v3 v3.6.7 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.78.0 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 google.golang.org/protobuf v1.36.11 ) diff --git a/go.sum b/go.sum index 8e89633..c1cc4b0 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From b004fef9ecba2fb5508118eaf734159001621367 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 5 Feb 2026 09:40:24 +0100 Subject: [PATCH 497/549] Simplify error type checks. Based on "errors.AsType" from upcoming Go 1.26. --- cmd/proxy/proxy_server.go | 4 +-- config/config.go | 6 ++-- internal/as_error.go | 50 ++++++++++++++++++++++++++++++++ internal/as_error_test.go | 61 +++++++++++++++++++++++++++++++++++++++ server/backend_server.go | 7 +++-- server/federation.go | 4 +-- server/hub.go | 32 ++++++++------------ test/network.go | 11 +++---- 8 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 internal/as_error.go create mode 100644 internal/as_error_test.go diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index 53e0051..18bfb24 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -54,6 +54,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/proxy" "github.com/strukturag/nextcloud-spreed-signaling/session" @@ -1224,8 +1225,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s defer cancel() if err := publisher.PublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { - var je *janusapi.ErrorMsg - if !errors.As(err, &je) || je.Err.Code != janusapi.JANUS_VIDEOROOM_ERROR_ID_EXISTS { + if je, ok := internal.AsErrorType[*janusapi.ErrorMsg](err); !ok || je.Err.Code != janusapi.JANUS_VIDEOROOM_ERROR_ID_EXISTS { s.logger.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return diff --git a/config/config.go b/config/config.go index 25e8833..a8dd096 100644 --- a/config/config.go +++ b/config/config.go @@ -22,11 +22,12 @@ package config import ( - "errors" "os" "regexp" "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) var ( @@ -71,8 +72,7 @@ func GetStringOptions(config *goconf.ConfigFile, section string, ignoreErrors bo continue } - var ge goconf.GetError - if errors.As(err, &ge) && ge.Reason == goconf.OptionNotFound { + if ge, ok := internal.AsErrorType[goconf.GetError](err); ok && ge.Reason == goconf.OptionNotFound { // Skip options from "default" section. continue } diff --git a/internal/as_error.go b/internal/as_error.go new file mode 100644 index 0000000..a7ab70e --- /dev/null +++ b/internal/as_error.go @@ -0,0 +1,50 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 internal + +import ( + "errors" +) + +// AsErrorType finds the first error in err's tree that matches the type E, +// and if one is found, returns that error value and true. Otherwise, it +// returns the zero value of E and false. +// +// The tree consists of err itself, followed by the errors obtained by +// repeatedly calling its Unwrap() error or Unwrap() []error method. +// When err wraps multiple errors, AsErrorType examines err followed by a +// depth-first traversal of its children. +// +// An error err matches the type E if the type assertion err.(E) holds, +// or if the error has a method As(any) bool such that err.As(target) +// returns true when target is a non-nil *E. In the latter case, the As +// method is responsible for setting target. +func AsErrorType[E error](err error) (E, bool) { + var e E + if err == nil { + return e, false + } else if errors.As(err, &e) { + return e, true + } + + return e, false +} diff --git a/internal/as_error_test.go b/internal/as_error_test.go new file mode 100644 index 0000000..5fc2564 --- /dev/null +++ b/internal/as_error_test.go @@ -0,0 +1,61 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 internal + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +type testError struct{} + +func (e testError) Error() string { + return "test error" +} + +func TestAsErrorType(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + if e, ok := AsErrorType[*testError](nil); assert.False(ok) { + assert.Nil(e) + } + + err1 := &testError{} + if e, ok := AsErrorType[*testError](err1); assert.True(ok) { + assert.Same(err1, e) + } + + err2 := errors.New("other error") + if e, ok := AsErrorType[*testError](err2); assert.False(ok) { + assert.Nil(e) + } + + err3 := fmt.Errorf("wrapped error: %w", err1) + if e, ok := AsErrorType[*testError](err3); assert.True(ok) { + assert.Same(err1, e) + } +} diff --git a/server/backend_server.go b/server/backend_server.go index 303cd39..cf8be1f 100644 --- a/server/backend_server.go +++ b/server/backend_server.go @@ -831,9 +831,10 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend response, err := b.startDialoutInSession(ctx, session, roomid, backend, backendUrl, request) if err != nil { b.logger.Printf("Error starting dialout request %+v in session %s: %+v", request.Dialout, session.PublicId(), err) - var e *api.Error - if sessionError == nil && errors.As(err, &e) { - sessionError = e + if sessionError == nil { + if e, ok := internal.AsErrorType[*api.Error](err); ok { + sessionError = e + } } continue } diff --git a/server/federation.go b/server/federation.go index c9be1e4..a80beed 100644 --- a/server/federation.go +++ b/server/federation.go @@ -469,8 +469,8 @@ func (c *FederationClient) writePump() { func (c *FederationClient) closeWithError(err error) { c.Close() - var e *api.Error - if !errors.As(err, &e) { + e, ok := internal.AsErrorType[*api.Error](err) + if !ok { e = api.NewError("federation_error", err.Error()) } diff --git a/server/hub.go b/server/hub.go index dd5e024..3251dbd 100644 --- a/server/hub.go +++ b/server/hub.go @@ -1844,39 +1844,31 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { if session.UserId() == "" && client == nil { h.startWaitAnonymousSessionRoom(session) } - var ae *api.Error - if errors.As(err, &ae) { + if ae, ok := internal.AsErrorType[*api.Error](err); ok { session.SendMessage(message.NewErrorServerMessage(ae)) return } var details any - var ce *tls.CertificateVerificationError - if errors.As(err, &ce) { + if ce, ok := internal.AsErrorType[*tls.CertificateVerificationError](err); ok { details = map[string]string{ "code": "certificate_verification_error", "message": ce.Error(), } - } - var ne net.Error - if details == nil && errors.As(err, &ne) { + } else if ne, ok := internal.AsErrorType[net.Error](err); ok { 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(), - } + } else if errors.Is(err, websocket.ErrBadHandshake) { + details = map[string]string{ + "code": "network_error", + "message": err.Error(), + } + } else if we, ok := internal.AsErrorType[websocket.HandshakeError](err); ok { + details = map[string]string{ + "code": "network_error", + "message": we.Error(), } } diff --git a/test/network.go b/test/network.go index ec7d582..6d7cc78 100644 --- a/test/network.go +++ b/test/network.go @@ -22,19 +22,20 @@ package test import ( - "errors" "os" "runtime" "syscall" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) func IsErrorAddressAlreadyInUse(err error) bool { - var eOsSyscall *os.SyscallError - if !errors.As(err, &eOsSyscall) { + eOsSyscall, ok := internal.AsErrorType[*os.SyscallError](err) + if !ok { return false } - var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr) - if !errors.As(eOsSyscall, &errErrno) { + errErrno, ok := internal.AsErrorType[syscall.Errno](eOsSyscall) + if !ok { return false } if errErrno == syscall.EADDRINUSE { From 4a7ecc3ac572e6b04023a95121223b776a188010 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 5 Feb 2026 10:01:17 +0100 Subject: [PATCH 498/549] readme: Add example websocket urls for Janus events. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7398c48..7b2cf85 100644 --- a/README.md +++ b/README.md @@ -152,8 +152,8 @@ To enable sending of events from Janus, the option `broadcast` must be set to `true` in the block `events` of `janus.jcfg`. In the configuration of the websocket events handler (`janus.eventhandler.wsevh.jcfg`), the module must be enabled by setting `enabled` to `true`, the `backend` must be set to the -websocket url of the signaling server (or signaling proxy) and `subprotocol` -must be set to `janus-events`. +websocket url of the signaling server (`ws://127.0.0.1:port/spreed`) or -proxy +(`ws://127.0.0.1:port/proxy`) and `subprotocol` must be set to `janus-events`. At least events of type `handles`, `media` and `webrtc` must be subscribed. Edit the `server.conf` and enter the URL to the websocket endpoint of Janus in From a0d3dd100f7b83cbe3d5d1a235a803335fa17310 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:35:12 +0000 Subject: [PATCH 499/549] Bump markdown from 3.10.1 to 3.10.2 in /docs Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.10.1 to 3.10.2. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.10.1...3.10.2) --- updated-dependencies: - dependency-name: markdown dependency-version: 3.10.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 58ca5bc..1794104 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ jinja2==3.1.6 -markdown==3.10.1 +markdown==3.10.2 mkdocs==1.6.1 readthedocs-sphinx-search==0.3.2 sphinx==9.1.0 From af732300e3b2b000d3a57a714a6a2a2f360202ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:23:52 +0000 Subject: [PATCH 500/549] Bump github.com/pion/dtls/v3 from 3.0.10 to 3.1.0 Bumps [github.com/pion/dtls/v3](https://github.com/pion/dtls) from 3.0.10 to 3.1.0. - [Release notes](https://github.com/pion/dtls/releases) - [Commits](https://github.com/pion/dtls/compare/v3.0.10...v3.1.0) --- updated-dependencies: - dependency-name: github.com/pion/dtls/v3 dependency-version: 3.1.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 11193b2..e7c7285 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.0.10 // indirect + github.com/pion/dtls/v3 v3.1.0 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index c1cc4b0..7339a70 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg= -github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= +github.com/pion/dtls/v3 v3.1.0 h1:bz3alDjKL1DDGe8GETGcq5rDKjXFQX9mniuUo36Up0E= +github.com/pion/dtls/v3 v3.1.0/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw= github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= From 43e51083732be9c30c0694188a1595ae80bf6194 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:43:12 +0000 Subject: [PATCH 501/549] Bump golang from 1.25-alpine to 1.26-alpine in /docker/proxy Bumps golang from 1.25-alpine to 1.26-alpine. --- updated-dependencies: - dependency-name: golang dependency-version: 1.26-alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker/proxy/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile index 87eaa23..953c7bd 100644 --- a/docker/proxy/Dockerfile +++ b/docker/proxy/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM} golang:1.25-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.26-alpine AS builder ARG TARGETARCH ARG TARGETOS From 383494585d961acc295bcf1daf53635db6b3ed5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:43:19 +0000 Subject: [PATCH 502/549] Bump golang from 1.25-alpine to 1.26-alpine in /docker/server Bumps golang from 1.25-alpine to 1.26-alpine. --- updated-dependencies: - dependency-name: golang dependency-version: 1.26-alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker/server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 71e30fb..7a27886 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM} golang:1.25-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.26-alpine AS builder ARG TARGETARCH ARG TARGETOS From 73af7bb3672a5598a8d8f6f5e1aa37f595da7889 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:27:33 +0100 Subject: [PATCH 503/549] CI: Test with Golang 1.26 --- .github/workflows/tarball.yml | 3 +++ .github/workflows/test.yml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 1e54cc2..3ad6174 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -26,6 +26,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -50,6 +51,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest needs: [create] steps: @@ -95,6 +97,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest needs: [create] steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14f5a07..58d9684 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -51,6 +52,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -71,6 +73,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -91,6 +94,7 @@ jobs: go-version: - "1.24" - "1.25" + - "1.26" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 From f9f2347d1114a5c52d363a0f6709a9bcfcaa225e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:27:58 +0100 Subject: [PATCH 504/549] CI: Run modernize and checklocks with Golang 1.26 --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f528ff4..f8b9180 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" - name: moderize run: | @@ -63,7 +63,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" check-latest: true - name: checklocks From ba482a544b3d6965f40151c075e3f10963946fa9 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:39:15 +0100 Subject: [PATCH 505/549] make: Only need "synctest" GOEXPERIMENT on go1.24 --- Makefile | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 7f29cb7..7299df0 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GODIR := $(shell dirname "$(GO)") GOFMT := "$(GODIR)/gofmt" GOOS ?= linux GOARCH ?= amd64 -GOVERSION := $(shell "$(GO)" env GOVERSION | sed "s|go||" ) +GOVERSION := $(shell "$(GO)" env GOVERSION | sed -E 's|go([0-9]+\.[0-9]+)\..*|\1|') TMPDIR := $(CURDIR)/tmp BINDIR := $(CURDIR)/bin VENDORDIR := "$(CURDIR)/vendor" @@ -77,6 +77,12 @@ else GOPATHBIN := $(GOPATH)/bin/$(GOOS)_$(GOARCH) endif +ifeq ($(GOVERSION), 1.24) +GOEXPERIMENT := synctest +else +GOEXPERIMENT := +endif + hook: [ ! -d "$(CURDIR)/.git/hooks" ] || ln -sf "$(CURDIR)/scripts/pre-commit.hook" "$(CURDIR)/.git/hooks/pre-commit" @@ -110,24 +116,24 @@ fmt: hook | $(PROTO_GO_FILES) $(GOFMT) -s -w *.go cmd/client cmd/proxy cmd/server vet: - GOEXPERIMENT=synctest $(GO) vet ./... + GOEXPERIMENT=$(GOEXPERIMENT) $(GO) vet ./... test: vet - GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) $(TESTARGS) ./... + GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -timeout $(TIMEOUT) $(TESTARGS) ./... benchmark: - GOEXPERIMENT=synctest $(GO) test -bench=$(BENCHMARK) -benchmem -run=^$$ -timeout $(TIMEOUT) $(TESTARGS) ./... + GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -bench=$(BENCHMARK) -benchmem -run=^$$ -timeout $(TIMEOUT) $(TESTARGS) ./... checklocks: $(GOPATHBIN)/checklocks - GOEXPERIMENT=synctest go vet -vettool=$(GOPATHBIN)/checklocks ./... + GOEXPERIMENT=$(GOEXPERIMENT) go vet -vettool=$(GOPATHBIN)/checklocks ./... cover: vet rm -f cover.out && \ - GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... + GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... coverhtml: vet rm -f cover.out && \ - GOEXPERIMENT=synctest $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... && \ + GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./... && \ sed -i "/_easyjson/d" cover.out && \ sed -i "/\.pb\.go/d" cover.out && \ $(GO) tool cover -html=cover.out -o coverage.html From 9b062a994a8131f37f0ba26bf7d4888382eefac7 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:43:55 +0100 Subject: [PATCH 506/549] Temporarily run some tests only on Go 1.25 or newer. Prevents error "synctest.Wait requires go1.25 or later (file is go1.24)". --- api/transient_data_test.go | 2 ++ async/notifier_test.go | 2 ++ async/single_notifier_test.go | 2 ++ 3 files changed, 6 insertions(+) diff --git a/api/transient_data_test.go b/api/transient_data_test.go index bbb5920..0e34eae 100644 --- a/api/transient_data_test.go +++ b/api/transient_data_test.go @@ -1,3 +1,5 @@ +//go:build go1.25 + /** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2021 struktur AG diff --git a/async/notifier_test.go b/async/notifier_test.go index 9bd61eb..b8d1b99 100644 --- a/async/notifier_test.go +++ b/async/notifier_test.go @@ -1,3 +1,5 @@ +//go:build go1.25 + /** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2021 struktur AG diff --git a/async/single_notifier_test.go b/async/single_notifier_test.go index 1f2c7dd..9748451 100644 --- a/async/single_notifier_test.go +++ b/async/single_notifier_test.go @@ -1,3 +1,5 @@ +//go:build go1.25 + /** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2022 struktur AG From 15d6e516bdacb674b613d7e7020faa313394702f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 09:02:24 +0100 Subject: [PATCH 507/549] CI: No longer test with Golang 1.24 --- .github/workflows/govuln.yml | 4 ++-- .github/workflows/lint.yml | 6 ++---- .github/workflows/tarball.yml | 3 --- .github/workflows/test.yml | 4 ---- Makefile | 4 ---- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/.github/workflows/govuln.yml b/.github/workflows/govuln.yml index ef2990a..e0047fa 100644 --- a/.github/workflows/govuln.yml +++ b/.github/workflows/govuln.yml @@ -24,8 +24,8 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" + - "1.26" steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 @@ -39,4 +39,4 @@ jobs: run: | set -euo pipefail go install golang.org/x/vuln/cmd/govulncheck@latest - GOEXPERIMENT=synctest govulncheck ./... + govulncheck ./... diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f8b9180..1e69405 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: "1.24" + go-version: "1.25" - name: lint uses: golangci/golangci-lint-action@v9.2.0 @@ -38,8 +38,6 @@ jobs: version: latest args: --timeout=2m0s skip-cache: true - env: - GOEXPERIMENT: synctest modernize: name: modernize @@ -82,7 +80,7 @@ jobs: - name: Check minimum supported version of Go run: | - go mod tidy -go=1.24.0 -compat=1.24.0 + go mod tidy -go=1.25.0 -compat=1.25.0 - name: Check go.mod / go.sum run: | diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 3ad6174..1e18455 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -24,7 +24,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest @@ -49,7 +48,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest @@ -95,7 +93,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 58d9684..2dff4ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest @@ -50,7 +49,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest @@ -71,7 +69,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest @@ -92,7 +89,6 @@ jobs: strategy: matrix: go-version: - - "1.24" - "1.25" - "1.26" runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 7299df0..96a9943 100644 --- a/Makefile +++ b/Makefile @@ -77,11 +77,7 @@ else GOPATHBIN := $(GOPATH)/bin/$(GOOS)_$(GOARCH) endif -ifeq ($(GOVERSION), 1.24) -GOEXPERIMENT := synctest -else GOEXPERIMENT := -endif hook: [ ! -d "$(CURDIR)/.git/hooks" ] || ln -sf "$(CURDIR)/scripts/pre-commit.hook" "$(CURDIR)/.git/hooks/pre-commit" From 95c66f9d82e5dd5494f22d6f371967ad87f01161 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:52:42 +0100 Subject: [PATCH 508/549] Revert "Temporarily run some tests only on Go 1.25 or newer." This reverts commit 9b062a994a8131f37f0ba26bf7d4888382eefac7. --- api/transient_data_test.go | 2 -- async/notifier_test.go | 2 -- async/single_notifier_test.go | 2 -- 3 files changed, 6 deletions(-) diff --git a/api/transient_data_test.go b/api/transient_data_test.go index 0e34eae..bbb5920 100644 --- a/api/transient_data_test.go +++ b/api/transient_data_test.go @@ -1,5 +1,3 @@ -//go:build go1.25 - /** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2021 struktur AG diff --git a/async/notifier_test.go b/async/notifier_test.go index b8d1b99..9bd61eb 100644 --- a/async/notifier_test.go +++ b/async/notifier_test.go @@ -1,5 +1,3 @@ -//go:build go1.25 - /** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2021 struktur AG diff --git a/async/single_notifier_test.go b/async/single_notifier_test.go index 9748451..1f2c7dd 100644 --- a/async/single_notifier_test.go +++ b/async/single_notifier_test.go @@ -1,5 +1,3 @@ -//go:build go1.25 - /** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2022 struktur AG From 6caac6fbcaa94d8c20ea12d3d9e562b2dfaaaa2f Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:58:29 +0100 Subject: [PATCH 509/549] Remove Golang 1.24 compatibility for log output. --- log/test/log.go | 21 +-------------------- log/test/log_24.go | 34 ---------------------------------- log/test/log_25.go | 33 --------------------------------- 3 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 log/test/log_24.go delete mode 100644 log/test/log_25.go diff --git a/log/test/log.go b/log/test/log.go index 2524a31..fa013c7 100644 --- a/log/test/log.go +++ b/log/test/log.go @@ -22,30 +22,13 @@ package test import ( - "bytes" stdlog "log" - "sync" "testing" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/test" ) -type testLogWriter struct { - mu sync.Mutex - t testing.TB -} - -func (w *testLogWriter) Write(b []byte) (int, error) { - w.t.Helper() - if !bytes.HasSuffix(b, []byte("\n")) { - b = append(b, '\n') - } - w.mu.Lock() - defer w.mu.Unlock() - return writeTestOutput(w.t, b) -} - var ( testLoggers test.Storage[log.Logger] ) @@ -55,9 +38,7 @@ func NewLoggerForTest(t testing.TB) log.Logger { logger, found := testLoggers.Get(t) if !found { - logger = stdlog.New(&testLogWriter{ - t: t, - }, t.Name()+": ", stdlog.LstdFlags|stdlog.Lmicroseconds|stdlog.Lshortfile) + logger = stdlog.New(t.Output(), t.Name()+": ", stdlog.LstdFlags|stdlog.Lmicroseconds|stdlog.Lshortfile) testLoggers.Set(t, logger) } diff --git a/log/test/log_24.go b/log/test/log_24.go deleted file mode 100644 index 29ed970..0000000 --- a/log/test/log_24.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !go1.25 - -/** - * 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 test - -import ( - "testing" -) - -func writeTestOutput(t testing.TB, p []byte) (int, error) { - t.Helper() - t.Logf("%s", string(p)) - return len(p), nil -} diff --git a/log/test/log_25.go b/log/test/log_25.go deleted file mode 100644 index 8fad2b3..0000000 --- a/log/test/log_25.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build go1.25 - -/** - * 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 test - -import ( - "testing" -) - -func writeTestOutput(t testing.TB, p []byte) (int, error) { - t.Helper() - return t.Output().Write(p) -} From bd8c7588472d567372abb32a012db8f347c076ec Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 08:59:34 +0100 Subject: [PATCH 510/549] Remove Golang 1.24 compatibility for synctest.Test. --- api/transient_data_test.go | 6 ++--- async/backoff_test.go | 5 ++-- async/deferred_executor_test.go | 4 ++-- async/notifier_test.go | 4 +--- async/single_notifier_test.go | 4 +--- async/throttle_test.go | 16 ++++++------- cmd/proxy/proxy_server_test.go | 4 ++-- test/synctest24.go | 41 --------------------------------- test/synctest25.go | 32 ------------------------- test/synctest_test.go | 38 ------------------------------ 10 files changed, 18 insertions(+), 136 deletions(-) delete mode 100644 test/synctest24.go delete mode 100644 test/synctest25.go delete mode 100644 test/synctest_test.go diff --git a/api/transient_data_test.go b/api/transient_data_test.go index bbb5920..ae3dd62 100644 --- a/api/transient_data_test.go +++ b/api/transient_data_test.go @@ -29,13 +29,11 @@ import ( "time" "github.com/stretchr/testify/assert" - - "github.com/strukturag/nextcloud-spreed-signaling/test" ) func Test_TransientData(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) data := NewTransientData() assert.False(data.Set("foo", nil)) @@ -190,7 +188,7 @@ func Test_TransientDataNotifyInitial(t *testing.T) { func Test_TransientDataSetInitial(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) now := time.Now() diff --git a/async/backoff_test.go b/async/backoff_test.go index 08af543..7ade659 100644 --- a/async/backoff_test.go +++ b/async/backoff_test.go @@ -24,17 +24,16 @@ package async import ( "context" "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestBackoff_Exponential(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) minWait := 100 * time.Millisecond backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond) diff --git a/async/deferred_executor_test.go b/async/deferred_executor_test.go index ccd78f2..778817d 100644 --- a/async/deferred_executor_test.go +++ b/async/deferred_executor_test.go @@ -23,12 +23,12 @@ package async import ( "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestDeferredExecutor_MultiClose(t *testing.T) { @@ -43,7 +43,7 @@ func TestDeferredExecutor_MultiClose(t *testing.T) { func TestDeferredExecutor_QueueSize(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { logger := logtest.NewLoggerForTest(t) e := NewDeferredExecutor(logger, 0) defer e.waitForStop() diff --git a/async/notifier_test.go b/async/notifier_test.go index 9bd61eb..b81c967 100644 --- a/async/notifier_test.go +++ b/async/notifier_test.go @@ -29,8 +29,6 @@ import ( "time" "github.com/stretchr/testify/assert" - - "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestNotifierNoWaiter(t *testing.T) { @@ -120,7 +118,7 @@ func TestNotifierResetWillNotify(t *testing.T) { func TestNotifierDuplicate(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { var notifier Notifier var done sync.WaitGroup diff --git a/async/single_notifier_test.go b/async/single_notifier_test.go index 1f2c7dd..5bd9381 100644 --- a/async/single_notifier_test.go +++ b/async/single_notifier_test.go @@ -29,8 +29,6 @@ import ( "time" "github.com/stretchr/testify/assert" - - "github.com/strukturag/nextcloud-spreed-signaling/test" ) func TestSingleNotifierNoWaiter(t *testing.T) { @@ -120,7 +118,7 @@ func TestSingleNotifierResetWillNotify(t *testing.T) { func TestSingleNotifierDuplicate(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { var notifier SingleNotifier var done sync.WaitGroup diff --git a/async/throttle_test.go b/async/throttle_test.go index fbbf729..83e61da 100644 --- a/async/throttle_test.go +++ b/async/throttle_test.go @@ -23,6 +23,7 @@ package async import ( "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" @@ -30,7 +31,6 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" ) func newMemoryThrottlerForTest(t *testing.T) Throttler { @@ -55,7 +55,7 @@ func expectDelay(t *testing.T, f func(), delay time.Duration) { func TestThrottler(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -90,7 +90,7 @@ func TestThrottler(t *testing.T) { func TestThrottlerIPv6(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -128,7 +128,7 @@ func TestThrottlerIPv6(t *testing.T) { func TestThrottler_Bruteforce(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -155,7 +155,7 @@ func TestThrottler_Bruteforce(t *testing.T) { func TestThrottler_Cleanup(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) throttler := newMemoryThrottlerForTest(t) th, ok := throttler.(*memoryThrottler) @@ -212,7 +212,7 @@ func TestThrottler_Cleanup(t *testing.T) { func TestThrottler_ExpirePartial(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -245,7 +245,7 @@ func TestThrottler_ExpirePartial(t *testing.T) { func TestThrottler_ExpireAll(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) @@ -278,7 +278,7 @@ func TestThrottler_ExpireAll(t *testing.T) { func TestThrottler_Negative(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) th := newMemoryThrottlerForTest(t) diff --git a/cmd/proxy/proxy_server_test.go b/cmd/proxy/proxy_server_test.go index 4aa212d..174c2f9 100644 --- a/cmd/proxy/proxy_server_test.go +++ b/cmd/proxy/proxy_server_test.go @@ -36,6 +36,7 @@ import ( "sync" "sync/atomic" "testing" + "testing/synctest" "time" "github.com/dlintw/goconf" @@ -53,7 +54,6 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/proxy" "github.com/strukturag/nextcloud-spreed-signaling/sfu" "github.com/strukturag/nextcloud-spreed-signaling/talk" - "github.com/strukturag/nextcloud-spreed-signaling/test" ) const ( @@ -1705,7 +1705,7 @@ func TestProxyUnpublishRemoteOnSessionClose(t *testing.T) { func TestExpireSessions(t *testing.T) { t.Parallel() - test.SynctestTest(t, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { assert := assert.New(t) require := require.New(t) proxyServer, key, server := newProxyServerForTest(t) diff --git a/test/synctest24.go b/test/synctest24.go deleted file mode 100644 index 47be565..0000000 --- a/test/synctest24.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build !go1.25 - -/** - * 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 test - -import ( - "testing" - "testing/synctest" -) - -func SynctestTest(t *testing.T, f func(t *testing.T)) { - t.Helper() - - synctest.Run(func() { - t.Run("synctest", func(t *testing.T) { - // synctest of Go 1.25 doesn't support "t.Parallel()" but we can't prevent - // this here. Invalid calls will be detected when running with Go 1.25. - f(t) - }) - }) -} diff --git a/test/synctest25.go b/test/synctest25.go deleted file mode 100644 index e0e6da2..0000000 --- a/test/synctest25.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build go1.25 - -/** - * 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 test - -import ( - "testing/synctest" -) - -var ( - SynctestTest = synctest.Test -) diff --git a/test/synctest_test.go b/test/synctest_test.go deleted file mode 100644 index de90478..0000000 --- a/test/synctest_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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 test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestSynctest(t *testing.T) { - t.Parallel() - SynctestTest(t, func(t *testing.T) { - start := time.Now() - time.Sleep(time.Second) - assert.Equal(t, time.Second, time.Since(start)) - }) -} From e4013b9f149a5b4b70be452a19c888e2234e3526 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 09:02:51 +0100 Subject: [PATCH 511/549] Drop support for Golang 1.24 --- README.md | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b2cf85..9e2fdc6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ information on the API of the signaling server. The following tools are required for building the signaling server. - git -- go >= 1.24 +- go >= 1.25 - make Usually the last two versions of Go are supported. This follows the release diff --git a/go.mod b/go.mod index e7c7285..fb71d11 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/strukturag/nextcloud-spreed-signaling -go 1.24.0 +go 1.25.0 require ( github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490 From a5f70b6e470326df0136ad6b3ffc9b74a45db1f1 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 09:25:53 +0100 Subject: [PATCH 512/549] Switch to safer waitgroup.Go where possible. --- async/notifier_test.go | 29 +++++++++--------------- async/single_notifier_test.go | 29 +++++++++--------------- cmd/client/main.go | 17 ++++---------- container/concurrentmap_test.go | 7 ++---- dns/monitor_test.go | 7 ++---- internal/closer_test.go | 6 ++--- internal/flags_test.go | 6 ++--- server/backend_server.go | 36 +++++++++++------------------- server/backend_server_test.go | 36 ++++++++++++------------------ server/hub.go | 39 +++++++++++++-------------------- server/hub_test.go | 7 ++---- server/room.go | 38 +++++++++++--------------------- server/room_ping.go | 6 ++--- server/roomsessions_builtin.go | 7 ++---- sfu/proxy/config_etcd.go | 6 ++--- sfu/proxy/proxy.go | 12 ++++------ talk/backend_storage_etcd.go | 6 ++--- talk/capabilities_test.go | 6 ++--- 18 files changed, 103 insertions(+), 197 deletions(-) diff --git a/async/notifier_test.go b/async/notifier_test.go index b81c967..21ba219 100644 --- a/async/notifier_test.go +++ b/async/notifier_test.go @@ -41,20 +41,17 @@ func TestNotifierNoWaiter(t *testing.T) { func TestNotifierSimple(t *testing.T) { t.Parallel() + var notifier Notifier - - var wg sync.WaitGroup - wg.Add(1) - waiter := notifier.NewWaiter("foo") defer notifier.Release(waiter) - go func() { - defer wg.Done() + var wg sync.WaitGroup + wg.Go(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, waiter.Wait(ctx)) - }() + }) notifier.Notify("foo") wg.Wait() @@ -97,20 +94,17 @@ func TestNotifierWaitClosedMulti(t *testing.T) { func TestNotifierResetWillNotify(t *testing.T) { t.Parallel() + var notifier Notifier - - var wg sync.WaitGroup - wg.Add(1) - waiter := notifier.NewWaiter("foo") defer notifier.Release(waiter) - go func() { - defer wg.Done() + var wg sync.WaitGroup + wg.Go(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, waiter.Wait(ctx)) - }() + }) notifier.Reset() wg.Wait() @@ -123,17 +117,14 @@ func TestNotifierDuplicate(t *testing.T) { var done sync.WaitGroup for range 2 { - done.Add(1) - - go func() { - defer done.Done() + done.Go(func() { waiter := notifier.NewWaiter("foo") defer notifier.Release(waiter) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, waiter.Wait(ctx)) - }() + }) } synctest.Wait() diff --git a/async/single_notifier_test.go b/async/single_notifier_test.go index 5bd9381..83de3e6 100644 --- a/async/single_notifier_test.go +++ b/async/single_notifier_test.go @@ -41,20 +41,17 @@ func TestSingleNotifierNoWaiter(t *testing.T) { func TestSingleNotifierSimple(t *testing.T) { t.Parallel() + var notifier SingleNotifier - - var wg sync.WaitGroup - wg.Add(1) - waiter := notifier.NewWaiter() defer notifier.Release(waiter) - go func() { - defer wg.Done() + var wg sync.WaitGroup + wg.Go(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, waiter.Wait(ctx)) - }() + }) notifier.Notify() wg.Wait() @@ -97,20 +94,17 @@ func TestSingleNotifierWaitClosedMulti(t *testing.T) { func TestSingleNotifierResetWillNotify(t *testing.T) { t.Parallel() + var notifier SingleNotifier - - var wg sync.WaitGroup - wg.Add(1) - waiter := notifier.NewWaiter() defer notifier.Release(waiter) - go func() { - defer wg.Done() + var wg sync.WaitGroup + wg.Go(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, waiter.Wait(ctx)) - }() + }) notifier.Reset() wg.Wait() @@ -123,17 +117,14 @@ func TestSingleNotifierDuplicate(t *testing.T) { var done sync.WaitGroup for range 2 { - done.Add(1) - - go func() { - defer done.Done() + done.Go(func() { waiter := notifier.NewWaiter() defer notifier.Release(waiter) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, waiter.Wait(ctx)) - }() + }) } synctest.Wait() diff --git a/cmd/client/main.go b/cmd/client/main.go index da1e264..684b40a 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -161,15 +161,8 @@ func NewSignalingClient(url string, stats *Stats, readyWg *sync.WaitGroup, doneW stopChan: make(chan struct{}), } - doneWg.Add(2) - go func() { - defer doneWg.Done() - client.readPump() - }() - go func() { - defer doneWg.Done() - client.writePump() - }() + doneWg.Go(client.readPump) + doneWg.Go(client.writePump) return client, nil } @@ -621,11 +614,9 @@ func main() { log.Println("All connections established") for _, c := range clients { - doneWg.Add(1) - go func(c *SignalingClient) { - defer doneWg.Done() + doneWg.Go(func() { c.SendMessages(clients) - }(c) + }) } stats.start = time.Now() diff --git a/container/concurrentmap_test.go b/container/concurrentmap_test.go index 41679b6..98b126d 100644 --- a/container/concurrentmap_test.go +++ b/container/concurrentmap_test.go @@ -79,10 +79,7 @@ func TestConcurrentStringStringMap(t *testing.T) { concurrency := 100 count := 1000 for x := range concurrency { - wg.Add(1) - go func(x int) { - defer wg.Done() - + wg.Go(func() { key := "key-" + strconv.Itoa(x) rnd := rand.Text() for y := range count { @@ -96,7 +93,7 @@ func TestConcurrentStringStringMap(t *testing.T) { return } } - }(x) + }) } wg.Wait() assert.Equal(concurrency, m.Len()) diff --git a/dns/monitor_test.go b/dns/monitor_test.go index f8316b1..b4bb95b 100644 --- a/dns/monitor_test.go +++ b/dns/monitor_test.go @@ -324,16 +324,13 @@ func (r *deadlockMonitorReceiver) OnLookup(entry *MonitorEntry, all []net.IP, ad } r.triggered = true - r.wg.Add(1) - go func() { - defer r.wg.Done() - + r.wg.Go(func() { r.mu.RLock() defer r.mu.RUnlock() close(r.started) time.Sleep(50 * time.Millisecond) - }() + }) } func (r *deadlockMonitorReceiver) Start() { diff --git a/internal/closer_test.go b/internal/closer_test.go index f74a166..519a7cc 100644 --- a/internal/closer_test.go +++ b/internal/closer_test.go @@ -35,11 +35,9 @@ func TestCloserMulti(t *testing.T) { var wg sync.WaitGroup count := 10 for range count { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-closer.C - }() + }) } assert.False(t, closer.IsClosed()) diff --git a/internal/flags_test.go b/internal/flags_test.go index 9664955..84eacaf 100644 --- a/internal/flags_test.go +++ b/internal/flags_test.go @@ -56,14 +56,12 @@ func runConcurrentFlags(t *testing.T, count int, f func()) { var ready sync.WaitGroup var done sync.WaitGroup for range count { - done.Add(1) ready.Add(1) - go func() { - defer done.Done() + done.Go(func() { ready.Done() start.Wait() f() - }() + }) } ready.Wait() start.Done() diff --git a/server/backend_server.go b/server/backend_server.go index cf8be1f..231ccb0 100644 --- a/server/backend_server.go +++ b/server/backend_server.go @@ -385,9 +385,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *talk.Backend, continue } - wg.Add(1) - go func(sessionid api.RoomSessionId) { - defer wg.Done() + wg.Go(func() { if sid, err := b.lookupByRoomSessionId(ctx, sessionid, nil); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", sessionid, err) } else if sid != "" { @@ -395,7 +393,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *talk.Backend, b.logger.Printf("Could not publish room disinvite for session %s: %s", sid, err) } } - }(sessionid) + }) } wg.Wait() } @@ -476,19 +474,17 @@ func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container. continue } - wg.Add(1) - go func(roomSessionId api.RoomSessionId, u api.StringMap) { - defer wg.Done() + wg.Go(func() { if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) - delete(u, "sessionId") + delete(user, "sessionId") } else if sessionId != "" { - u["sessionId"] = sessionId + user["sessionId"] = sessionId } else { // sessionId == "" - delete(u, "sessionId") + delete(user, "sessionId") } - }(roomSessionId, user) + }) } wg.Wait() @@ -568,10 +564,8 @@ loop: } permissions = append(permissions, api.Permission(permission)) } - wg.Add(1) - go func(sessionId api.PublicSessionId, permissions []api.Permission) { - defer wg.Done() + wg.Go(func() { message := &events.AsyncMessage{ Type: "permissions", Permissions: permissions, @@ -579,7 +573,7 @@ loop: if err := b.events.PublishSessionMessage(sessionId, backend, message); err != nil { b.logger.Printf("Could not send permissions update (%+v) to session %s: %s", permissions, sessionId, err) } - }(sessionId, permissions) + }) } wg.Wait() @@ -625,9 +619,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac continue } - wg.Add(1) - go func(roomSessionId api.RoomSessionId) { - defer wg.Done() + wg.Go(func() { if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) } else if sessionId != "" { @@ -635,7 +627,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac defer mu.Unlock() internalSessionsList = append(internalSessionsList, sessionId) } - }(roomSessionId) + }) } wg.Wait() @@ -663,9 +655,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac continue } - wg.Add(1) - go func(roomSessionId api.RoomSessionId, details json.RawMessage) { - defer wg.Done() + wg.Go(func() { if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil { b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err) } else if sessionId != "" { @@ -673,7 +663,7 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac defer mu.Unlock() internalSessionsMap[sessionId] = details } - }(roomSessionId, details) + }) } wg.Wait() diff --git a/server/backend_server_test.go b/server/backend_server_test.go index f133bd7..8bc63a1 100644 --- a/server/backend_server_test.go +++ b/server/backend_server_test.go @@ -972,9 +972,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { msg := &talk.BackendServerRoomRequest{ Type: "incall", InCall: &talk.BackendRoomInCallRequest{ @@ -1014,14 +1012,12 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { body, err := io.ReadAll(res.Body) assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - }() + }) // Ensure the first request is being processed. time.Sleep(100 * time.Millisecond) - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { msg := &talk.BackendServerRoomRequest{ Type: "incall", InCall: &talk.BackendRoomInCallRequest{ @@ -1061,7 +1057,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { body, err := io.ReadAll(res.Body) assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - }() + }) wg.Wait() if t.Failed() { @@ -1159,9 +1155,7 @@ func TestBackendServer_InCallAll(t *testing.T) { assert.False(room2.IsSessionInCall(session2), "Session %s should not be in room %s", session2.PublicId(), room2.Id()) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { msg := &talk.BackendServerRoomRequest{ Type: "incall", InCall: &talk.BackendRoomInCallRequest{ @@ -1182,7 +1176,7 @@ func TestBackendServer_InCallAll(t *testing.T) { body, err := io.ReadAll(res.Body) assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - }() + }) wg.Wait() if t.Failed() { @@ -1216,9 +1210,7 @@ func TestBackendServer_InCallAll(t *testing.T) { client2.RunUntilErrorIs(ctx3, ErrNoMessageReceived, context.DeadlineExceeded) - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { msg := &talk.BackendServerRoomRequest{ Type: "incall", InCall: &talk.BackendRoomInCallRequest{ @@ -1239,7 +1231,7 @@ func TestBackendServer_InCallAll(t *testing.T) { body, err := io.ReadAll(res.Body) assert.NoError(err) assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body)) - }() + }) wg.Wait() if t.Failed() { @@ -1778,8 +1770,6 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { var wg sync.WaitGroup runClient := func(client *TestClient) { - defer wg.Done() - msg, ok := client.RunUntilMessage(ctx) if !ok { return @@ -1827,10 +1817,12 @@ func TestBackendServer_DialoutFirstFailed(t *testing.T) { assert.NoError(client.WriteJSON(response)) } - wg.Add(1) - go runClient(client1) - wg.Add(1) - go runClient(client2) + wg.Go(func() { + runClient(client1) + }) + wg.Go(func() { + runClient(client2) + }) defer func() { wg.Wait() diff --git a/server/hub.go b/server/hub.go index 3251dbd..ce99306 100644 --- a/server/hub.go +++ b/server/hub.go @@ -1087,22 +1087,19 @@ func (h *Hub) processRegister(client ClientWithSession, message *api.ClientMessa var wg sync.WaitGroup ctx, cancel := context.WithTimeout(client.Context(), time.Second) defer cancel() - for _, client := range h.rpcClients.GetClients() { - wg.Add(1) - go func(c *grpc.Client) { - defer wg.Done() - - count, err := c.GetSessionCount(ctx, session.BackendUrl()) + for _, grpcClient := range h.rpcClients.GetClients() { + wg.Go(func() { + count, err := grpcClient.GetSessionCount(ctx, session.BackendUrl()) if err != nil { - h.logger.Printf("Received error while getting session count for %s from %s: %s", session.BackendUrl(), c.Target(), err) + h.logger.Printf("Received error while getting session count for %s from %s: %s", session.BackendUrl(), grpcClient.Target(), err) return } if count > 0 { - h.logger.Printf("%d sessions connected for %s on %s", count, session.BackendUrl(), c.Target()) + h.logger.Printf("%d sessions connected for %s on %s", count, session.BackendUrl(), grpcClient.Target()) totalCount.Add(count) } - }(client) + }) } wg.Wait() if totalCount.Load() > limit { @@ -1288,27 +1285,24 @@ func (h *Hub) tryProxyResume(c ClientWithSession, resumeId api.PrivateSessionId, defer cancel() var remoteClient atomic.Pointer[remoteClientInfo] - for _, c := range clients { - wg.Add(1) - go func(client *grpc.Client) { - defer wg.Done() - - if client.IsSelf() { + for _, grpcClient := range clients { + wg.Go(func() { + if grpcClient.IsSelf() { return } - response, err := client.LookupResumeId(ctx, resumeId) + response, err := grpcClient.LookupResumeId(ctx, resumeId) if err != nil { - h.logger.Printf("Could not lookup resume id %s on %s: %s", resumeId, client.Target(), err) + h.logger.Printf("Could not lookup resume id %s on %s: %s", resumeId, grpcClient.Target(), err) return } cancel() remoteClient.CompareAndSwap(nil, &remoteClientInfo{ - client: client, + client: grpcClient, response: response, }) - }(c) + }) } wg.Wait() @@ -2869,10 +2863,7 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi rpcCtx, cancel := context.WithCancel(ctx) defer cancel() for _, client := range clients { - wg.Add(1) - go func(client *grpc.Client) { - defer wg.Done() - + wg.Go(func() { inCall, err := client.IsSessionInCall(rpcCtx, recipientSessionId, senderRoom.Id(), senderSession.BackendUrl()) if errors.Is(err, context.Canceled) { return @@ -2885,7 +2876,7 @@ func (h *Hub) isInSameCallRemote(ctx context.Context, senderSession *ClientSessi cancel() result.Store(true) - }(client) + }) } wg.Wait() diff --git a/server/hub_test.go b/server/hub_test.go index 13f0af0..50b9505 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -1365,10 +1365,7 @@ func TestSessionIdsUnordered(t *testing.T) { var publicSessionIds []api.PublicSessionId var wg sync.WaitGroup for range 20 { - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -1391,7 +1388,7 @@ func TestSessionIdsUnordered(t *testing.T) { mu.Lock() publicSessionIds = append(publicSessionIds, session.PublicId()) mu.Unlock() - }() + }) } wg.Wait() diff --git a/server/room.go b/server/room.go index 74894a9..b6601b1 100644 --- a/server/room.go +++ b/server/room.go @@ -725,13 +725,10 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSes var mu sync.Mutex var wg sync.WaitGroup for _, client := range r.hub.rpcClients.GetClients() { - wg.Add(1) - go func(c *grpc.Client) { - defer wg.Done() - - clientInternal, clientVirtual, err := c.GetInternalSessions(ctx, r.Id(), r.Backend().Urls()) + wg.Go(func() { + clientInternal, clientVirtual, err := client.GetInternalSessions(ctx, r.Id(), r.Backend().Urls()) if err != nil { - r.logger.Printf("Received error while getting internal sessions for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) + r.logger.Printf("Received error while getting internal sessions for %s@%s from %s: %s", r.Id(), r.Backend().Id(), client.Target(), err) return } @@ -745,7 +742,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSes virtual = make(map[api.PublicSessionId]*grpc.VirtualSessionData, len(clientVirtual)) } maps.Copy(virtual, clientVirtual) - }(client) + }) } wg.Wait() @@ -1245,26 +1242,20 @@ func (r *Room) publishSwitchTo(message *talk.BackendRoomSwitchToMessageRequest) } for _, sessionId := range message.SessionsList { - wg.Add(1) - go func(sessionId api.PublicSessionId) { - defer wg.Done() - + wg.Go(func() { if err := r.events.PublishSessionMessage(sessionId, r.backend, &events.AsyncMessage{ Type: "message", Message: msg, }); err != nil { r.logger.Printf("Error publishing switchto event to session %s: %s", sessionId, err) } - }(sessionId) + }) } } if len(message.SessionsMap) > 0 { for sessionId, details := range message.SessionsMap { - wg.Add(1) - go func(sessionId api.PublicSessionId, details json.RawMessage) { - defer wg.Done() - + wg.Go(func() { msg := &api.ServerMessage{ Type: "event", Event: &api.EventServerMessage{ @@ -1283,7 +1274,7 @@ func (r *Room) publishSwitchTo(message *talk.BackendRoomSwitchToMessageRequest) }); err != nil { r.logger.Printf("Error publishing switchto event to session %s: %s", sessionId, err) } - }(sessionId, details) + }) } } wg.Wait() @@ -1383,26 +1374,23 @@ func (r *Room) fetchInitialTransientData() { // +checklocks:mu var initial api.TransientDataEntries for _, client := range r.hub.rpcClients.GetClients() { - wg.Add(1) - go func(c *grpc.Client) { - defer wg.Done() - - data, err := c.GetTransientData(ctx, r.Id(), r.Backend()) + wg.Go(func() { + data, err := client.GetTransientData(ctx, r.Id(), r.Backend()) if err != nil { - r.logger.Printf("Received error while getting transient data for %s@%s from %s: %s", r.Id(), r.Backend().Id(), c.Target(), err) + r.logger.Printf("Received error while getting transient data for %s@%s from %s: %s", r.Id(), r.Backend().Id(), client.Target(), err) return } else if len(data) == 0 { return } - r.logger.Printf("Received initial transient data %+v from %s", data, c.Target()) + r.logger.Printf("Received initial transient data %+v from %s", data, client.Target()) mu.Lock() defer mu.Unlock() if initial == nil { initial = make(api.TransientDataEntries) } maps.Copy(initial, data) - }(client) + }) } wg.Wait() diff --git a/server/room_ping.go b/server/room_ping.go index 536054b..05d87c9 100644 --- a/server/room_ping.go +++ b/server/room_ping.go @@ -153,12 +153,10 @@ func (p *RoomPing) publishActiveSessions(ctx context.Context) { } entries := p.getAndClearEntries() var wg sync.WaitGroup - wg.Add(len(entries)) for _, e := range entries { - go func(e *pingEntries) { - defer wg.Done() + wg.Go(func() { p.publishEntries(ctx, e, timeout) - }(e) + }) } wg.Wait() } diff --git a/server/roomsessions_builtin.go b/server/roomsessions_builtin.go index f69d9cf..9154d4c 100644 --- a/server/roomsessions_builtin.go +++ b/server/roomsessions_builtin.go @@ -122,10 +122,7 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId var result atomic.Value logger := log.LoggerFromContext(ctx) for _, client := range clients { - wg.Add(1) - go func(client *grpc.Client) { - defer wg.Done() - + wg.Go(func() { sid, err := client.LookupSessionId(lookupctx, roomSessionId, disconnectReason) if errors.Is(err, context.Canceled) { return @@ -139,7 +136,7 @@ func (r *BuiltinRoomSessions) LookupSessionId(ctx context.Context, roomSessionId cancel() // Cancel pending RPC calls. result.Store(sid) - }(client) + }) } wg.Wait() diff --git a/sfu/proxy/config_etcd.go b/sfu/proxy/config_etcd.go index 0dc81e5..708ac36 100644 --- a/sfu/proxy/config_etcd.go +++ b/sfu/proxy/config_etcd.go @@ -132,9 +132,7 @@ func (p *configEtcd) EtcdClientCreated(client etcd.Client) { return } - p.runningDone.Add(1) - go func() { - defer p.runningDone.Done() + p.runningDone.Go(func() { defer p.initializedFunc() if err := client.WaitForConnection(p.closeCtx); err != nil { if errors.Is(err, context.Canceled) { @@ -191,7 +189,7 @@ func (p *configEtcd) EtcdClientCreated(client etcd.Client) { backoff.Wait(p.closeCtx) } } - }() + }) } func (p *configEtcd) EtcdWatchCreated(client etcd.Client, key string) { diff --git a/sfu/proxy/proxy.go b/sfu/proxy/proxy.go index 469d604..9170ca8 100644 --- a/sfu/proxy/proxy.go +++ b/sfu/proxy/proxy.go @@ -2280,9 +2280,7 @@ func (m *proxySFU) NewSubscriber(ctx context.Context, listener sfu.Listener, pub var wg sync.WaitGroup // Wait for publisher to be created locally. - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { if conn := m.waitForPublisherConnection(getctx, publisher, streamType); conn != nil { cancel() // Cancel pending RPC calls. @@ -2301,14 +2299,12 @@ func (m *proxySFU) NewSubscriber(ctx context.Context, listener sfu.Listener, pub conn: conn, } } - }() + }) // Wait for publisher to be created on one of the other servers in the cluster. if clients := m.rpcClients.GetClients(); len(clients) > 0 { for _, client := range clients { - wg.Add(1) - go func(client *grpc.Client) { - defer wg.Done() + wg.Go(func() { id, url, ip, connectToken, publisherToken, err := client.GetPublisherId(getctx, publisher, streamType) if errors.Is(err, context.Canceled) { return @@ -2369,7 +2365,7 @@ func (m *proxySFU) NewSubscriber(ctx context.Context, listener sfu.Listener, pub conn: publisherConn, token: publisherToken, } - }(client) + }) } } diff --git a/talk/backend_storage_etcd.go b/talk/backend_storage_etcd.go index f17f7f5..0799f9e 100644 --- a/talk/backend_storage_etcd.go +++ b/talk/backend_storage_etcd.go @@ -122,9 +122,7 @@ func (s *backendStorageEtcd) EtcdClientCreated(client etcd.Client) { return } - s.runningDone.Add(1) - go func() { - defer s.runningDone.Done() + s.runningDone.Go(func() { defer s.initializedFunc() if err := client.WaitForConnection(s.closeCtx); err != nil { if errors.Is(err, context.Canceled) { @@ -179,7 +177,7 @@ func (s *backendStorageEtcd) EtcdClientCreated(client etcd.Client) { } return } - }() + }) } func (s *backendStorageEtcd) EtcdWatchCreated(client etcd.Client, key string) { diff --git a/talk/capabilities_test.go b/talk/capabilities_test.go index d2ceacd..c9578f5 100644 --- a/talk/capabilities_test.go +++ b/talk/capabilities_test.go @@ -575,9 +575,7 @@ func TestConcurrentExpired(t *testing.T) { var numFetched atomic.Uint32 var finished sync.WaitGroup for range count { - finished.Add(1) - go func() { - defer finished.Done() + finished.Go(func() { <-start if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); assert.True(found) { assert.Equal(expectedString, value) @@ -587,7 +585,7 @@ func TestConcurrentExpired(t *testing.T) { numFetched.Add(1) } } - }() + }) } SetCapabilitiesGetNow(t, capabilities, func() time.Time { From de03ae2514db37577213f66176effeb46363a376 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 10:11:37 +0100 Subject: [PATCH 513/549] make: Use "GO" variable for checklocks. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 96a9943..22221e5 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,7 @@ benchmark: GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -bench=$(BENCHMARK) -benchmem -run=^$$ -timeout $(TIMEOUT) $(TESTARGS) ./... checklocks: $(GOPATHBIN)/checklocks - GOEXPERIMENT=$(GOEXPERIMENT) go vet -vettool=$(GOPATHBIN)/checklocks ./... + GOEXPERIMENT=$(GOEXPERIMENT) $(GO) vet -vettool=$(GOPATHBIN)/checklocks ./... cover: vet rm -f cover.out && \ From d47c280d315bb0d3c9819ef5a2aa7c7d434b66b6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 10:24:50 +0100 Subject: [PATCH 514/549] make: Run checklocks tool directly. Running through "go vet" caused JSON output since switching to Go 1.26 and doesn't fail the job on errors. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 22221e5..c2fbab3 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,7 @@ benchmark: GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -bench=$(BENCHMARK) -benchmem -run=^$$ -timeout $(TIMEOUT) $(TESTARGS) ./... checklocks: $(GOPATHBIN)/checklocks - GOEXPERIMENT=$(GOEXPERIMENT) $(GO) vet -vettool=$(GOPATHBIN)/checklocks ./... + GOEXPERIMENT=$(GOEXPERIMENT) $(GOPATHBIN)/checklocks ./... cover: vet rm -f cover.out && \ From 5da3d601fe8d8fddb3cbef0625d946537b58456c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Feb 2026 10:29:43 +0100 Subject: [PATCH 515/549] Ignore more checklocks false positives. --- dns/monitor_test.go | 2 +- server/hub.go | 6 +++--- sfu/proxy/config_static.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dns/monitor_test.go b/dns/monitor_test.go index b4bb95b..43f5a9e 100644 --- a/dns/monitor_test.go +++ b/dns/monitor_test.go @@ -293,7 +293,7 @@ type deadlockMonitorReceiver struct { monitor *Monitor // +checklocksignore: Only written to from constructor. mu sync.RWMutex - wg sync.WaitGroup + wg sync.WaitGroup // +checklocksignore: Only written to from constructor. // +checklocks:mu entry *MonitorEntry diff --git a/server/hub.go b/server/hub.go index ce99306..c243b6a 100644 --- a/server/hub.go +++ b/server/hub.go @@ -110,13 +110,13 @@ var ( defaultFederationTimeoutSeconds = 10 // New connections have to send a "Hello" request after 2 seconds. - initialHelloTimeout = 2 * time.Second + initialHelloTimeout = 2 * time.Second // +checklocksignore: Global readonly variable. // Anonymous clients have to join a room after 10 seconds. - anonmyousJoinRoomTimeout = 10 * time.Second + anonmyousJoinRoomTimeout = 10 * time.Second // +checklocksignore: Global readonly variable. // Sessions expire 30 seconds after the connection closed. - sessionExpireDuration = 30 * time.Second + sessionExpireDuration = 30 * time.Second // +checklocksignore: Global readonly variable. // Run housekeeping jobs once per second housekeepingInterval = time.Second diff --git a/sfu/proxy/config_static.go b/sfu/proxy/config_static.go index f5bf4d5..341fa7f 100644 --- a/sfu/proxy/config_static.go +++ b/sfu/proxy/config_static.go @@ -48,7 +48,7 @@ type configStatic struct { mu sync.Mutex proxy McuProxy - dnsMonitor *dns.Monitor + dnsMonitor *dns.Monitor // +checklocksignore: Only written to from constructor. // +checklocks:mu dnsDiscovery bool From 9d45e78809fadcbc6cb66dbf57a9abd44cdd343d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:19:13 +0000 Subject: [PATCH 516/549] Bump github.com/pion/dtls/v3 from 3.1.0 to 3.1.1 Bumps [github.com/pion/dtls/v3](https://github.com/pion/dtls) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/pion/dtls/releases) - [Commits](https://github.com/pion/dtls/compare/v3.1.0...v3.1.1) --- updated-dependencies: - dependency-name: github.com/pion/dtls/v3 dependency-version: 3.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fb71d11..8d494d2 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.1.0 // indirect + github.com/pion/dtls/v3 v3.1.1 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index 7339a70..88c75d8 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.1.0 h1:bz3alDjKL1DDGe8GETGcq5rDKjXFQX9mniuUo36Up0E= -github.com/pion/dtls/v3 v3.1.0/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= +github.com/pion/dtls/v3 v3.1.1 h1:wSLMam9Kf7DL1A74hnqRvEb9OT+aXPAsQ5VS+BdXOJ0= +github.com/pion/dtls/v3 v3.1.1/go.mod h1:7FGvVYpHsUV6+aywaFpG7aE4Vz8nBOx74odPRFue6cI= github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw= github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= From 750656ea892cb0f389165069e15853968a501a6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 20:42:50 +0000 Subject: [PATCH 517/549] Bump google.golang.org/grpc from 1.78.0 to 1.79.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.78.0 to 1.79.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.79.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index fb71d11..6e66c6f 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.7 go.etcd.io/etcd/server/v3 v3.6.7 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 google.golang.org/protobuf v1.36.11 ) @@ -79,12 +79,12 @@ require ( go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect @@ -93,8 +93,8 @@ require ( golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect diff --git a/go.sum b/go.sum index 7339a70..6ff2487 100644 --- a/go.sum +++ b/go.sum @@ -157,20 +157,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -223,12 +223,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE= -google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From 194494cd2ca7b98be7f26e1626b4d6b304ad8806 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 20:51:25 +0000 Subject: [PATCH 518/549] Bump the etcd group with 4 updates Bumps the etcd group with 4 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/pkg/v3](https://github.com/etcd-io/etcd), [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/server/v3](https://github.com/etcd-io/etcd). Updates `go.etcd.io/etcd/api/v3` from 3.6.7 to 3.6.8 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.7...v3.6.8) Updates `go.etcd.io/etcd/client/pkg/v3` from 3.6.7 to 3.6.8 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.7...v3.6.8) Updates `go.etcd.io/etcd/client/v3` from 3.6.7 to 3.6.8 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.7...v3.6.8) Updates `go.etcd.io/etcd/server/v3` from 3.6.7 to 3.6.8 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.7...v3.6.8) --- updated-dependencies: - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/pkg/v3 dependency-version: 3.6.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd - dependency-name: go.etcd.io/etcd/server/v3 dependency-version: 3.6.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: etcd ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 6e66c6f..605782e 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/api/v3 v3.6.7 - go.etcd.io/etcd/client/pkg/v3 v3.6.7 - go.etcd.io/etcd/client/v3 v3.6.7 - go.etcd.io/etcd/server/v3 v3.6.7 + go.etcd.io/etcd/api/v3 v3.6.8 + go.etcd.io/etcd/client/pkg/v3 v3.6.8 + go.etcd.io/etcd/client/v3 v3.6.8 + go.etcd.io/etcd/server/v3 v3.6.8 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.79.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 @@ -75,7 +75,7 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.7 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.8 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index 6ff2487..830b3ef 100644 --- a/go.sum +++ b/go.sum @@ -141,16 +141,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0= -go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI= -go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs= -go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q= -go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U= -go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE= -go.etcd.io/etcd/pkg/v3 v3.6.7 h1:qIxdSI+LAmKFAjMy42yHQzSNqG/sWES4QjhFSGsMDpY= -go.etcd.io/etcd/pkg/v3 v3.6.7/go.mod h1:nPbpIExp9Q6tR/EVI2aZe0VBlflLys5VGFWSCmqUOyk= -go.etcd.io/etcd/server/v3 v3.6.7 h1:8dEGQ877tj0cQJFEfD2bDoZDA76qbS2OkvCNjwAyrSo= -go.etcd.io/etcd/server/v3 v3.6.7/go.mod h1:LEM328bPA2uVMhN0+Ht/vAsADW127QS1oM7EuHrOTy0= +go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM= +go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q= +go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50= +go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw= +go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY= +go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8= +go.etcd.io/etcd/pkg/v3 v3.6.8 h1:Xe+LIL974spy8b4nEx3H0KMr1ofq3r0kh6FbU3aw4es= +go.etcd.io/etcd/pkg/v3 v3.6.8/go.mod h1:TRibVNe+FqJIe1abOAA1PsuQ4wqO87ZaOoprg09Tn8c= +go.etcd.io/etcd/server/v3 v3.6.8 h1:U2strdSEy1U8qcSzRIdkYpvOPtBy/9i/IfaaCI9flZ4= +go.etcd.io/etcd/server/v3 v3.6.8/go.mod h1:88dCtwUnSirkUoJbflQxxWXqtBSZa6lSG0Kuej+dois= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= From bd9a7b2ba95318a498380645630322b52802d52c Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 15 Feb 2026 20:23:04 +0100 Subject: [PATCH 519/549] pin spreedbackend uid and add user group --- docker/server/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 7a27886..643403b 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -12,7 +12,8 @@ RUN touch /.dockerenv && \ FROM alpine:3 ENV CONFIG=/config/server.conf -RUN adduser -D -S -H spreedbackend && \ +RUN addgroup -g 850 spreedbackend && \ + adduser -D --uid 850 -S -H -G spreedbackend spreedbackend && \ apk add --no-cache bash tzdata ca-certificates su-exec COPY --from=builder /workdir/bin/signaling /usr/bin/nextcloud-spreed-signaling From fa18ce95dd3e980b7c4d3ddd95ddd5a1bbea4000 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 16 Feb 2026 19:26:09 +0100 Subject: [PATCH 520/549] pin proxy uid and add user group --- docker/proxy/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile index 953c7bd..4ecc624 100644 --- a/docker/proxy/Dockerfile +++ b/docker/proxy/Dockerfile @@ -12,7 +12,8 @@ RUN touch /.dockerenv && \ FROM alpine:3 ENV CONFIG=/config/proxy.conf -RUN adduser -D -S -H spreedbackend && \ +RUN addgroup -g 850 spreedbackend && \ + adduser -D --uid 850 -S -H -G spreedbackend spreedbackend && \ apk add --no-cache bash tzdata ca-certificates su-exec COPY --from=builder /workdir/bin/proxy /usr/bin/nextcloud-spreed-signaling-proxy From fd61bdce81436db857c44855ebabd22d827e7786 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 17 Feb 2026 08:31:58 +0100 Subject: [PATCH 521/549] CI: Use "docker compose" instead of downloading docker-compose binary. --- .github/workflows/docker-compose.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker-compose.yml b/.github/workflows/docker-compose.yml index 7636c95..3cc8019 100644 --- a/.github/workflows/docker-compose.yml +++ b/.github/workflows/docker-compose.yml @@ -24,13 +24,8 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Update docker-compose - run: | - curl -SL https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-x86_64 -o docker-compose - chmod a+x docker-compose - - name: Pull Docker images - run: ./docker-compose -f docker/docker-compose.yml pull + run: docker compose -f docker/docker-compose.yml pull build: runs-on: ubuntu-latest @@ -38,10 +33,5 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Update docker-compose - run: | - curl -SL https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-x86_64 -o docker-compose - chmod a+x docker-compose - - name: Build Docker images - run: ./docker-compose -f docker/docker-compose.yml build + run: docker compose -f docker/docker-compose.yml build From 3a3ce9f3f86eec723a9132c9d7ee19375a2c6931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:42:22 +0000 Subject: [PATCH 522/549] Bump github.com/pion/sdp/v3 from 3.0.17 to 3.0.18 Bumps [github.com/pion/sdp/v3](https://github.com/pion/sdp) from 3.0.17 to 3.0.18. - [Release notes](https://github.com/pion/sdp/releases) - [Commits](https://github.com/pion/sdp/compare/v3.0.17...v3.0.18) --- updated-dependencies: - dependency-name: github.com/pion/sdp/v3 dependency-version: 3.0.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 38e70ca..c6c3f2e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.2.0 - github.com/pion/sdp/v3 v3.0.17 + github.com/pion/sdp/v3 v3.0.18 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 5950de4..74e7223 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo= -github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= +github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= +github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= From 988ba0e8fd1bb2908ae42c0b4edf5c37e22f94f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 20:42:33 +0000 Subject: [PATCH 523/549] Bump github.com/pion/ice/v4 from 4.2.0 to 4.2.1 Bumps [github.com/pion/ice/v4](https://github.com/pion/ice) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/pion/ice/releases) - [Commits](https://github.com/pion/ice/compare/v4.2.0...v4.2.1) --- updated-dependencies: - dependency-name: github.com/pion/ice/v4 dependency-version: 4.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c6c3f2e..23a075e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/nats-io/nats.go v1.48.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 - github.com/pion/ice/v4 v4.2.0 + github.com/pion/ice/v4 v4.2.1 github.com/pion/sdp/v3 v3.0.18 github.com/pquerna/cachecontrol v0.2.0 github.com/prometheus/client_golang v1.23.2 @@ -57,7 +57,7 @@ require ( github.com/nats-io/jwt/v2 v2.8.0 // indirect github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pion/dtls/v3 v3.1.1 // indirect + github.com/pion/dtls/v3 v3.1.2 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index 74e7223..3d310e8 100644 --- a/go.sum +++ b/go.sum @@ -86,10 +86,10 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pion/dtls/v3 v3.1.1 h1:wSLMam9Kf7DL1A74hnqRvEb9OT+aXPAsQ5VS+BdXOJ0= -github.com/pion/dtls/v3 v3.1.1/go.mod h1:7FGvVYpHsUV6+aywaFpG7aE4Vz8nBOx74odPRFue6cI= -github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw= -github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4= +github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= +github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= +github.com/pion/ice/v4 v4.2.1 h1:XPRYXaLiFq3LFDG7a7bMrmr3mFr27G/gtXN3v/TVfxY= +github.com/pion/ice/v4 v4.2.1/go.mod h1:2quLV1S5v1tAx3VvAJaH//KGitRXvo4RKlX6D3tnN+c= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= From 6da684846d40633648e74bc70fd81403ac0ef24a Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 26 Feb 2026 07:51:10 +0100 Subject: [PATCH 524/549] Update "go.opentelemetry.io/otel/sdk" to fix "GO-2026-4394". --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 23a075e..8f421d6 100644 --- a/go.mod +++ b/go.mod @@ -79,12 +79,12 @@ require ( go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/go.sum b/go.sum index 3d310e8..216dd49 100644 --- a/go.sum +++ b/go.sum @@ -157,20 +157,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= From 4468b9234f07db93e1002024e850a5d9d58a2aaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 07:01:25 +0000 Subject: [PATCH 525/549] Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0 Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.48.0 to 1.49.0. - [Release notes](https://github.com/nats-io/nats.go/releases) - [Commits](https://github.com/nats-io/nats.go/compare/v1.48.0...v1.49.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats.go dependency-version: 1.49.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8f421d6..dd92518 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 github.com/nats-io/nats-server/v2 v2.12.4 - github.com/nats-io/nats.go v1.48.0 + github.com/nats-io/nats.go v1.49.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/ice/v4 v4.2.1 diff --git a/go.sum b/go.sum index 216dd49..4de5da8 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts= github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg= -github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= -github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE= +github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw= github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc= github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= From af250e4813b82170ab3717f0ceee63b0ca54072d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:42:32 +0000 Subject: [PATCH 526/549] Bump the artifacts group with 2 updates Bumps the artifacts group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 6 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) Updates `actions/download-artifact` from 7 to 8 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts ... Signed-off-by: dependabot[bot] --- .github/workflows/tarball.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index 1e18455..5a19f10 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -39,7 +39,7 @@ jobs: make tarball - name: Upload tarball - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: tarball-${{ matrix.go-version }} path: nextcloud-spreed-signaling*.tar.gz @@ -58,7 +58,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Download tarball - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: tarball-${{ matrix.go-version }} @@ -103,7 +103,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Download tarball - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: tarball-${{ matrix.go-version }} From d3d164484820e9764bbac55d4c3a78d52b3151ed Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 4 Mar 2026 20:41:06 +0100 Subject: [PATCH 527/549] Bump module version to "v2" so versions 2.x can be imported by others. --- api/bandwidth.go | 2 +- api/signaling.go | 6 ++-- api/signaling_easyjson.go | 2 +- api/signaling_test.go | 4 +-- async/deferred_executor.go | 2 +- async/deferred_executor_test.go | 2 +- async/events/api.go | 4 +-- async/events/api_easyjson.go | 4 +-- async/events/async_events.go | 8 ++--- async/events/async_events_nats.go | 8 ++--- async/events/async_events_nats_test.go | 6 ++-- async/events/async_events_test.go | 12 +++---- async/events/test/events.go | 10 +++--- async/events/test/events_test.go | 8 ++--- async/throttle.go | 4 +-- async/throttle_stats_prometheus.go | 2 +- async/throttle_test.go | 4 +-- client/client.go | 10 +++--- client/client_test.go | 8 ++--- client/ip.go | 2 +- client/ip_test.go | 2 +- client/stats_prometheus.go | 2 +- cmd/client/main.go | 8 ++--- cmd/client/stats.go | 2 +- cmd/client/stats_test.go | 2 +- cmd/proxy/main.go | 6 ++-- cmd/proxy/proxy_client.go | 4 +-- cmd/proxy/proxy_remote.go | 8 ++--- cmd/proxy/proxy_remote_test.go | 2 +- cmd/proxy/proxy_server.go | 26 +++++++------- cmd/proxy/proxy_server_test.go | 16 ++++----- cmd/proxy/proxy_session.go | 8 ++--- cmd/proxy/proxy_testclient_test.go | 4 +-- cmd/proxy/proxy_tokens_etcd.go | 6 ++-- cmd/proxy/proxy_tokens_etcd_test.go | 4 +-- cmd/proxy/proxy_tokens_static.go | 4 +-- cmd/proxy/proxy_tokens_static_test.go | 4 +-- cmd/server/main.go | 24 ++++++------- config/config.go | 2 +- container/ip_list.go | 2 +- dns/monitor.go | 2 +- dns/monitor_test.go | 4 +-- dns/test/dns.go | 6 ++-- dns/test/dns_test.go | 2 +- etcd/api.go | 4 +-- etcd/api_easyjson.go | 2 +- etcd/client.go | 6 ++-- etcd/client_test.go | 8 ++--- etcd/test/etcd.go | 6 ++-- etcd/test/etcd_test.go | 2 +- geoip/maxmind.go | 2 +- geoip/maxmind_test.go | 2 +- geoip/overrides.go | 4 +-- geoip/overrides_test.go | 4 +-- go.mod | 2 +- grpc/client.go | 18 +++++----- grpc/client_stats_prometheus.go | 2 +- grpc/client_test.go | 14 ++++---- grpc/common.go | 4 +-- grpc/server.go | 10 +++--- grpc/server_id.go | 2 +- grpc/server_stats_prometheus.go | 2 +- grpc/server_test.go | 16 ++++----- grpc/test/client.go | 14 ++++---- grpc/test/client_test.go | 2 +- grpc/test/server.go | 14 ++++---- grpc/test/server_test.go | 8 ++--- log/test/log.go | 4 +-- nats/client.go | 4 +-- nats/loopback.go | 2 +- nats/loopback_test.go | 2 +- nats/native.go | 2 +- nats/native_test.go | 6 ++-- nats/test/server.go | 2 +- nats/test/server_test.go | 6 ++-- pool/http_client_pool_stats_prometheus.go | 2 +- proxy/api.go | 4 +-- proxy/api_easyjson.go | 4 +-- proxy/api_test.go | 8 ++--- security/certificate_reloader.go | 4 +-- security/certificate_reloader_test.go | 4 +-- security/internal/file_watcher.go | 2 +- security/internal/file_watcher_test.go | 4 +-- server/backend_server.go | 18 +++++----- server/backend_server_test.go | 20 +++++------ server/clientsession.go | 18 +++++----- server/clientsession_test.go | 12 +++---- server/federation.go | 6 ++-- server/federation_test.go | 6 ++-- server/grpc_remote_client.go | 10 +++--- server/hub.go | 30 ++++++++-------- server/hub_client.go | 6 ++-- server/hub_client_stats_prometheus.go | 2 +- server/hub_stats_prometheus.go | 2 +- server/hub_test.go | 34 +++++++++---------- server/hub_transient_data_test.go | 2 +- server/remotesession.go | 10 +++--- server/room.go | 14 ++++---- server/room_ping.go | 6 ++-- server/room_ping_test.go | 6 ++-- server/room_stats_prometheus.go | 2 +- server/room_test.go | 8 ++--- server/roomsessions.go | 2 +- server/roomsessions_builtin.go | 6 ++-- server/roomsessions_test.go | 6 ++-- server/session.go | 6 ++-- server/session_test.go | 2 +- server/stats_prometheus.go | 2 +- server/testclient_test.go | 6 ++-- server/testutils_test.go | 2 +- server/virtualsession.go | 14 ++++---- server/virtualsession_test.go | 4 +-- session/sessionid_codec.go | 2 +- session/sessionid_codec_test.go | 4 +-- sfu/common.go | 6 ++-- sfu/internal/settings.go | 4 +-- sfu/internal/stats_prometheus.go | 2 +- sfu/internal/stats_prometheus_test.go | 2 +- sfu/janus/client.go | 8 ++--- sfu/janus/events_handler.go | 10 +++--- sfu/janus/events_handler_test.go | 12 +++---- sfu/janus/janus.go | 18 +++++----- sfu/janus/janus/janus.go | 4 +-- sfu/janus/janus_test.go | 18 +++++----- sfu/janus/publisher.go | 10 +++--- sfu/janus/publisher_stats_counter.go | 2 +- sfu/janus/publisher_stats_counter_test.go | 2 +- sfu/janus/publisher_test.go | 8 ++--- sfu/janus/remote_publisher.go | 6 ++-- sfu/janus/remote_subscriber.go | 2 +- sfu/janus/stats_prometheus.go | 4 +-- sfu/janus/stream_selection.go | 4 +-- sfu/janus/stream_selection_test.go | 2 +- sfu/janus/subscriber.go | 8 ++--- sfu/janus/test/janus.go | 6 ++-- sfu/mock/mock.go | 6 ++-- sfu/proxy/config_etcd.go | 8 ++--- sfu/proxy/config_etcd_test.go | 4 +-- sfu/proxy/config_static.go | 8 ++--- sfu/proxy/config_static_test.go | 6 ++-- sfu/proxy/proxy.go | 26 +++++++------- sfu/proxy/proxy_test.go | 32 ++++++++--------- sfu/proxy/stats_prometheus.go | 4 +-- sfu/proxy/test/proxy.go | 20 +++++------ sfu/proxy/testserver/server.go | 10 +++--- sfu/test/sfu.go | 12 +++---- talk/api.go | 8 ++--- talk/api_easyjson.go | 6 ++-- talk/api_test.go | 2 +- talk/backend.go | 8 ++--- talk/backend_client.go | 6 ++-- talk/backend_client_stats_prometheus.go | 2 +- talk/backend_client_test.go | 6 ++-- talk/backend_configuration.go | 6 ++-- .../backend_configuration_stats_prometheus.go | 2 +- talk/backend_configuration_test.go | 6 ++-- talk/backend_stats_prometheus.go | 2 +- talk/backend_storage_etcd.go | 6 ++-- talk/backend_storage_etcd_test.go | 8 ++--- talk/backend_storage_static.go | 6 ++-- talk/capabilities.go | 6 ++-- talk/capabilities_test.go | 8 ++--- test/network.go | 2 +- 163 files changed, 549 insertions(+), 549 deletions(-) diff --git a/api/bandwidth.go b/api/bandwidth.go index ad5b3d9..52f66ae 100644 --- a/api/bandwidth.go +++ b/api/bandwidth.go @@ -26,7 +26,7 @@ import ( "math" "sync/atomic" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) var ( diff --git a/api/signaling.go b/api/signaling.go index b9e488a..8effa1a 100644 --- a/api/signaling.go +++ b/api/signaling.go @@ -36,9 +36,9 @@ import ( "github.com/pion/ice/v4" "github.com/pion/sdp/v3" - "github.com/strukturag/nextcloud-spreed-signaling/container" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) const ( diff --git a/api/signaling_easyjson.go b/api/signaling_easyjson.go index c385b66..3d069e7 100644 --- a/api/signaling_easyjson.go +++ b/api/signaling_easyjson.go @@ -8,7 +8,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" - geoip "github.com/strukturag/nextcloud-spreed-signaling/geoip" + geoip "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" time "time" ) diff --git a/api/signaling_test.go b/api/signaling_test.go index ed8c54c..b03faf4 100644 --- a/api/signaling_test.go +++ b/api/signaling_test.go @@ -32,8 +32,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/container" - "github.com/strukturag/nextcloud-spreed-signaling/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" ) func TestRoomSessionIds(t *testing.T) { diff --git a/async/deferred_executor.go b/async/deferred_executor.go index a251433..be04695 100644 --- a/async/deferred_executor.go +++ b/async/deferred_executor.go @@ -27,7 +27,7 @@ import ( "runtime/debug" "sync" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) // DeferredExecutor will asynchronously execute functions while maintaining diff --git a/async/deferred_executor_test.go b/async/deferred_executor_test.go index 778817d..7ebf8b8 100644 --- a/async/deferred_executor_test.go +++ b/async/deferred_executor_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/assert" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func TestDeferredExecutor_MultiClose(t *testing.T) { diff --git a/async/events/api.go b/async/events/api.go index d91049b..26bb6ea 100644 --- a/async/events/api.go +++ b/async/events/api.go @@ -26,8 +26,8 @@ import ( "fmt" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type AsyncMessage struct { diff --git a/async/events/api_easyjson.go b/async/events/api_easyjson.go index 42552c5..a20eccf 100644 --- a/async/events/api_easyjson.go +++ b/async/events/api_easyjson.go @@ -7,8 +7,8 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" - api "github.com/strukturag/nextcloud-spreed-signaling/api" - talk "github.com/strukturag/nextcloud-spreed-signaling/talk" + api "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + talk "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) // suppress unused package warning diff --git a/async/events/async_events.go b/async/events/async_events.go index 30efb9c..aec0baa 100644 --- a/async/events/async_events.go +++ b/async/events/async_events.go @@ -25,10 +25,10 @@ import ( "context" "errors" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "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/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) var ( diff --git a/async/events/async_events_nats.go b/async/events/async_events_nats.go index 86856c1..4cc776d 100644 --- a/async/events/async_events_nats.go +++ b/async/events/async_events_nats.go @@ -27,10 +27,10 @@ import ( "sync" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "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/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func GetSubjectForBackendRoomId(roomId string, backend *talk.Backend) string { diff --git a/async/events/async_events_nats_test.go b/async/events/async_events_nats_test.go index 82d0dda..45f28f6 100644 --- a/async/events/async_events_nats_test.go +++ b/async/events/async_events_nats_test.go @@ -24,9 +24,9 @@ package events import ( "testing" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func Benchmark_GetSubjectForSessionId(b *testing.B) { diff --git a/async/events/async_events_test.go b/async/events/async_events_test.go index 2b2f1a9..deb5f41 100644 --- a/async/events/async_events_test.go +++ b/async/events/async_events_test.go @@ -29,12 +29,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/nats" - natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/v2/nats/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type TestBackendRoomListener struct { diff --git a/async/events/test/events.go b/async/events/test/events.go index 0695478..e5d52d1 100644 --- a/async/events/test/events.go +++ b/async/events/test/events.go @@ -30,11 +30,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/nats" - natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/v2/nats/test" ) var ( diff --git a/async/events/test/events_test.go b/async/events/test/events_test.go index 658d72c..b353385 100644 --- a/async/events/test/events_test.go +++ b/async/events/test/events_test.go @@ -28,10 +28,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/nats" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type testListener struct { diff --git a/async/throttle.go b/async/throttle.go index 40dc693..7b1ebe5 100644 --- a/async/throttle.go +++ b/async/throttle.go @@ -29,8 +29,8 @@ import ( "sync" "time" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/async/throttle_stats_prometheus.go b/async/throttle_stats_prometheus.go index 2e04984..64c140b 100644 --- a/async/throttle_stats_prometheus.go +++ b/async/throttle_stats_prometheus.go @@ -24,7 +24,7 @@ package async import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/async/throttle_test.go b/async/throttle_test.go index 83e61da..97d1313 100644 --- a/async/throttle_test.go +++ b/async/throttle_test.go @@ -29,8 +29,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func newMemoryThrottlerForTest(t *testing.T) Throttler { diff --git a/client/client.go b/client/client.go index 796c9db..7f53bb0 100644 --- a/client/client.go +++ b/client/client.go @@ -37,11 +37,11 @@ import ( "github.com/gorilla/websocket" "github.com/mailru/easyjson" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" ) const ( diff --git a/client/client_test.go b/client/client_test.go index 6ec13a2..ef3aa2c 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -41,10 +41,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func TestCounterWriter(t *testing.T) { diff --git a/client/ip.go b/client/ip.go index d897278..5f73195 100644 --- a/client/ip.go +++ b/client/ip.go @@ -27,7 +27,7 @@ import ( "slices" "strings" - "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" ) var ( diff --git a/client/ip_test.go b/client/ip_test.go index f12b62e..fbe5358 100644 --- a/client/ip_test.go +++ b/client/ip_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" ) func TestGetRealUserIP(t *testing.T) { diff --git a/client/stats_prometheus.go b/client/stats_prometheus.go index 86ae928..2d263b3 100644 --- a/client/stats_prometheus.go +++ b/client/stats_prometheus.go @@ -24,7 +24,7 @@ package client import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/cmd/client/main.go b/cmd/client/main.go index 684b40a..f04d3db 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -47,10 +47,10 @@ import ( "github.com/mailru/easyjson/jlexer" "github.com/mailru/easyjson/jwriter" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) var ( diff --git a/cmd/client/stats.go b/cmd/client/stats.go index 63e4e8b..a7acc99 100644 --- a/cmd/client/stats.go +++ b/cmd/client/stats.go @@ -26,7 +26,7 @@ import ( "sync/atomic" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) type Stats struct { diff --git a/cmd/client/stats_test.go b/cmd/client/stats_test.go index 0bbafe6..bb3e492 100644 --- a/cmd/client/stats_test.go +++ b/cmd/client/stats_test.go @@ -26,7 +26,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) func TestStats(t *testing.T) { diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 2f406e6..7d1ecfc 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -37,9 +37,9 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + signalinglog "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) var ( diff --git a/cmd/proxy/proxy_client.go b/cmd/proxy/proxy_client.go index aec1f03..1a16670 100644 --- a/cmd/proxy/proxy_client.go +++ b/cmd/proxy/proxy_client.go @@ -28,8 +28,8 @@ import ( "github.com/gorilla/websocket" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/client" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/client" ) type ProxyClient struct { diff --git a/cmd/proxy/proxy_remote.go b/cmd/proxy/proxy_remote.go index 3e326c3..d6092f3 100644 --- a/cmd/proxy/proxy_remote.go +++ b/cmd/proxy/proxy_remote.go @@ -39,10 +39,10 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/gorilla/websocket" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" ) const ( diff --git a/cmd/proxy/proxy_remote_test.go b/cmd/proxy/proxy_remote_test.go index 8b4dd47..0b3f41b 100644 --- a/cmd/proxy/proxy_remote_test.go +++ b/cmd/proxy/proxy_remote_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" ) func (c *RemoteConnection) WaitForConnection(ctx context.Context) error { diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index 18bfb24..99d42b0 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -48,19 +48,19 @@ import ( "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/client" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/container" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" - "github.com/strukturag/nextcloud-spreed-signaling/session" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" - janusapi "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/client" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus" + janusapi "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) const ( diff --git a/cmd/proxy/proxy_server_test.go b/cmd/proxy/proxy_server_test.go index 174c2f9..305e9f3 100644 --- a/cmd/proxy/proxy_server_test.go +++ b/cmd/proxy/proxy_server_test.go @@ -46,14 +46,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/cmd/proxy/proxy_session.go b/cmd/proxy/proxy_session.go index 7bc7133..d5345b1 100644 --- a/cmd/proxy/proxy_session.go +++ b/cmd/proxy/proxy_session.go @@ -28,10 +28,10 @@ import ( "sync/atomic" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) const ( diff --git a/cmd/proxy/proxy_testclient_test.go b/cmd/proxy/proxy_testclient_test.go index 6304abc..0eae10a 100644 --- a/cmd/proxy/proxy_testclient_test.go +++ b/cmd/proxy/proxy_testclient_test.go @@ -35,8 +35,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" ) var ( diff --git a/cmd/proxy/proxy_tokens_etcd.go b/cmd/proxy/proxy_tokens_etcd.go index 22d22d9..84dc66a 100644 --- a/cmd/proxy/proxy_tokens_etcd.go +++ b/cmd/proxy/proxy_tokens_etcd.go @@ -33,9 +33,9 @@ import ( "github.com/dlintw/goconf" "github.com/golang-jwt/jwt/v5" - "github.com/strukturag/nextcloud-spreed-signaling/container" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/cmd/proxy/proxy_tokens_etcd_test.go b/cmd/proxy/proxy_tokens_etcd_test.go index aba5a5b..a278801 100644 --- a/cmd/proxy/proxy_tokens_etcd_test.go +++ b/cmd/proxy/proxy_tokens_etcd_test.go @@ -41,8 +41,8 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) var ( diff --git a/cmd/proxy/proxy_tokens_static.go b/cmd/proxy/proxy_tokens_static.go index baa9148..6e68509 100644 --- a/cmd/proxy/proxy_tokens_static.go +++ b/cmd/proxy/proxy_tokens_static.go @@ -30,8 +30,8 @@ import ( "github.com/dlintw/goconf" "github.com/golang-jwt/jwt/v5" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type tokensStatic struct { diff --git a/cmd/proxy/proxy_tokens_static_test.go b/cmd/proxy/proxy_tokens_static_test.go index 662174e..a7bc0fa 100644 --- a/cmd/proxy/proxy_tokens_static_test.go +++ b/cmd/proxy/proxy_tokens_static_test.go @@ -32,8 +32,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func TestStaticTokens(t *testing.T) { diff --git a/cmd/server/main.go b/cmd/server/main.go index 24a226b..cd93789 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -41,18 +41,18 @@ import ( "github.com/dlintw/goconf" "github.com/gorilla/mux" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/nats" - "github.com/strukturag/nextcloud-spreed-signaling/server" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + signalinglog "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/server" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/proxy" ) var ( diff --git a/config/config.go b/config/config.go index a8dd096..719cc67 100644 --- a/config/config.go +++ b/config/config.go @@ -27,7 +27,7 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) var ( diff --git a/container/ip_list.go b/container/ip_list.go index 6a592bf..ce6ed24 100644 --- a/container/ip_list.go +++ b/container/ip_list.go @@ -28,7 +28,7 @@ import ( "slices" "strings" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) type IPList struct { diff --git a/dns/monitor.go b/dns/monitor.go index 03d7021..05e458c 100644 --- a/dns/monitor.go +++ b/dns/monitor.go @@ -31,7 +31,7 @@ import ( "sync/atomic" "time" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/dns/monitor_test.go b/dns/monitor_test.go index 43f5a9e..d92868d 100644 --- a/dns/monitor_test.go +++ b/dns/monitor_test.go @@ -33,8 +33,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns/internal" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *internal.MockLookup) *Monitor { diff --git a/dns/test/dns.go b/dns/test/dns.go index a6aa729..dd71a99 100644 --- a/dns/test/dns.go +++ b/dns/test/dns.go @@ -27,9 +27,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/dns/internal" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) type MockLookup = internal.MockLookup diff --git a/dns/test/dns_test.go b/dns/test/dns_test.go index e67c734..c743c02 100644 --- a/dns/test/dns_test.go +++ b/dns/test/dns_test.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" ) func TestDnsMonitor(t *testing.T) { diff --git a/etcd/api.go b/etcd/api.go index 557269a..b93c38f 100644 --- a/etcd/api.go +++ b/etcd/api.go @@ -28,8 +28,8 @@ import ( "net/url" "slices" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" clientv3 "go.etcd.io/etcd/client/v3" ) diff --git a/etcd/api_easyjson.go b/etcd/api_easyjson.go index f580a59..96846a0 100644 --- a/etcd/api_easyjson.go +++ b/etcd/api_easyjson.go @@ -7,7 +7,7 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" - api "github.com/strukturag/nextcloud-spreed-signaling/api" + api "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) // suppress unused package warning diff --git a/etcd/client.go b/etcd/client.go index a9cbc93..1c60687 100644 --- a/etcd/client.go +++ b/etcd/client.go @@ -38,9 +38,9 @@ import ( "go.uber.org/zap/zapcore" "google.golang.org/grpc/connectivity" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) var ( diff --git a/etcd/client_test.go b/etcd/client_test.go index 7a6862a..f7bac1e 100644 --- a/etcd/client_test.go +++ b/etcd/client_test.go @@ -43,10 +43,10 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) const ( diff --git a/etcd/test/etcd.go b/etcd/test/etcd.go index 1cb8f52..e29499b 100644 --- a/etcd/test/etcd.go +++ b/etcd/test/etcd.go @@ -45,9 +45,9 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) var ( diff --git a/etcd/test/etcd_test.go b/etcd/test/etcd_test.go index 2278d68..652112e 100644 --- a/etcd/test/etcd_test.go +++ b/etcd/test/etcd_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/require" clientv3 "go.etcd.io/etcd/client/v3" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" ) var ( diff --git a/geoip/maxmind.go b/geoip/maxmind.go index d5f1c8c..52fdd02 100644 --- a/geoip/maxmind.go +++ b/geoip/maxmind.go @@ -37,7 +37,7 @@ import ( "github.com/oschwald/maxminddb-golang" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) var ( diff --git a/geoip/maxmind_test.go b/geoip/maxmind_test.go index c73c450..ba6b139 100644 --- a/geoip/maxmind_test.go +++ b/geoip/maxmind_test.go @@ -36,7 +36,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func testLookupReader(t *testing.T, reader *Lookup) { diff --git a/geoip/overrides.go b/geoip/overrides.go index a15faa7..fd522f2 100644 --- a/geoip/overrides.go +++ b/geoip/overrides.go @@ -30,8 +30,8 @@ import ( "sync/atomic" "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type Overrides map[*net.IPNet]Country diff --git a/geoip/overrides_test.go b/geoip/overrides_test.go index bb893a5..211eef4 100644 --- a/geoip/overrides_test.go +++ b/geoip/overrides_test.go @@ -30,8 +30,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func mustSucceed1[T any, A1 any](t *testing.T, f func(a1 A1) (T, bool), a1 A1) T { diff --git a/go.mod b/go.mod index dd92518..3352de7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/strukturag/nextcloud-spreed-signaling +module github.com/strukturag/nextcloud-spreed-signaling/v2 go 1.25.0 diff --git a/grpc/client.go b/grpc/client.go index d913afe..083903f 100644 --- a/grpc/client.go +++ b/grpc/client.go @@ -43,15 +43,15 @@ import ( "google.golang.org/grpc/resolver" status "google.golang.org/grpc/status" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/grpc/client_stats_prometheus.go b/grpc/client_stats_prometheus.go index 02f791f..8f7070e 100644 --- a/grpc/client_stats_prometheus.go +++ b/grpc/client_stats_prometheus.go @@ -24,7 +24,7 @@ package grpc import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/grpc/client_test.go b/grpc/client_test.go index c17c972..6f6680c 100644 --- a/grpc/client_test.go +++ b/grpc/client_test.go @@ -32,13 +32,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dnstest.MockLookup) (*Clients, *dns.Monitor) { diff --git a/grpc/common.go b/grpc/common.go index 1464932..085b13f 100644 --- a/grpc/common.go +++ b/grpc/common.go @@ -33,8 +33,8 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/security" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/security" ) type reloadableCredentials struct { diff --git a/grpc/server.go b/grpc/server.go index f1b2863..1a99f8c 100644 --- a/grpc/server.go +++ b/grpc/server.go @@ -35,11 +35,11 @@ import ( "google.golang.org/grpc/credentials" status "google.golang.org/grpc/status" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func init() { diff --git a/grpc/server_id.go b/grpc/server_id.go index 8198f52..216ea6c 100644 --- a/grpc/server_id.go +++ b/grpc/server_id.go @@ -27,7 +27,7 @@ import ( "fmt" "os" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) var ( diff --git a/grpc/server_stats_prometheus.go b/grpc/server_stats_prometheus.go index e04b0e6..2602fd9 100644 --- a/grpc/server_stats_prometheus.go +++ b/grpc/server_stats_prometheus.go @@ -24,7 +24,7 @@ package grpc import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/grpc/server_test.go b/grpc/server_test.go index 273c261..ebec287 100644 --- a/grpc/server_test.go +++ b/grpc/server_test.go @@ -46,14 +46,14 @@ import ( "google.golang.org/grpc/metadata" status "google.golang.org/grpc/status" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) type CertificateReloadWaiter interface { diff --git a/grpc/test/client.go b/grpc/test/client.go index 69c5bfc..fcb22f9 100644 --- a/grpc/test/client.go +++ b/grpc/test/client.go @@ -29,13 +29,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func NewClientsForTestWithConfig(t *testing.T, config *goconf.ConfigFile, etcdClient etcd.Client, lookup *dnstest.MockLookup) (*grpc.Clients, *dns.Monitor) { diff --git a/grpc/test/client_test.go b/grpc/test/client_test.go index cc9391c..5f5c7f2 100644 --- a/grpc/test/client_test.go +++ b/grpc/test/client_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" ) func TestClientsWithEtcd(t *testing.T) { diff --git a/grpc/test/server.go b/grpc/test/server.go index ef67170..edb55cb 100644 --- a/grpc/test/server.go +++ b/grpc/test/server.go @@ -33,13 +33,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) func NewServerForTestWithConfig(t *testing.T, config *goconf.ConfigFile) (server *grpc.Server, addr string) { diff --git a/grpc/test/server_test.go b/grpc/test/server_test.go index 323334e..03cf00a 100644 --- a/grpc/test/server_test.go +++ b/grpc/test/server_test.go @@ -30,10 +30,10 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type emptyReceiver struct { diff --git a/log/test/log.go b/log/test/log.go index fa013c7..75b539f 100644 --- a/log/test/log.go +++ b/log/test/log.go @@ -25,8 +25,8 @@ import ( stdlog "log" "testing" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) var ( diff --git a/nats/client.go b/nats/client.go index 12dd75f..027d691 100644 --- a/nats/client.go +++ b/nats/client.go @@ -32,8 +32,8 @@ import ( "time" "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/nats/loopback.go b/nats/loopback.go index ad0fa7a..a0c83ef 100644 --- a/nats/loopback.go +++ b/nats/loopback.go @@ -30,7 +30,7 @@ import ( "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type LoopbackClient struct { diff --git a/nats/loopback_test.go b/nats/loopback_test.go index 968a29a..f839e32 100644 --- a/nats/loopback_test.go +++ b/nats/loopback_test.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func CreateLoopbackClientForTest(t *testing.T) Client { diff --git a/nats/native.go b/nats/native.go index fdf36a7..49b78f0 100644 --- a/nats/native.go +++ b/nats/native.go @@ -28,7 +28,7 @@ import ( "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type NativeClient struct { diff --git a/nats/native_test.go b/nats/native_test.go index 6d3b0d4..6a1eeeb 100644 --- a/nats/native_test.go +++ b/nats/native_test.go @@ -34,9 +34,9 @@ import ( natsservertest "github.com/nats-io/nats-server/v2/test" "github.com/nats-io/nats.go" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) func StartLocalServer(t *testing.T) (*server.Server, int) { diff --git a/nats/test/server.go b/nats/test/server.go index 54faea4..6daff42 100644 --- a/nats/test/server.go +++ b/nats/test/server.go @@ -30,7 +30,7 @@ import ( "github.com/nats-io/nats-server/v2/test" "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" ) func StartLocalServer(t *testing.T) (*server.Server, int) { diff --git a/nats/test/server_test.go b/nats/test/server_test.go index 5855307..190d2b7 100644 --- a/nats/test/server_test.go +++ b/nats/test/server_test.go @@ -29,9 +29,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" ) func TestLocalServer(t *testing.T) { diff --git a/pool/http_client_pool_stats_prometheus.go b/pool/http_client_pool_stats_prometheus.go index 8d2c3b4..45a5fe6 100644 --- a/pool/http_client_pool_stats_prometheus.go +++ b/pool/http_client_pool_stats_prometheus.go @@ -24,7 +24,7 @@ package pool import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/proxy/api.go b/proxy/api.go index 1cfad5d..3663460 100644 --- a/proxy/api.go +++ b/proxy/api.go @@ -29,8 +29,8 @@ import ( "github.com/golang-jwt/jwt/v5" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) type ClientMessage struct { diff --git a/proxy/api_easyjson.go b/proxy/api_easyjson.go index 820e366..d09bd52 100644 --- a/proxy/api_easyjson.go +++ b/proxy/api_easyjson.go @@ -8,8 +8,8 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" - api "github.com/strukturag/nextcloud-spreed-signaling/api" - sfu "github.com/strukturag/nextcloud-spreed-signaling/sfu" + api "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + sfu "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) // suppress unused package warning diff --git a/proxy/api_test.go b/proxy/api_test.go index 7d5b1ca..873ce32 100644 --- a/proxy/api_test.go +++ b/proxy/api_test.go @@ -26,10 +26,10 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) func TestValidate(t *testing.T) { diff --git a/security/certificate_reloader.go b/security/certificate_reloader.go index fde5185..fa14cdf 100644 --- a/security/certificate_reloader.go +++ b/security/certificate_reloader.go @@ -29,8 +29,8 @@ import ( "sync/atomic" "testing" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/security/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/security/internal" ) type CertificateReloader struct { diff --git a/security/certificate_reloader_test.go b/security/certificate_reloader_test.go index f8c61b0..b74d780 100644 --- a/security/certificate_reloader_test.go +++ b/security/certificate_reloader_test.go @@ -32,8 +32,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) type withReloadCounter interface { diff --git a/security/internal/file_watcher.go b/security/internal/file_watcher.go index 338c472..e1d48db 100644 --- a/security/internal/file_watcher.go +++ b/security/internal/file_watcher.go @@ -33,7 +33,7 @@ import ( "github.com/fsnotify/fsnotify" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/security/internal/file_watcher_test.go b/security/internal/file_watcher_test.go index 614ca4a..7e515c7 100644 --- a/security/internal/file_watcher_test.go +++ b/security/internal/file_watcher_test.go @@ -30,8 +30,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) var ( diff --git a/server/backend_server.go b/server/backend_server.go index 231ccb0..67c549a 100644 --- a/server/backend_server.go +++ b/server/backend_server.go @@ -48,15 +48,15 @@ import ( "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "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/pool" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/server/backend_server_test.go b/server/backend_server_test.go index 8bc63a1..80338bd 100644 --- a/server/backend_server_test.go +++ b/server/backend_server_test.go @@ -46,16 +46,16 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - eventstest "github.com/strukturag/nextcloud-spreed-signaling/async/events/test" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/nats" - natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + eventstest "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events/test" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + natstest "github.com/strukturag/nextcloud-spreed-signaling/v2/nats/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) var ( diff --git a/server/clientsession.go b/server/clientsession.go index 10f1145..e087366 100644 --- a/server/clientsession.go +++ b/server/clientsession.go @@ -35,15 +35,15 @@ import ( "github.com/pion/sdp/v3" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "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/session" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) var ( diff --git a/server/clientsession_test.go b/server/clientsession_test.go index 5c2def2..6152a71 100644 --- a/server/clientsession_test.go +++ b/server/clientsession_test.go @@ -32,12 +32,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func TestBandwidth_Client(t *testing.T) { diff --git a/server/federation.go b/server/federation.go index a80beed..0c3dbb7 100644 --- a/server/federation.go +++ b/server/federation.go @@ -37,9 +37,9 @@ import ( "github.com/gorilla/websocket" "github.com/mailru/easyjson" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/server/federation_test.go b/server/federation_test.go index 7861480..5c92364 100644 --- a/server/federation_test.go +++ b/server/federation_test.go @@ -32,9 +32,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/test" ) func Test_FederationInvalidToken(t *testing.T) { diff --git a/server/grpc_remote_client.go b/server/grpc_remote_client.go index 94281e0..a81fdd2 100644 --- a/server/grpc_remote_client.go +++ b/server/grpc_remote_client.go @@ -33,11 +33,11 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/client" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/client" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/server/hub.go b/server/hub.go index c243b6a..b97546e 100644 --- a/server/hub.go +++ b/server/hub.go @@ -53,21 +53,21 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/client" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/container" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/session" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/client" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) var ( diff --git a/server/hub_client.go b/server/hub_client.go index 6cf596a..3a7b973 100644 --- a/server/hub_client.go +++ b/server/hub_client.go @@ -29,9 +29,9 @@ import ( "github.com/gorilla/websocket" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/client" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/client" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" ) var ( diff --git a/server/hub_client_stats_prometheus.go b/server/hub_client_stats_prometheus.go index 85fec5c..518548c 100644 --- a/server/hub_client_stats_prometheus.go +++ b/server/hub_client_stats_prometheus.go @@ -24,7 +24,7 @@ package server import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/server/hub_stats_prometheus.go b/server/hub_stats_prometheus.go index 2297a10..9082dd9 100644 --- a/server/hub_stats_prometheus.go +++ b/server/hub_stats_prometheus.go @@ -24,7 +24,7 @@ package server import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/server/hub_test.go b/server/hub_test.go index 50b9505..ed57b89 100644 --- a/server/hub_test.go +++ b/server/hub_test.go @@ -52,23 +52,23 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - eventstest "github.com/strukturag/nextcloud-spreed-signaling/async/events/test" - "github.com/strukturag/nextcloud-spreed-signaling/container" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - natstest "github.com/strukturag/nextcloud-spreed-signaling/nats/test" - "github.com/strukturag/nextcloud-spreed-signaling/session" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" - "github.com/strukturag/nextcloud-spreed-signaling/talk" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + eventstest "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + natstest "github.com/strukturag/nextcloud-spreed-signaling/v2/nats/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + sfutest "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) const ( diff --git a/server/hub_transient_data_test.go b/server/hub_transient_data_test.go index f029e2a..ab6e4dd 100644 --- a/server/hub_transient_data_test.go +++ b/server/hub_transient_data_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) func Test_TransientMessages(t *testing.T) { diff --git a/server/remotesession.go b/server/remotesession.go index 92cbcdc..9bd17bf 100644 --- a/server/remotesession.go +++ b/server/remotesession.go @@ -28,11 +28,11 @@ import ( "sync/atomic" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/client" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/client" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type RemoteSession struct { diff --git a/server/room.go b/server/room.go index b6601b1..d4179c2 100644 --- a/server/room.go +++ b/server/room.go @@ -35,13 +35,13 @@ import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "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/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/server/room_ping.go b/server/room_ping.go index 05d87c9..c657364 100644 --- a/server/room_ping.go +++ b/server/room_ping.go @@ -28,9 +28,9 @@ import ( "sync" "time" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type pingEntries struct { diff --git a/server/room_ping_test.go b/server/room_ping_test.go index 5667e2c..9572895 100644 --- a/server/room_ping_test.go +++ b/server/room_ping_test.go @@ -31,9 +31,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func NewRoomPingForTest(ctx context.Context, t *testing.T) (*url.URL, *RoomPing) { diff --git a/server/room_stats_prometheus.go b/server/room_stats_prometheus.go index 973bdc1..098ee25 100644 --- a/server/room_stats_prometheus.go +++ b/server/room_stats_prometheus.go @@ -24,7 +24,7 @@ package server import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/server/room_test.go b/server/room_test.go index 9f18fc4..3944512 100644 --- a/server/room_test.go +++ b/server/room_test.go @@ -35,10 +35,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func TestRoom_InCallFlag(t *testing.T) { diff --git a/server/roomsessions.go b/server/roomsessions.go index 3211a35..9692655 100644 --- a/server/roomsessions.go +++ b/server/roomsessions.go @@ -25,7 +25,7 @@ import ( "context" "errors" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) var ( diff --git a/server/roomsessions_builtin.go b/server/roomsessions_builtin.go index 9154d4c..fd1adcd 100644 --- a/server/roomsessions_builtin.go +++ b/server/roomsessions_builtin.go @@ -27,9 +27,9 @@ import ( "sync" "sync/atomic" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type BuiltinRoomSessions struct { diff --git a/server/roomsessions_test.go b/server/roomsessions_test.go index b7f2b5c..d8ee8b0 100644 --- a/server/roomsessions_test.go +++ b/server/roomsessions_test.go @@ -31,9 +31,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/session" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type DummySession struct { diff --git a/server/session.go b/server/session.go index 5cc7eda..0a76ed3 100644 --- a/server/session.go +++ b/server/session.go @@ -28,9 +28,9 @@ import ( "sync" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/session" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) type Session interface { diff --git a/server/session_test.go b/server/session_test.go index 7f4aae7..5cc677e 100644 --- a/server/session_test.go +++ b/server/session_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) func assertSessionHasPermission(t *testing.T, session Session, permission api.Permission) { diff --git a/server/stats_prometheus.go b/server/stats_prometheus.go index e5e41e8..9b6b973 100644 --- a/server/stats_prometheus.go +++ b/server/stats_prometheus.go @@ -24,7 +24,7 @@ package server import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/server/testclient_test.go b/server/testclient_test.go index 453d8e7..62d55cd 100644 --- a/server/testclient_test.go +++ b/server/testclient_test.go @@ -42,9 +42,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" ) var ( diff --git a/server/testutils_test.go b/server/testutils_test.go index 62379f5..6360d06 100644 --- a/server/testutils_test.go +++ b/server/testutils_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) func WaitForUsersJoined(ctx context.Context, t *testing.T, client1 *TestClient, hello1 *api.ServerMessage, client2 *TestClient, hello2 *api.ServerMessage) { diff --git a/server/virtualsession.go b/server/virtualsession.go index 776d4f7..bb484a9 100644 --- a/server/virtualsession.go +++ b/server/virtualsession.go @@ -29,13 +29,13 @@ import ( "sync/atomic" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async/events" - "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/session" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/nats" + "github.com/strukturag/nextcloud-spreed-signaling/v2/session" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/server/virtualsession_test.go b/server/virtualsession_test.go index cc943a2..85181bb 100644 --- a/server/virtualsession_test.go +++ b/server/virtualsession_test.go @@ -30,8 +30,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func TestVirtualSession(t *testing.T) { diff --git a/session/sessionid_codec.go b/session/sessionid_codec.go index ed3cf58..3e2c576 100644 --- a/session/sessionid_codec.go +++ b/session/sessionid_codec.go @@ -38,7 +38,7 @@ import ( "google.golang.org/protobuf/proto" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) const ( diff --git a/session/sessionid_codec_test.go b/session/sessionid_codec_test.go index f932bcf..3e5fb4b 100644 --- a/session/sessionid_codec_test.go +++ b/session/sessionid_codec_test.go @@ -28,8 +28,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) func TestReverseSessionId(t *testing.T) { diff --git a/sfu/common.go b/sfu/common.go index ce92e16..00aed84 100644 --- a/sfu/common.go +++ b/sfu/common.go @@ -30,9 +30,9 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/sfu/internal/settings.go b/sfu/internal/settings.go index 7596105..1dc2ebd 100644 --- a/sfu/internal/settings.go +++ b/sfu/internal/settings.go @@ -27,8 +27,8 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) var ( diff --git a/sfu/internal/stats_prometheus.go b/sfu/internal/stats_prometheus.go index 2424296..a79448a 100644 --- a/sfu/internal/stats_prometheus.go +++ b/sfu/internal/stats_prometheus.go @@ -23,7 +23,7 @@ package internal import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/sfu/internal/stats_prometheus_test.go b/sfu/internal/stats_prometheus_test.go index e10bdc6..cf4dd5b 100644 --- a/sfu/internal/stats_prometheus_test.go +++ b/sfu/internal/stats_prometheus_test.go @@ -24,7 +24,7 @@ package internal import ( "testing" - "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics/test" ) func TestCommonMcuStats(t *testing.T) { diff --git a/sfu/janus/client.go b/sfu/janus/client.go index b9b4501..04b3593 100644 --- a/sfu/janus/client.go +++ b/sfu/janus/client.go @@ -28,10 +28,10 @@ import ( "sync" "sync/atomic" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) type janusClient struct { diff --git a/sfu/janus/events_handler.go b/sfu/janus/events_handler.go index 9a326ba..fed5139 100644 --- a/sfu/janus/events_handler.go +++ b/sfu/janus/events_handler.go @@ -35,11 +35,11 @@ import ( "github.com/gorilla/websocket" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/pool" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) const ( diff --git a/sfu/janus/events_handler_test.go b/sfu/janus/events_handler_test.go index b38bb6e..346aaa2 100644 --- a/sfu/janus/events_handler_test.go +++ b/sfu/janus/events_handler_test.go @@ -38,12 +38,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - sfutest "github.com/strukturag/nextcloud-spreed-signaling/sfu/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + metricstest "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + sfutest "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/test" ) type TestJanusEventsServerHandler struct { diff --git a/sfu/janus/janus.go b/sfu/janus/janus.go index 57ac60e..6f1baac 100644 --- a/sfu/janus/janus.go +++ b/sfu/janus/janus.go @@ -33,15 +33,15 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "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/sfu" - sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/container" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/sfu/janus/janus/janus.go b/sfu/janus/janus/janus.go index f70ab8c..bf45b90 100644 --- a/sfu/janus/janus/janus.go +++ b/sfu/janus/janus/janus.go @@ -44,8 +44,8 @@ import ( "github.com/gorilla/websocket" "github.com/notedit/janus-go" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) const ( diff --git a/sfu/janus/janus_test.go b/sfu/janus/janus_test.go index 54c7c9c..6852e66 100644 --- a/sfu/janus/janus_test.go +++ b/sfu/janus/janus_test.go @@ -34,15 +34,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" - janustest "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + metricstest "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" + janustest "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/test" ) const ( diff --git a/sfu/janus/publisher.go b/sfu/janus/publisher.go index 9a17eb6..a7b2fab 100644 --- a/sfu/janus/publisher.go +++ b/sfu/janus/publisher.go @@ -31,11 +31,11 @@ import ( "github.com/pion/sdp/v3" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) const ( diff --git a/sfu/janus/publisher_stats_counter.go b/sfu/janus/publisher_stats_counter.go index 8209253..57ebeed 100644 --- a/sfu/janus/publisher_stats_counter.go +++ b/sfu/janus/publisher_stats_counter.go @@ -24,7 +24,7 @@ package janus import ( "sync" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) type publisherStatsCounterStats interface { diff --git a/sfu/janus/publisher_stats_counter_test.go b/sfu/janus/publisher_stats_counter_test.go index 123d710..3356440 100644 --- a/sfu/janus/publisher_stats_counter_test.go +++ b/sfu/janus/publisher_stats_counter_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) type mockPublisherStats struct { diff --git a/sfu/janus/publisher_test.go b/sfu/janus/publisher_test.go index 52b1558..6dd5078 100644 --- a/sfu/janus/publisher_test.go +++ b/sfu/janus/publisher_test.go @@ -29,10 +29,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" - janustest "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" + janustest "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/test" ) func TestGetFmtpValueH264(t *testing.T) { diff --git a/sfu/janus/remote_publisher.go b/sfu/janus/remote_publisher.go index ae96dca..6276d2e 100644 --- a/sfu/janus/remote_publisher.go +++ b/sfu/janus/remote_publisher.go @@ -25,9 +25,9 @@ import ( "context" "sync/atomic" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) type janusRemotePublisher struct { diff --git a/sfu/janus/remote_subscriber.go b/sfu/janus/remote_subscriber.go index 60004e9..a8c12e1 100644 --- a/sfu/janus/remote_subscriber.go +++ b/sfu/janus/remote_subscriber.go @@ -26,7 +26,7 @@ import ( "strconv" "sync/atomic" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) type janusRemoteSubscriber struct { diff --git a/sfu/janus/stats_prometheus.go b/sfu/janus/stats_prometheus.go index a9c8300..312f11d 100644 --- a/sfu/janus/stats_prometheus.go +++ b/sfu/janus/stats_prometheus.go @@ -24,8 +24,8 @@ package janus import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/internal" ) var ( diff --git a/sfu/janus/stream_selection.go b/sfu/janus/stream_selection.go index aa7ad10..f6430f7 100644 --- a/sfu/janus/stream_selection.go +++ b/sfu/janus/stream_selection.go @@ -24,8 +24,8 @@ package janus import ( "fmt" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) type streamSelection struct { diff --git a/sfu/janus/stream_selection_test.go b/sfu/janus/stream_selection_test.go index 8d5463d..cc0ed1d 100644 --- a/sfu/janus/stream_selection_test.go +++ b/sfu/janus/stream_selection_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" ) func TestStreamSelection(t *testing.T) { diff --git a/sfu/janus/subscriber.go b/sfu/janus/subscriber.go index d581d40..e74c5aa 100644 --- a/sfu/janus/subscriber.go +++ b/sfu/janus/subscriber.go @@ -26,10 +26,10 @@ import ( "fmt" "strconv" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) type janusSubscriber struct { diff --git a/sfu/janus/test/janus.go b/sfu/janus/test/janus.go index 200ff71..319ea23 100644 --- a/sfu/janus/test/janus.go +++ b/sfu/janus/test/janus.go @@ -34,9 +34,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/janus/janus" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/janus/janus" ) const ( diff --git a/sfu/mock/mock.go b/sfu/mock/mock.go index cc685eb..0f47340 100644 --- a/sfu/mock/mock.go +++ b/sfu/mock/mock.go @@ -22,9 +22,9 @@ package mock import ( - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" ) type Listener struct { diff --git a/sfu/proxy/config_etcd.go b/sfu/proxy/config_etcd.go index 708ac36..2714231 100644 --- a/sfu/proxy/config_etcd.go +++ b/sfu/proxy/config_etcd.go @@ -32,10 +32,10 @@ import ( "github.com/dlintw/goconf" clientv3 "go.etcd.io/etcd/client/v3" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" ) const ( diff --git a/sfu/proxy/config_etcd_test.go b/sfu/proxy/config_etcd_test.go index 0eea860..553fb52 100644 --- a/sfu/proxy/config_etcd_test.go +++ b/sfu/proxy/config_etcd_test.go @@ -30,8 +30,8 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) type TestProxyInformationEtcd struct { diff --git a/sfu/proxy/config_static.go b/sfu/proxy/config_static.go index 341fa7f..1db4ad3 100644 --- a/sfu/proxy/config_static.go +++ b/sfu/proxy/config_static.go @@ -30,10 +30,10 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type ipList struct { diff --git a/sfu/proxy/config_static_test.go b/sfu/proxy/config_static_test.go index 4895e82..50e5b74 100644 --- a/sfu/proxy/config_static_test.go +++ b/sfu/proxy/config_static_test.go @@ -30,9 +30,9 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" ) func newProxyConfigStatic(t *testing.T, proxy McuProxy, dnsDiscovery bool, lookup *dnstest.MockLookup, urls ...string) (Config, *dns.Monitor) { diff --git a/sfu/proxy/proxy.go b/sfu/proxy/proxy.go index 9170ca8..12ea56f 100644 --- a/sfu/proxy/proxy.go +++ b/sfu/proxy/proxy.go @@ -45,19 +45,19 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/gorilla/websocket" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/dns" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + sfuinternal "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) const ( diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go index 47c225f..932177e 100644 --- a/sfu/proxy/proxy_test.go +++ b/sfu/proxy/proxy_test.go @@ -41,22 +41,22 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/async" - dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/grpc" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - metricstest "github.com/strukturag/nextcloud-spreed-signaling/metrics/test" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + metricstest "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/proxy/testserver" ) const ( diff --git a/sfu/proxy/stats_prometheus.go b/sfu/proxy/stats_prometheus.go index 5889959..1984f1c 100644 --- a/sfu/proxy/stats_prometheus.go +++ b/sfu/proxy/stats_prometheus.go @@ -25,8 +25,8 @@ package proxy import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/internal" ) var ( diff --git a/sfu/proxy/test/proxy.go b/sfu/proxy/test/proxy.go index 809c673..c3d44ec 100644 --- a/sfu/proxy/test/proxy.go +++ b/sfu/proxy/test/proxy.go @@ -36,16 +36,16 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - dnstest "github.com/strukturag/nextcloud-spreed-signaling/dns/test" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - grpctest "github.com/strukturag/nextcloud-spreed-signaling/grpc/test" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy" - "github.com/strukturag/nextcloud-spreed-signaling/sfu/proxy/testserver" + dnstest "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + grpctest "github.com/strukturag/nextcloud-spreed-signaling/v2/grpc/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu/proxy/testserver" ) const ( diff --git a/sfu/proxy/testserver/server.go b/sfu/proxy/testserver/server.go index 7b2d6bd..c0c25e4 100644 --- a/sfu/proxy/testserver/server.go +++ b/sfu/proxy/testserver/server.go @@ -41,11 +41,11 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/proxy" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/proxy" ) const ( diff --git a/sfu/test/sfu.go b/sfu/test/sfu.go index dabd659..6b64edc 100644 --- a/sfu/test/sfu.go +++ b/sfu/test/sfu.go @@ -34,12 +34,12 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/mock" - "github.com/strukturag/nextcloud-spreed-signaling/sfu" - "github.com/strukturag/nextcloud-spreed-signaling/talk" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/mock" + "github.com/strukturag/nextcloud-spreed-signaling/v2/sfu" + "github.com/strukturag/nextcloud-spreed-signaling/v2/talk" ) var ( diff --git a/talk/api.go b/talk/api.go index d54d1b3..1bc0032 100644 --- a/talk/api.go +++ b/talk/api.go @@ -32,10 +32,10 @@ import ( "regexp" "time" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/geoip" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) const ( diff --git a/talk/api_easyjson.go b/talk/api_easyjson.go index 0fda7b6..2cf1a8d 100644 --- a/talk/api_easyjson.go +++ b/talk/api_easyjson.go @@ -7,9 +7,9 @@ import ( easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" - api "github.com/strukturag/nextcloud-spreed-signaling/api" - etcd "github.com/strukturag/nextcloud-spreed-signaling/etcd" - geoip "github.com/strukturag/nextcloud-spreed-signaling/geoip" + api "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + etcd "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + geoip "github.com/strukturag/nextcloud-spreed-signaling/v2/geoip" time "time" ) diff --git a/talk/api_test.go b/talk/api_test.go index 3bce351..01c4510 100644 --- a/talk/api_test.go +++ b/talk/api_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) func TestBackendChecksum(t *testing.T) { diff --git a/talk/backend.go b/talk/backend.go index 3a664f8..c9ef31c 100644 --- a/talk/backend.go +++ b/talk/backend.go @@ -31,10 +31,10 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) var ( diff --git a/talk/backend_client.go b/talk/backend_client.go index 8ca63da..16b9614 100644 --- a/talk/backend_client.go +++ b/talk/backend_client.go @@ -33,9 +33,9 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" ) var ( diff --git a/talk/backend_client_stats_prometheus.go b/talk/backend_client_stats_prometheus.go index cab22c5..8c07ee5 100644 --- a/talk/backend_client_stats_prometheus.go +++ b/talk/backend_client_stats_prometheus.go @@ -24,7 +24,7 @@ package talk import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/talk/backend_client_test.go b/talk/backend_client_test.go index 643be3f..8443c78 100644 --- a/talk/backend_client_test.go +++ b/talk/backend_client_test.go @@ -35,9 +35,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" ) func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { diff --git a/talk/backend_configuration.go b/talk/backend_configuration.go index 4234a3e..e76785e 100644 --- a/talk/backend_configuration.go +++ b/talk/backend_configuration.go @@ -30,9 +30,9 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/talk/backend_configuration_stats_prometheus.go b/talk/backend_configuration_stats_prometheus.go index 57bc049..9941db8 100644 --- a/talk/backend_configuration_stats_prometheus.go +++ b/talk/backend_configuration_stats_prometheus.go @@ -24,7 +24,7 @@ package talk import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/talk/backend_configuration_test.go b/talk/backend_configuration_test.go index c2c8b41..83f2f15 100644 --- a/talk/backend_configuration_test.go +++ b/talk/backend_configuration_test.go @@ -33,9 +33,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) var ( diff --git a/talk/backend_stats_prometheus.go b/talk/backend_stats_prometheus.go index 1deeb3b..f335df4 100644 --- a/talk/backend_stats_prometheus.go +++ b/talk/backend_stats_prometheus.go @@ -24,7 +24,7 @@ package talk import ( "github.com/prometheus/client_golang/prometheus" - "github.com/strukturag/nextcloud-spreed-signaling/metrics" + "github.com/strukturag/nextcloud-spreed-signaling/v2/metrics" ) var ( diff --git a/talk/backend_storage_etcd.go b/talk/backend_storage_etcd.go index 0799f9e..caf30af 100644 --- a/talk/backend_storage_etcd.go +++ b/talk/backend_storage_etcd.go @@ -34,9 +34,9 @@ import ( "github.com/dlintw/goconf" clientv3 "go.etcd.io/etcd/client/v3" - "github.com/strukturag/nextcloud-spreed-signaling/async" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/async" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) const ( diff --git a/talk/backend_storage_etcd_test.go b/talk/backend_storage_etcd_test.go index 3f5a2ab..b00feb0 100644 --- a/talk/backend_storage_etcd_test.go +++ b/talk/backend_storage_etcd_test.go @@ -27,10 +27,10 @@ import ( "github.com/dlintw/goconf" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/etcd" - etcdtest "github.com/strukturag/nextcloud-spreed-signaling/etcd/test" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" + etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/test" ) func (s *backendStorageEtcd) getWakeupChannelForTesting() <-chan struct{} { diff --git a/talk/backend_storage_static.go b/talk/backend_storage_static.go index 033faf1..b1ced2b 100644 --- a/talk/backend_storage_static.go +++ b/talk/backend_storage_static.go @@ -28,9 +28,9 @@ import ( "github.com/dlintw/goconf" - "github.com/strukturag/nextcloud-spreed-signaling/config" - "github.com/strukturag/nextcloud-spreed-signaling/internal" - "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/config" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" ) type backendStorageStatic struct { diff --git a/talk/capabilities.go b/talk/capabilities.go index 6a60493..4df1267 100644 --- a/talk/capabilities.go +++ b/talk/capabilities.go @@ -33,9 +33,9 @@ import ( "github.com/pquerna/cachecontrol/cacheobject" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" ) const ( diff --git a/talk/capabilities_test.go b/talk/capabilities_test.go index c9578f5..8e2ab17 100644 --- a/talk/capabilities_test.go +++ b/talk/capabilities_test.go @@ -41,10 +41,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/strukturag/nextcloud-spreed-signaling/api" - "github.com/strukturag/nextcloud-spreed-signaling/log" - logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test" - "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/v2/api" + "github.com/strukturag/nextcloud-spreed-signaling/v2/log" + logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test" + "github.com/strukturag/nextcloud-spreed-signaling/v2/pool" ) const ( diff --git a/test/network.go b/test/network.go index 6d7cc78..dfc4326 100644 --- a/test/network.go +++ b/test/network.go @@ -26,7 +26,7 @@ import ( "runtime" "syscall" - "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" ) func IsErrorAddressAlreadyInUse(err error) bool { From bdb862c017dd0dbf66785949a5f6947c3eacbab6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:42:34 +0000 Subject: [PATCH 528/549] Bump docker/login-action from 3 to 4 Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploydocker.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index bcf1629..98875ee 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -55,14 +55,14 @@ jobs: ${{ runner.os }}-buildx- - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Login to GHCR if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -70,7 +70,7 @@ jobs: - name: Login to quay.io if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: quay.io username: ${{ secrets.QUAY_IO_USERNAME }} @@ -134,14 +134,14 @@ jobs: ${{ runner.os }}-buildx- - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Login to GHCR if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -149,7 +149,7 @@ jobs: - name: Login to quay.io if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: quay.io username: ${{ secrets.QUAY_IO_USERNAME }} From 65ec614fcda679c018c69bdd3fc755150ed46cd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:42:39 +0000 Subject: [PATCH 529/549] Bump docker/setup-qemu-action from 3 to 4 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploydocker.yml | 4 ++-- .github/workflows/docker.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index bcf1629..82346e4 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Generate Docker metadata id: meta @@ -104,7 +104,7 @@ jobs: uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Generate Docker metadata id: meta diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 352ba27..6f8e242 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -55,7 +55,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 From 9771976acf751c4b03dab65a35dff1ea325e8892 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:42:41 +0000 Subject: [PATCH 530/549] Bump docker/metadata-action from 5 to 6 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploydocker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 7166469..287ead8 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -34,7 +34,7 @@ jobs: - name: Generate Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | strukturag/nextcloud-spreed-signaling @@ -108,7 +108,7 @@ jobs: - name: Generate Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | strukturag/nextcloud-spreed-signaling From ddb0cdd72fe89e4b3848882ce7e7b9d80ed8ee59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:42:47 +0000 Subject: [PATCH 531/549] Bump docker/setup-buildx-action from 3 to 4 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploydocker.yml | 4 ++-- .github/workflows/docker-janus.yml | 2 +- .github/workflows/docker.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 7166469..4e473ac 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -78,7 +78,7 @@ jobs: - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push id: docker_build @@ -157,7 +157,7 @@ jobs: - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push id: docker_build diff --git a/.github/workflows/docker-janus.yml b/.github/workflows/docker-janus.yml index a6b82d7..ea2262a 100644 --- a/.github/workflows/docker-janus.yml +++ b/.github/workflows/docker-janus.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build Docker image uses: docker/build-push-action@v6 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6f8e242..e7584ab 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build Docker image uses: docker/build-push-action@v6 @@ -58,7 +58,7 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build Docker image uses: docker/build-push-action@v6 From ebd28a1ffedc0ec3e9a9676df784f680fb99eb71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:42:42 +0000 Subject: [PATCH 532/549] Bump docker/build-push-action from 6 to 7 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploydocker.yml | 4 ++-- .github/workflows/docker-janus.yml | 2 +- .github/workflows/docker.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploydocker.yml b/.github/workflows/deploydocker.yml index 7166469..ba010cb 100644 --- a/.github/workflows/deploydocker.yml +++ b/.github/workflows/deploydocker.yml @@ -82,7 +82,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: ./docker/server/Dockerfile @@ -161,7 +161,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: ./docker/proxy/Dockerfile diff --git a/.github/workflows/docker-janus.yml b/.github/workflows/docker-janus.yml index a6b82d7..702b201 100644 --- a/.github/workflows/docker-janus.yml +++ b/.github/workflows/docker-janus.yml @@ -29,7 +29,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: docker/janus load: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6f8e242..6398f02 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -42,7 +42,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: docker/server/Dockerfile @@ -61,7 +61,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: docker/proxy/Dockerfile From a3afe9429dd36999257a1a25ac3319ba388f9175 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 9 Mar 2026 09:51:12 +0100 Subject: [PATCH 533/549] CI: Update paths for checking generated files. --- .github/workflows/generated.yml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/generated.yml b/.github/workflows/generated.yml index 1c08588..b649a23 100644 --- a/.github/workflows/generated.yml +++ b/.github/workflows/generated.yml @@ -5,20 +5,24 @@ on: branches: [ master ] paths: - '.github/workflows/generated.yml' - - 'api*.go' - - '*_easyjson.go' - - '*.pb.go' - - '*.proto' + - '**/api*.go' + - '**/*_easyjson.go' + - '**/*.pb.go' + - '**/*.proto' - 'go.*' + - 'api/signaling.go' + - 'talk/ocs.go' pull_request: branches: [ master ] paths: - '.github/workflows/generated.yml' - - 'api*.go' - - '*_easyjson.go' - - '*.pb.go' - - '*.proto' + - '**/api*.go' + - '**/*_easyjson.go' + - '**/*.pb.go' + - '**/*.proto' - 'go.*' + - 'api/signaling.go' + - 'talk/ocs.go' env: CODE_GENERATOR_NAME: struktur AG service user @@ -78,16 +82,15 @@ jobs: if [ "$CHECKOUT_SHA" != "${{github.event.pull_request.head.sha}}" ]; then echo "More changes since this commit ${{github.event.pull_request.head.sha}}, skipping" else - git add *_easyjson.go *.pb.go + git add --all CHANGES=$(git status --porcelain) if [ -z "$CHANGES" ]; then echo "No files have changed, no need to commit / push." else go mod tidy - git add go.* git config user.name "$CODE_GENERATOR_NAME" git config user.email "$CODE_GENERATOR_EMAIL" - git commit --author="$(git log -n 1 --pretty=format:%an) <$(git log -n 1 --pretty=format:%ae)>" -m "Update generated files from ${{github.event.pull_request.head.sha}}" + git commit --all --author="$(git log -n 1 --pretty=format:%an) <$(git log -n 1 --pretty=format:%ae)>" -m "Update generated files from ${{github.event.pull_request.head.sha}}" git push fi fi @@ -113,5 +116,5 @@ jobs: - name: Check generated files run: | - git add *.go - git diff --cached --exit-code *.go + git add --all + git diff --cached --exit-code From 10c1d25f20cc663f9dcdf9bc5edd5cbbc8fc23ed Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 9 Mar 2026 09:58:57 +0100 Subject: [PATCH 534/549] Update generated files. --- api/signaling_easyjson.go | 612 +++++++++++++++++------------------ async/events/api_easyjson.go | 36 +-- etcd/api_easyjson.go | 24 +- grpc/api_easyjson.go | 12 +- proxy/api_easyjson.go | 184 +++++------ talk/api_easyjson.go | 432 ++++++++++++------------- talk/ocs_easyjson.go | 36 +-- 7 files changed, 668 insertions(+), 668 deletions(-) diff --git a/api/signaling_easyjson.go b/api/signaling_easyjson.go index 3d069e7..877c6e8 100644 --- a/api/signaling_easyjson.go +++ b/api/signaling_easyjson.go @@ -20,7 +20,7 @@ var ( _ easyjson.Marshaler ) -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(in *jlexer.Lexer, out *WelcomeServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api(in *jlexer.Lexer, out *WelcomeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -83,7 +83,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(in *jle in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(out *jwriter.Writer, in WelcomeServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api(out *jwriter.Writer, in WelcomeServerMessage) { out.RawByte('{') first := true _ = first @@ -117,27 +117,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(out *jw // MarshalJSON supports json.Marshaler interface func (v WelcomeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v WelcomeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *WelcomeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *WelcomeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(in *jlexer.Lexer, out *UpdateSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api1(in *jlexer.Lexer, out *UpdateSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -201,7 +201,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(out *jwriter.Writer, in UpdateSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api1(out *jwriter.Writer, in UpdateSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -242,27 +242,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(out *j // MarshalJSON supports json.Marshaler interface func (v UpdateSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v UpdateSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi1(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *UpdateSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *UpdateSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi1(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api1(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(in *jlexer.Lexer, out *TransientDataServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api2(in *jlexer.Lexer, out *TransientDataServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -340,7 +340,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(out *jwriter.Writer, in TransientDataServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api2(out *jwriter.Writer, in TransientDataServerMessage) { out.RawByte('{') first := true _ = first @@ -407,27 +407,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(out *j // MarshalJSON supports json.Marshaler interface func (v TransientDataServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TransientDataServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi2(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TransientDataServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TransientDataServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi2(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api2(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(in *jlexer.Lexer, out *TransientDataClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api3(in *jlexer.Lexer, out *TransientDataClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -477,7 +477,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(out *jwriter.Writer, in TransientDataClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api3(out *jwriter.Writer, in TransientDataClientMessage) { out.RawByte('{') first := true _ = first @@ -507,27 +507,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(out *j // MarshalJSON supports json.Marshaler interface func (v TransientDataClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TransientDataClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi3(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TransientDataClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TransientDataClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi3(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api3(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(in *jlexer.Lexer, out *ServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api4(in *jlexer.Lexer, out *ServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -717,7 +717,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(out *jwriter.Writer, in ServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api4(out *jwriter.Writer, in ServerMessage) { out.RawByte('{') first := true _ = first @@ -798,27 +798,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(out *j // MarshalJSON supports json.Marshaler interface func (v ServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi4(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi4(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api4(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(in *jlexer.Lexer, out *RoomServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api5(in *jlexer.Lexer, out *RoomServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -870,7 +870,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(out *jwriter.Writer, in RoomServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api5(out *jwriter.Writer, in RoomServerMessage) { out.RawByte('{') first := true _ = first @@ -895,27 +895,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(out *j // MarshalJSON supports json.Marshaler interface func (v RoomServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api5(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi5(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api5(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api5(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi5(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api5(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(in *jlexer.Lexer, out *RoomFlagsServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api6(in *jlexer.Lexer, out *RoomFlagsServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -957,7 +957,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(out *jwriter.Writer, in RoomFlagsServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api6(out *jwriter.Writer, in RoomFlagsServerMessage) { out.RawByte('{') first := true _ = first @@ -982,27 +982,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(out *j // MarshalJSON supports json.Marshaler interface func (v RoomFlagsServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomFlagsServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi6(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomFlagsServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomFlagsServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi6(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api6(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(in *jlexer.Lexer, out *RoomFederationMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api7(in *jlexer.Lexer, out *RoomFederationMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1050,7 +1050,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(out *jwriter.Writer, in RoomFederationMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api7(out *jwriter.Writer, in RoomFederationMessage) { out.RawByte('{') first := true _ = first @@ -1080,27 +1080,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(out *j // MarshalJSON supports json.Marshaler interface func (v RoomFederationMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomFederationMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi7(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomFederationMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomFederationMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi7(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api7(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(in *jlexer.Lexer, out *RoomEventServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api8(in *jlexer.Lexer, out *RoomEventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1246,7 +1246,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(out *jwriter.Writer, in RoomEventServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api8(out *jwriter.Writer, in RoomEventServerMessage) { out.RawByte('{') first := true _ = first @@ -1348,27 +1348,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(out *j // MarshalJSON supports json.Marshaler interface func (v RoomEventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi8(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api8(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(in *jlexer.Lexer, out *RoomEventMessageDataChat) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api9(in *jlexer.Lexer, out *RoomEventMessageDataChat) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1435,7 +1435,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(in *jl in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(out *jwriter.Writer, in RoomEventMessageDataChat) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api9(out *jwriter.Writer, in RoomEventMessageDataChat) { out.RawByte('{') first := true _ = first @@ -1480,27 +1480,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(out *j // MarshalJSON supports json.Marshaler interface func (v RoomEventMessageDataChat) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessageDataChat) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi9(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessageDataChat) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessageDataChat) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi9(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api9(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(in *jlexer.Lexer, out *RoomEventMessageData) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api10(in *jlexer.Lexer, out *RoomEventMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1544,7 +1544,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(out *jwriter.Writer, in RoomEventMessageData) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api10(out *jwriter.Writer, in RoomEventMessageData) { out.RawByte('{') first := true _ = first @@ -1564,27 +1564,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(out * // MarshalJSON supports json.Marshaler interface func (v RoomEventMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi10(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi10(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api10(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(in *jlexer.Lexer, out *RoomEventMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api11(in *jlexer.Lexer, out *RoomEventMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1622,7 +1622,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(out *jwriter.Writer, in RoomEventMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api11(out *jwriter.Writer, in RoomEventMessage) { out.RawByte('{') first := true _ = first @@ -1642,27 +1642,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(out * // MarshalJSON supports json.Marshaler interface func (v RoomEventMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi11(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi11(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api11(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(in *jlexer.Lexer, out *RoomErrorDetails) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api12(in *jlexer.Lexer, out *RoomErrorDetails) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1700,7 +1700,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(out *jwriter.Writer, in RoomErrorDetails) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api12(out *jwriter.Writer, in RoomErrorDetails) { out.RawByte('{') first := true _ = first @@ -1719,27 +1719,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(out * // MarshalJSON supports json.Marshaler interface func (v RoomErrorDetails) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomErrorDetails) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi12(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomErrorDetails) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomErrorDetails) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi12(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api12(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *jlexer.Lexer, out *RoomDisinviteEventServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api13(in *jlexer.Lexer, out *RoomDisinviteEventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1891,7 +1891,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(out *jwriter.Writer, in RoomDisinviteEventServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api13(out *jwriter.Writer, in RoomDisinviteEventServerMessage) { out.RawByte('{') first := true _ = first @@ -1998,27 +1998,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(out * // MarshalJSON supports json.Marshaler interface func (v RoomDisinviteEventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomDisinviteEventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi13(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomDisinviteEventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomDisinviteEventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api13(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(in *jlexer.Lexer, out *RoomClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api14(in *jlexer.Lexer, out *RoomClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2068,7 +2068,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(out *jwriter.Writer, in RoomClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api14(out *jwriter.Writer, in RoomClientMessage) { out.RawByte('{') first := true _ = first @@ -2093,27 +2093,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(out * // MarshalJSON supports json.Marshaler interface func (v RoomClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi14(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi14(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api14(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(in *jlexer.Lexer, out *RoomBandwidth) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api15(in *jlexer.Lexer, out *RoomBandwidth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2149,7 +2149,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(out *jwriter.Writer, in RoomBandwidth) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api15(out *jwriter.Writer, in RoomBandwidth) { out.RawByte('{') first := true _ = first @@ -2169,27 +2169,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(out * // MarshalJSON supports json.Marshaler interface func (v RoomBandwidth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomBandwidth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi15(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomBandwidth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi15(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api15(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api16(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2231,7 +2231,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api16(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -2262,27 +2262,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(out * // MarshalJSON supports json.Marshaler interface func (v RemoveSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RemoveSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi16(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi16(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api16(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(in *jlexer.Lexer, out *MessageServerMessageSender) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api17(in *jlexer.Lexer, out *MessageServerMessageSender) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2324,7 +2324,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(out *jwriter.Writer, in MessageServerMessageSender) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api17(out *jwriter.Writer, in MessageServerMessageSender) { out.RawByte('{') first := true _ = first @@ -2349,27 +2349,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(out * // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageSender) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageSender) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi17(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageSender) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageSender) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi17(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api17(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(in *jlexer.Lexer, out *MessageServerMessageData) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api18(in *jlexer.Lexer, out *MessageServerMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2399,7 +2399,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(out *jwriter.Writer, in MessageServerMessageData) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api18(out *jwriter.Writer, in MessageServerMessageData) { out.RawByte('{') first := true _ = first @@ -2414,27 +2414,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(out * // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi18(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi18(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api18(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(in *jlexer.Lexer, out *MessageServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api19(in *jlexer.Lexer, out *MessageServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2494,7 +2494,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(out *jwriter.Writer, in MessageServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api19(out *jwriter.Writer, in MessageServerMessage) { out.RawByte('{') first := true _ = first @@ -2523,27 +2523,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(out * // MarshalJSON supports json.Marshaler interface func (v MessageServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi19(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi19(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api19(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(in *jlexer.Lexer, out *MessageClientMessageRecipient) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api20(in *jlexer.Lexer, out *MessageClientMessageRecipient) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2585,7 +2585,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(out *jwriter.Writer, in MessageClientMessageRecipient) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api20(out *jwriter.Writer, in MessageClientMessageRecipient) { out.RawByte('{') first := true _ = first @@ -2610,27 +2610,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(out * // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageRecipient) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageRecipient) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi20(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi20(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api20(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(in *jlexer.Lexer, out *MessageClientMessageData) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api21(in *jlexer.Lexer, out *MessageClientMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2724,7 +2724,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(out *jwriter.Writer, in MessageClientMessageData) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api21(out *jwriter.Writer, in MessageClientMessageData) { out.RawByte('{') first := true _ = first @@ -2801,27 +2801,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(out * // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi21(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi21(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api21(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(in *jlexer.Lexer, out *MessageClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api22(in *jlexer.Lexer, out *MessageClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2859,7 +2859,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(out *jwriter.Writer, in MessageClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api22(out *jwriter.Writer, in MessageClientMessage) { out.RawByte('{') first := true _ = first @@ -2879,27 +2879,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(out * // MarshalJSON supports json.Marshaler interface func (v MessageClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi22(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi22(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api22(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(in *jlexer.Lexer, out *InternalServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api23(in *jlexer.Lexer, out *InternalServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2943,7 +2943,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(out *jwriter.Writer, in InternalServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api23(out *jwriter.Writer, in InternalServerMessage) { out.RawByte('{') first := true _ = first @@ -2963,27 +2963,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(out * // MarshalJSON supports json.Marshaler interface func (v InternalServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi23(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi23(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api23(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(in *jlexer.Lexer, out *InternalServerDialoutRequestContents) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api24(in *jlexer.Lexer, out *InternalServerDialoutRequestContents) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3021,7 +3021,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(out *jwriter.Writer, in InternalServerDialoutRequestContents) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api24(out *jwriter.Writer, in InternalServerDialoutRequestContents) { out.RawByte('{') first := true _ = first @@ -3041,27 +3041,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(out * // MarshalJSON supports json.Marshaler interface func (v InternalServerDialoutRequestContents) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerDialoutRequestContents) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi24(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerDialoutRequestContents) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerDialoutRequestContents) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi24(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api24(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(in *jlexer.Lexer, out *InternalServerDialoutRequest) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api25(in *jlexer.Lexer, out *InternalServerDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3111,7 +3111,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(out *jwriter.Writer, in InternalServerDialoutRequest) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api25(out *jwriter.Writer, in InternalServerDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3140,27 +3140,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(out * // MarshalJSON supports json.Marshaler interface func (v InternalServerDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi25(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi25(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api25(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(in *jlexer.Lexer, out *InternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api26(in *jlexer.Lexer, out *InternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3260,7 +3260,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(out *jwriter.Writer, in InternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api26(out *jwriter.Writer, in InternalClientMessage) { out.RawByte('{') first := true _ = first @@ -3300,27 +3300,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(out * // MarshalJSON supports json.Marshaler interface func (v InternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi26(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi26(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api26(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(in *jlexer.Lexer, out *InCallInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api27(in *jlexer.Lexer, out *InCallInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3350,7 +3350,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(out *jwriter.Writer, in InCallInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api27(out *jwriter.Writer, in InCallInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -3365,27 +3365,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(out * // MarshalJSON supports json.Marshaler interface func (v InCallInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InCallInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi27(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi27(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api27(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(in *jlexer.Lexer, out *HelloV2TokenClaims) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api28(in *jlexer.Lexer, out *HelloV2TokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3491,7 +3491,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(out *jwriter.Writer, in HelloV2TokenClaims) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api28(out *jwriter.Writer, in HelloV2TokenClaims) { out.RawByte('{') first := true _ = first @@ -3577,27 +3577,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(out * // MarshalJSON supports json.Marshaler interface func (v HelloV2TokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2TokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi28(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi28(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api28(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(in *jlexer.Lexer, out *HelloV2AuthParams) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api29(in *jlexer.Lexer, out *HelloV2AuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3627,7 +3627,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(out *jwriter.Writer, in HelloV2AuthParams) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api29(out *jwriter.Writer, in HelloV2AuthParams) { out.RawByte('{') first := true _ = first @@ -3642,27 +3642,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(out * // MarshalJSON supports json.Marshaler interface func (v HelloV2AuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2AuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi29(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi29(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api29(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(in *jlexer.Lexer, out *HelloServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api30(in *jlexer.Lexer, out *HelloServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3724,7 +3724,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(out *jwriter.Writer, in HelloServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api30(out *jwriter.Writer, in HelloServerMessage) { out.RawByte('{') first := true _ = first @@ -3759,27 +3759,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(out * // MarshalJSON supports json.Marshaler interface func (v HelloServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi30(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi30(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api30(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(in *jlexer.Lexer, out *HelloClientMessageAuth) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api31(in *jlexer.Lexer, out *HelloClientMessageAuth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3823,7 +3823,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(out *jwriter.Writer, in HelloClientMessageAuth) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api31(out *jwriter.Writer, in HelloClientMessageAuth) { out.RawByte('{') first := true _ = first @@ -3854,27 +3854,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(out * // MarshalJSON supports json.Marshaler interface func (v HelloClientMessageAuth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessageAuth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi31(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi31(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api31(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(in *jlexer.Lexer, out *HelloClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api32(in *jlexer.Lexer, out *HelloClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3951,7 +3951,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(out *jwriter.Writer, in HelloClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api32(out *jwriter.Writer, in HelloClientMessage) { out.RawByte('{') first := true _ = first @@ -3990,27 +3990,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(out * // MarshalJSON supports json.Marshaler interface func (v HelloClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi32(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi32(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api32(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(in *jlexer.Lexer, out *FederationTokenClaims) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api33(in *jlexer.Lexer, out *FederationTokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4116,7 +4116,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(out *jwriter.Writer, in FederationTokenClaims) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api33(out *jwriter.Writer, in FederationTokenClaims) { out.RawByte('{') first := true _ = first @@ -4202,27 +4202,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(out * // MarshalJSON supports json.Marshaler interface func (v FederationTokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationTokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi33(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationTokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi33(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api33(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(in *jlexer.Lexer, out *FederationAuthParams) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api34(in *jlexer.Lexer, out *FederationAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4252,7 +4252,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(out *jwriter.Writer, in FederationAuthParams) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api34(out *jwriter.Writer, in FederationAuthParams) { out.RawByte('{') first := true _ = first @@ -4267,27 +4267,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(out * // MarshalJSON supports json.Marshaler interface func (v FederationAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v FederationAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi34(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *FederationAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *FederationAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi34(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api34(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api35(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4325,7 +4325,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(out *jwriter.Writer, in EventServerMessageSwitchTo) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api35(out *jwriter.Writer, in EventServerMessageSwitchTo) { out.RawByte('{') first := true _ = first @@ -4345,27 +4345,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(out * // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSwitchTo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSwitchTo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi35(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi35(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api35(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api36(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4448,7 +4448,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(out *jwriter.Writer, in EventServerMessageSessionEntry) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api36(out *jwriter.Writer, in EventServerMessageSessionEntry) { out.RawByte('{') first := true _ = first @@ -4497,27 +4497,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(out * // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSessionEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSessionEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi36(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi36(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api36(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(in *jlexer.Lexer, out *EventServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api37(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4732,7 +4732,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(out *jwriter.Writer, in EventServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api37(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -4829,27 +4829,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(out * // MarshalJSON supports json.Marshaler interface func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api37(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi37(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api37(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api37(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi37(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api37(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(in *jlexer.Lexer, out *Error) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api38(in *jlexer.Lexer, out *Error) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4893,7 +4893,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(out *jwriter.Writer, in Error) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api38(out *jwriter.Writer, in Error) { out.RawByte('{') first := true _ = first @@ -4918,27 +4918,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(out * // MarshalJSON supports json.Marshaler interface func (v Error) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api38(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v Error) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi38(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api38(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *Error) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api38(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi38(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api38(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api39(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4992,7 +4992,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api39(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5027,27 +5027,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(out * // MarshalJSON supports json.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api39(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi39(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api39(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api39(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi39(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api39(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(in *jlexer.Lexer, out *DialoutInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api40(in *jlexer.Lexer, out *DialoutInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5111,7 +5111,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(out *jwriter.Writer, in DialoutInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api40(out *jwriter.Writer, in DialoutInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5141,27 +5141,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(out * // MarshalJSON supports json.Marshaler interface func (v DialoutInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api40(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi40(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api40(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api40(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi40(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api40(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(in *jlexer.Lexer, out *ControlServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api41(in *jlexer.Lexer, out *ControlServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5221,7 +5221,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(out *jwriter.Writer, in ControlServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api41(out *jwriter.Writer, in ControlServerMessage) { out.RawByte('{') first := true _ = first @@ -5250,27 +5250,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(out * // MarshalJSON supports json.Marshaler interface func (v ControlServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api41(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi41(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api41(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api41(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi41(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api41(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(in *jlexer.Lexer, out *ControlClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api42(in *jlexer.Lexer, out *ControlClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5308,7 +5308,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(out *jwriter.Writer, in ControlClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api42(out *jwriter.Writer, in ControlClientMessage) { out.RawByte('{') first := true _ = first @@ -5328,27 +5328,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(out * // MarshalJSON supports json.Marshaler interface func (v ControlClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api42(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi42(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api42(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api42(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi42(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api42(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api43(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5384,7 +5384,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(out *jwriter.Writer, in CommonSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api43(out *jwriter.Writer, in CommonSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5404,27 +5404,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(out * // MarshalJSON supports json.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api43(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi43(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api43(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api43(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi43(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api43(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api44(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5466,7 +5466,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(out *jwriter.Writer, in ClientTypeInternalAuthParams) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api44(out *jwriter.Writer, in ClientTypeInternalAuthParams) { out.RawByte('{') first := true _ = first @@ -5491,27 +5491,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(out * // MarshalJSON supports json.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api44(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi44(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api44(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api44(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi44(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api44(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(in *jlexer.Lexer, out *ClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api45(in *jlexer.Lexer, out *ClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5645,7 +5645,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(out *jwriter.Writer, in ClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api45(out *jwriter.Writer, in ClientMessage) { out.RawByte('{') first := true _ = first @@ -5706,27 +5706,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(out * // MarshalJSON supports json.Marshaler interface func (v ClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api45(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi45(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api45(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api45(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi45(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api45(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(in *jlexer.Lexer, out *ByeServerMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api46(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5756,7 +5756,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(out *jwriter.Writer, in ByeServerMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api46(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -5771,27 +5771,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(out * // MarshalJSON supports json.Marshaler interface func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api46(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi46(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api46(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api46(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi46(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api46(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(in *jlexer.Lexer, out *ByeClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api47(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5815,7 +5815,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(out *jwriter.Writer, in ByeClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api47(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -5825,27 +5825,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(out * // MarshalJSON supports json.Marshaler interface func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api47(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi47(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api47(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api47(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi47(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api47(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(in *jlexer.Lexer, out *AnswerOfferMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api48(in *jlexer.Lexer, out *AnswerOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5921,7 +5921,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(out *jwriter.Writer, in AnswerOfferMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api48(out *jwriter.Writer, in AnswerOfferMessage) { out.RawByte('{') first := true _ = first @@ -5983,27 +5983,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(out * // MarshalJSON supports json.Marshaler interface func (v AnswerOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api48(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AnswerOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi48(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api48(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api48(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi48(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api48(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(in *jlexer.Lexer, out *AddSessionOptions) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api49(in *jlexer.Lexer, out *AddSessionOptions) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -6039,7 +6039,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(out *jwriter.Writer, in AddSessionOptions) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api49(out *jwriter.Writer, in AddSessionOptions) { out.RawByte('{') first := true _ = first @@ -6065,27 +6065,27 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(out * // MarshalJSON supports json.Marshaler interface func (v AddSessionOptions) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api49(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionOptions) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi49(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api49(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionOptions) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api49(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionOptions) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi49(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api49(l, v) } -func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { +func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api50(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -6169,7 +6169,7 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(in *j in.Consumed() } } -func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(out *jwriter.Writer, in AddSessionInternalClientMessage) { +func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api50(out *jwriter.Writer, in AddSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -6240,23 +6240,23 @@ func easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(out * // MarshalJSON supports json.Marshaler interface func (v AddSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(&w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api50(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingApi50(w, v) + easyjson6128dd2EncodeGithubComStrukturagNextcloudSpreedSignalingV2Api50(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(&r, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api50(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi50(l, v) + easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingV2Api50(l, v) } diff --git a/async/events/api_easyjson.go b/async/events/api_easyjson.go index a20eccf..86cc164 100644 --- a/async/events/api_easyjson.go +++ b/async/events/api_easyjson.go @@ -19,7 +19,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(in *jlexer.Lexer, out *SendOfferMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(in *jlexer.Lexer, out *SendOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -69,7 +69,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvent in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(out *jwriter.Writer, in SendOfferMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(out *jwriter.Writer, in SendOfferMessage) { out.RawByte('{') first := true _ = first @@ -104,27 +104,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvent // MarshalJSON supports json.Marshaler interface func (v SendOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v SendOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *SendOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *SendOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(in *jlexer.Lexer, out *AsyncRoomMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(in *jlexer.Lexer, out *AsyncRoomMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -166,7 +166,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvent in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(out *jwriter.Writer, in AsyncRoomMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(out *jwriter.Writer, in AsyncRoomMessage) { out.RawByte('{') first := true _ = first @@ -191,27 +191,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvent // MarshalJSON supports json.Marshaler interface func (v AsyncRoomMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AsyncRoomMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AsyncRoomMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AsyncRoomMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents1(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(in *jlexer.Lexer, out *AsyncMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(in *jlexer.Lexer, out *AsyncMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -338,7 +338,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvent in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(out *jwriter.Writer, in AsyncMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(out *jwriter.Writer, in AsyncMessage) { out.RawByte('{') first := true _ = first @@ -397,23 +397,23 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvent // MarshalJSON supports json.Marshaler interface func (v AsyncMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AsyncMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AsyncMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AsyncMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingAsyncEvents2(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(l, v) } diff --git a/etcd/api_easyjson.go b/etcd/api_easyjson.go index 96846a0..16f93ba 100644 --- a/etcd/api_easyjson.go +++ b/etcd/api_easyjson.go @@ -18,7 +18,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(in *jlexer.Lexer, out *BackendServerInfoEtcd) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd(in *jlexer.Lexer, out *BackendServerInfoEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -89,7 +89,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(in *j in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(out *jwriter.Writer, in BackendServerInfoEtcd) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd(out *jwriter.Writer, in BackendServerInfoEtcd) { out.RawByte('{') first := true _ = first @@ -125,27 +125,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(out * // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(in *jlexer.Lexer, out *BackendInformationEtcd) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd1(in *jlexer.Lexer, out *BackendInformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -226,7 +226,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(out *jwriter.Writer, in BackendInformationEtcd) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd1(out *jwriter.Writer, in BackendInformationEtcd) { out.RawByte('{') first := true _ = first @@ -286,23 +286,23 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(out // MarshalJSON supports json.Marshaler interface func (v BackendInformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendInformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingEtcd1(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Etcd1(l, v) } diff --git a/grpc/api_easyjson.go b/grpc/api_easyjson.go index 055f6ad..f61268c 100644 --- a/grpc/api_easyjson.go +++ b/grpc/api_easyjson.go @@ -17,7 +17,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(in *jlexer.Lexer, out *TargetInformationEtcd) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Grpc(in *jlexer.Lexer, out *TargetInformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -47,7 +47,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(in *j in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(out *jwriter.Writer, in TargetInformationEtcd) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Grpc(out *jwriter.Writer, in TargetInformationEtcd) { out.RawByte('{') first := true _ = first @@ -62,23 +62,23 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(out * // MarshalJSON supports json.Marshaler interface func (v TargetInformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Grpc(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TargetInformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingGrpc(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Grpc(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TargetInformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Grpc(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TargetInformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingGrpc(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Grpc(l, v) } diff --git a/proxy/api_easyjson.go b/proxy/api_easyjson.go index d09bd52..163a495 100644 --- a/proxy/api_easyjson.go +++ b/proxy/api_easyjson.go @@ -20,7 +20,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(in *jlexer.Lexer, out *TokenClaims) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy(in *jlexer.Lexer, out *TokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -118,7 +118,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(out *jwriter.Writer, in TokenClaims) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy(out *jwriter.Writer, in TokenClaims) { out.RawByte('{') first := true _ = first @@ -194,27 +194,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(out // MarshalJSON supports json.Marshaler interface func (v TokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(in *jlexer.Lexer, out *ServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy1(in *jlexer.Lexer, out *ServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -334,7 +334,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(out *jwriter.Writer, in ServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy1(out *jwriter.Writer, in ServerMessage) { out.RawByte('{') first := true _ = first @@ -390,27 +390,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(out // MarshalJSON supports json.Marshaler interface func (v ServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy1(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy1(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy1(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(in *jlexer.Lexer, out *PayloadServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy2(in *jlexer.Lexer, out *PayloadServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -468,7 +468,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(out *jwriter.Writer, in PayloadServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy2(out *jwriter.Writer, in PayloadServerMessage) { out.RawByte('{') first := true _ = first @@ -515,27 +515,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(out // MarshalJSON supports json.Marshaler interface func (v PayloadServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v PayloadServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy2(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *PayloadServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *PayloadServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy2(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy2(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(in *jlexer.Lexer, out *PayloadClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy3(in *jlexer.Lexer, out *PayloadClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -603,7 +603,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(out *jwriter.Writer, in PayloadClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy3(out *jwriter.Writer, in PayloadClientMessage) { out.RawByte('{') first := true _ = first @@ -653,27 +653,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(out // MarshalJSON supports json.Marshaler interface func (v PayloadClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v PayloadClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy3(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *PayloadClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *PayloadClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy3(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy3(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(in *jlexer.Lexer, out *InformationEtcd) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy4(in *jlexer.Lexer, out *InformationEtcd) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -703,7 +703,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(out *jwriter.Writer, in InformationEtcd) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy4(out *jwriter.Writer, in InformationEtcd) { out.RawByte('{') first := true _ = first @@ -718,27 +718,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(out // MarshalJSON supports json.Marshaler interface func (v InformationEtcd) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InformationEtcd) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy4(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InformationEtcd) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InformationEtcd) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy4(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy4(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(in *jlexer.Lexer, out *HelloServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy5(in *jlexer.Lexer, out *HelloServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -788,7 +788,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(out *jwriter.Writer, in HelloServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy5(out *jwriter.Writer, in HelloServerMessage) { out.RawByte('{') first := true _ = first @@ -813,27 +813,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(out // MarshalJSON supports json.Marshaler interface func (v HelloServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy5(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy5(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy5(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy5(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy5(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy5(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(in *jlexer.Lexer, out *HelloClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy6(in *jlexer.Lexer, out *HelloClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -902,7 +902,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(out *jwriter.Writer, in HelloClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy6(out *jwriter.Writer, in HelloClientMessage) { out.RawByte('{') first := true _ = first @@ -941,27 +941,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(out // MarshalJSON supports json.Marshaler interface func (v HelloClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy6(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy6(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy6(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(in *jlexer.Lexer, out *EventServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy7(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1023,7 +1023,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(out *jwriter.Writer, in EventServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy7(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -1058,27 +1058,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(out // MarshalJSON supports json.Marshaler interface func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy7(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy7(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy7(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(in *jlexer.Lexer, out *EventServerBandwidth) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy8(in *jlexer.Lexer, out *EventServerBandwidth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1142,7 +1142,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(out *jwriter.Writer, in EventServerBandwidth) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy8(out *jwriter.Writer, in EventServerBandwidth) { out.RawByte('{') first := true _ = first @@ -1188,27 +1188,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(out // MarshalJSON supports json.Marshaler interface func (v EventServerBandwidth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerBandwidth) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy8(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerBandwidth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy8(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy8(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(in *jlexer.Lexer, out *CommandServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy9(in *jlexer.Lexer, out *CommandServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1257,7 +1257,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(in } for !in.IsDelim(']') { var v8 sfu.PublisherStream - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu(in, &v8) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu(in, &v8) out.Streams = append(out.Streams, v8) in.WantComma() } @@ -1273,7 +1273,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(out *jwriter.Writer, in CommandServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy9(out *jwriter.Writer, in CommandServerMessage) { out.RawByte('{') first := true _ = first @@ -1317,7 +1317,7 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(out if v9 > 0 { out.RawByte(',') } - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu(out, v10) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu(out, v10) } out.RawByte(']') } @@ -1328,27 +1328,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(out // MarshalJSON supports json.Marshaler interface func (v CommandServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommandServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy9(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommandServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommandServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy9(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy9(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu(in *jlexer.Lexer, out *sfu.PublisherStream) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu(in *jlexer.Lexer, out *sfu.PublisherStream) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1462,7 +1462,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu(in *jl in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu(out *jwriter.Writer, in sfu.PublisherStream) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu(out *jwriter.Writer, in sfu.PublisherStream) { out.RawByte('{') first := true _ = first @@ -1543,7 +1543,7 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu(out *j } out.RawByte('}') } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(in *jlexer.Lexer, out *CommandClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy10(in *jlexer.Lexer, out *CommandClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1607,7 +1607,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(in if out.PublisherSettings == nil { out.PublisherSettings = new(sfu.NewPublisherSettings) } - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu1(in, out.PublisherSettings) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu1(in, out.PublisherSettings) } case "remoteUrl": if in.IsNull() { @@ -1649,7 +1649,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(out *jwriter.Writer, in CommandClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy10(out *jwriter.Writer, in CommandClientMessage) { out.RawByte('{') first := true _ = first @@ -1691,7 +1691,7 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(ou if in.PublisherSettings != nil { const prefix string = ",\"publisherSettings\":" out.RawString(prefix) - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu1(out, *in.PublisherSettings) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu1(out, *in.PublisherSettings) } if in.RemoteUrl != "" { const prefix string = ",\"remoteUrl\":" @@ -1724,27 +1724,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(ou // MarshalJSON supports json.Marshaler interface func (v CommandClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommandClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy10(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommandClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommandClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy10(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy10(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu1(in *jlexer.Lexer, out *sfu.NewPublisherSettings) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu1(in *jlexer.Lexer, out *sfu.NewPublisherSettings) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1804,7 +1804,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingSfu1(in *j in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu1(out *jwriter.Writer, in sfu.NewPublisherSettings) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Sfu1(out *jwriter.Writer, in sfu.NewPublisherSettings) { out.RawByte('{') first := true _ = first @@ -1866,7 +1866,7 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingSfu1(out * } out.RawByte('}') } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(in *jlexer.Lexer, out *ClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy11(in *jlexer.Lexer, out *ClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1958,7 +1958,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(out *jwriter.Writer, in ClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy11(out *jwriter.Writer, in ClientMessage) { out.RawByte('{') first := true _ = first @@ -2004,27 +2004,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(ou // MarshalJSON supports json.Marshaler interface func (v ClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy11(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy11(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy11(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(in *jlexer.Lexer, out *ByeServerMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy12(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2054,7 +2054,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(out *jwriter.Writer, in ByeServerMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy12(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -2069,27 +2069,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(ou // MarshalJSON supports json.Marshaler interface func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy12(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy12(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy12(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(in *jlexer.Lexer, out *ByeClientMessage) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy13(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2113,7 +2113,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(out *jwriter.Writer, in ByeClientMessage) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy13(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -2123,23 +2123,23 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(ou // MarshalJSON supports json.Marshaler interface func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingProxy13(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingProxy13(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Proxy13(l, v) } diff --git a/talk/api_easyjson.go b/talk/api_easyjson.go index 2cf1a8d..d4703a3 100644 --- a/talk/api_easyjson.go +++ b/talk/api_easyjson.go @@ -21,7 +21,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(in *jlexer.Lexer, out *TurnCredentials) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(in *jlexer.Lexer, out *TurnCredentials) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -90,7 +90,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(in *j in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(out *jwriter.Writer, in TurnCredentials) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(out *jwriter.Writer, in TurnCredentials) { out.RawByte('{') first := true _ = first @@ -131,27 +131,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(out * // MarshalJSON supports json.Marshaler interface func (v TurnCredentials) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v TurnCredentials) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *TurnCredentials) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *TurnCredentials) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(in *jlexer.Lexer, out *RoomSessionData) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(in *jlexer.Lexer, out *RoomSessionData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -181,7 +181,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(out *jwriter.Writer, in RoomSessionData) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(out *jwriter.Writer, in RoomSessionData) { out.RawByte('{') first := true _ = first @@ -197,27 +197,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(out // MarshalJSON supports json.Marshaler interface func (v RoomSessionData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomSessionData) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomSessionData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomSessionData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(in *jlexer.Lexer, out *BackendServerRoomResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(in *jlexer.Lexer, out *BackendServerRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -261,7 +261,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(out *jwriter.Writer, in BackendServerRoomResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(out *jwriter.Writer, in BackendServerRoomResponse) { out.RawByte('{') first := true _ = first @@ -281,27 +281,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(out // MarshalJSON supports json.Marshaler interface func (v BackendServerRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(in *jlexer.Lexer, out *BackendServerRoomRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk3(in *jlexer.Lexer, out *BackendServerRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -477,7 +477,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(out *jwriter.Writer, in BackendServerRoomRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk3(out *jwriter.Writer, in BackendServerRoomRequest) { out.RawByte('{') first := true _ = first @@ -552,27 +552,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(out // MarshalJSON supports json.Marshaler interface func (v BackendServerRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk3(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk3(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk3(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk4(in *jlexer.Lexer, out *BackendServerInfoVideoRoom) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -614,7 +614,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(out *jwriter.Writer, in BackendServerInfoVideoRoom) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk4(out *jwriter.Writer, in BackendServerInfoVideoRoom) { out.RawByte('{') first := true _ = first @@ -650,27 +650,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoVideoRoom) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk4(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoVideoRoom) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk4(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk4(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoVideoRoom) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk4(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoVideoRoom) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk4(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk4(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(in *jlexer.Lexer, out *BackendServerInfoSfuProxyBandwidth) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk5(in *jlexer.Lexer, out *BackendServerInfoSfuProxyBandwidth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -734,7 +734,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(out *jwriter.Writer, in BackendServerInfoSfuProxyBandwidth) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk5(out *jwriter.Writer, in BackendServerInfoSfuProxyBandwidth) { out.RawByte('{') first := true _ = first @@ -780,27 +780,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuProxyBandwidth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk5(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuProxyBandwidth) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk5(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk5(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuProxyBandwidth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk5(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuProxyBandwidth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk5(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk5(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk6(in *jlexer.Lexer, out *BackendServerInfoSfuProxy) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -945,7 +945,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(out *jwriter.Writer, in BackendServerInfoSfuProxy) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk6(out *jwriter.Writer, in BackendServerInfoSfuProxy) { out.RawByte('{') first := true _ = first @@ -1019,27 +1019,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuProxy) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk6(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuProxy) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk6(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk6(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuProxy) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk6(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuProxy) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk6(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk6(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk7(in *jlexer.Lexer, out *BackendServerInfoSfuJanus) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1155,7 +1155,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(out *jwriter.Writer, in BackendServerInfoSfuJanus) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk7(out *jwriter.Writer, in BackendServerInfoSfuJanus) { out.RawByte('{') first := true _ = first @@ -1215,27 +1215,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfuJanus) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk7(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfuJanus) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk7(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk7(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfuJanus) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk7(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfuJanus) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk7(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk7(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(in *jlexer.Lexer, out *BackendServerInfoSfu) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk8(in *jlexer.Lexer, out *BackendServerInfoSfu) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1306,7 +1306,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(out *jwriter.Writer, in BackendServerInfoSfu) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk8(out *jwriter.Writer, in BackendServerInfoSfu) { out.RawByte('{') first := true _ = first @@ -1340,27 +1340,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoSfu) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoSfu) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk8(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoSfu) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoSfu) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk8(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk8(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(in *jlexer.Lexer, out *BackendServerInfoNats) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk9(in *jlexer.Lexer, out *BackendServerInfoNats) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1441,7 +1441,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(in * in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(out *jwriter.Writer, in BackendServerInfoNats) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk9(out *jwriter.Writer, in BackendServerInfoNats) { out.RawByte('{') first := true _ = first @@ -1492,27 +1492,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoNats) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoNats) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk9(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoNats) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoNats) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk9(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk9(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(in *jlexer.Lexer, out *BackendServerInfoGrpc) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk10(in *jlexer.Lexer, out *BackendServerInfoGrpc) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1560,7 +1560,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(out *jwriter.Writer, in BackendServerInfoGrpc) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk10(out *jwriter.Writer, in BackendServerInfoGrpc) { out.RawByte('{') first := true _ = first @@ -1590,27 +1590,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoGrpc) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoGrpc) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk10(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoGrpc) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoGrpc) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk10(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk10(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(in *jlexer.Lexer, out *BackendServerInfoDialout) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk11(in *jlexer.Lexer, out *BackendServerInfoDialout) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1691,7 +1691,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(out *jwriter.Writer, in BackendServerInfoDialout) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk11(out *jwriter.Writer, in BackendServerInfoDialout) { out.RawByte('{') first := true _ = first @@ -1740,27 +1740,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfoDialout) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfoDialout) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk11(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfoDialout) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk11(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk11(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(in *jlexer.Lexer, out *BackendServerInfo) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk12(in *jlexer.Lexer, out *BackendServerInfo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1913,7 +1913,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(out *jwriter.Writer, in BackendServerInfo) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk12(out *jwriter.Writer, in BackendServerInfo) { out.RawByte('{') first := true _ = first @@ -1987,27 +1987,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(out // MarshalJSON supports json.Marshaler interface func (v BackendServerInfo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendServerInfo) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk12(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendServerInfo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendServerInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk12(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk12(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk13(in *jlexer.Lexer, out *BackendRoomUpdateRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2066,7 +2066,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(out *jwriter.Writer, in BackendRoomUpdateRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk13(out *jwriter.Writer, in BackendRoomUpdateRequest) { out.RawByte('{') first := true _ = first @@ -2101,27 +2101,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomUpdateRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomUpdateRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomUpdateRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk13(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(in *jlexer.Lexer, out *BackendRoomTransientRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk14(in *jlexer.Lexer, out *BackendRoomTransientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2171,7 +2171,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(out *jwriter.Writer, in BackendRoomTransientRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk14(out *jwriter.Writer, in BackendRoomTransientRequest) { out.RawByte('{') first := true _ = first @@ -2207,27 +2207,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomTransientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomTransientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk14(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomTransientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk14(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk14(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk15(in *jlexer.Lexer, out *BackendRoomSwitchToMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2318,7 +2318,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk15(out *jwriter.Writer, in BackendRoomSwitchToMessageRequest) { out.RawByte('{') first := true _ = first @@ -2371,27 +2371,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomSwitchToMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomSwitchToMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk15(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk16(in *jlexer.Lexer, out *BackendRoomParticipantsRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2509,7 +2509,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(out *jwriter.Writer, in BackendRoomParticipantsRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk16(out *jwriter.Writer, in BackendRoomParticipantsRequest) { out.RawByte('{') first := true _ = first @@ -2597,27 +2597,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomParticipantsRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomParticipantsRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk16(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(in *jlexer.Lexer, out *BackendRoomMessageRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk17(in *jlexer.Lexer, out *BackendRoomMessageRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2649,7 +2649,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(out *jwriter.Writer, in BackendRoomMessageRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk17(out *jwriter.Writer, in BackendRoomMessageRequest) { out.RawByte('{') first := true _ = first @@ -2665,27 +2665,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomMessageRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomMessageRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk17(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomMessageRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk17(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk17(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(in *jlexer.Lexer, out *BackendRoomInviteRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk18(in *jlexer.Lexer, out *BackendRoomInviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2771,7 +2771,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(out *jwriter.Writer, in BackendRoomInviteRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk18(out *jwriter.Writer, in BackendRoomInviteRequest) { out.RawByte('{') first := true _ = first @@ -2825,27 +2825,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomInviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk18(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(in *jlexer.Lexer, out *BackendRoomInCallRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk19(in *jlexer.Lexer, out *BackendRoomInCallRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2977,7 +2977,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(out *jwriter.Writer, in BackendRoomInCallRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk19(out *jwriter.Writer, in BackendRoomInCallRequest) { out.RawByte('{') first := true _ = first @@ -3085,27 +3085,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomInCallRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomInCallRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomInCallRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk19(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk20(in *jlexer.Lexer, out *BackendRoomDisinviteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3218,7 +3218,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(out *jwriter.Writer, in BackendRoomDisinviteRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk20(out *jwriter.Writer, in BackendRoomDisinviteRequest) { out.RawByte('{') first := true _ = first @@ -3291,27 +3291,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDisinviteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDisinviteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk20(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk21(in *jlexer.Lexer, out *BackendRoomDialoutResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3355,7 +3355,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(out *jwriter.Writer, in BackendRoomDialoutResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk21(out *jwriter.Writer, in BackendRoomDialoutResponse) { out.RawByte('{') first := true _ = first @@ -3381,27 +3381,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk21(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk21(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk21(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk22(in *jlexer.Lexer, out *BackendRoomDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3439,7 +3439,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(out *jwriter.Writer, in BackendRoomDialoutRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk22(out *jwriter.Writer, in BackendRoomDialoutRequest) { out.RawByte('{') first := true _ = first @@ -3459,27 +3459,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk22(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk22(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk22(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(in *jlexer.Lexer, out *BackendRoomDialoutError) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk23(in *jlexer.Lexer, out *BackendRoomDialoutError) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3515,7 +3515,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(out *jwriter.Writer, in BackendRoomDialoutError) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk23(out *jwriter.Writer, in BackendRoomDialoutError) { out.RawByte('{') first := true _ = first @@ -3535,27 +3535,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomDialoutError) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDialoutError) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk23(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDialoutError) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk23(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk23(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk24(in *jlexer.Lexer, out *BackendRoomDeleteRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3606,7 +3606,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(out *jwriter.Writer, in BackendRoomDeleteRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk24(out *jwriter.Writer, in BackendRoomDeleteRequest) { out.RawByte('{') first := true _ = first @@ -3631,27 +3631,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(out // MarshalJSON supports json.Marshaler interface func (v BackendRoomDeleteRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendRoomDeleteRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendRoomDeleteRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk24(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(in *jlexer.Lexer, out *BackendPingEntry) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk25(in *jlexer.Lexer, out *BackendPingEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3687,7 +3687,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(out *jwriter.Writer, in BackendPingEntry) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk25(out *jwriter.Writer, in BackendPingEntry) { out.RawByte('{') first := true _ = first @@ -3713,27 +3713,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(out // MarshalJSON supports json.Marshaler interface func (v BackendPingEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendPingEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk25(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendPingEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendPingEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk25(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk25(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(in *jlexer.Lexer, out *BackendClientSessionResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk26(in *jlexer.Lexer, out *BackendClientSessionResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3769,7 +3769,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(out *jwriter.Writer, in BackendClientSessionResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk26(out *jwriter.Writer, in BackendClientSessionResponse) { out.RawByte('{') first := true _ = first @@ -3789,27 +3789,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(out // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk26(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk26(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk26(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(in *jlexer.Lexer, out *BackendClientSessionRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk27(in *jlexer.Lexer, out *BackendClientSessionRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3871,7 +3871,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(out *jwriter.Writer, in BackendClientSessionRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk27(out *jwriter.Writer, in BackendClientSessionRequest) { out.RawByte('{') first := true _ = first @@ -3911,27 +3911,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(out // MarshalJSON supports json.Marshaler interface func (v BackendClientSessionRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientSessionRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk27(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientSessionRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk27(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk27(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(in *jlexer.Lexer, out *BackendClientRoomResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk28(in *jlexer.Lexer, out *BackendClientRoomResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4018,7 +4018,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(out *jwriter.Writer, in BackendClientRoomResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk28(out *jwriter.Writer, in BackendClientRoomResponse) { out.RawByte('{') first := true _ = first @@ -4064,27 +4064,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(out // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk28(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(in *jlexer.Lexer, out *BackendClientRoomRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk29(in *jlexer.Lexer, out *BackendClientRoomRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4156,7 +4156,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(out *jwriter.Writer, in BackendClientRoomRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk29(out *jwriter.Writer, in BackendClientRoomRequest) { out.RawByte('{') first := true _ = first @@ -4206,27 +4206,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(out // MarshalJSON supports json.Marshaler interface func (v BackendClientRoomRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRoomRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk29(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRoomRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk29(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk29(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(in *jlexer.Lexer, out *BackendClientRingResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk30(in *jlexer.Lexer, out *BackendClientRingResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4262,7 +4262,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(out *jwriter.Writer, in BackendClientRingResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk30(out *jwriter.Writer, in BackendClientRingResponse) { out.RawByte('{') first := true _ = first @@ -4282,27 +4282,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(out // MarshalJSON supports json.Marshaler interface func (v BackendClientRingResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRingResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk30(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRingResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk30(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk30(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(in *jlexer.Lexer, out *BackendClientResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk31(in *jlexer.Lexer, out *BackendClientResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4402,7 +4402,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(out *jwriter.Writer, in BackendClientResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk31(out *jwriter.Writer, in BackendClientResponse) { out.RawByte('{') first := true _ = first @@ -4442,27 +4442,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(out // MarshalJSON supports json.Marshaler interface func (v BackendClientResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk31(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk31(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk31(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(in *jlexer.Lexer, out *BackendClientRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk32(in *jlexer.Lexer, out *BackendClientRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4548,7 +4548,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(out *jwriter.Writer, in BackendClientRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk32(out *jwriter.Writer, in BackendClientRequest) { out.RawByte('{') first := true _ = first @@ -4583,27 +4583,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(out // MarshalJSON supports json.Marshaler interface func (v BackendClientRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk32(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk32(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk32(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk32(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk32(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk32(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(in *jlexer.Lexer, out *BackendClientPingRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk33(in *jlexer.Lexer, out *BackendClientPingRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4666,7 +4666,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(out *jwriter.Writer, in BackendClientPingRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk33(out *jwriter.Writer, in BackendClientPingRequest) { out.RawByte('{') first := true _ = first @@ -4702,27 +4702,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(out // MarshalJSON supports json.Marshaler interface func (v BackendClientPingRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk33(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientPingRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk33(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk33(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientPingRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk33(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(in *jlexer.Lexer, out *BackendClientAuthResponse) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk34(in *jlexer.Lexer, out *BackendClientAuthResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4766,7 +4766,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(out *jwriter.Writer, in BackendClientAuthResponse) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk34(out *jwriter.Writer, in BackendClientAuthResponse) { out.RawByte('{') first := true _ = first @@ -4791,27 +4791,27 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(out // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk34(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk34(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk34(l, v) } -func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(in *jlexer.Lexer, out *BackendClientAuthRequest) { +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk35(in *jlexer.Lexer, out *BackendClientAuthRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4849,7 +4849,7 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(in in.Consumed() } } -func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(out *jwriter.Writer, in BackendClientAuthRequest) { +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk35(out *jwriter.Writer, in BackendClientAuthRequest) { out.RawByte('{') first := true _ = first @@ -4869,23 +4869,23 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(out // MarshalJSON supports json.Marshaler interface func (v BackendClientAuthRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(&w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v BackendClientAuthRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk35(w, v) + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(&r, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *BackendClientAuthRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk35(l, v) + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk35(l, v) } diff --git a/talk/ocs_easyjson.go b/talk/ocs_easyjson.go index 60aa069..efcec5f 100644 --- a/talk/ocs_easyjson.go +++ b/talk/ocs_easyjson.go @@ -17,7 +17,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(in *jlexer.Lexer, out *OcsResponse) { +func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(in *jlexer.Lexer, out *OcsResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -55,7 +55,7 @@ func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(in *j in.Consumed() } } -func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(out *jwriter.Writer, in OcsResponse) { +func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(out *jwriter.Writer, in OcsResponse) { out.RawByte('{') first := true _ = first @@ -74,27 +74,27 @@ func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(out * // MarshalJSON supports json.Marshaler interface func (v OcsResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(&w, v) + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v OcsResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk(w, v) + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *OcsResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(&r, v) + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *OcsResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk(l, v) + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk(l, v) } -func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(in *jlexer.Lexer, out *OcsMeta) { +func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(in *jlexer.Lexer, out *OcsMeta) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -136,7 +136,7 @@ func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(in * in.Consumed() } } -func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(out *jwriter.Writer, in OcsMeta) { +func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(out *jwriter.Writer, in OcsMeta) { out.RawByte('{') first := true _ = first @@ -161,27 +161,27 @@ func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(out // MarshalJSON supports json.Marshaler interface func (v OcsMeta) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&w, v) + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v OcsMeta) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk1(w, v) + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *OcsMeta) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(&r, v) + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *OcsMeta) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk1(l, v) + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk1(l, v) } -func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(in *jlexer.Lexer, out *OcsBody) { +func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(in *jlexer.Lexer, out *OcsBody) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -219,7 +219,7 @@ func easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(in * in.Consumed() } } -func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(out *jwriter.Writer, in OcsBody) { +func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(out *jwriter.Writer, in OcsBody) { out.RawByte('{') first := true _ = first @@ -239,23 +239,23 @@ func easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(out // MarshalJSON supports json.Marshaler interface func (v OcsBody) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&w, v) + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v OcsBody) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingTalk2(w, v) + easyjsonD5833a6fEncodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *OcsBody) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(&r, v) + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *OcsBody) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingTalk2(l, v) + easyjsonD5833a6fDecodeGithubComStrukturagNextcloudSpreedSignalingV2Talk2(l, v) } From c0a056d3ebc02e7e27449559cbb0d850df4b88c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:08:46 +0000 Subject: [PATCH 535/549] Bump google.golang.org/grpc from 1.79.1 to 1.79.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.1 to 1.79.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.79.1...v1.79.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.79.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3352de7..3a8e771 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.8 go.etcd.io/etcd/server/v3 v3.6.8 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.1 + google.golang.org/grpc v1.79.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 google.golang.org/protobuf v1.36.11 ) diff --git a/go.sum b/go.sum index 4de5da8..9514d1b 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1: google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From c7cb648b606565ad920cf2bb495e134533f79737 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 26 Feb 2026 08:08:27 +0100 Subject: [PATCH 536/549] Don't limit size of received Janus events. Combined events of media stats could get larger than previous limit causing interruptions in the connection from Janus and piling up unsent events on Janus side. --- sfu/janus/events_handler.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sfu/janus/events_handler.go b/sfu/janus/events_handler.go index fed5139..475fb8c 100644 --- a/sfu/janus/events_handler.go +++ b/sfu/janus/events_handler.go @@ -82,9 +82,6 @@ const ( // Send pings to peer with this period. Must be less than pongWait. pingPeriod = (pongWait * 9) / 10 - - // Maximum message size allowed from peer. - maxMessageSize = 64 * 1024 ) var ( @@ -727,7 +724,6 @@ func (h *EventsHandler) readPump() { return } - conn.SetReadLimit(maxMessageSize) conn.SetPongHandler(func(msg string) error { now := time.Now() conn.SetReadDeadline(now.Add(pongWait)) // nolint From eca3ee8bfb4b3100597ce82bd5d2b4d57068bc83 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 26 Feb 2026 08:15:10 +0100 Subject: [PATCH 537/549] Move Janus event types into api.go It will then get picked up by the easyjson generator to speed up parsing. --- sfu/janus/api.go | 514 ++++++++++++++++++++++++++++++++++++ sfu/janus/events_handler.go | 485 ---------------------------------- 2 files changed, 514 insertions(+), 485 deletions(-) create mode 100644 sfu/janus/api.go diff --git a/sfu/janus/api.go b/sfu/janus/api.go new file mode 100644 index 0000000..b28c7b6 --- /dev/null +++ b/sfu/janus/api.go @@ -0,0 +1,514 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 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 janus + +import ( + "encoding/json" + "fmt" +) + +const ( + janusEventTypeSession = 1 + + janusEventTypeHandle = 2 + + janusEventTypeExternal = 4 + + janusEventTypeJSEP = 8 + + janusEventTypeWebRTC = 16 + janusEventSubTypeWebRTCICE = 1 + janusEventSubTypeWebRTCLocalCandidate = 2 + janusEventSubTypeWebRTCRemoteCandidate = 3 + janusEventSubTypeWebRTCSelectedPair = 4 + janusEventSubTypeWebRTCDTLS = 5 + janusEventSubTypeWebRTCPeerConnection = 6 + + janusEventTypeMedia = 32 + janusEventSubTypeMediaState = 1 + janusEventSubTypeMediaSlowLink = 2 + janusEventSubTypeMediaStats = 3 + + janusEventTypePlugin = 64 + + janusEventTypeTransport = 128 + + janusEventTypeCore = 256 + janusEventSubTypeCoreStatusStartup = 1 + janusEventSubTypeCoreStatusShutdown = 2 +) + +func unmarshalEvent[T any](data json.RawMessage) (*T, error) { + var e T + if err := json.Unmarshal(data, &e); err != nil { + return nil, err + } + + return &e, nil +} + +func marshalEvent[T any](e T) string { + data, err := json.Marshal(e) + if err != nil { + return fmt.Sprintf("Could not serialize %#v: %s", e, err) + } + + return string(data) +} + +type janusEvent struct { + Emitter string `json:"emitter"` + Type int `json:"type"` + SubType int `json:"subtype,omitempty"` + Timestamp uint64 `json:"timestamp"` + SessionId uint64 `json:"session_id,omitempty"` + HandleId uint64 `json:"handle_id,omitempty"` + OpaqueId uint64 `json:"opaque_id,omitempty"` + Event json.RawMessage `json:"event"` +} + +func (e janusEvent) String() string { + return marshalEvent(e) +} + +func (e janusEvent) Decode() (any, error) { + switch e.Type { + case janusEventTypeSession: + return unmarshalEvent[janusEventSession](e.Event) + case janusEventTypeHandle: + return unmarshalEvent[janusEventHandle](e.Event) + case janusEventTypeExternal: + return unmarshalEvent[janusEventExternal](e.Event) + case janusEventTypeJSEP: + return unmarshalEvent[janusEventJSEP](e.Event) + case janusEventTypeWebRTC: + switch e.SubType { + case janusEventSubTypeWebRTCICE: + return unmarshalEvent[janusEventWebRTCICE](e.Event) + case janusEventSubTypeWebRTCLocalCandidate: + return unmarshalEvent[janusEventWebRTCLocalCandidate](e.Event) + case janusEventSubTypeWebRTCRemoteCandidate: + return unmarshalEvent[janusEventWebRTCRemoteCandidate](e.Event) + case janusEventSubTypeWebRTCSelectedPair: + return unmarshalEvent[janusEventWebRTCSelectedPair](e.Event) + case janusEventSubTypeWebRTCDTLS: + return unmarshalEvent[janusEventWebRTCDTLS](e.Event) + case janusEventSubTypeWebRTCPeerConnection: + return unmarshalEvent[janusEventWebRTCPeerConnection](e.Event) + } + case janusEventTypeMedia: + switch e.SubType { + case janusEventSubTypeMediaState: + return unmarshalEvent[janusEventMediaState](e.Event) + case janusEventSubTypeMediaSlowLink: + return unmarshalEvent[janusEventMediaSlowLink](e.Event) + case janusEventSubTypeMediaStats: + return unmarshalEvent[janusEventMediaStats](e.Event) + } + case janusEventTypePlugin: + return unmarshalEvent[janusEventPlugin](e.Event) + case janusEventTypeTransport: + return unmarshalEvent[janusEventTransport](e.Event) + case janusEventTypeCore: + switch e.SubType { + case janusEventSubTypeCoreStatusStartup: + event, err := unmarshalEvent[janusEventCoreStartup](e.Event) + if err != nil { + return nil, err + } + + switch event.Status { + case "started": + return unmarshalEvent[janusEventStatusStartupInfo](event.Info) + case "update": + return unmarshalEvent[janusEventStatusUpdateInfo](event.Info) + } + + return event, nil + case janusEventSubTypeCoreStatusShutdown: + return unmarshalEvent[janusEventCoreShutdown](e.Event) + } + } + + return nil, fmt.Errorf("unsupported event type %d", e.Type) +} + +type janusEventSessionTransport struct { + Transport string `json:"transport"` + ID string `json:"id"` +} + +// type=1 +type janusEventSession struct { + Name string `json:"name"` // "created", "destroyed", "timeout" + + Transport *janusEventSessionTransport `json:"transport,omitempty"` +} + +func (e janusEventSession) String() string { + return marshalEvent(e) +} + +// type=2 +type janusEventHandle struct { + Name string `json:"name"` // "attached", "detached" + Plugin string `json:"plugin"` + Token string `json:"token,omitempty"` + // Deprecated + OpaqueId string `json:"opaque_id,omitempty"` +} + +func (e janusEventHandle) String() string { + return marshalEvent(e) +} + +// type=4 +type janusEventExternal struct { + Schema string `json:"schema"` + Data json.RawMessage `json:"data"` +} + +func (e janusEventExternal) String() string { + return marshalEvent(e) +} + +// type=8 +type janusEventJSEP struct { + Owner string `json:"owner"` + Jsep struct { + Type string `json:"type"` + SDP string `json:"sdp"` + } `json:"jsep"` +} + +func (e janusEventJSEP) String() string { + return marshalEvent(e) +} + +// type=16, subtype=1 +type janusEventWebRTCICE struct { + ICE string `json:"ice"` // "gathering", "connecting", "connected", "ready" + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` +} + +func (e janusEventWebRTCICE) String() string { + return marshalEvent(e) +} + +// type=16, subtype=2 +type janusEventWebRTCLocalCandidate struct { + LocalCandidate string `json:"local-candidate"` + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` +} + +func (e janusEventWebRTCLocalCandidate) String() string { + return marshalEvent(e) +} + +// type=16, subtype=3 +type janusEventWebRTCRemoteCandidate struct { + RemoteCandidate string `json:"remote-candidate"` + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` +} + +func (e janusEventWebRTCRemoteCandidate) String() string { + return marshalEvent(e) +} + +type janusEventCandidate struct { + Address string `json:"address"` + Port int `json:"port"` + Type string `json:"type"` + Transport string `json:"transport"` + Family int `json:"family"` +} + +func (e janusEventCandidate) String() string { + return marshalEvent(e) +} + +type janusEventCandidates struct { + Local janusEventCandidate `json:"local"` + Remote janusEventCandidate `json:"remote"` +} + +func (e janusEventCandidates) String() string { + return marshalEvent(e) +} + +// type=16, subtype=4 +type janusEventWebRTCSelectedPair struct { + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` + + SelectedPair string `json:"selected-pair"` + Candidates janusEventCandidates `json:"candidates"` +} + +func (e janusEventWebRTCSelectedPair) String() string { + return marshalEvent(e) +} + +// type=16, subtype=5 +type janusEventWebRTCDTLS struct { + DTLS string `json:"dtls"` // "trying", "connected" + + StreamID int `json:"stream_id"` + ComponentID int `json:"component_id"` + + Retransmissions int `json:"retransmissions"` +} + +func (e janusEventWebRTCDTLS) String() string { + return marshalEvent(e) +} + +// type=16, subtype=6 +type janusEventWebRTCPeerConnection struct { + Connection string `json:"connection"` // "webrtcup", "hangup" + Reason string `json:"reason,omitempty"` // Only if "connection" == "hangup" +} + +func (e janusEventWebRTCPeerConnection) String() string { + return marshalEvent(e) +} + +// type=32, subtype=1 +type janusEventMediaState struct { + Media string `json:"media"` // "audio", "video" + MID string `json:"mid"` + SubStream *int `json:"substream,omitempty"` + Receiving bool `json:"receiving"` + Seconds int `json:"seconds"` +} + +func (e janusEventMediaState) String() string { + return marshalEvent(e) +} + +// type=32, subtype=2 +type janusEventMediaSlowLink struct { + Media string `json:"media"` // "audio", "video" + MID string `json:"mid"` + SlowLink string `json:"slow_link"` // "uplink", "downlink" + LostLastSec int `json:"lost_lastsec"` +} + +func (e janusEventMediaSlowLink) String() string { + return marshalEvent(e) +} + +type janusMediaStatsRTTValues struct { + NTP uint32 `json:"ntp"` + LSR uint32 `json:"lsr"` + DLSR uint32 `json:"dlsr"` +} + +func (e janusMediaStatsRTTValues) String() string { + return marshalEvent(e) +} + +// type=32, subtype=3 +type janusEventMediaStats struct { + MID string `json:"mid"` + MIndex int `json:"mindex"` + Media string `json:"media"` // "audio", "video", "video-sim1", "video-sim2" + + // Audio / video only + Codec string `json:"codec,omitempty"` + Base uint32 `json:"base"` + Lost int32 `json:"lost"` + LostByRemote int32 `json:"lost-by-remote"` + JitterLocal uint32 `json:"jitter-local"` + JitterRemote uint32 `json:"jitter-remote"` + InLinkQuality uint32 `json:"in-link-quality"` + InMediaLinkQuality uint32 `json:"in-media-link-quality"` + OutLinkQuality uint32 `json:"out-link-quality"` + OutMediaLinkQuality uint32 `json:"out-media-link-quality"` + BytesReceivedLastSec uint32 `json:"bytes-received-lastsec"` + BytesSentLastSec uint32 `json:"bytes-sent-lastsec"` + NacksReceived uint32 `json:"nacks-received"` + NacksSent uint32 `json:"nacks-sent"` + RetransmissionsReceived uint32 `json:"retransmissions-received"` + + // Only for audio / video on layer 0 + RTT uint32 `json:"rtt,omitempty"` + // Only for audio / video on layer 0 if RTCP is active + RTTValues *janusMediaStatsRTTValues `json:"rtt-values,omitempty"` + + // For all media on all layers + PacketsReceived uint32 `json:"packets-received"` + PacketsSent uint32 `json:"packets-sent"` + BytesReceived uint64 `json:"bytes-received"` + BytesSent uint64 `json:"bytes-sent"` + + // For layer 0 if REMB is enabled + REMBBitrate uint32 `json:"remb-bitrate"` +} + +func (e janusEventMediaStats) String() string { + return marshalEvent(e) +} + +// type=64 +type janusEventPlugin struct { + Plugin string `json:"plugin"` + Data json.RawMessage `json:"data"` +} + +func (e janusEventPlugin) String() string { + return marshalEvent(e) +} + +type janusEventTransportWebsocket struct { + Event string `json:"event"` + AdminApi bool `json:"admin_api,omitempty"` + IP string `json:"ip,omitempty"` +} + +// type=128 +type janusEventTransport struct { + Transport string `json:"transport"` + Id string `json:"id"` + Data janusEventTransportWebsocket `json:"data"` +} + +func (e janusEventTransport) String() string { + return marshalEvent(e) +} + +type janusEventDependenciesInfo struct { + Glib2 string `json:"glib2"` + Jansson string `json:"jansson"` + Libnice string `json:"libnice"` + Libsrtp string `json:"libsrtp"` + Libcurl string `json:"libcurl,omitempty"` + Crypto string `json:"crypto"` +} + +func (e janusEventDependenciesInfo) String() string { + return marshalEvent(e) +} + +type janusEventPluginInfo struct { + Name string `json:"name"` + Author string `json:"author"` + Description string `json:"description"` + VersionString string `json:"version_string"` + Version int `json:"version"` +} + +func (e janusEventPluginInfo) String() string { + return marshalEvent(e) +} + +// type=256, subtype=1, status="startup" +type janusEventStatusStartupInfo struct { + Janus string `json:"janus"` + Version int `json:"version"` + VersionString string `json:"version_string"` + Author string `json:"author"` + CommitHash string `json:"commit-hash"` + CompileTime string `json:"compile-time"` + LogToStdout bool `json:"log-to-stdout"` + LogToFile bool `json:"log-to-file"` + LogPath string `json:"log-path,omitempty"` + DataChannels bool `json:"data_channels"` + AcceptingNewSessions bool `json:"accepting-new-sessions"` + SessionTimeout int `json:"session-timeout"` + ReclaimSessionTimeout int `json:"reclaim-session-timeout"` + CandidatesTimeout int `json:"candidates-timeout"` + ServerName string `json:"server-name"` + LocalIP string `json:"local-ip"` + PublicIP string `json:"public-ip,omitempty"` + PublicIPs []string `json:"public-ips,omitempty"` + IPv6 bool `json:"ipv6"` + IPv6LinkLocal bool `json:"ipv6-link-local,omitempty"` + ICELite bool `json:"ice-lite"` + ICETCP bool `json:"ice-tcp"` + ICENomination string `json:"ice-nomination,omitempty"` + ICEConsentFreshness bool `json:"ice-consent-freshness"` + ICEKeepaliveConncheck bool `json:"ice-keepalive-conncheck"` + HangupOnFailed bool `json:"hangup-on-failed"` + FullTrickle bool `json:"full-trickle"` + MDNSEnabled bool `json:"mdns-enabled"` + MinNACKQueue int `json:"min-nack-queue"` + NACKOptimizations bool `json:"nack-optimizations"` + TWCCPeriod int `json:"twcc-period"` + DSCP int `json:"dscp,omitempty"` + DTLSMCU int `json:"dtls-mcu"` + STUNServer string `json:"stun-server,omitempty"` + TURNServer string `json:"turn-server,omitempty"` + AllowForceRelay bool `json:"allow-force-relay,omitempty"` + StaticEventLoops int `json:"static-event-loops"` + LoopIndication bool `json:"loop-indication,omitempty"` + APISecret bool `json:"api_secret"` + AuthToken bool `json:"auth_token"` + EventHandlers bool `json:"event_handlers"` + OpaqueIdInAPI bool `json:"opaqueid_in_api"` + WebRTCEncryption bool `json:"webrtc_encryption"` + + Dependencies *janusEventDependenciesInfo `json:"dependencies,omitempty"` + Transports map[string]janusEventPluginInfo `json:"transports,omitempty"` + Events map[string]janusEventPluginInfo `json:"events,omitempty"` + Loggers map[string]janusEventPluginInfo `json:"loggers,omitempty"` + Plugins map[string]janusEventPluginInfo `json:"plugins,omitempty"` +} + +func (e janusEventStatusStartupInfo) String() string { + return marshalEvent(e) +} + +// type=256, subtype=1, status="update" +type janusEventStatusUpdateInfo struct { + Sessions int `json:"sessions"` + Handles int `json:"handles"` + PeerConnections int `json:"peerconnections"` + StatsPeriod int `json:"stats-period"` +} + +func (e janusEventStatusUpdateInfo) String() string { + return marshalEvent(e) +} + +// type=256, subtype=1 +type janusEventCoreStartup struct { + Status string `json:"status"` + Info json.RawMessage `json:"info"` +} + +func (e janusEventCoreStartup) String() string { + return marshalEvent(e) +} + +// type=256, subtype=2 +type janusEventCoreShutdown struct { + Status string `json:"status"` + Signum int `json:"signum"` +} + +func (e janusEventCoreShutdown) String() string { + return marshalEvent(e) +} diff --git a/sfu/janus/events_handler.go b/sfu/janus/events_handler.go index 475fb8c..37cd4f3 100644 --- a/sfu/janus/events_handler.go +++ b/sfu/janus/events_handler.go @@ -45,35 +45,6 @@ import ( const ( EventsSubprotocol = "janus-events" - janusEventTypeSession = 1 - - janusEventTypeHandle = 2 - - janusEventTypeExternal = 4 - - janusEventTypeJSEP = 8 - - janusEventTypeWebRTC = 16 - janusEventSubTypeWebRTCICE = 1 - janusEventSubTypeWebRTCLocalCandidate = 2 - janusEventSubTypeWebRTCRemoteCandidate = 3 - janusEventSubTypeWebRTCSelectedPair = 4 - janusEventSubTypeWebRTCDTLS = 5 - janusEventSubTypeWebRTCPeerConnection = 6 - - janusEventTypeMedia = 32 - janusEventSubTypeMediaState = 1 - janusEventSubTypeMediaSlowLink = 2 - janusEventSubTypeMediaStats = 3 - - janusEventTypePlugin = 64 - - janusEventTypeTransport = 128 - - janusEventTypeCore = 256 - janusEventSubTypeCoreStatusStartup = 1 - janusEventSubTypeCoreStatusShutdown = 2 - // Time allowed to write a message to the peer. writeWait = 10 * time.Second @@ -88,462 +59,6 @@ var ( bufferPool pool.BufferPool ) -func unmarshalEvent[T any](data json.RawMessage) (*T, error) { - var e T - if err := json.Unmarshal(data, &e); err != nil { - return nil, err - } - - return &e, nil -} - -func marshalEvent[T any](e T) string { - data, err := json.Marshal(e) - if err != nil { - return fmt.Sprintf("Could not serialize %#v: %s", e, err) - } - - return string(data) -} - -type janusEvent struct { - Emitter string `json:"emitter"` - Type int `json:"type"` - SubType int `json:"subtype,omitempty"` - Timestamp uint64 `json:"timestamp"` - SessionId uint64 `json:"session_id,omitempty"` - HandleId uint64 `json:"handle_id,omitempty"` - OpaqueId uint64 `json:"opaque_id,omitempty"` - Event json.RawMessage `json:"event"` -} - -func (e janusEvent) String() string { - return marshalEvent(e) -} - -func (e janusEvent) Decode() (any, error) { - switch e.Type { - case janusEventTypeSession: - return unmarshalEvent[janusEventSession](e.Event) - case janusEventTypeHandle: - return unmarshalEvent[janusEventHandle](e.Event) - case janusEventTypeExternal: - return unmarshalEvent[janusEventExternal](e.Event) - case janusEventTypeJSEP: - return unmarshalEvent[janusEventJSEP](e.Event) - case janusEventTypeWebRTC: - switch e.SubType { - case janusEventSubTypeWebRTCICE: - return unmarshalEvent[janusEventWebRTCICE](e.Event) - case janusEventSubTypeWebRTCLocalCandidate: - return unmarshalEvent[janusEventWebRTCLocalCandidate](e.Event) - case janusEventSubTypeWebRTCRemoteCandidate: - return unmarshalEvent[janusEventWebRTCRemoteCandidate](e.Event) - case janusEventSubTypeWebRTCSelectedPair: - return unmarshalEvent[janusEventWebRTCSelectedPair](e.Event) - case janusEventSubTypeWebRTCDTLS: - return unmarshalEvent[janusEventWebRTCDTLS](e.Event) - case janusEventSubTypeWebRTCPeerConnection: - return unmarshalEvent[janusEventWebRTCPeerConnection](e.Event) - } - case janusEventTypeMedia: - switch e.SubType { - case janusEventSubTypeMediaState: - return unmarshalEvent[janusEventMediaState](e.Event) - case janusEventSubTypeMediaSlowLink: - return unmarshalEvent[janusEventMediaSlowLink](e.Event) - case janusEventSubTypeMediaStats: - return unmarshalEvent[janusEventMediaStats](e.Event) - } - case janusEventTypePlugin: - return unmarshalEvent[janusEventPlugin](e.Event) - case janusEventTypeTransport: - return unmarshalEvent[janusEventTransport](e.Event) - case janusEventTypeCore: - switch e.SubType { - case janusEventSubTypeCoreStatusStartup: - event, err := unmarshalEvent[janusEventCoreStartup](e.Event) - if err != nil { - return nil, err - } - - switch event.Status { - case "started": - return unmarshalEvent[janusEventStatusStartupInfo](event.Info) - case "update": - return unmarshalEvent[janusEventStatusUpdateInfo](event.Info) - } - - return event, nil - case janusEventSubTypeCoreStatusShutdown: - return unmarshalEvent[janusEventCoreShutdown](e.Event) - } - } - - return nil, fmt.Errorf("unsupported event type %d", e.Type) -} - -type janusEventSessionTransport struct { - Transport string `json:"transport"` - ID string `json:"id"` -} - -// type=1 -type janusEventSession struct { - Name string `json:"name"` // "created", "destroyed", "timeout" - - Transport *janusEventSessionTransport `json:"transport,omitempty"` -} - -func (e janusEventSession) String() string { - return marshalEvent(e) -} - -// type=2 -type janusEventHandle struct { - Name string `json:"name"` // "attached", "detached" - Plugin string `json:"plugin"` - Token string `json:"token,omitempty"` - // Deprecated - OpaqueId string `json:"opaque_id,omitempty"` -} - -func (e janusEventHandle) String() string { - return marshalEvent(e) -} - -// type=4 -type janusEventExternal struct { - Schema string `json:"schema"` - Data json.RawMessage `json:"data"` -} - -func (e janusEventExternal) String() string { - return marshalEvent(e) -} - -// type=8 -type janusEventJSEP struct { - Owner string `json:"owner"` - Jsep struct { - Type string `json:"type"` - SDP string `json:"sdp"` - } `json:"jsep"` -} - -func (e janusEventJSEP) String() string { - return marshalEvent(e) -} - -// type=16, subtype=1 -type janusEventWebRTCICE struct { - ICE string `json:"ice"` // "gathering", "connecting", "connected", "ready" - StreamID int `json:"stream_id"` - ComponentID int `json:"component_id"` -} - -func (e janusEventWebRTCICE) String() string { - return marshalEvent(e) -} - -// type=16, subtype=2 -type janusEventWebRTCLocalCandidate struct { - LocalCandidate string `json:"local-candidate"` - StreamID int `json:"stream_id"` - ComponentID int `json:"component_id"` -} - -func (e janusEventWebRTCLocalCandidate) String() string { - return marshalEvent(e) -} - -// type=16, subtype=3 -type janusEventWebRTCRemoteCandidate struct { - RemoteCandidate string `json:"remote-candidate"` - StreamID int `json:"stream_id"` - ComponentID int `json:"component_id"` -} - -func (e janusEventWebRTCRemoteCandidate) String() string { - return marshalEvent(e) -} - -type janusEventCandidate struct { - Address string `json:"address"` - Port int `json:"port"` - Type string `json:"type"` - Transport string `json:"transport"` - Family int `json:"family"` -} - -func (e janusEventCandidate) String() string { - return marshalEvent(e) -} - -type janusEventCandidates struct { - Local janusEventCandidate `json:"local"` - Remote janusEventCandidate `json:"remote"` -} - -func (e janusEventCandidates) String() string { - return marshalEvent(e) -} - -// type=16, subtype=4 -type janusEventWebRTCSelectedPair struct { - StreamID int `json:"stream_id"` - ComponentID int `json:"component_id"` - - SelectedPair string `json:"selected-pair"` - Candidates janusEventCandidates `json:"candidates"` -} - -func (e janusEventWebRTCSelectedPair) String() string { - return marshalEvent(e) -} - -// type=16, subtype=5 -type janusEventWebRTCDTLS struct { - DTLS string `json:"dtls"` // "trying", "connected" - - StreamID int `json:"stream_id"` - ComponentID int `json:"component_id"` - - Retransmissions int `json:"retransmissions"` -} - -func (e janusEventWebRTCDTLS) String() string { - return marshalEvent(e) -} - -// type=16, subtype=6 -type janusEventWebRTCPeerConnection struct { - Connection string `json:"connection"` // "webrtcup", "hangup" - Reason string `json:"reason,omitempty"` // Only if "connection" == "hangup" -} - -func (e janusEventWebRTCPeerConnection) String() string { - return marshalEvent(e) -} - -// type=32, subtype=1 -type janusEventMediaState struct { - Media string `json:"media"` // "audio", "video" - MID string `json:"mid"` - SubStream *int `json:"substream,omitempty"` - Receiving bool `json:"receiving"` - Seconds int `json:"seconds"` -} - -func (e janusEventMediaState) String() string { - return marshalEvent(e) -} - -// type=32, subtype=2 -type janusEventMediaSlowLink struct { - Media string `json:"media"` // "audio", "video" - MID string `json:"mid"` - SlowLink string `json:"slow_link"` // "uplink", "downlink" - LostLastSec int `json:"lost_lastsec"` -} - -func (e janusEventMediaSlowLink) String() string { - return marshalEvent(e) -} - -type janusMediaStatsRTTValues struct { - NTP uint32 `json:"ntp"` - LSR uint32 `json:"lsr"` - DLSR uint32 `json:"dlsr"` -} - -func (e janusMediaStatsRTTValues) String() string { - return marshalEvent(e) -} - -// type=32, subtype=3 -type janusEventMediaStats struct { - MID string `json:"mid"` - MIndex int `json:"mindex"` - Media string `json:"media"` // "audio", "video", "video-sim1", "video-sim2" - - // Audio / video only - Codec string `json:"codec,omitempty"` - Base uint32 `json:"base"` - Lost int32 `json:"lost"` - LostByRemote int32 `json:"lost-by-remote"` - JitterLocal uint32 `json:"jitter-local"` - JitterRemote uint32 `json:"jitter-remote"` - InLinkQuality uint32 `json:"in-link-quality"` - InMediaLinkQuality uint32 `json:"in-media-link-quality"` - OutLinkQuality uint32 `json:"out-link-quality"` - OutMediaLinkQuality uint32 `json:"out-media-link-quality"` - BytesReceivedLastSec uint32 `json:"bytes-received-lastsec"` - BytesSentLastSec uint32 `json:"bytes-sent-lastsec"` - NacksReceived uint32 `json:"nacks-received"` - NacksSent uint32 `json:"nacks-sent"` - RetransmissionsReceived uint32 `json:"retransmissions-received"` - - // Only for audio / video on layer 0 - RTT uint32 `json:"rtt,omitempty"` - // Only for audio / video on layer 0 if RTCP is active - RTTValues *janusMediaStatsRTTValues `json:"rtt-values,omitempty"` - - // For all media on all layers - PacketsReceived uint32 `json:"packets-received"` - PacketsSent uint32 `json:"packets-sent"` - BytesReceived uint64 `json:"bytes-received"` - BytesSent uint64 `json:"bytes-sent"` - - // For layer 0 if REMB is enabled - REMBBitrate uint32 `json:"remb-bitrate"` -} - -func (e janusEventMediaStats) String() string { - return marshalEvent(e) -} - -// type=64 -type janusEventPlugin struct { - Plugin string `json:"plugin"` - Data json.RawMessage `json:"data"` -} - -func (e janusEventPlugin) String() string { - return marshalEvent(e) -} - -type janusEventTransportWebsocket struct { - Event string `json:"event"` - AdminApi bool `json:"admin_api,omitempty"` - IP string `json:"ip,omitempty"` -} - -// type=128 -type janusEventTransport struct { - Transport string `json:"transport"` - Id string `json:"id"` - Data janusEventTransportWebsocket `json:"data"` -} - -func (e janusEventTransport) String() string { - return marshalEvent(e) -} - -type janusEventDependenciesInfo struct { - Glib2 string `json:"glib2"` - Jansson string `json:"jansson"` - Libnice string `json:"libnice"` - Libsrtp string `json:"libsrtp"` - Libcurl string `json:"libcurl,omitempty"` - Crypto string `json:"crypto"` -} - -func (e janusEventDependenciesInfo) String() string { - return marshalEvent(e) -} - -type janusEventPluginInfo struct { - Name string `json:"name"` - Author string `json:"author"` - Description string `json:"description"` - VersionString string `json:"version_string"` - Version int `json:"version"` -} - -func (e janusEventPluginInfo) String() string { - return marshalEvent(e) -} - -// type=256, subtype=1, status="startup" -type janusEventStatusStartupInfo struct { - Janus string `json:"janus"` - Version int `json:"version"` - VersionString string `json:"version_string"` - Author string `json:"author"` - CommitHash string `json:"commit-hash"` - CompileTime string `json:"compile-time"` - LogToStdout bool `json:"log-to-stdout"` - LogToFile bool `json:"log-to-file"` - LogPath string `json:"log-path,omitempty"` - DataChannels bool `json:"data_channels"` - AcceptingNewSessions bool `json:"accepting-new-sessions"` - SessionTimeout int `json:"session-timeout"` - ReclaimSessionTimeout int `json:"reclaim-session-timeout"` - CandidatesTimeout int `json:"candidates-timeout"` - ServerName string `json:"server-name"` - LocalIP string `json:"local-ip"` - PublicIP string `json:"public-ip,omitempty"` - PublicIPs []string `json:"public-ips,omitempty"` - IPv6 bool `json:"ipv6"` - IPv6LinkLocal bool `json:"ipv6-link-local,omitempty"` - ICELite bool `json:"ice-lite"` - ICETCP bool `json:"ice-tcp"` - ICENomination string `json:"ice-nomination,omitempty"` - ICEConsentFreshness bool `json:"ice-consent-freshness"` - ICEKeepaliveConncheck bool `json:"ice-keepalive-conncheck"` - HangupOnFailed bool `json:"hangup-on-failed"` - FullTrickle bool `json:"full-trickle"` - MDNSEnabled bool `json:"mdns-enabled"` - MinNACKQueue int `json:"min-nack-queue"` - NACKOptimizations bool `json:"nack-optimizations"` - TWCCPeriod int `json:"twcc-period"` - DSCP int `json:"dscp,omitempty"` - DTLSMCU int `json:"dtls-mcu"` - STUNServer string `json:"stun-server,omitempty"` - TURNServer string `json:"turn-server,omitempty"` - AllowForceRelay bool `json:"allow-force-relay,omitempty"` - StaticEventLoops int `json:"static-event-loops"` - LoopIndication bool `json:"loop-indication,omitempty"` - APISecret bool `json:"api_secret"` - AuthToken bool `json:"auth_token"` - EventHandlers bool `json:"event_handlers"` - OpaqueIdInAPI bool `json:"opaqueid_in_api"` - WebRTCEncryption bool `json:"webrtc_encryption"` - - Dependencies *janusEventDependenciesInfo `json:"dependencies,omitempty"` - Transports map[string]janusEventPluginInfo `json:"transports,omitempty"` - Events map[string]janusEventPluginInfo `json:"events,omitempty"` - Loggers map[string]janusEventPluginInfo `json:"loggers,omitempty"` - Plugins map[string]janusEventPluginInfo `json:"plugins,omitempty"` -} - -func (e janusEventStatusStartupInfo) String() string { - return marshalEvent(e) -} - -// type=256, subtype=1, status="update" -type janusEventStatusUpdateInfo struct { - Sessions int `json:"sessions"` - Handles int `json:"handles"` - PeerConnections int `json:"peerconnections"` - StatsPeriod int `json:"stats-period"` -} - -func (e janusEventStatusUpdateInfo) String() string { - return marshalEvent(e) -} - -// type=256, subtype=1 -type janusEventCoreStartup struct { - Status string `json:"status"` - Info json.RawMessage `json:"info"` -} - -func (e janusEventCoreStartup) String() string { - return marshalEvent(e) -} - -// type=256, subtype=2 -type janusEventCoreShutdown struct { - Status string `json:"status"` - Signum int `json:"signum"` -} - -func (e janusEventCoreShutdown) String() string { - return marshalEvent(e) -} - type EventHandler interface { UpdateBandwidth(handle uint64, media string, sent api.Bandwidth, received api.Bandwidth) } From 26f4a31871b9e2289df48094421e45f0ea8fddf4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 26 Feb 2026 08:16:31 +0100 Subject: [PATCH 538/549] Update generated files. --- sfu/janus/api_easyjson.go | 3467 +++++++++++++++++++++++++++++++++++++ 1 file changed, 3467 insertions(+) create mode 100644 sfu/janus/api_easyjson.go diff --git a/sfu/janus/api_easyjson.go b/sfu/janus/api_easyjson.go new file mode 100644 index 0000000..ed78b1a --- /dev/null +++ b/sfu/janus/api_easyjson.go @@ -0,0 +1,3467 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package janus + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus(in *jlexer.Lexer, out *janusMediaStatsRTTValues) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "ntp": + if in.IsNull() { + in.Skip() + } else { + out.NTP = uint32(in.Uint32()) + } + case "lsr": + if in.IsNull() { + in.Skip() + } else { + out.LSR = uint32(in.Uint32()) + } + case "dlsr": + if in.IsNull() { + in.Skip() + } else { + out.DLSR = uint32(in.Uint32()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus(out *jwriter.Writer, in janusMediaStatsRTTValues) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ntp\":" + out.RawString(prefix[1:]) + out.Uint32(uint32(in.NTP)) + } + { + const prefix string = ",\"lsr\":" + out.RawString(prefix) + out.Uint32(uint32(in.LSR)) + } + { + const prefix string = ",\"dlsr\":" + out.RawString(prefix) + out.Uint32(uint32(in.DLSR)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusMediaStatsRTTValues) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusMediaStatsRTTValues) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusMediaStatsRTTValues) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusMediaStatsRTTValues) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus1(in *jlexer.Lexer, out *janusEventWebRTCSelectedPair) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "stream_id": + if in.IsNull() { + in.Skip() + } else { + out.StreamID = int(in.Int()) + } + case "component_id": + if in.IsNull() { + in.Skip() + } else { + out.ComponentID = int(in.Int()) + } + case "selected-pair": + if in.IsNull() { + in.Skip() + } else { + out.SelectedPair = string(in.String()) + } + case "candidates": + if in.IsNull() { + in.Skip() + } else { + (out.Candidates).UnmarshalEasyJSON(in) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus1(out *jwriter.Writer, in janusEventWebRTCSelectedPair) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"stream_id\":" + out.RawString(prefix[1:]) + out.Int(int(in.StreamID)) + } + { + const prefix string = ",\"component_id\":" + out.RawString(prefix) + out.Int(int(in.ComponentID)) + } + { + const prefix string = ",\"selected-pair\":" + out.RawString(prefix) + out.String(string(in.SelectedPair)) + } + { + const prefix string = ",\"candidates\":" + out.RawString(prefix) + (in.Candidates).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventWebRTCSelectedPair) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventWebRTCSelectedPair) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventWebRTCSelectedPair) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventWebRTCSelectedPair) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus1(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus2(in *jlexer.Lexer, out *janusEventWebRTCRemoteCandidate) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "remote-candidate": + if in.IsNull() { + in.Skip() + } else { + out.RemoteCandidate = string(in.String()) + } + case "stream_id": + if in.IsNull() { + in.Skip() + } else { + out.StreamID = int(in.Int()) + } + case "component_id": + if in.IsNull() { + in.Skip() + } else { + out.ComponentID = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus2(out *jwriter.Writer, in janusEventWebRTCRemoteCandidate) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"remote-candidate\":" + out.RawString(prefix[1:]) + out.String(string(in.RemoteCandidate)) + } + { + const prefix string = ",\"stream_id\":" + out.RawString(prefix) + out.Int(int(in.StreamID)) + } + { + const prefix string = ",\"component_id\":" + out.RawString(prefix) + out.Int(int(in.ComponentID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventWebRTCRemoteCandidate) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventWebRTCRemoteCandidate) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventWebRTCRemoteCandidate) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventWebRTCRemoteCandidate) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus2(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus3(in *jlexer.Lexer, out *janusEventWebRTCPeerConnection) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "connection": + if in.IsNull() { + in.Skip() + } else { + out.Connection = string(in.String()) + } + case "reason": + if in.IsNull() { + in.Skip() + } else { + out.Reason = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus3(out *jwriter.Writer, in janusEventWebRTCPeerConnection) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"connection\":" + out.RawString(prefix[1:]) + out.String(string(in.Connection)) + } + if in.Reason != "" { + const prefix string = ",\"reason\":" + out.RawString(prefix) + out.String(string(in.Reason)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventWebRTCPeerConnection) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventWebRTCPeerConnection) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventWebRTCPeerConnection) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventWebRTCPeerConnection) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus3(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus4(in *jlexer.Lexer, out *janusEventWebRTCLocalCandidate) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "local-candidate": + if in.IsNull() { + in.Skip() + } else { + out.LocalCandidate = string(in.String()) + } + case "stream_id": + if in.IsNull() { + in.Skip() + } else { + out.StreamID = int(in.Int()) + } + case "component_id": + if in.IsNull() { + in.Skip() + } else { + out.ComponentID = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus4(out *jwriter.Writer, in janusEventWebRTCLocalCandidate) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"local-candidate\":" + out.RawString(prefix[1:]) + out.String(string(in.LocalCandidate)) + } + { + const prefix string = ",\"stream_id\":" + out.RawString(prefix) + out.Int(int(in.StreamID)) + } + { + const prefix string = ",\"component_id\":" + out.RawString(prefix) + out.Int(int(in.ComponentID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventWebRTCLocalCandidate) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus4(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventWebRTCLocalCandidate) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus4(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventWebRTCLocalCandidate) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus4(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventWebRTCLocalCandidate) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus4(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus5(in *jlexer.Lexer, out *janusEventWebRTCICE) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "ice": + if in.IsNull() { + in.Skip() + } else { + out.ICE = string(in.String()) + } + case "stream_id": + if in.IsNull() { + in.Skip() + } else { + out.StreamID = int(in.Int()) + } + case "component_id": + if in.IsNull() { + in.Skip() + } else { + out.ComponentID = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus5(out *jwriter.Writer, in janusEventWebRTCICE) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ice\":" + out.RawString(prefix[1:]) + out.String(string(in.ICE)) + } + { + const prefix string = ",\"stream_id\":" + out.RawString(prefix) + out.Int(int(in.StreamID)) + } + { + const prefix string = ",\"component_id\":" + out.RawString(prefix) + out.Int(int(in.ComponentID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventWebRTCICE) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus5(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventWebRTCICE) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus5(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventWebRTCICE) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus5(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventWebRTCICE) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus5(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus6(in *jlexer.Lexer, out *janusEventWebRTCDTLS) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "dtls": + if in.IsNull() { + in.Skip() + } else { + out.DTLS = string(in.String()) + } + case "stream_id": + if in.IsNull() { + in.Skip() + } else { + out.StreamID = int(in.Int()) + } + case "component_id": + if in.IsNull() { + in.Skip() + } else { + out.ComponentID = int(in.Int()) + } + case "retransmissions": + if in.IsNull() { + in.Skip() + } else { + out.Retransmissions = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus6(out *jwriter.Writer, in janusEventWebRTCDTLS) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"dtls\":" + out.RawString(prefix[1:]) + out.String(string(in.DTLS)) + } + { + const prefix string = ",\"stream_id\":" + out.RawString(prefix) + out.Int(int(in.StreamID)) + } + { + const prefix string = ",\"component_id\":" + out.RawString(prefix) + out.Int(int(in.ComponentID)) + } + { + const prefix string = ",\"retransmissions\":" + out.RawString(prefix) + out.Int(int(in.Retransmissions)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventWebRTCDTLS) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus6(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventWebRTCDTLS) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus6(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventWebRTCDTLS) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus6(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventWebRTCDTLS) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus6(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus7(in *jlexer.Lexer, out *janusEventTransportWebsocket) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "event": + if in.IsNull() { + in.Skip() + } else { + out.Event = string(in.String()) + } + case "admin_api": + if in.IsNull() { + in.Skip() + } else { + out.AdminApi = bool(in.Bool()) + } + case "ip": + if in.IsNull() { + in.Skip() + } else { + out.IP = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus7(out *jwriter.Writer, in janusEventTransportWebsocket) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"event\":" + out.RawString(prefix[1:]) + out.String(string(in.Event)) + } + if in.AdminApi { + const prefix string = ",\"admin_api\":" + out.RawString(prefix) + out.Bool(bool(in.AdminApi)) + } + if in.IP != "" { + const prefix string = ",\"ip\":" + out.RawString(prefix) + out.String(string(in.IP)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventTransportWebsocket) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus7(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventTransportWebsocket) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus7(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventTransportWebsocket) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus7(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventTransportWebsocket) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus7(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus8(in *jlexer.Lexer, out *janusEventTransport) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "transport": + if in.IsNull() { + in.Skip() + } else { + out.Transport = string(in.String()) + } + case "id": + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } + case "data": + if in.IsNull() { + in.Skip() + } else { + (out.Data).UnmarshalEasyJSON(in) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus8(out *jwriter.Writer, in janusEventTransport) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"transport\":" + out.RawString(prefix[1:]) + out.String(string(in.Transport)) + } + { + const prefix string = ",\"id\":" + out.RawString(prefix) + out.String(string(in.Id)) + } + { + const prefix string = ",\"data\":" + out.RawString(prefix) + (in.Data).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventTransport) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus8(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventTransport) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus8(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventTransport) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus8(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventTransport) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus8(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus9(in *jlexer.Lexer, out *janusEventStatusUpdateInfo) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "sessions": + if in.IsNull() { + in.Skip() + } else { + out.Sessions = int(in.Int()) + } + case "handles": + if in.IsNull() { + in.Skip() + } else { + out.Handles = int(in.Int()) + } + case "peerconnections": + if in.IsNull() { + in.Skip() + } else { + out.PeerConnections = int(in.Int()) + } + case "stats-period": + if in.IsNull() { + in.Skip() + } else { + out.StatsPeriod = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus9(out *jwriter.Writer, in janusEventStatusUpdateInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"sessions\":" + out.RawString(prefix[1:]) + out.Int(int(in.Sessions)) + } + { + const prefix string = ",\"handles\":" + out.RawString(prefix) + out.Int(int(in.Handles)) + } + { + const prefix string = ",\"peerconnections\":" + out.RawString(prefix) + out.Int(int(in.PeerConnections)) + } + { + const prefix string = ",\"stats-period\":" + out.RawString(prefix) + out.Int(int(in.StatsPeriod)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventStatusUpdateInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus9(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventStatusUpdateInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus9(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventStatusUpdateInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus9(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventStatusUpdateInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus9(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus10(in *jlexer.Lexer, out *janusEventStatusStartupInfo) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "janus": + if in.IsNull() { + in.Skip() + } else { + out.Janus = string(in.String()) + } + case "version": + if in.IsNull() { + in.Skip() + } else { + out.Version = int(in.Int()) + } + case "version_string": + if in.IsNull() { + in.Skip() + } else { + out.VersionString = string(in.String()) + } + case "author": + if in.IsNull() { + in.Skip() + } else { + out.Author = string(in.String()) + } + case "commit-hash": + if in.IsNull() { + in.Skip() + } else { + out.CommitHash = string(in.String()) + } + case "compile-time": + if in.IsNull() { + in.Skip() + } else { + out.CompileTime = string(in.String()) + } + case "log-to-stdout": + if in.IsNull() { + in.Skip() + } else { + out.LogToStdout = bool(in.Bool()) + } + case "log-to-file": + if in.IsNull() { + in.Skip() + } else { + out.LogToFile = bool(in.Bool()) + } + case "log-path": + if in.IsNull() { + in.Skip() + } else { + out.LogPath = string(in.String()) + } + case "data_channels": + if in.IsNull() { + in.Skip() + } else { + out.DataChannels = bool(in.Bool()) + } + case "accepting-new-sessions": + if in.IsNull() { + in.Skip() + } else { + out.AcceptingNewSessions = bool(in.Bool()) + } + case "session-timeout": + if in.IsNull() { + in.Skip() + } else { + out.SessionTimeout = int(in.Int()) + } + case "reclaim-session-timeout": + if in.IsNull() { + in.Skip() + } else { + out.ReclaimSessionTimeout = int(in.Int()) + } + case "candidates-timeout": + if in.IsNull() { + in.Skip() + } else { + out.CandidatesTimeout = int(in.Int()) + } + case "server-name": + if in.IsNull() { + in.Skip() + } else { + out.ServerName = string(in.String()) + } + case "local-ip": + if in.IsNull() { + in.Skip() + } else { + out.LocalIP = string(in.String()) + } + case "public-ip": + if in.IsNull() { + in.Skip() + } else { + out.PublicIP = string(in.String()) + } + case "public-ips": + if in.IsNull() { + in.Skip() + out.PublicIPs = nil + } else { + in.Delim('[') + if out.PublicIPs == nil { + if !in.IsDelim(']') { + out.PublicIPs = make([]string, 0, 4) + } else { + out.PublicIPs = []string{} + } + } else { + out.PublicIPs = (out.PublicIPs)[:0] + } + for !in.IsDelim(']') { + var v1 string + if in.IsNull() { + in.Skip() + } else { + v1 = string(in.String()) + } + out.PublicIPs = append(out.PublicIPs, v1) + in.WantComma() + } + in.Delim(']') + } + case "ipv6": + if in.IsNull() { + in.Skip() + } else { + out.IPv6 = bool(in.Bool()) + } + case "ipv6-link-local": + if in.IsNull() { + in.Skip() + } else { + out.IPv6LinkLocal = bool(in.Bool()) + } + case "ice-lite": + if in.IsNull() { + in.Skip() + } else { + out.ICELite = bool(in.Bool()) + } + case "ice-tcp": + if in.IsNull() { + in.Skip() + } else { + out.ICETCP = bool(in.Bool()) + } + case "ice-nomination": + if in.IsNull() { + in.Skip() + } else { + out.ICENomination = string(in.String()) + } + case "ice-consent-freshness": + if in.IsNull() { + in.Skip() + } else { + out.ICEConsentFreshness = bool(in.Bool()) + } + case "ice-keepalive-conncheck": + if in.IsNull() { + in.Skip() + } else { + out.ICEKeepaliveConncheck = bool(in.Bool()) + } + case "hangup-on-failed": + if in.IsNull() { + in.Skip() + } else { + out.HangupOnFailed = bool(in.Bool()) + } + case "full-trickle": + if in.IsNull() { + in.Skip() + } else { + out.FullTrickle = bool(in.Bool()) + } + case "mdns-enabled": + if in.IsNull() { + in.Skip() + } else { + out.MDNSEnabled = bool(in.Bool()) + } + case "min-nack-queue": + if in.IsNull() { + in.Skip() + } else { + out.MinNACKQueue = int(in.Int()) + } + case "nack-optimizations": + if in.IsNull() { + in.Skip() + } else { + out.NACKOptimizations = bool(in.Bool()) + } + case "twcc-period": + if in.IsNull() { + in.Skip() + } else { + out.TWCCPeriod = int(in.Int()) + } + case "dscp": + if in.IsNull() { + in.Skip() + } else { + out.DSCP = int(in.Int()) + } + case "dtls-mcu": + if in.IsNull() { + in.Skip() + } else { + out.DTLSMCU = int(in.Int()) + } + case "stun-server": + if in.IsNull() { + in.Skip() + } else { + out.STUNServer = string(in.String()) + } + case "turn-server": + if in.IsNull() { + in.Skip() + } else { + out.TURNServer = string(in.String()) + } + case "allow-force-relay": + if in.IsNull() { + in.Skip() + } else { + out.AllowForceRelay = bool(in.Bool()) + } + case "static-event-loops": + if in.IsNull() { + in.Skip() + } else { + out.StaticEventLoops = int(in.Int()) + } + case "loop-indication": + if in.IsNull() { + in.Skip() + } else { + out.LoopIndication = bool(in.Bool()) + } + case "api_secret": + if in.IsNull() { + in.Skip() + } else { + out.APISecret = bool(in.Bool()) + } + case "auth_token": + if in.IsNull() { + in.Skip() + } else { + out.AuthToken = bool(in.Bool()) + } + case "event_handlers": + if in.IsNull() { + in.Skip() + } else { + out.EventHandlers = bool(in.Bool()) + } + case "opaqueid_in_api": + if in.IsNull() { + in.Skip() + } else { + out.OpaqueIdInAPI = bool(in.Bool()) + } + case "webrtc_encryption": + if in.IsNull() { + in.Skip() + } else { + out.WebRTCEncryption = bool(in.Bool()) + } + case "dependencies": + if in.IsNull() { + in.Skip() + out.Dependencies = nil + } else { + if out.Dependencies == nil { + out.Dependencies = new(janusEventDependenciesInfo) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Dependencies).UnmarshalEasyJSON(in) + } + } + case "transports": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Transports = make(map[string]janusEventPluginInfo) + } else { + out.Transports = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v2 janusEventPluginInfo + if in.IsNull() { + in.Skip() + } else { + (v2).UnmarshalEasyJSON(in) + } + (out.Transports)[key] = v2 + in.WantComma() + } + in.Delim('}') + } + case "events": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Events = make(map[string]janusEventPluginInfo) + } else { + out.Events = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v3 janusEventPluginInfo + if in.IsNull() { + in.Skip() + } else { + (v3).UnmarshalEasyJSON(in) + } + (out.Events)[key] = v3 + in.WantComma() + } + in.Delim('}') + } + case "loggers": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Loggers = make(map[string]janusEventPluginInfo) + } else { + out.Loggers = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v4 janusEventPluginInfo + if in.IsNull() { + in.Skip() + } else { + (v4).UnmarshalEasyJSON(in) + } + (out.Loggers)[key] = v4 + in.WantComma() + } + in.Delim('}') + } + case "plugins": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Plugins = make(map[string]janusEventPluginInfo) + } else { + out.Plugins = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v5 janusEventPluginInfo + if in.IsNull() { + in.Skip() + } else { + (v5).UnmarshalEasyJSON(in) + } + (out.Plugins)[key] = v5 + in.WantComma() + } + in.Delim('}') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus10(out *jwriter.Writer, in janusEventStatusStartupInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"janus\":" + out.RawString(prefix[1:]) + out.String(string(in.Janus)) + } + { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.Int(int(in.Version)) + } + { + const prefix string = ",\"version_string\":" + out.RawString(prefix) + out.String(string(in.VersionString)) + } + { + const prefix string = ",\"author\":" + out.RawString(prefix) + out.String(string(in.Author)) + } + { + const prefix string = ",\"commit-hash\":" + out.RawString(prefix) + out.String(string(in.CommitHash)) + } + { + const prefix string = ",\"compile-time\":" + out.RawString(prefix) + out.String(string(in.CompileTime)) + } + { + const prefix string = ",\"log-to-stdout\":" + out.RawString(prefix) + out.Bool(bool(in.LogToStdout)) + } + { + const prefix string = ",\"log-to-file\":" + out.RawString(prefix) + out.Bool(bool(in.LogToFile)) + } + if in.LogPath != "" { + const prefix string = ",\"log-path\":" + out.RawString(prefix) + out.String(string(in.LogPath)) + } + { + const prefix string = ",\"data_channels\":" + out.RawString(prefix) + out.Bool(bool(in.DataChannels)) + } + { + const prefix string = ",\"accepting-new-sessions\":" + out.RawString(prefix) + out.Bool(bool(in.AcceptingNewSessions)) + } + { + const prefix string = ",\"session-timeout\":" + out.RawString(prefix) + out.Int(int(in.SessionTimeout)) + } + { + const prefix string = ",\"reclaim-session-timeout\":" + out.RawString(prefix) + out.Int(int(in.ReclaimSessionTimeout)) + } + { + const prefix string = ",\"candidates-timeout\":" + out.RawString(prefix) + out.Int(int(in.CandidatesTimeout)) + } + { + const prefix string = ",\"server-name\":" + out.RawString(prefix) + out.String(string(in.ServerName)) + } + { + const prefix string = ",\"local-ip\":" + out.RawString(prefix) + out.String(string(in.LocalIP)) + } + if in.PublicIP != "" { + const prefix string = ",\"public-ip\":" + out.RawString(prefix) + out.String(string(in.PublicIP)) + } + if len(in.PublicIPs) != 0 { + const prefix string = ",\"public-ips\":" + out.RawString(prefix) + { + out.RawByte('[') + for v6, v7 := range in.PublicIPs { + if v6 > 0 { + out.RawByte(',') + } + out.String(string(v7)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"ipv6\":" + out.RawString(prefix) + out.Bool(bool(in.IPv6)) + } + if in.IPv6LinkLocal { + const prefix string = ",\"ipv6-link-local\":" + out.RawString(prefix) + out.Bool(bool(in.IPv6LinkLocal)) + } + { + const prefix string = ",\"ice-lite\":" + out.RawString(prefix) + out.Bool(bool(in.ICELite)) + } + { + const prefix string = ",\"ice-tcp\":" + out.RawString(prefix) + out.Bool(bool(in.ICETCP)) + } + if in.ICENomination != "" { + const prefix string = ",\"ice-nomination\":" + out.RawString(prefix) + out.String(string(in.ICENomination)) + } + { + const prefix string = ",\"ice-consent-freshness\":" + out.RawString(prefix) + out.Bool(bool(in.ICEConsentFreshness)) + } + { + const prefix string = ",\"ice-keepalive-conncheck\":" + out.RawString(prefix) + out.Bool(bool(in.ICEKeepaliveConncheck)) + } + { + const prefix string = ",\"hangup-on-failed\":" + out.RawString(prefix) + out.Bool(bool(in.HangupOnFailed)) + } + { + const prefix string = ",\"full-trickle\":" + out.RawString(prefix) + out.Bool(bool(in.FullTrickle)) + } + { + const prefix string = ",\"mdns-enabled\":" + out.RawString(prefix) + out.Bool(bool(in.MDNSEnabled)) + } + { + const prefix string = ",\"min-nack-queue\":" + out.RawString(prefix) + out.Int(int(in.MinNACKQueue)) + } + { + const prefix string = ",\"nack-optimizations\":" + out.RawString(prefix) + out.Bool(bool(in.NACKOptimizations)) + } + { + const prefix string = ",\"twcc-period\":" + out.RawString(prefix) + out.Int(int(in.TWCCPeriod)) + } + if in.DSCP != 0 { + const prefix string = ",\"dscp\":" + out.RawString(prefix) + out.Int(int(in.DSCP)) + } + { + const prefix string = ",\"dtls-mcu\":" + out.RawString(prefix) + out.Int(int(in.DTLSMCU)) + } + if in.STUNServer != "" { + const prefix string = ",\"stun-server\":" + out.RawString(prefix) + out.String(string(in.STUNServer)) + } + if in.TURNServer != "" { + const prefix string = ",\"turn-server\":" + out.RawString(prefix) + out.String(string(in.TURNServer)) + } + if in.AllowForceRelay { + const prefix string = ",\"allow-force-relay\":" + out.RawString(prefix) + out.Bool(bool(in.AllowForceRelay)) + } + { + const prefix string = ",\"static-event-loops\":" + out.RawString(prefix) + out.Int(int(in.StaticEventLoops)) + } + if in.LoopIndication { + const prefix string = ",\"loop-indication\":" + out.RawString(prefix) + out.Bool(bool(in.LoopIndication)) + } + { + const prefix string = ",\"api_secret\":" + out.RawString(prefix) + out.Bool(bool(in.APISecret)) + } + { + const prefix string = ",\"auth_token\":" + out.RawString(prefix) + out.Bool(bool(in.AuthToken)) + } + { + const prefix string = ",\"event_handlers\":" + out.RawString(prefix) + out.Bool(bool(in.EventHandlers)) + } + { + const prefix string = ",\"opaqueid_in_api\":" + out.RawString(prefix) + out.Bool(bool(in.OpaqueIdInAPI)) + } + { + const prefix string = ",\"webrtc_encryption\":" + out.RawString(prefix) + out.Bool(bool(in.WebRTCEncryption)) + } + if in.Dependencies != nil { + const prefix string = ",\"dependencies\":" + out.RawString(prefix) + (*in.Dependencies).MarshalEasyJSON(out) + } + if len(in.Transports) != 0 { + const prefix string = ",\"transports\":" + out.RawString(prefix) + { + out.RawByte('{') + v8First := true + for v8Name, v8Value := range in.Transports { + if v8First { + v8First = false + } else { + out.RawByte(',') + } + out.String(string(v8Name)) + out.RawByte(':') + (v8Value).MarshalEasyJSON(out) + } + out.RawByte('}') + } + } + if len(in.Events) != 0 { + const prefix string = ",\"events\":" + out.RawString(prefix) + { + out.RawByte('{') + v9First := true + for v9Name, v9Value := range in.Events { + if v9First { + v9First = false + } else { + out.RawByte(',') + } + out.String(string(v9Name)) + out.RawByte(':') + (v9Value).MarshalEasyJSON(out) + } + out.RawByte('}') + } + } + if len(in.Loggers) != 0 { + const prefix string = ",\"loggers\":" + out.RawString(prefix) + { + out.RawByte('{') + v10First := true + for v10Name, v10Value := range in.Loggers { + if v10First { + v10First = false + } else { + out.RawByte(',') + } + out.String(string(v10Name)) + out.RawByte(':') + (v10Value).MarshalEasyJSON(out) + } + out.RawByte('}') + } + } + if len(in.Plugins) != 0 { + const prefix string = ",\"plugins\":" + out.RawString(prefix) + { + out.RawByte('{') + v11First := true + for v11Name, v11Value := range in.Plugins { + if v11First { + v11First = false + } else { + out.RawByte(',') + } + out.String(string(v11Name)) + out.RawByte(':') + (v11Value).MarshalEasyJSON(out) + } + out.RawByte('}') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventStatusStartupInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus10(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventStatusStartupInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus10(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventStatusStartupInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus10(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventStatusStartupInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus10(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus11(in *jlexer.Lexer, out *janusEventSessionTransport) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "transport": + if in.IsNull() { + in.Skip() + } else { + out.Transport = string(in.String()) + } + case "id": + if in.IsNull() { + in.Skip() + } else { + out.ID = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus11(out *jwriter.Writer, in janusEventSessionTransport) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"transport\":" + out.RawString(prefix[1:]) + out.String(string(in.Transport)) + } + { + const prefix string = ",\"id\":" + out.RawString(prefix) + out.String(string(in.ID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventSessionTransport) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus11(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventSessionTransport) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus11(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventSessionTransport) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus11(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventSessionTransport) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus11(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus12(in *jlexer.Lexer, out *janusEventSession) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "name": + if in.IsNull() { + in.Skip() + } else { + out.Name = string(in.String()) + } + case "transport": + if in.IsNull() { + in.Skip() + out.Transport = nil + } else { + if out.Transport == nil { + out.Transport = new(janusEventSessionTransport) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Transport).UnmarshalEasyJSON(in) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus12(out *jwriter.Writer, in janusEventSession) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"name\":" + out.RawString(prefix[1:]) + out.String(string(in.Name)) + } + if in.Transport != nil { + const prefix string = ",\"transport\":" + out.RawString(prefix) + (*in.Transport).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventSession) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus12(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventSession) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus12(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventSession) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus12(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventSession) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus12(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus13(in *jlexer.Lexer, out *janusEventPluginInfo) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "name": + if in.IsNull() { + in.Skip() + } else { + out.Name = string(in.String()) + } + case "author": + if in.IsNull() { + in.Skip() + } else { + out.Author = string(in.String()) + } + case "description": + if in.IsNull() { + in.Skip() + } else { + out.Description = string(in.String()) + } + case "version_string": + if in.IsNull() { + in.Skip() + } else { + out.VersionString = string(in.String()) + } + case "version": + if in.IsNull() { + in.Skip() + } else { + out.Version = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus13(out *jwriter.Writer, in janusEventPluginInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"name\":" + out.RawString(prefix[1:]) + out.String(string(in.Name)) + } + { + const prefix string = ",\"author\":" + out.RawString(prefix) + out.String(string(in.Author)) + } + { + const prefix string = ",\"description\":" + out.RawString(prefix) + out.String(string(in.Description)) + } + { + const prefix string = ",\"version_string\":" + out.RawString(prefix) + out.String(string(in.VersionString)) + } + { + const prefix string = ",\"version\":" + out.RawString(prefix) + out.Int(int(in.Version)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventPluginInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus13(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventPluginInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus13(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventPluginInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus13(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventPluginInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus13(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus14(in *jlexer.Lexer, out *janusEventPlugin) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "plugin": + if in.IsNull() { + in.Skip() + } else { + out.Plugin = string(in.String()) + } + case "data": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus14(out *jwriter.Writer, in janusEventPlugin) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"plugin\":" + out.RawString(prefix[1:]) + out.String(string(in.Plugin)) + } + { + const prefix string = ",\"data\":" + out.RawString(prefix) + out.Raw((in.Data).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventPlugin) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus14(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventPlugin) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus14(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventPlugin) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus14(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventPlugin) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus14(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus15(in *jlexer.Lexer, out *janusEventMediaStats) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "mid": + if in.IsNull() { + in.Skip() + } else { + out.MID = string(in.String()) + } + case "mindex": + if in.IsNull() { + in.Skip() + } else { + out.MIndex = int(in.Int()) + } + case "media": + if in.IsNull() { + in.Skip() + } else { + out.Media = string(in.String()) + } + case "codec": + if in.IsNull() { + in.Skip() + } else { + out.Codec = string(in.String()) + } + case "base": + if in.IsNull() { + in.Skip() + } else { + out.Base = uint32(in.Uint32()) + } + case "lost": + if in.IsNull() { + in.Skip() + } else { + out.Lost = int32(in.Int32()) + } + case "lost-by-remote": + if in.IsNull() { + in.Skip() + } else { + out.LostByRemote = int32(in.Int32()) + } + case "jitter-local": + if in.IsNull() { + in.Skip() + } else { + out.JitterLocal = uint32(in.Uint32()) + } + case "jitter-remote": + if in.IsNull() { + in.Skip() + } else { + out.JitterRemote = uint32(in.Uint32()) + } + case "in-link-quality": + if in.IsNull() { + in.Skip() + } else { + out.InLinkQuality = uint32(in.Uint32()) + } + case "in-media-link-quality": + if in.IsNull() { + in.Skip() + } else { + out.InMediaLinkQuality = uint32(in.Uint32()) + } + case "out-link-quality": + if in.IsNull() { + in.Skip() + } else { + out.OutLinkQuality = uint32(in.Uint32()) + } + case "out-media-link-quality": + if in.IsNull() { + in.Skip() + } else { + out.OutMediaLinkQuality = uint32(in.Uint32()) + } + case "bytes-received-lastsec": + if in.IsNull() { + in.Skip() + } else { + out.BytesReceivedLastSec = uint32(in.Uint32()) + } + case "bytes-sent-lastsec": + if in.IsNull() { + in.Skip() + } else { + out.BytesSentLastSec = uint32(in.Uint32()) + } + case "nacks-received": + if in.IsNull() { + in.Skip() + } else { + out.NacksReceived = uint32(in.Uint32()) + } + case "nacks-sent": + if in.IsNull() { + in.Skip() + } else { + out.NacksSent = uint32(in.Uint32()) + } + case "retransmissions-received": + if in.IsNull() { + in.Skip() + } else { + out.RetransmissionsReceived = uint32(in.Uint32()) + } + case "rtt": + if in.IsNull() { + in.Skip() + } else { + out.RTT = uint32(in.Uint32()) + } + case "rtt-values": + if in.IsNull() { + in.Skip() + out.RTTValues = nil + } else { + if out.RTTValues == nil { + out.RTTValues = new(janusMediaStatsRTTValues) + } + if in.IsNull() { + in.Skip() + } else { + (*out.RTTValues).UnmarshalEasyJSON(in) + } + } + case "packets-received": + if in.IsNull() { + in.Skip() + } else { + out.PacketsReceived = uint32(in.Uint32()) + } + case "packets-sent": + if in.IsNull() { + in.Skip() + } else { + out.PacketsSent = uint32(in.Uint32()) + } + case "bytes-received": + if in.IsNull() { + in.Skip() + } else { + out.BytesReceived = uint64(in.Uint64()) + } + case "bytes-sent": + if in.IsNull() { + in.Skip() + } else { + out.BytesSent = uint64(in.Uint64()) + } + case "remb-bitrate": + if in.IsNull() { + in.Skip() + } else { + out.REMBBitrate = uint32(in.Uint32()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus15(out *jwriter.Writer, in janusEventMediaStats) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"mid\":" + out.RawString(prefix[1:]) + out.String(string(in.MID)) + } + { + const prefix string = ",\"mindex\":" + out.RawString(prefix) + out.Int(int(in.MIndex)) + } + { + const prefix string = ",\"media\":" + out.RawString(prefix) + out.String(string(in.Media)) + } + if in.Codec != "" { + const prefix string = ",\"codec\":" + out.RawString(prefix) + out.String(string(in.Codec)) + } + { + const prefix string = ",\"base\":" + out.RawString(prefix) + out.Uint32(uint32(in.Base)) + } + { + const prefix string = ",\"lost\":" + out.RawString(prefix) + out.Int32(int32(in.Lost)) + } + { + const prefix string = ",\"lost-by-remote\":" + out.RawString(prefix) + out.Int32(int32(in.LostByRemote)) + } + { + const prefix string = ",\"jitter-local\":" + out.RawString(prefix) + out.Uint32(uint32(in.JitterLocal)) + } + { + const prefix string = ",\"jitter-remote\":" + out.RawString(prefix) + out.Uint32(uint32(in.JitterRemote)) + } + { + const prefix string = ",\"in-link-quality\":" + out.RawString(prefix) + out.Uint32(uint32(in.InLinkQuality)) + } + { + const prefix string = ",\"in-media-link-quality\":" + out.RawString(prefix) + out.Uint32(uint32(in.InMediaLinkQuality)) + } + { + const prefix string = ",\"out-link-quality\":" + out.RawString(prefix) + out.Uint32(uint32(in.OutLinkQuality)) + } + { + const prefix string = ",\"out-media-link-quality\":" + out.RawString(prefix) + out.Uint32(uint32(in.OutMediaLinkQuality)) + } + { + const prefix string = ",\"bytes-received-lastsec\":" + out.RawString(prefix) + out.Uint32(uint32(in.BytesReceivedLastSec)) + } + { + const prefix string = ",\"bytes-sent-lastsec\":" + out.RawString(prefix) + out.Uint32(uint32(in.BytesSentLastSec)) + } + { + const prefix string = ",\"nacks-received\":" + out.RawString(prefix) + out.Uint32(uint32(in.NacksReceived)) + } + { + const prefix string = ",\"nacks-sent\":" + out.RawString(prefix) + out.Uint32(uint32(in.NacksSent)) + } + { + const prefix string = ",\"retransmissions-received\":" + out.RawString(prefix) + out.Uint32(uint32(in.RetransmissionsReceived)) + } + if in.RTT != 0 { + const prefix string = ",\"rtt\":" + out.RawString(prefix) + out.Uint32(uint32(in.RTT)) + } + if in.RTTValues != nil { + const prefix string = ",\"rtt-values\":" + out.RawString(prefix) + (*in.RTTValues).MarshalEasyJSON(out) + } + { + const prefix string = ",\"packets-received\":" + out.RawString(prefix) + out.Uint32(uint32(in.PacketsReceived)) + } + { + const prefix string = ",\"packets-sent\":" + out.RawString(prefix) + out.Uint32(uint32(in.PacketsSent)) + } + { + const prefix string = ",\"bytes-received\":" + out.RawString(prefix) + out.Uint64(uint64(in.BytesReceived)) + } + { + const prefix string = ",\"bytes-sent\":" + out.RawString(prefix) + out.Uint64(uint64(in.BytesSent)) + } + { + const prefix string = ",\"remb-bitrate\":" + out.RawString(prefix) + out.Uint32(uint32(in.REMBBitrate)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventMediaStats) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus15(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventMediaStats) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus15(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventMediaStats) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus15(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventMediaStats) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus15(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus16(in *jlexer.Lexer, out *janusEventMediaState) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "media": + if in.IsNull() { + in.Skip() + } else { + out.Media = string(in.String()) + } + case "mid": + if in.IsNull() { + in.Skip() + } else { + out.MID = string(in.String()) + } + case "substream": + if in.IsNull() { + in.Skip() + out.SubStream = nil + } else { + if out.SubStream == nil { + out.SubStream = new(int) + } + if in.IsNull() { + in.Skip() + } else { + *out.SubStream = int(in.Int()) + } + } + case "receiving": + if in.IsNull() { + in.Skip() + } else { + out.Receiving = bool(in.Bool()) + } + case "seconds": + if in.IsNull() { + in.Skip() + } else { + out.Seconds = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus16(out *jwriter.Writer, in janusEventMediaState) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"media\":" + out.RawString(prefix[1:]) + out.String(string(in.Media)) + } + { + const prefix string = ",\"mid\":" + out.RawString(prefix) + out.String(string(in.MID)) + } + if in.SubStream != nil { + const prefix string = ",\"substream\":" + out.RawString(prefix) + out.Int(int(*in.SubStream)) + } + { + const prefix string = ",\"receiving\":" + out.RawString(prefix) + out.Bool(bool(in.Receiving)) + } + { + const prefix string = ",\"seconds\":" + out.RawString(prefix) + out.Int(int(in.Seconds)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventMediaState) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus16(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventMediaState) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus16(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventMediaState) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus16(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventMediaState) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus16(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus17(in *jlexer.Lexer, out *janusEventMediaSlowLink) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "media": + if in.IsNull() { + in.Skip() + } else { + out.Media = string(in.String()) + } + case "mid": + if in.IsNull() { + in.Skip() + } else { + out.MID = string(in.String()) + } + case "slow_link": + if in.IsNull() { + in.Skip() + } else { + out.SlowLink = string(in.String()) + } + case "lost_lastsec": + if in.IsNull() { + in.Skip() + } else { + out.LostLastSec = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus17(out *jwriter.Writer, in janusEventMediaSlowLink) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"media\":" + out.RawString(prefix[1:]) + out.String(string(in.Media)) + } + { + const prefix string = ",\"mid\":" + out.RawString(prefix) + out.String(string(in.MID)) + } + { + const prefix string = ",\"slow_link\":" + out.RawString(prefix) + out.String(string(in.SlowLink)) + } + { + const prefix string = ",\"lost_lastsec\":" + out.RawString(prefix) + out.Int(int(in.LostLastSec)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventMediaSlowLink) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus17(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventMediaSlowLink) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus17(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventMediaSlowLink) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus17(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventMediaSlowLink) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus17(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus18(in *jlexer.Lexer, out *janusEventJSEP) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "owner": + if in.IsNull() { + in.Skip() + } else { + out.Owner = string(in.String()) + } + case "jsep": + easyjsonC1cedd36Decode(in, &out.Jsep) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus18(out *jwriter.Writer, in janusEventJSEP) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"owner\":" + out.RawString(prefix[1:]) + out.String(string(in.Owner)) + } + { + const prefix string = ",\"jsep\":" + out.RawString(prefix) + easyjsonC1cedd36Encode(out, in.Jsep) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventJSEP) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus18(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventJSEP) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus18(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventJSEP) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus18(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventJSEP) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus18(l, v) +} +func easyjsonC1cedd36Decode(in *jlexer.Lexer, out *struct { + Type string `json:"type"` + SDP string `json:"sdp"` +}) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "type": + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } + case "sdp": + if in.IsNull() { + in.Skip() + } else { + out.SDP = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36Encode(out *jwriter.Writer, in struct { + Type string `json:"type"` + SDP string `json:"sdp"` +}) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"type\":" + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"sdp\":" + out.RawString(prefix) + out.String(string(in.SDP)) + } + out.RawByte('}') +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus19(in *jlexer.Lexer, out *janusEventHandle) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "name": + if in.IsNull() { + in.Skip() + } else { + out.Name = string(in.String()) + } + case "plugin": + if in.IsNull() { + in.Skip() + } else { + out.Plugin = string(in.String()) + } + case "token": + if in.IsNull() { + in.Skip() + } else { + out.Token = string(in.String()) + } + case "opaque_id": + if in.IsNull() { + in.Skip() + } else { + out.OpaqueId = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus19(out *jwriter.Writer, in janusEventHandle) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"name\":" + out.RawString(prefix[1:]) + out.String(string(in.Name)) + } + { + const prefix string = ",\"plugin\":" + out.RawString(prefix) + out.String(string(in.Plugin)) + } + if in.Token != "" { + const prefix string = ",\"token\":" + out.RawString(prefix) + out.String(string(in.Token)) + } + if in.OpaqueId != "" { + const prefix string = ",\"opaque_id\":" + out.RawString(prefix) + out.String(string(in.OpaqueId)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventHandle) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus19(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventHandle) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus19(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventHandle) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus19(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventHandle) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus19(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus20(in *jlexer.Lexer, out *janusEventExternal) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "schema": + if in.IsNull() { + in.Skip() + } else { + out.Schema = string(in.String()) + } + case "data": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Data).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus20(out *jwriter.Writer, in janusEventExternal) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"schema\":" + out.RawString(prefix[1:]) + out.String(string(in.Schema)) + } + { + const prefix string = ",\"data\":" + out.RawString(prefix) + out.Raw((in.Data).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventExternal) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus20(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventExternal) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus20(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventExternal) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus20(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventExternal) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus20(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus21(in *jlexer.Lexer, out *janusEventDependenciesInfo) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "glib2": + if in.IsNull() { + in.Skip() + } else { + out.Glib2 = string(in.String()) + } + case "jansson": + if in.IsNull() { + in.Skip() + } else { + out.Jansson = string(in.String()) + } + case "libnice": + if in.IsNull() { + in.Skip() + } else { + out.Libnice = string(in.String()) + } + case "libsrtp": + if in.IsNull() { + in.Skip() + } else { + out.Libsrtp = string(in.String()) + } + case "libcurl": + if in.IsNull() { + in.Skip() + } else { + out.Libcurl = string(in.String()) + } + case "crypto": + if in.IsNull() { + in.Skip() + } else { + out.Crypto = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus21(out *jwriter.Writer, in janusEventDependenciesInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"glib2\":" + out.RawString(prefix[1:]) + out.String(string(in.Glib2)) + } + { + const prefix string = ",\"jansson\":" + out.RawString(prefix) + out.String(string(in.Jansson)) + } + { + const prefix string = ",\"libnice\":" + out.RawString(prefix) + out.String(string(in.Libnice)) + } + { + const prefix string = ",\"libsrtp\":" + out.RawString(prefix) + out.String(string(in.Libsrtp)) + } + if in.Libcurl != "" { + const prefix string = ",\"libcurl\":" + out.RawString(prefix) + out.String(string(in.Libcurl)) + } + { + const prefix string = ",\"crypto\":" + out.RawString(prefix) + out.String(string(in.Crypto)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventDependenciesInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus21(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventDependenciesInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus21(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventDependenciesInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus21(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventDependenciesInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus21(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus22(in *jlexer.Lexer, out *janusEventCoreStartup) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "status": + if in.IsNull() { + in.Skip() + } else { + out.Status = string(in.String()) + } + case "info": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Info).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus22(out *jwriter.Writer, in janusEventCoreStartup) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + { + const prefix string = ",\"info\":" + out.RawString(prefix) + out.Raw((in.Info).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventCoreStartup) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus22(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventCoreStartup) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus22(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventCoreStartup) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus22(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventCoreStartup) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus22(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus23(in *jlexer.Lexer, out *janusEventCoreShutdown) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "status": + if in.IsNull() { + in.Skip() + } else { + out.Status = string(in.String()) + } + case "signum": + if in.IsNull() { + in.Skip() + } else { + out.Signum = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus23(out *jwriter.Writer, in janusEventCoreShutdown) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + { + const prefix string = ",\"signum\":" + out.RawString(prefix) + out.Int(int(in.Signum)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventCoreShutdown) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus23(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventCoreShutdown) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus23(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventCoreShutdown) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus23(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventCoreShutdown) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus23(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus24(in *jlexer.Lexer, out *janusEventCandidates) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "local": + if in.IsNull() { + in.Skip() + } else { + (out.Local).UnmarshalEasyJSON(in) + } + case "remote": + if in.IsNull() { + in.Skip() + } else { + (out.Remote).UnmarshalEasyJSON(in) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus24(out *jwriter.Writer, in janusEventCandidates) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"local\":" + out.RawString(prefix[1:]) + (in.Local).MarshalEasyJSON(out) + } + { + const prefix string = ",\"remote\":" + out.RawString(prefix) + (in.Remote).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventCandidates) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus24(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventCandidates) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus24(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventCandidates) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus24(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventCandidates) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus24(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus25(in *jlexer.Lexer, out *janusEventCandidate) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "address": + if in.IsNull() { + in.Skip() + } else { + out.Address = string(in.String()) + } + case "port": + if in.IsNull() { + in.Skip() + } else { + out.Port = int(in.Int()) + } + case "type": + if in.IsNull() { + in.Skip() + } else { + out.Type = string(in.String()) + } + case "transport": + if in.IsNull() { + in.Skip() + } else { + out.Transport = string(in.String()) + } + case "family": + if in.IsNull() { + in.Skip() + } else { + out.Family = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus25(out *jwriter.Writer, in janusEventCandidate) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"address\":" + out.RawString(prefix[1:]) + out.String(string(in.Address)) + } + { + const prefix string = ",\"port\":" + out.RawString(prefix) + out.Int(int(in.Port)) + } + { + const prefix string = ",\"type\":" + out.RawString(prefix) + out.String(string(in.Type)) + } + { + const prefix string = ",\"transport\":" + out.RawString(prefix) + out.String(string(in.Transport)) + } + { + const prefix string = ",\"family\":" + out.RawString(prefix) + out.Int(int(in.Family)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEventCandidate) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus25(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEventCandidate) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus25(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEventCandidate) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus25(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEventCandidate) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus25(l, v) +} +func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus26(in *jlexer.Lexer, out *janusEvent) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "emitter": + if in.IsNull() { + in.Skip() + } else { + out.Emitter = string(in.String()) + } + case "type": + if in.IsNull() { + in.Skip() + } else { + out.Type = int(in.Int()) + } + case "subtype": + if in.IsNull() { + in.Skip() + } else { + out.SubType = int(in.Int()) + } + case "timestamp": + if in.IsNull() { + in.Skip() + } else { + out.Timestamp = uint64(in.Uint64()) + } + case "session_id": + if in.IsNull() { + in.Skip() + } else { + out.SessionId = uint64(in.Uint64()) + } + case "handle_id": + if in.IsNull() { + in.Skip() + } else { + out.HandleId = uint64(in.Uint64()) + } + case "opaque_id": + if in.IsNull() { + in.Skip() + } else { + out.OpaqueId = uint64(in.Uint64()) + } + case "event": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Event).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus26(out *jwriter.Writer, in janusEvent) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"emitter\":" + out.RawString(prefix[1:]) + out.String(string(in.Emitter)) + } + { + const prefix string = ",\"type\":" + out.RawString(prefix) + out.Int(int(in.Type)) + } + if in.SubType != 0 { + const prefix string = ",\"subtype\":" + out.RawString(prefix) + out.Int(int(in.SubType)) + } + { + const prefix string = ",\"timestamp\":" + out.RawString(prefix) + out.Uint64(uint64(in.Timestamp)) + } + if in.SessionId != 0 { + const prefix string = ",\"session_id\":" + out.RawString(prefix) + out.Uint64(uint64(in.SessionId)) + } + if in.HandleId != 0 { + const prefix string = ",\"handle_id\":" + out.RawString(prefix) + out.Uint64(uint64(in.HandleId)) + } + if in.OpaqueId != 0 { + const prefix string = ",\"opaque_id\":" + out.RawString(prefix) + out.Uint64(uint64(in.OpaqueId)) + } + { + const prefix string = ",\"event\":" + out.RawString(prefix) + out.Raw((in.Event).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v janusEvent) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus26(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v janusEvent) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus26(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *janusEvent) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus26(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *janusEvent) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2SfuJanus26(l, v) +} From 0616b9e0cee6fd9bf416865e0a9cf77c685f7650 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 26 Feb 2026 08:26:12 +0100 Subject: [PATCH 539/549] Add warning about Janus queuing unsent events. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9e2fdc6..ae20958 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,13 @@ websocket url of the signaling server (`ws://127.0.0.1:port/spreed`) or -proxy (`ws://127.0.0.1:port/proxy`) and `subprotocol` must be set to `janus-events`. At least events of type `handles`, `media` and `webrtc` must be subscribed. +Warning: If the configuration between Janus and the signaling endpoint is +interrupted or can't be established, unsent events will be queued by Janus +and will use potentially lots of memory there. This can be limited by setting +`events_cap_on_reconnect` in `janus.eventhandler.wsevh.jcfg`. By default, all +events will be queued as the connection between Janus and the signaling endpoint +is assumed to be stable (most likely will be on the same machine). + Edit the `server.conf` and enter the URL to the websocket endpoint of Janus in the section `[mcu]` and key `url`. During startup, the signaling server will connect to Janus and log information of the gateway. From 4d42c5e53849b2994f81f2444e20d1022f906d17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:41:06 +0000 Subject: [PATCH 540/549] Bump github.com/nats-io/nats-server/v2 from 2.12.4 to 2.12.5 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.4 to 2.12.5. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md) - [Commits](https://github.com/nats-io/nats-server/compare/v2.12.4...v2.12.5) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 3a8e771..a211d9a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/mailru/easyjson v0.9.1 - github.com/nats-io/nats-server/v2 v2.12.4 + github.com/nats-io/nats-server/v2 v2.12.5 github.com/nats-io/nats.go v1.49.0 github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 github.com/oschwald/maxminddb-golang v1.13.1 @@ -30,7 +30,7 @@ require ( ) require ( - github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op // indirect + github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -50,12 +50,12 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/jwt/v2 v2.8.0 // indirect - github.com/nats-io/nkeys v0.4.12 // indirect + github.com/nats-io/nkeys v0.4.15 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pion/dtls/v3 v3.1.2 // indirect github.com/pion/logging v0.2.4 // indirect @@ -88,11 +88,11 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/time v0.14.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 9514d1b..4d9b63e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op h1:Ucf+QxEKMbPogRO5guBNe5cgd9uZgfoJLOYs8WWhtjM= -github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op h1:kpBdlEPbRvff0mDD1gk7o9BhI16b9p5yYAXRlidpqJE= +github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -58,8 +58,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -74,12 +74,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts= -github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg= +github.com/nats-io/nats-server/v2 v2.12.5 h1:EOHLbsLJgUHUwzkj9gBTOlubkX+dmSs0EYWMdBiHivU= +github.com/nats-io/nats-server/v2 v2.12.5/go.mod h1:JQDAKcwdXs0NRhvYO31dzsXkzCyDkOBS7SKU3Nozu14= github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE= github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw= -github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc= -github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg= +github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= +github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0 h1:EFU9iv8BMPyBo8iFMHvQleYlF5M3PY6zpAbxsngImjE= @@ -184,8 +184,8 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -193,8 +193,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -205,14 +205,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From f195492f8e42a92314feb69611d4751e5e9ef191 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 08:36:50 +0100 Subject: [PATCH 541/549] Use "sync.Cond" instead of SingleNotifier. --- sfu/proxy/proxy.go | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/sfu/proxy/proxy.go b/sfu/proxy/proxy.go index 12ea56f..77c60a4 100644 --- a/sfu/proxy/proxy.go +++ b/sfu/proxy/proxy.go @@ -390,7 +390,8 @@ type proxyConnection struct { // +checklocks:mu conn *websocket.Conn - helloProcessed atomic.Bool + // +checklocks:mu + helloProcessed bool connectedSince atomic.Int64 reconnectTimer *time.Timer reconnectInterval atomic.Int64 @@ -399,7 +400,7 @@ type proxyConnection struct { trackClose atomic.Bool temporary atomic.Bool - connectedNotifier async.SingleNotifier + connectedCond sync.Cond msgId atomic.Int64 helloMsgId string @@ -444,6 +445,7 @@ func newProxyConnection(proxy *proxySFU, baseUrl string, ip net.IP, token string publisherIds: make(map[sfu.StreamId]api.PublicSessionId), subscribers: make(map[string]*proxySubscriber), } + conn.connectedCond.L = &conn.mu conn.reconnectInterval.Store(int64(initialReconnectInterval)) conn.load.Store(loadNotConnected) conn.bandwidth.Store(nil) @@ -588,7 +590,7 @@ func (c *proxyConnection) SessionId() api.PublicSessionId { func (c *proxyConnection) IsConnected() bool { c.mu.Lock() defer c.mu.Unlock() - return c.conn != nil && c.helloProcessed.Load() && c.SessionId() != "" + return c.conn != nil && c.helloProcessed && c.SessionId() != "" } func (c *proxyConnection) IsTemporary() bool { @@ -746,12 +748,10 @@ func (c *proxyConnection) stop(ctx context.Context) { } func (c *proxyConnection) close() { - c.helloProcessed.Store(false) - c.mu.Lock() defer c.mu.Unlock() - c.connectedNotifier.Reset() + c.helloProcessed = false if c.conn != nil { c.conn.Close() @@ -857,10 +857,10 @@ func (c *proxyConnection) reconnect() { c.logger.Printf("Connected to %s", c) c.closed.Store(false) - c.helloProcessed.Store(false) c.connectedSince.Store(time.Now().UnixMicro()) c.mu.Lock() + c.helloProcessed = false c.conn = conn c.mu.Unlock() @@ -887,12 +887,20 @@ func (c *proxyConnection) waitUntilConnected(ctx context.Context) error { return nil } - waiter := c.connectedNotifier.NewWaiter() - defer c.connectedNotifier.Release(waiter) + stop := context.AfterFunc(ctx, func() { + c.connectedCond.Broadcast() + }) + defer stop() - c.mu.Unlock() - defer c.mu.Lock() - return waiter.Wait(ctx) + for !c.helloProcessed { + if err := ctx.Err(); err != nil { + return err + } + + c.connectedCond.Wait() + } + + return nil } func (c *proxyConnection) removePublisher(publisher *proxyPublisher) { @@ -1057,8 +1065,10 @@ func (c *proxyConnection) processMessage(msg *proxy.ServerMessage) { statsConnectedProxyBackendsCurrent.WithLabelValues(string(c.Country())).Inc() } - c.helloProcessed.Store(true) - c.connectedNotifier.Notify() + c.mu.Lock() + c.helloProcessed = true + c.connectedCond.Broadcast() + c.mu.Unlock() default: c.logger.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c) c.scheduleReconnect() From 9c10675867fa2af161d1472c034f65955447b3ee Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 08:50:45 +0100 Subject: [PATCH 542/549] Simplify notifier code and remove unused. --- async/notifier.go | 68 ++++++++++------- async/notifier_test.go | 26 +++++++ async/single_notifier.go | 128 -------------------------------- async/single_notifier_test.go | 134 ---------------------------------- 4 files changed, 67 insertions(+), 289 deletions(-) delete mode 100644 async/single_notifier.go delete mode 100644 async/single_notifier_test.go diff --git a/async/notifier.go b/async/notifier.go index 06305aa..ab9138a 100644 --- a/async/notifier.go +++ b/async/notifier.go @@ -26,21 +26,34 @@ import ( "sync" ) +type rootWaiter struct { + key string + ch chan struct{} +} + +func (w *rootWaiter) notify() { + close(w.ch) +} + type Waiter struct { key string - - sw *SingleWaiter + ch <-chan struct{} } func (w *Waiter) Wait(ctx context.Context) error { - return w.sw.Wait(ctx) + select { + case <-w.ch: + return nil + case <-ctx.Done(): + return ctx.Err() + } } type Notifier struct { sync.Mutex // +checklocks:Mutex - waiters map[string]*Waiter + waiters map[string]*rootWaiter // +checklocks:Mutex waiterMap map[string]map[*Waiter]bool } @@ -50,31 +63,30 @@ func (n *Notifier) NewWaiter(key string) *Waiter { defer n.Unlock() waiter, found := n.waiters[key] - if found { - w := &Waiter{ + if !found { + waiter = &rootWaiter{ key: key, - sw: waiter.sw, + ch: make(chan struct{}), + } + + if n.waiters == nil { + n.waiters = make(map[string]*rootWaiter) + } + if n.waiterMap == nil { + n.waiterMap = make(map[string]map[*Waiter]bool) + } + n.waiters[key] = waiter + if _, found := n.waiterMap[key]; !found { + n.waiterMap[key] = make(map[*Waiter]bool) } - n.waiterMap[key][w] = true - return w } - waiter = &Waiter{ + w := &Waiter{ key: key, - sw: newSingleWaiter(), + ch: waiter.ch, } - if n.waiters == nil { - n.waiters = make(map[string]*Waiter) - } - if n.waiterMap == nil { - n.waiterMap = make(map[string]map[*Waiter]bool) - } - n.waiters[key] = waiter - if _, found := n.waiterMap[key]; !found { - n.waiterMap[key] = make(map[*Waiter]bool) - } - n.waiterMap[key][waiter] = true - return waiter + n.waiterMap[key][w] = true + return w } func (n *Notifier) Reset() { @@ -82,7 +94,7 @@ func (n *Notifier) Reset() { defer n.Unlock() for _, w := range n.waiters { - w.sw.cancel() + w.notify() } n.waiters = nil n.waiterMap = nil @@ -96,8 +108,10 @@ func (n *Notifier) Release(w *Waiter) { if _, found := waiters[w]; found { delete(waiters, w) if len(waiters) == 0 { - delete(n.waiters, w.key) - w.sw.cancel() + if root, found := n.waiters[w.key]; found { + delete(n.waiters, w.key) + root.notify() + } } } } @@ -108,7 +122,7 @@ func (n *Notifier) Notify(key string) { defer n.Unlock() if w, found := n.waiters[key]; found { - w.sw.cancel() + w.notify() delete(n.waiters, w.key) delete(n.waiterMap, w.key) } diff --git a/async/notifier_test.go b/async/notifier_test.go index 21ba219..0596767 100644 --- a/async/notifier_test.go +++ b/async/notifier_test.go @@ -39,6 +39,32 @@ func TestNotifierNoWaiter(t *testing.T) { notifier.Notify("foo") } +func TestNotifierWaitTimeout(t *testing.T) { + t.Parallel() + synctest.Test(t, func(t *testing.T) { + var notifier Notifier + + notified := make(chan struct{}) + go func() { + defer close(notified) + time.Sleep(time.Second) + notifier.Notify("foo") + }() + + ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond) + defer cancel() + + waiter := notifier.NewWaiter("foo") + defer notifier.Release(waiter) + + err := waiter.Wait(ctx) + assert.ErrorIs(t, err, context.DeadlineExceeded) + <-notified + + assert.NoError(t, waiter.Wait(t.Context())) + }) +} + func TestNotifierSimple(t *testing.T) { t.Parallel() diff --git a/async/single_notifier.go b/async/single_notifier.go deleted file mode 100644 index 28c3a45..0000000 --- a/async/single_notifier.go +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2022 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 async - -import ( - "context" - "sync" -) - -type SingleWaiter struct { - root bool - ch chan struct{} - once sync.Once -} - -func newSingleWaiter() *SingleWaiter { - return &SingleWaiter{ - root: true, - ch: make(chan struct{}), - } -} - -func (w *SingleWaiter) subWaiter() *SingleWaiter { - return &SingleWaiter{ - ch: w.ch, - } -} - -func (w *SingleWaiter) Wait(ctx context.Context) error { - select { - case <-w.ch: - return nil - case <-ctx.Done(): - return ctx.Err() - } -} - -func (w *SingleWaiter) cancel() { - if !w.root { - return - } - - w.once.Do(func() { - close(w.ch) - }) -} - -type SingleNotifier struct { - sync.Mutex - - // +checklocks:Mutex - waiter *SingleWaiter - // +checklocks:Mutex - waiters map[*SingleWaiter]bool -} - -func (n *SingleNotifier) NewWaiter() *SingleWaiter { - n.Lock() - defer n.Unlock() - - if n.waiter == nil { - n.waiter = newSingleWaiter() - } - - if n.waiters == nil { - n.waiters = make(map[*SingleWaiter]bool) - } - - w := n.waiter.subWaiter() - n.waiters[w] = true - return w -} - -func (n *SingleNotifier) Reset() { - n.Lock() - defer n.Unlock() - - if n.waiter != nil { - n.waiter.cancel() - n.waiter = nil - } - n.waiters = nil -} - -func (n *SingleNotifier) Release(w *SingleWaiter) { - n.Lock() - defer n.Unlock() - - if _, found := n.waiters[w]; found { - delete(n.waiters, w) - if len(n.waiters) == 0 { - n.waiters = nil - if n.waiter != nil { - n.waiter.cancel() - n.waiter = nil - } - } - } -} - -func (n *SingleNotifier) Notify() { - n.Lock() - defer n.Unlock() - - if n.waiter != nil { - n.waiter.cancel() - } - n.waiters = nil -} diff --git a/async/single_notifier_test.go b/async/single_notifier_test.go deleted file mode 100644 index 83de3e6..0000000 --- a/async/single_notifier_test.go +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2022 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 async - -import ( - "context" - "sync" - "testing" - "testing/synctest" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestSingleNotifierNoWaiter(t *testing.T) { - t.Parallel() - var notifier SingleNotifier - - // Notifications can be sent even if no waiter exists. - notifier.Notify() -} - -func TestSingleNotifierSimple(t *testing.T) { - t.Parallel() - - var notifier SingleNotifier - waiter := notifier.NewWaiter() - defer notifier.Release(waiter) - - var wg sync.WaitGroup - wg.Go(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - assert.NoError(t, waiter.Wait(ctx)) - }) - - notifier.Notify() - wg.Wait() -} - -func TestSingleNotifierMultiNotify(t *testing.T) { - t.Parallel() - var notifier SingleNotifier - - waiter := notifier.NewWaiter() - defer notifier.Release(waiter) - - notifier.Notify() - // The second notification will be ignored while the first is still pending. - notifier.Notify() -} - -func TestSingleNotifierWaitClosed(t *testing.T) { - t.Parallel() - var notifier SingleNotifier - - waiter := notifier.NewWaiter() - notifier.Release(waiter) - - assert.NoError(t, waiter.Wait(context.Background())) -} - -func TestSingleNotifierWaitClosedMulti(t *testing.T) { - t.Parallel() - var notifier SingleNotifier - - waiter1 := notifier.NewWaiter() - waiter2 := notifier.NewWaiter() - notifier.Release(waiter1) - notifier.Release(waiter2) - - assert.NoError(t, waiter1.Wait(context.Background())) - assert.NoError(t, waiter2.Wait(context.Background())) -} - -func TestSingleNotifierResetWillNotify(t *testing.T) { - t.Parallel() - - var notifier SingleNotifier - waiter := notifier.NewWaiter() - defer notifier.Release(waiter) - - var wg sync.WaitGroup - wg.Go(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - assert.NoError(t, waiter.Wait(ctx)) - }) - - notifier.Reset() - wg.Wait() -} - -func TestSingleNotifierDuplicate(t *testing.T) { - t.Parallel() - synctest.Test(t, func(t *testing.T) { - var notifier SingleNotifier - var done sync.WaitGroup - - for range 2 { - done.Go(func() { - waiter := notifier.NewWaiter() - defer notifier.Release(waiter) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - assert.NoError(t, waiter.Wait(ctx)) - }) - } - - synctest.Wait() - notifier.Notify() - done.Wait() - }) -} From a9e58ec60ae019d5feed8303e0f2c8198ffb6be4 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 08:59:43 +0100 Subject: [PATCH 543/549] Use "sync.Cond" to wait for publisher to be created. --- server/clientsession.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/server/clientsession.go b/server/clientsession.go index e087366..2ad59c1 100644 --- a/server/clientsession.go +++ b/server/clientsession.go @@ -36,7 +36,6 @@ import ( "github.com/pion/sdp/v3" "github.com/strukturag/nextcloud-spreed-signaling/v2/api" - "github.com/strukturag/nextcloud-spreed-signaling/v2/async" "github.com/strukturag/nextcloud-spreed-signaling/v2/async/events" "github.com/strukturag/nextcloud-spreed-signaling/v2/internal" "github.com/strukturag/nextcloud-spreed-signaling/v2/log" @@ -98,7 +97,7 @@ type ClientSession struct { // +checklocks:roomSessionIdLock roomSessionId api.RoomSessionId - publisherWaiters async.ChannelWaiters // +checklocksignore + publishersCond sync.Cond // +checklocks:mu publishers map[sfu.StreamType]sfu.Publisher @@ -148,6 +147,7 @@ func NewClientSession(hub *Hub, privateId api.PrivateSessionId, publicId api.Pub backend: backend, asyncCh: make(events.AsyncChannel, events.DefaultAsyncChannelSize), } + s.publishersCond.L = &s.mu if s.clientType == api.HelloClientTypeInternal { s.backendUrl = hello.Auth.InternalParams.Backend s.parsedBackendUrl = hello.Auth.InternalParams.ParsedBackend @@ -991,7 +991,7 @@ func (s *ClientSession) GetOrCreatePublisher(ctx context.Context, mcu sfu.SFU, s s.publishers[streamType] = publisher } s.logger.Printf("Publishing %s as %s for session %s", streamType, publisher.Id(), s.PublicId()) - s.publisherWaiters.Wakeup() + s.publishersCond.Broadcast() } else { publisher.SetMedia(mediaTypes) } @@ -1020,24 +1020,20 @@ func (s *ClientSession) GetOrWaitForPublisher(ctx context.Context, streamType sf return publisher } - ch := make(chan struct{}, 1) - id := s.publisherWaiters.Add(ch) - defer s.publisherWaiters.Remove(id) + stop := context.AfterFunc(ctx, func() { + s.publishersCond.Broadcast() + }) + defer stop() - for { - s.mu.Unlock() - select { - case <-ch: - s.mu.Lock() - publisher := s.getPublisherLocked(streamType) - if publisher != nil { - return publisher - } - case <-ctx.Done(): - s.mu.Lock() + for publisher == nil { + if err := ctx.Err(); err != nil { return nil } + + s.publishersCond.Wait() + publisher = s.getPublisherLocked(streamType) } + return publisher } func (s *ClientSession) GetOrCreateSubscriber(ctx context.Context, mcu sfu.SFU, id api.PublicSessionId, streamType sfu.StreamType) (sfu.Subscriber, error) { From a6a1c3534746452087b75ef615dd922a406b9482 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 09:05:59 +0100 Subject: [PATCH 544/549] Use "sync.Cond" to wait for publisher connection. --- sfu/proxy/proxy.go | 35 +++++++++++++++-------------------- sfu/proxy/proxy_test.go | 37 +++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/sfu/proxy/proxy.go b/sfu/proxy/proxy.go index 77c60a4..52278c9 100644 --- a/sfu/proxy/proxy.go +++ b/sfu/proxy/proxy.go @@ -46,7 +46,6 @@ import ( "github.com/gorilla/websocket" "github.com/strukturag/nextcloud-spreed-signaling/v2/api" - "github.com/strukturag/nextcloud-spreed-signaling/v2/async" "github.com/strukturag/nextcloud-spreed-signaling/v2/config" "github.com/strukturag/nextcloud-spreed-signaling/v2/dns" "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" @@ -1540,9 +1539,8 @@ type proxySFU struct { mu sync.RWMutex // +checklocks:mu - publishers map[sfu.StreamId]*proxyConnection - - publisherWaiters async.ChannelWaiters + publishers map[sfu.StreamId]*proxyConnection + publishersCond sync.Cond continentsMap atomic.Value @@ -1595,6 +1593,7 @@ func NewProxySFU(ctx context.Context, config *goconf.ConfigFile, etcdClient etcd rpcClients: rpcClients, } + mcu.publishersCond.L = &mcu.mu if err := mcu.loadContinentsMap(config); err != nil { return nil, err @@ -2132,9 +2131,9 @@ func (m *proxySFU) createPublisher(ctx context.Context, listener sfu.Listener, i } m.mu.Lock() + defer m.mu.Unlock() m.publishers[sfu.GetStreamId(id, streamType)] = conn - m.mu.Unlock() - m.publisherWaiters.Wakeup() + m.publishersCond.Broadcast() return publisher } @@ -2211,25 +2210,21 @@ func (m *proxySFU) waitForPublisherConnection(ctx context.Context, publisher api return conn } - ch := make(chan struct{}, 1) - id := m.publisherWaiters.Add(ch) - defer m.publisherWaiters.Remove(id) + stop := context.AfterFunc(ctx, func() { + m.publishersCond.Broadcast() + }) + defer stop() sfuinternal.StatsWaitingForPublisherTotal.WithLabelValues(string(streamType)).Inc() - for { - m.mu.Unlock() - select { - case <-ch: - m.mu.Lock() - conn = m.publishers[sfu.GetStreamId(publisher, streamType)] - if conn != nil { - return conn - } - case <-ctx.Done(): - m.mu.Lock() + for conn == nil { + if err := ctx.Err(); err != nil { return nil } + + m.publishersCond.Wait() + conn = m.publishers[sfu.GetStreamId(publisher, streamType)] } + return conn } type proxyPublisherInfo struct { diff --git a/sfu/proxy/proxy_test.go b/sfu/proxy/proxy_test.go index 932177e..9bab3e9 100644 --- a/sfu/proxy/proxy_test.go +++ b/sfu/proxy/proxy_test.go @@ -42,7 +42,6 @@ import ( "github.com/stretchr/testify/require" "github.com/strukturag/nextcloud-spreed-signaling/v2/api" - "github.com/strukturag/nextcloud-spreed-signaling/v2/async" dnstest "github.com/strukturag/nextcloud-spreed-signaling/v2/dns/test" "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd" etcdtest "github.com/strukturag/nextcloud-spreed-signaling/v2/etcd/test" @@ -1248,14 +1247,16 @@ type publisherHub struct { mu sync.Mutex // +checklocks:mu - publishers map[api.PublicSessionId]*proxyPublisher - waiter async.ChannelWaiters // +checklocksignore: Has its own locking. + publishers map[api.PublicSessionId]*proxyPublisher + publishersCond sync.Cond } func newPublisherHub() *publisherHub { - return &publisherHub{ + hub := &publisherHub{ publishers: make(map[api.PublicSessionId]*proxyPublisher), } + hub.publishersCond.L = &hub.mu + return hub } func (h *publisherHub) addPublisher(publisher *proxyPublisher) { @@ -1263,30 +1264,26 @@ func (h *publisherHub) addPublisher(publisher *proxyPublisher) { defer h.mu.Unlock() h.publishers[publisher.PublisherId()] = publisher - h.waiter.Wakeup() + h.publishersCond.Broadcast() } func (h *publisherHub) GetPublisherIdForSessionId(ctx context.Context, sessionId api.PublicSessionId, streamType sfu.StreamType) (*grpc.GetPublisherIdReply, error) { h.mu.Lock() defer h.mu.Unlock() - pub, found := h.publishers[sessionId] - if !found { - ch := make(chan struct{}, 1) - id := h.waiter.Add(ch) - defer h.waiter.Remove(id) + stop := context.AfterFunc(ctx, func() { + h.publishersCond.Broadcast() + }) + defer stop() - for !found { - h.mu.Unlock() - select { - case <-ch: - h.mu.Lock() - pub, found = h.publishers[sessionId] - case <-ctx.Done(): - h.mu.Lock() - return nil, ctx.Err() - } + pub, found := h.publishers[sessionId] + for !found { + if err := ctx.Err(); err != nil { + return nil, err } + + h.publishersCond.Wait() + pub, found = h.publishers[sessionId] } connToken, err := pub.conn.proxy.CreateToken("") From 2bf6d00a13335efa3a3cef25245196cd866a9234 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 09:06:23 +0100 Subject: [PATCH 545/549] Remove deprecated ChannelWaiter. --- async/channel_waiter.go | 64 --------------------------------- async/channel_waiter_test.go | 69 ------------------------------------ 2 files changed, 133 deletions(-) delete mode 100644 async/channel_waiter.go delete mode 100644 async/channel_waiter_test.go diff --git a/async/channel_waiter.go b/async/channel_waiter.go deleted file mode 100644 index 928c7d5..0000000 --- a/async/channel_waiter.go +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2023 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 async - -import ( - "sync" -) - -type ChannelWaiters struct { - mu sync.RWMutex - // +checklocks:mu - id uint64 - // +checklocks:mu - waiters map[uint64]chan struct{} -} - -func (w *ChannelWaiters) Wakeup() { - w.mu.RLock() - defer w.mu.RUnlock() - for _, ch := range w.waiters { - select { - case ch <- struct{}{}: - default: - // Receiver is still processing previous wakeup. - } - } -} - -func (w *ChannelWaiters) Add(ch chan struct{}) uint64 { - w.mu.Lock() - defer w.mu.Unlock() - if w.waiters == nil { - w.waiters = make(map[uint64]chan struct{}) - } - id := w.id - w.id++ - w.waiters[id] = ch - return id -} - -func (w *ChannelWaiters) Remove(id uint64) { - w.mu.Lock() - defer w.mu.Unlock() - delete(w.waiters, id) -} diff --git a/async/channel_waiter_test.go b/async/channel_waiter_test.go deleted file mode 100644 index 3528e64..0000000 --- a/async/channel_waiter_test.go +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Standalone signaling server for the Nextcloud Spreed app. - * Copyright (C) 2023 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 async - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestChannelWaiters(t *testing.T) { - t.Parallel() - var waiters ChannelWaiters - - ch1 := make(chan struct{}, 1) - id1 := waiters.Add(ch1) - defer waiters.Remove(id1) - - ch2 := make(chan struct{}, 1) - id2 := waiters.Add(ch2) - defer waiters.Remove(id2) - - waiters.Wakeup() - <-ch1 - <-ch2 - - select { - case <-ch1: - assert.Fail(t, "should have not received another event") - case <-ch2: - assert.Fail(t, "should have not received another event") - default: - } - - ch3 := make(chan struct{}, 1) - id3 := waiters.Add(ch3) - waiters.Remove(id3) - - // Multiple wakeups work even without processing. - waiters.Wakeup() - waiters.Wakeup() - waiters.Wakeup() - <-ch1 - <-ch2 - select { - case <-ch3: - assert.Fail(t, "should have not received another event") - default: - } -} From bd3c06c9ebf1efcff19baa1c630eab0aa32bf50e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 09:38:26 +0100 Subject: [PATCH 546/549] Use "sync.Cond" to wait for created publishers. --- sfu/janus/janus.go | 96 ++++++++++++++++++++++++++--------- sfu/janus/publisher.go | 2 +- sfu/janus/remote_publisher.go | 2 +- sfu/janus/subscriber.go | 4 +- 4 files changed, 77 insertions(+), 27 deletions(-) diff --git a/sfu/janus/janus.go b/sfu/janus/janus.go index 6f1baac..aacdf76 100644 --- a/sfu/janus/janus.go +++ b/sfu/janus/janus.go @@ -231,6 +231,11 @@ func (s *prometheusJanusStats) DecSubscriber(streamType sfu.StreamType) { sfuinternal.StatsSubscribersCurrent.WithLabelValues(string(streamType)).Dec() } +type refcountCond struct { + ref int + cond *sync.Cond +} + type janusSFU struct { logger log.Logger @@ -257,8 +262,9 @@ type janusSFU struct { clientId atomic.Uint64 // +checklocks:mu - publishers map[sfu.StreamId]*janusPublisher - publisherCreated async.Notifier + publishers map[sfu.StreamId]*janusPublisher + // +checklocks:mu + publisherCreated map[sfu.StreamId]*refcountCond publisherConnected async.Notifier // +checklocks:mu remotePublishers map[sfu.StreamId]*janusRemotePublisher @@ -289,6 +295,7 @@ func NewJanusSFU(ctx context.Context, url string, config *goconf.ConfigFile) (sf clients: make(map[uint64]clientInterface), publishers: make(map[sfu.StreamId]*janusPublisher), + publisherCreated: make(map[sfu.StreamId]*refcountCond), remotePublishers: make(map[sfu.StreamId]*janusRemotePublisher), createJanusGateway: func(ctx context.Context, wsURL string, listener janus.GatewayListener) (janus.GatewayInterface, error) { @@ -438,7 +445,10 @@ func (m *janusSFU) doReconnect(ctx context.Context) { m.logger.Println("Reconnection to Janus gateway successful") m.mu.Lock() clear(m.publishers) - m.publisherCreated.Reset() + for _, c := range m.publisherCreated { + c.cond.Broadcast() + } + clear(m.publisherCreated) m.publisherConnected.Reset() m.reconnectInterval = initialReconnectInterval m.mu.Unlock() @@ -854,40 +864,80 @@ func (m *janusSFU) NewPublisher(ctx context.Context, listener sfu.Listener, id a m.registerClient(client) m.logger.Printf("Publisher %s is using handle %d", client.id, handle.Id) go client.run(handle, client.closeChan) - m.mu.Lock() - m.publishers[sfu.GetStreamId(id, streamType)] = client - m.publisherCreated.Notify(string(sfu.GetStreamId(id, streamType))) - m.mu.Unlock() + + m.notifyPublisherCreated(id, streamType, client) sfuinternal.StatsPublishersCurrent.WithLabelValues(string(streamType)).Inc() sfuinternal.StatsPublishersTotal.WithLabelValues(string(streamType)).Inc() return client, nil } +func (m *janusSFU) notifyPublisherCreated(id api.PublicSessionId, streamType sfu.StreamType, client *janusPublisher) { + key := sfu.GetStreamId(id, streamType) + m.mu.Lock() + defer m.mu.Unlock() + + m.publishers[key] = client + if c, found := m.publisherCreated[key]; found { + c.cond.Broadcast() + delete(m.publisherCreated, key) + } +} + +func (m *janusSFU) notifyPublisherConnected(id api.PublicSessionId, streamType sfu.StreamType) { + key := sfu.GetStreamId(id, streamType) + m.publisherConnected.Notify(string(key)) +} + +func (m *janusSFU) newPublisherConnectedWaiter(id api.PublicSessionId, streamType sfu.StreamType) (*async.Waiter, func()) { + key := sfu.GetStreamId(id, streamType) + + waiter := m.publisherConnected.NewWaiter(string(key)) + stopFunc := func() { + m.publisherConnected.Release(waiter) + } + + return waiter, stopFunc +} + func (m *janusSFU) getPublisher(ctx context.Context, publisher api.PublicSessionId, streamType sfu.StreamType) (*janusPublisher, error) { // Do the direct check immediately as this should be the normal case. key := sfu.GetStreamId(publisher, streamType) m.mu.Lock() - if result, found := m.publishers[key]; found { - m.mu.Unlock() + defer m.mu.Unlock() + result, found := m.publishers[key] + if found { return result, nil } - waiter := m.publisherCreated.NewWaiter(string(key)) - m.mu.Unlock() - defer m.publisherCreated.Release(waiter) - - for { - m.mu.Lock() - result := m.publishers[key] - m.mu.Unlock() - if result != nil { - return result, nil - } - - if err := waiter.Wait(ctx); err != nil { - return nil, err + c, found := m.publisherCreated[key] + if !found { + c = &refcountCond{ + cond: sync.NewCond(&m.mu), } + m.publisherCreated[key] = c } + c.ref++ + + stop := context.AfterFunc(ctx, func() { + c.cond.Broadcast() + }) + defer stop() + + for result == nil && ctx.Err() == nil { + c.cond.Wait() + result = m.publishers[key] + } + + c.ref-- + if c.ref == 0 { + delete(m.publisherCreated, key) + } + + if err := ctx.Err(); err != nil { + return nil, err + } + + return result, nil } func (m *janusSFU) getOrCreateSubscriberHandle(ctx context.Context, publisher api.PublicSessionId, streamType sfu.StreamType) (*janus.Handle, *janusPublisher, error) { diff --git a/sfu/janus/publisher.go b/sfu/janus/publisher.go index a7b2fab..3a2a511 100644 --- a/sfu/janus/publisher.go +++ b/sfu/janus/publisher.go @@ -93,7 +93,7 @@ func (p *janusPublisher) handleDetached(event *janus.DetachedMsg) { func (p *janusPublisher) handleConnected(event *janus.WebRTCUpMsg) { p.logger.Printf("Publisher %d received connected", p.handleId.Load()) - p.mcu.publisherConnected.Notify(string(sfu.GetStreamId(p.id, p.streamType))) + p.mcu.notifyPublisherConnected(p.id, p.streamType) } func (p *janusPublisher) handleSlowLink(event *janus.SlowLinkMsg) { diff --git a/sfu/janus/remote_publisher.go b/sfu/janus/remote_publisher.go index 6276d2e..dd85657 100644 --- a/sfu/janus/remote_publisher.go +++ b/sfu/janus/remote_publisher.go @@ -86,7 +86,7 @@ func (p *janusRemotePublisher) handleDetached(event *janus.DetachedMsg) { func (p *janusRemotePublisher) handleConnected(event *janus.WebRTCUpMsg) { p.logger.Printf("Remote publisher %d received connected", p.handleId.Load()) - p.mcu.publisherConnected.Notify(string(sfu.GetStreamId(p.id, p.streamType))) + p.mcu.notifyPublisherConnected(p.id, p.streamType) } func (p *janusRemotePublisher) handleSlowLink(event *janus.SlowLinkMsg) { diff --git a/sfu/janus/subscriber.go b/sfu/janus/subscriber.go index e74c5aa..0d3bd73 100644 --- a/sfu/janus/subscriber.go +++ b/sfu/janus/subscriber.go @@ -169,8 +169,8 @@ func (p *janusSubscriber) joinRoom(ctx context.Context, stream *streamSelection, return } - waiter := p.mcu.publisherConnected.NewWaiter(string(sfu.GetStreamId(p.publisher, p.streamType))) - defer p.mcu.publisherConnected.Release(waiter) + waiter, stop := p.mcu.newPublisherConnectedWaiter(p.publisher, p.streamType) + defer stop() loggedNotPublishingYet := false retry: From 182a0b78e2f6607694d169e279488e5632046429 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 10 Mar 2026 10:04:18 +0100 Subject: [PATCH 547/549] Simplify API for waiter releasing. --- async/notifier.go | 11 ++++++++--- async/notifier_test.go | 32 ++++++++++++++++---------------- sfu/janus/janus.go | 9 ++------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/async/notifier.go b/async/notifier.go index ab9138a..747ec5e 100644 --- a/async/notifier.go +++ b/async/notifier.go @@ -58,7 +58,9 @@ type Notifier struct { waiterMap map[string]map[*Waiter]bool } -func (n *Notifier) NewWaiter(key string) *Waiter { +type ReleaseFunc func() + +func (n *Notifier) NewWaiter(key string) (*Waiter, ReleaseFunc) { n.Lock() defer n.Unlock() @@ -86,7 +88,10 @@ func (n *Notifier) NewWaiter(key string) *Waiter { ch: waiter.ch, } n.waiterMap[key][w] = true - return w + releaseFunc := func() { + n.release(w) + } + return w, releaseFunc } func (n *Notifier) Reset() { @@ -100,7 +105,7 @@ func (n *Notifier) Reset() { n.waiterMap = nil } -func (n *Notifier) Release(w *Waiter) { +func (n *Notifier) release(w *Waiter) { n.Lock() defer n.Unlock() diff --git a/async/notifier_test.go b/async/notifier_test.go index 0596767..74f88ed 100644 --- a/async/notifier_test.go +++ b/async/notifier_test.go @@ -54,8 +54,8 @@ func TestNotifierWaitTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond) defer cancel() - waiter := notifier.NewWaiter("foo") - defer notifier.Release(waiter) + waiter, release := notifier.NewWaiter("foo") + defer release() err := waiter.Wait(ctx) assert.ErrorIs(t, err, context.DeadlineExceeded) @@ -69,8 +69,8 @@ func TestNotifierSimple(t *testing.T) { t.Parallel() var notifier Notifier - waiter := notifier.NewWaiter("foo") - defer notifier.Release(waiter) + waiter, release := notifier.NewWaiter("foo") + defer release() var wg sync.WaitGroup wg.Go(func() { @@ -87,8 +87,8 @@ func TestNotifierMultiNotify(t *testing.T) { t.Parallel() var notifier Notifier - waiter := notifier.NewWaiter("foo") - defer notifier.Release(waiter) + _, release := notifier.NewWaiter("foo") + defer release() notifier.Notify("foo") // The second notification will be ignored while the first is still pending. @@ -99,8 +99,8 @@ func TestNotifierWaitClosed(t *testing.T) { t.Parallel() var notifier Notifier - waiter := notifier.NewWaiter("foo") - notifier.Release(waiter) + waiter, release := notifier.NewWaiter("foo") + release() assert.NoError(t, waiter.Wait(context.Background())) } @@ -109,10 +109,10 @@ func TestNotifierWaitClosedMulti(t *testing.T) { t.Parallel() var notifier Notifier - waiter1 := notifier.NewWaiter("foo") - waiter2 := notifier.NewWaiter("foo") - notifier.Release(waiter1) - notifier.Release(waiter2) + waiter1, release1 := notifier.NewWaiter("foo") + waiter2, release2 := notifier.NewWaiter("foo") + release1() + release2() assert.NoError(t, waiter1.Wait(context.Background())) assert.NoError(t, waiter2.Wait(context.Background())) @@ -122,8 +122,8 @@ func TestNotifierResetWillNotify(t *testing.T) { t.Parallel() var notifier Notifier - waiter := notifier.NewWaiter("foo") - defer notifier.Release(waiter) + waiter, release := notifier.NewWaiter("foo") + defer release() var wg sync.WaitGroup wg.Go(func() { @@ -144,8 +144,8 @@ func TestNotifierDuplicate(t *testing.T) { for range 2 { done.Go(func() { - waiter := notifier.NewWaiter("foo") - defer notifier.Release(waiter) + waiter, release := notifier.NewWaiter("foo") + defer release() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() diff --git a/sfu/janus/janus.go b/sfu/janus/janus.go index aacdf76..9efbd9b 100644 --- a/sfu/janus/janus.go +++ b/sfu/janus/janus.go @@ -888,15 +888,10 @@ func (m *janusSFU) notifyPublisherConnected(id api.PublicSessionId, streamType s m.publisherConnected.Notify(string(key)) } -func (m *janusSFU) newPublisherConnectedWaiter(id api.PublicSessionId, streamType sfu.StreamType) (*async.Waiter, func()) { +func (m *janusSFU) newPublisherConnectedWaiter(id api.PublicSessionId, streamType sfu.StreamType) (*async.Waiter, async.ReleaseFunc) { key := sfu.GetStreamId(id, streamType) - waiter := m.publisherConnected.NewWaiter(string(key)) - stopFunc := func() { - m.publisherConnected.Release(waiter) - } - - return waiter, stopFunc + return m.publisherConnected.NewWaiter(string(key)) } func (m *janusSFU) getPublisher(ctx context.Context, publisher api.PublicSessionId, streamType sfu.StreamType) (*janusPublisher, error) { From e7a8fb7aa9b99ad9f41e0e1793ce147ed36ecc62 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Mar 2026 08:41:14 +0100 Subject: [PATCH 548/549] Update changelog for 2.1.1 --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6a397d..0a48ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,77 @@ All notable changes to this project will be documented in this file. +## 2.1.1 - 2026-03-12 + +### Changed +- Drop support for Golang 1.24 + [#1197](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1197) +- Simplify error type checks. + [#1190](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1190) +- readme: Add example websocket urls for Janus events. + [#1191](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1191) +- CI: Test with Golang 1.26 + [#1196](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1196) +- CI: Use "docker compose" instead of downloading docker-compose binary. + [#1203](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1203) +- docker: pin spreedbackend uid and add user group + [#1202](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1202) +- Bump module version to "v2" so versions 2.x can be imported by others. + [#1211](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1211) +- Update generated files for v2 module. + [#1218](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1218) +- Simplify async notifier code + [#1220](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1220) + +### Fixed +- Update "go.opentelemetry.io/otel/sdk" to fix "GO-2026-4394". + [#1207](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1207) +- Don't limit size of received Janus events. + [#1208](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1208) + +### Dependencies +- Bump google.golang.org/grpc/cmd/protoc-gen-go-grpc from 1.6.0 to 1.6.1 + [#1189](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1189) +- Bump markdown from 3.10.1 to 3.10.2 in /docs + [#1192](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1192) +- Bump github.com/pion/dtls/v3 from 3.0.10 to 3.1.0 + [#1193](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1193) +- Bump golang from 1.25-alpine to 1.26-alpine in /docker/proxy + [#1194](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1194) +- Bump golang from 1.25-alpine to 1.26-alpine in /docker/server + [#1195](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1195) +- Bump google.golang.org/grpc from 1.78.0 to 1.79.1 + [#1201](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1201) +- Bump github.com/pion/dtls/v3 from 3.1.0 to 3.1.1 + [#1199](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1199) +- Bump the etcd group with 4 updates + [#1200](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1200) +- Bump github.com/pion/sdp/v3 from 3.0.17 to 3.0.18 + [#1204](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1204) +- Bump github.com/pion/ice/v4 from 4.2.0 to 4.2.1 + [#1205](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1205) +- Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0 + [#1206](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1206) +- Bump the artifacts group with 2 updates + [#1209](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1209) +- Bump module version to "v2" so versions 2.x can be imported by others. + [#1211](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1211) +- Bump docker/login-action from 3 to 4 + [#1212](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1212) +- Bump docker/setup-qemu-action from 3 to 4 + [#1213](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1213) +- Bump docker/metadata-action from 5 to 6 + [#1214](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1214) +- Bump docker/setup-buildx-action from 3 to 4 + [#1215](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1215) +- Bump docker/build-push-action from 6 to 7 + [#1217](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1217) +- Bump google.golang.org/grpc from 1.79.1 to 1.79.2 + [#1216](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1216) +- Bump github.com/nats-io/nats-server/v2 from 2.12.4 to 2.12.5 + [#1219](https://github.com/strukturag/nextcloud-spreed-signaling/pull/1219) + + ## 2.1.0 - 2026-02-03 ### Added From aa7ca9d02fd5bcaf3332c60a0c50a6ec7d3ca85b Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 12 Mar 2026 10:57:12 +0100 Subject: [PATCH 549/549] Move tools helpers to internal package. --- tools.go => internal/tools/tools.go | 2 +- vendor_helper_test.go => internal/tools/vendor_helper_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tools.go => internal/tools/tools.go (98%) rename vendor_helper_test.go => internal/tools/vendor_helper_test.go (98%) diff --git a/tools.go b/internal/tools/tools.go similarity index 98% rename from tools.go rename to internal/tools/tools.go index ec075a1..8ffb487 100644 --- a/tools.go +++ b/internal/tools/tools.go @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package tools // Import applications that would otherwise not be detected by "go mod vendor". import ( diff --git a/vendor_helper_test.go b/internal/tools/vendor_helper_test.go similarity index 98% rename from vendor_helper_test.go rename to internal/tools/vendor_helper_test.go index 8d73f5e..2cab8d3 100644 --- a/vendor_helper_test.go +++ b/internal/tools/vendor_helper_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 tools // Import modules that would otherwise not be detected by "go mod vendor". import (