Compare commits

...

810 commits

Author SHA1 Message Date
Joachim Bauch
fbd910e489
Merge pull request #1222 from strukturag/tools-helpers-internal
Move tools helpers to internal package.
2026-03-12 11:02:42 +01:00
Joachim Bauch
aa7ca9d02f
Move tools helpers to internal package. 2026-03-12 10:57:12 +01:00
Joachim Bauch
e7a8fb7aa9
Update changelog for 2.1.1 2026-03-12 08:41:14 +01:00
Joachim Bauch
d069a924fc
Merge pull request #1208 from strukturag/readlimit-janus-events
Don't limit size of received Janus events.
2026-03-11 11:44:55 +01:00
Joachim Bauch
9d2cda499e
Merge pull request #1220 from strukturag/simplify-notifier
Simplify async notifier code
2026-03-10 10:11:42 +01:00
Joachim Bauch
182a0b78e2
Simplify API for waiter releasing. 2026-03-10 10:04:18 +01:00
Joachim Bauch
bd3c06c9eb
Use "sync.Cond" to wait for created publishers. 2026-03-10 09:57:54 +01:00
Joachim Bauch
2bf6d00a13
Remove deprecated ChannelWaiter. 2026-03-10 09:57:53 +01:00
Joachim Bauch
a6a1c35347
Use "sync.Cond" to wait for publisher connection. 2026-03-10 09:57:52 +01:00
Joachim Bauch
a9e58ec60a
Use "sync.Cond" to wait for publisher to be created. 2026-03-10 09:57:51 +01:00
Joachim Bauch
9c10675867
Simplify notifier code and remove unused. 2026-03-10 09:57:51 +01:00
Joachim Bauch
f195492f8e
Use "sync.Cond" instead of SingleNotifier. 2026-03-10 08:36:50 +01:00
Joachim Bauch
825d747f0a
Merge pull request #1219 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.5
Bump github.com/nats-io/nats-server/v2 from 2.12.4 to 2.12.5
2026-03-10 08:34:31 +01:00
dependabot[bot]
4d42c5e538
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] <support@github.com>
2026-03-09 21:41:06 +00:00
Joachim Bauch
0616b9e0ce
Add warning about Janus queuing unsent events. 2026-03-09 10:16:21 +01:00
Joachim Bauch
26f4a31871
Update generated files. 2026-03-09 10:16:20 +01:00
Joachim Bauch
eca3ee8bfb
Move Janus event types into api.go
It will then get picked up by the easyjson generator to speed up parsing.
2026-03-09 10:15:34 +01:00
Joachim Bauch
c7cb648b60
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.
2026-03-09 10:15:31 +01:00
Joachim Bauch
cdca1097d5
Merge pull request #1216 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.79.2
Bump google.golang.org/grpc from 1.79.1 to 1.79.2
2026-03-09 10:14:39 +01:00
dependabot[bot]
c0a056d3eb
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] <support@github.com>
2026-03-09 09:08:46 +00:00
Joachim Bauch
f3b2513ae4
Merge pull request #1218 from strukturag/update-easyjson-v2-module
Update generated files for v2 module.
2026-03-09 10:05:39 +01:00
Joachim Bauch
10c1d25f20
Update generated files. 2026-03-09 09:58:57 +01:00
Joachim Bauch
a3afe9429d
CI: Update paths for checking generated files. 2026-03-09 09:57:30 +01:00
Joachim Bauch
d97080c118
Merge pull request #1217 from strukturag/dependabot/github_actions/docker/build-push-action-7
Bump docker/build-push-action from 6 to 7
2026-03-09 09:46:15 +01:00
Joachim Bauch
0ca5d7c3dc
Merge pull request #1215 from strukturag/dependabot/github_actions/docker/setup-buildx-action-4
Bump docker/setup-buildx-action from 3 to 4
2026-03-09 09:45:43 +01:00
Joachim Bauch
c6b8308259
Merge pull request #1214 from strukturag/dependabot/github_actions/docker/metadata-action-6
Bump docker/metadata-action from 5 to 6
2026-03-09 09:45:21 +01:00
dependabot[bot]
ebd28a1ffe
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] <support@github.com>
2026-03-06 20:42:42 +00:00
dependabot[bot]
ddb0cdd72f
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] <support@github.com>
2026-03-05 20:42:47 +00:00
dependabot[bot]
9771976acf
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] <support@github.com>
2026-03-05 20:42:41 +00:00
Joachim Bauch
4e6bfc963c
Merge pull request #1213 from strukturag/dependabot/github_actions/docker/setup-qemu-action-4
Bump docker/setup-qemu-action from 3 to 4
2026-03-04 23:12:46 +01:00
Joachim Bauch
33a9c586c9
Merge pull request #1212 from strukturag/dependabot/github_actions/docker/login-action-4
Bump docker/login-action from 3 to 4
2026-03-04 23:12:29 +01:00
dependabot[bot]
65ec614fcd
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] <support@github.com>
2026-03-04 20:42:39 +00:00
dependabot[bot]
bdb862c017
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] <support@github.com>
2026-03-04 20:42:34 +00:00
Joachim Bauch
4a7eb3c2e4
Merge pull request #1211 from strukturag/v2-module
Bump module version to "v2" so versions 2.x can be imported by others.
2026-03-04 20:52:28 +01:00
Joachim Bauch
d3d1644848
Bump module version to "v2" so versions 2.x can be imported by others. 2026-03-04 20:41:06 +01:00
Joachim Bauch
764f46e1a0
Merge pull request #1209 from strukturag/dependabot/github_actions/artifacts-985357984d
Bump the artifacts group with 2 updates
2026-02-27 21:46:45 +01:00
dependabot[bot]
af250e4813
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] <support@github.com>
2026-02-26 20:42:32 +00:00
Joachim Bauch
6e9d948a8c
Merge pull request #1206 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.49.0
Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0
2026-02-26 08:07:52 +01:00
dependabot[bot]
4468b9234f
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] <support@github.com>
2026-02-26 07:01:25 +00:00
Joachim Bauch
8f3e67052d
Merge pull request #1207 from strukturag/update-otel-sdk
Update "go.opentelemetry.io/otel/sdk" to fix "GO-2026-4394".
2026-02-26 08:00:05 +01:00
Joachim Bauch
6da684846d
Update "go.opentelemetry.io/otel/sdk" to fix "GO-2026-4394". 2026-02-26 07:51:10 +01:00
Joachim Bauch
812b97e36a
Merge pull request #1205 from strukturag/dependabot/go_modules/github.com/pion/ice/v4-4.2.1
Bump github.com/pion/ice/v4 from 4.2.0 to 4.2.1
2026-02-20 12:24:17 +01:00
dependabot[bot]
988ba0e8fd
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] <support@github.com>
2026-02-19 20:42:33 +00:00
Joachim Bauch
e78e914a68
Merge pull request #1204 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.18
Bump github.com/pion/sdp/v3 from 3.0.17 to 3.0.18
2026-02-18 07:45:38 +01:00
dependabot[bot]
3a3ce9f3f8
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] <support@github.com>
2026-02-17 20:42:22 +00:00
Joachim Bauch
81fe82eb98
Merge pull request #1202 from lnobach/master
docker: pin spreedbackend uid and add user group
2026-02-17 08:42:49 +01:00
Joachim Bauch
3fcd93a0b5
Merge pull request #1203 from strukturag/ci-docker-compose
CI: Use "docker compose" instead of downloading docker-compose binary.
2026-02-17 08:41:26 +01:00
Joachim Bauch
fd61bdce81
CI: Use "docker compose" instead of downloading docker-compose binary. 2026-02-17 08:31:58 +01:00
Leo
fa18ce95dd pin proxy uid and add user group 2026-02-16 19:26:09 +01:00
Joachim Bauch
12dfad5c3a
Merge pull request #1200 from strukturag/dependabot/go_modules/etcd-1a55b60ceb
Bump the etcd group with 4 updates
2026-02-16 08:44:18 +01:00
Joachim Bauch
543fa397e6
Merge pull request #1199 from strukturag/dependabot/go_modules/github.com/pion/dtls/v3-3.1.1
Bump github.com/pion/dtls/v3 from 3.1.0 to 3.1.1
2026-02-16 08:43:59 +01:00
Leo
bd9a7b2ba9 pin spreedbackend uid and add user group 2026-02-15 20:23:04 +01:00
dependabot[bot]
194494cd2c
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] <support@github.com>
2026-02-13 20:51:25 +00:00
Joachim Bauch
5051a6b194
Merge pull request #1201 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.79.1
Bump google.golang.org/grpc from 1.78.0 to 1.79.1
2026-02-13 21:50:01 +01:00
dependabot[bot]
750656ea89
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] <support@github.com>
2026-02-13 20:42:50 +00:00
dependabot[bot]
9d45e78809
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] <support@github.com>
2026-02-13 14:19:13 +00:00
Joachim Bauch
d0cfa058d8
Merge pull request #1195 from strukturag/dependabot/docker/docker/server/golang-1.26-alpine
Bump golang from 1.25-alpine to 1.26-alpine in /docker/server
2026-02-12 10:46:04 +01:00
Joachim Bauch
50bcd61da0
Merge pull request #1194 from strukturag/dependabot/docker/docker/proxy/golang-1.26-alpine
Bump golang from 1.25-alpine to 1.26-alpine in /docker/proxy
2026-02-12 10:45:51 +01:00
Joachim Bauch
8d0cbfa88a
Merge pull request #1197 from strukturag/remove-go1.24
Drop support for Golang 1.24
2026-02-12 10:45:24 +01:00
Joachim Bauch
5da3d601fe
Ignore more checklocks false positives. 2026-02-12 10:29:43 +01:00
Joachim Bauch
d47c280d31
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.
2026-02-12 10:24:50 +01:00
Joachim Bauch
de03ae2514
make: Use "GO" variable for checklocks. 2026-02-12 10:11:37 +01:00
Joachim Bauch
a5f70b6e47
Switch to safer waitgroup.Go where possible. 2026-02-12 09:39:17 +01:00
Joachim Bauch
e4013b9f14
Drop support for Golang 1.24 2026-02-12 09:03:15 +01:00
Joachim Bauch
bd8c758847
Remove Golang 1.24 compatibility for synctest.Test. 2026-02-12 09:03:14 +01:00
Joachim Bauch
6caac6fbca
Remove Golang 1.24 compatibility for log output. 2026-02-12 09:03:14 +01:00
Joachim Bauch
95c66f9d82
Revert "Temporarily run some tests only on Go 1.25 or newer."
This reverts commit 9b062a994a.
2026-02-12 09:03:13 +01:00
Joachim Bauch
15d6e516bd
CI: No longer test with Golang 1.24 2026-02-12 09:03:13 +01:00
Joachim Bauch
083858294a
Merge pull request #1196 from strukturag/golang-1.26
CI: Test with Golang 1.26
2026-02-12 08:51:58 +01:00
Joachim Bauch
9b062a994a
Temporarily run some tests only on Go 1.25 or newer.
Prevents error "synctest.Wait requires go1.25 or later (file is go1.24)".
2026-02-12 08:43:55 +01:00
Joachim Bauch
ba482a544b
make: Only need "synctest" GOEXPERIMENT on go1.24 2026-02-12 08:39:15 +01:00
Joachim Bauch
f9f2347d11
CI: Run modernize and checklocks with Golang 1.26 2026-02-12 08:31:13 +01:00
Joachim Bauch
73af7bb367
CI: Test with Golang 1.26 2026-02-12 08:31:13 +01:00
Joachim Bauch
9b6616257b
Merge pull request #1193 from strukturag/dependabot/go_modules/github.com/pion/dtls/v3-3.1.0
Bump github.com/pion/dtls/v3 from 3.0.10 to 3.1.0
2026-02-12 08:24:20 +01:00
dependabot[bot]
383494585d
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] <support@github.com>
2026-02-11 20:43:19 +00:00
dependabot[bot]
43e5108373
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] <support@github.com>
2026-02-11 20:43:12 +00:00
dependabot[bot]
af732300e3
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] <support@github.com>
2026-02-11 15:23:52 +00:00
Joachim Bauch
e4dd763d02
Merge pull request #1192 from strukturag/dependabot/pip/docs/markdown-3.10.2
Bump markdown from 3.10.1 to 3.10.2 in /docs
2026-02-10 08:21:51 +01:00
dependabot[bot]
a0d3dd100f
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] <support@github.com>
2026-02-09 23:35:12 +00:00
Joachim Bauch
33a9c1bde2
Merge pull request #1191 from strukturag/janus-events-url
readme: Add example websocket urls for Janus events.
2026-02-05 10:03:32 +01:00
Joachim Bauch
4a7ecc3ac5
readme: Add example websocket urls for Janus events. 2026-02-05 10:01:17 +01:00
Joachim Bauch
0b46f9d17c
Merge pull request #1190 from strukturag/simplify-error-type
Simplify error type checks.
2026-02-05 09:59:33 +01:00
Joachim Bauch
b004fef9ec
Simplify error type checks.
Based on "errors.AsType" from upcoming Go 1.26.
2026-02-05 09:40:24 +01:00
Joachim Bauch
7dbce454e6
Merge pull request #1189 from strukturag/dependabot/go_modules/google.golang.org/grpc/cmd/protoc-gen-go-grpc-1.6.1
Bump google.golang.org/grpc/cmd/protoc-gen-go-grpc from 1.6.0 to 1.6.1
2026-02-05 08:16:53 +01:00
dependabot[bot]
cc87670153
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] <support@github.com>
2026-02-04 20:42:30 +00:00
Joachim Bauch
ecac46bf5f
Update changelog for 2.1.0 2026-02-03 11:10:44 +01:00
Joachim Bauch
a92819f3b2
Next version will be 2.1.0 2026-02-03 11:03:06 +01:00
Joachim Bauch
5ddfe3dc50
Add helper script to prepare a changelog. 2026-02-03 11:02:34 +01:00
Joachim Bauch
9c199a044b
Merge pull request #1186 from strukturag/move-tests
Move tests closer to code being checked
2026-02-03 10:00:50 +01:00
Joachim Bauch
f921e4a2c0
Remove duplicate code to load proxy configuration. 2026-02-03 09:54:10 +01:00
Joachim Bauch
99762a3ca9
Improxy proxy server test coverage. 2026-02-03 09:37:07 +01:00
Joachim Bauch
c2c9d0725f
Add test for reloading etcd token configuration. 2026-02-03 08:31:41 +01:00
Joachim Bauch
15f2d3cd5c
Add tests for static token configuration. 2026-02-02 16:56:11 +01:00
Joachim Bauch
806ef1f564
Add tests for Janus stream selection and simplify code. 2026-02-02 16:31:59 +01:00
Joachim Bauch
9cc07c8a65
Remove unused client method and increase test coverage. 2026-02-02 16:01:48 +01:00
Joachim Bauch
20c0fe086b
Test federated room session id handling. 2026-02-02 15:39:19 +01:00
Joachim Bauch
2a3c1660e3
Test validation of more client signaling messages. 2026-02-02 15:39:18 +01:00
Joachim Bauch
f1dc9d6c5f
Test validation of federation messages. 2026-02-02 15:39:18 +01:00
Joachim Bauch
a5b583910a
Add proxy API tests. 2026-02-02 15:39:17 +01:00
Joachim Bauch
10175d6cca
Add reloader tests. 2026-02-02 15:39:17 +01:00
Joachim Bauch
daa542f80a
Add buffer pool tests. 2026-02-02 15:39:16 +01:00
Joachim Bauch
9a1e4121d3
Add tests for prometheus registry. 2026-02-02 15:39:16 +01:00
Joachim Bauch
5fc709434b
Add tests for SFU-related session events. 2026-02-02 15:39:15 +01:00
Joachim Bauch
1d4ffd33fc
Move SFU Janus tests closer to the checked code. 2026-02-02 15:39:14 +01:00
Joachim Bauch
d129c5c55b
Add test for "Hub.GetPublisherIdForSessionId". 2026-02-02 15:39:14 +01:00
Joachim Bauch
b733b6fa60
Implement "GetConnectionURL" for SFUPublisher. 2026-02-02 15:39:14 +01:00
Joachim Bauch
710264e366
Move SFU proxy tests closer to the checked code. 2026-02-02 15:39:13 +01:00
Joachim Bauch
cde3ee53a9
Add mock ServerHub implementation. 2026-02-02 15:39:12 +01:00
Joachim Bauch
cb6df05cd3
Merge pull request #1185 from strukturag/multiple-chat-comments
Support receiving and forwarding multiple chat messages from Talk.
2026-01-29 14:11:46 +01:00
Joachim Bauch
15d734d77b
Document sending multiple chat messages from Talk. 2026-01-29 11:31:56 +01:00
Joachim Bauch
77f0672682
Update generated files. 2026-01-29 11:31:41 +01:00
Joachim Bauch
eafa39a1c5
Support receiving and forwarding multiple chat messages from Talk. 2026-01-29 11:31:08 +01:00
Joachim Bauch
043c854cd2
Merge pull request #1184 from strukturag/checklocks-generics
checklocks: Remove ignore since generics are supported now.
2026-01-29 10:30:44 +01:00
Joachim Bauch
d80143af4c
checklocks: Remove ignore since generics are supported now. 2026-01-29 10:26:48 +01:00
Joachim Bauch
66e2d73ee5
Merge pull request #1183 from strukturag/sessions-in-call-metrics
Add more metrics about sessions in calls.
2026-01-29 10:13:10 +01:00
Joachim Bauch
7ea4691404
Improve incall tests with leaving sessions. 2026-01-29 10:05:28 +01:00
Joachim Bauch
3b667ffcf6
Document new call metrics. 2026-01-29 09:21:38 +01:00
Joachim Bauch
4c4abb16ce
Add more metrics about sessions in calls. 2026-01-29 09:18:27 +01:00
Joachim Bauch
b11d80c0f3
Merge pull request #1182 from strukturag/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.3.1
Bump github.com/golang-jwt/jwt/v5 from 5.3.0 to 5.3.1
2026-01-29 08:11:29 +01:00
dependabot[bot]
9891003304
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] <support@github.com>
2026-01-28 20:42:24 +00:00
Joachim Bauch
9c8b82a60d
Merge pull request #1179 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.4
Bump github.com/nats-io/nats-server/v2 from 2.12.3 to 2.12.4
2026-01-27 22:27:34 +01:00
dependabot[bot]
9a525f86cc
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] <support@github.com>
2026-01-27 20:42:42 +00:00
Joachim Bauch
791ad0e294
Merge pull request #1177 from strukturag/dependabot/pip/docs/markdown-3.10.1
Bump markdown from 3.10 to 3.10.1 in /docs
2026-01-22 08:05:22 +01:00
dependabot[bot]
bc1ecd1f32
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] <support@github.com>
2026-01-21 20:43:05 +00:00
Joachim Bauch
39b0cca4b7
Merge pull request #1174 from strukturag/remove-unused-code
Remove unused testing code.
2026-01-13 15:26:12 +01:00
Joachim Bauch
88be60c0a6
Remove unused testing code.
Was moved to "sfu/test/sfu.go" in #1151 but not deleted.
2026-01-13 15:21:11 +01:00
Joachim Bauch
d6264dfe1d
Merge pull request #1151 from strukturag/refactor-packages
Refactor code into packages
2026-01-13 09:21:31 +01:00
Joachim Bauch
4eb2576e42
Rename package of async events test helpers. 2026-01-13 09:05:39 +01:00
Joachim Bauch
8293de75b1
Add metrics tests. 2026-01-13 08:50:45 +01:00
Joachim Bauch
3083c39215
Rename package of etcd test helpers. 2026-01-13 08:50:45 +01:00
Joachim Bauch
69d63ffd11
Move dns test helpers to separate package. 2026-01-13 08:50:44 +01:00
Joachim Bauch
a4631a19cf
Move test storage to "test" package. 2026-01-13 08:50:44 +01:00
Joachim Bauch
ee6f026bbb
Move log test helpers to separate package. 2026-01-13 08:50:40 +01:00
Joachim Bauch
3c6d3c0b7a
Move NATS testing code to separate module. 2026-01-13 08:49:28 +01:00
Joachim Bauch
c162b8bbeb
Evaluate expiration when setting initial transient data. 2026-01-13 08:49:27 +01:00
Joachim Bauch
be8353a54b
Move grpc server code to "grpc" package. 2026-01-13 08:49:27 +01:00
Joachim Bauch
9d321bb3ab
Move server-related code to "server" package. 2026-01-13 08:49:26 +01:00
Joachim Bauch
5eb0571d31
Move backend client to "talk" package. 2026-01-13 08:49:25 +01:00
Joachim Bauch
daaf16bbf8
Move session id codec to separate package. 2026-01-13 08:49:24 +01:00
Joachim Bauch
7dfd82c8df
Don't import errors for proxy from signaling package. 2026-01-13 08:49:24 +01:00
Joachim Bauch
3c41dcdbce
Move common client code to separate package. 2026-01-13 08:49:19 +01:00
Joachim Bauch
98a20e3879
Merge pull request #1168 from strukturag/dependabot/pip/docs/sphinx-9.1.0
Bump sphinx from 8.2.3 to 9.1.0 in /docs
2026-01-13 08:05:28 +01:00
dependabot[bot]
d40e67fb2f
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] <support@github.com>
2026-01-13 07:03:36 +00:00
Joachim Bauch
ebd7b6d054
Merge pull request #1172 from strukturag/dependabot/pip/docs/sphinx-rtd-theme-3.1.0
Bump sphinx-rtd-theme from 3.0.2 to 3.1.0 in /docs
2026-01-13 08:02:36 +01:00
dependabot[bot]
5284aa6915
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] <support@github.com>
2026-01-13 01:20:53 +00:00
Joachim Bauch
124c37108b
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.
2026-01-12 13:16:50 +01:00
Joachim Bauch
9bbc0588e3
Move code for commandline applications to "cmd" folder. 2026-01-12 13:16:49 +01:00
Joachim Bauch
315b2583e1
Relax order when checking for join/leave in RunTestClientTakeoverRoomSession. 2026-01-12 13:16:48 +01:00
Joachim Bauch
8c12403c4f
Filter "leave" events for which no "join" was sent before. 2026-01-12 13:16:48 +01:00
Joachim Bauch
13313c5d96
Stop using "DrainMessages" in tests which might process too many messages. 2026-01-12 13:16:47 +01:00
Joachim Bauch
b8b94dc802
Include session id in log message on ignored room event. 2026-01-12 13:16:47 +01:00
Joachim Bauch
348e7b3360
Store time when join room was successfull in session, not when it ended. 2026-01-12 13:16:46 +01:00
Joachim Bauch
b2934836a9
Move backend configuration code to "talk" package. 2026-01-12 13:16:45 +01:00
Joachim Bauch
88bb94bd2a
Allow transient data with both "initial" and "set" in TestDialoutStatus. 2026-01-12 13:16:45 +01:00
Joachim Bauch
85dc414627
Fix race in flaky "DoTestSwitchToOne" / "DoTestSwitchToMultiple". 2026-01-12 13:16:44 +01:00
Joachim Bauch
b82a26dadb
Don't log initial empty transient data. 2026-01-12 13:16:43 +01:00
Joachim Bauch
df678831d8
Process only single "joined" event instead of discarding any received. 2026-01-12 13:16:43 +01:00
Joachim Bauch
e3e0963327
Filter duplicate "flags" events. 2026-01-12 13:16:42 +01:00
Joachim Bauch
f517e554fe
Send updated load synchronously, assert on errors while waiting. 2026-01-12 13:16:42 +01:00
Joachim Bauch
80bdeb79fc
Fix flaky "TestDuplicateVirtualSessions" for mixed ordering of joined/update. 2026-01-12 13:16:41 +01:00
Joachim Bauch
64152f804b
Wait for initialization code to complete before stopping. 2026-01-12 13:16:40 +01:00
Joachim Bauch
f8da2cb0e5
Fix flaky "TestVirtualSessionCustomInCall" for cases where update is sent after joined. 2026-01-12 13:16:39 +01:00
Joachim Bauch
221b6adb8e
codecov: Ignore easyjson stubs in any folders. 2026-01-12 13:16:39 +01:00
Joachim Bauch
827de250ea
Move flags to "internal" package. 2026-01-12 13:16:38 +01:00
Joachim Bauch
75f6579efa
Update generated files. 2026-01-12 13:16:37 +01:00
Joachim Bauch
ecc25c402f
Move async events code to "async/events" package. 2026-01-12 13:16:36 +01:00
Joachim Bauch
27b46f7f39
Update generated files. 2026-01-12 13:16:35 +01:00
Joachim Bauch
fbf93dca42
Move Talk-specific API to "talk" package. 2026-01-12 13:16:34 +01:00
Joachim Bauch
231f7c8af4
Move geo related code to "geoip" package.
With that, add dedicated types for "Country" and "Continent".
2026-01-12 13:16:33 +01:00
Joachim Bauch
2275a5542e
Move certificate / pool reloader to "security" package. 2026-01-12 13:16:33 +01:00
Joachim Bauch
80040aaa6d
Move DNS monitor to "dns" package. 2026-01-12 13:16:32 +01:00
Joachim Bauch
f407b98443
Update generated files. 2026-01-12 13:16:31 +01:00
Joachim Bauch
6756520447
Move etcd client code to "etcd" package. 2026-01-12 13:16:30 +01:00
Joachim Bauch
3e18e6a4fa
Move function to check for address already in use to test package. 2026-01-12 13:16:29 +01:00
Joachim Bauch
407577ee8d
Update generated files. 2026-01-12 13:16:28 +01:00
Joachim Bauch
ff69ee5c91
Move backend object to talk package. 2026-01-12 13:16:28 +01:00
Joachim Bauch
98764f2782
Move confiuration helpers to config module. 2026-01-12 13:16:27 +01:00
Joachim Bauch
61491a786c
Update generated files. 2026-01-12 13:16:26 +01:00
Joachim Bauch
179498f28b
Move signaling api types to api package. 2026-01-12 13:16:25 +01:00
Joachim Bauch
00796dd8ad
Move crypto helper functions to internal package. 2026-01-12 13:16:24 +01:00
Joachim Bauch
cfd508005d
Update generated files. 2026-01-12 13:16:24 +01:00
Joachim Bauch
b7f8f83944
Move Talk capabilities to talk package. 2026-01-12 13:16:23 +01:00
Joachim Bauch
25e040ffb9
Move buffer/httpclient pools to pool package. 2026-01-12 13:16:22 +01:00
Joachim Bauch
5543046305
Move NATS client to nats package. 2026-01-12 13:16:21 +01:00
Joachim Bauch
9ba9256edb
Merge pull request #1171 from strukturag/dependabot/go_modules/github.com/pion/ice/v4-4.2.0
Bump github.com/pion/ice/v4 from 4.1.0 to 4.2.0
2026-01-10 20:52:22 +01:00
dependabot[bot]
04decde5aa
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] <support@github.com>
2026-01-09 20:54:10 +00:00
Joachim Bauch
f1a719cb23
Move ConcurrentMap to container package. 2026-01-08 10:24:07 +01:00
Joachim Bauch
e478b93ba0
Move LruCache to container package. 2026-01-08 10:24:06 +01:00
Joachim Bauch
bc9b353975
Move IPList (from AllowedIps) to container package. 2026-01-08 10:24:06 +01:00
Joachim Bauch
a1ec06d802
Move SplitEntries helper to internal package. 2026-01-08 10:24:05 +01:00
Joachim Bauch
1c3a03e972
Move throttler code to async package. 2026-01-08 10:24:04 +01:00
Joachim Bauch
446936f7ff
Move common stats code to metrics package. 2026-01-08 10:24:03 +01:00
Joachim Bauch
22f45ac482
Move closer helper to internal package. 2026-01-08 10:24:02 +01:00
Joachim Bauch
af4a7e7ab9
Move notifier code to async package. 2026-01-08 10:24:02 +01:00
Joachim Bauch
674b09d38d
Move channel waiter code to async package. 2026-01-08 10:24:01 +01:00
Joachim Bauch
ff4e736cf7
Move deferred executor code to async package. 2026-01-08 10:24:00 +01:00
Joachim Bauch
0006f74c2d
Move backoff code to async package. 2026-01-08 10:23:59 +01:00
Joachim Bauch
98a8465e12
Move Goroutines helpers to test package. 2026-01-08 10:23:59 +01:00
Joachim Bauch
8b2cb0fcff
Move synctest helper to test package. 2026-01-08 10:23:58 +01:00
Joachim Bauch
de9ea429e7
Merge pull request #1170 from strukturag/checklocks-1.25
CI: Run checklocks with Go 1.25
2026-01-08 10:23:41 +01:00
Joachim Bauch
0b89140ef1
CI: Run checklocks with Go 1.25 2026-01-08 10:21:49 +01:00
Joachim Bauch
894815f6d7
Merge pull request #1169 from strukturag/licensecheck-all-folders
CI: Process files in all folders with licensecheck.
2026-01-08 10:10:12 +01:00
Joachim Bauch
18174c6470
CI: Process files in all folders with licensecheck. 2026-01-08 10:08:55 +01:00
Joachim Bauch
8e62c68acb
Merge pull request #1167 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.78.0
Bump google.golang.org/grpc from 1.77.0 to 1.78.0
2026-01-07 11:10:41 +01:00
Joachim Bauch
65f7cc3a1a
Merge pull request #1166 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.17
Bump github.com/pion/sdp/v3 from 3.0.16 to 3.0.17
2026-01-07 11:08:41 +01:00
dependabot[bot]
ee908528d4
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] <support@github.com>
2025-12-23 20:01:37 +00:00
dependabot[bot]
eab1d4392a
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] <support@github.com>
2025-12-22 20:01:36 +00:00
Joachim Bauch
9461113cfa
Merge pull request #1165 from strukturag/single-nats-channel
Process all NATS messages for same target from single goroutine.
2025-12-22 16:20:54 +01:00
Joachim Bauch
f2eac4c3b3
Process all NATS messages for same target from single goroutine. 2025-12-22 15:19:54 +01:00
Joachim Bauch
01c4737ec0
Merge pull request #1164 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.3
Bump github.com/nats-io/nats-server/v2 from 2.12.2 to 2.12.3
2025-12-17 21:19:21 +01:00
Joachim Bauch
793a80d6dc
Merge pull request #1162 from strukturag/dependabot/go_modules/etcd-030f1bcb27
Bump the etcd group with 4 updates
2025-12-17 21:17:10 +01:00
dependabot[bot]
3f278d2005
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] <support@github.com>
2025-12-17 20:14:16 +00:00
Joachim Bauch
11866dc217
Merge pull request #1163 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.48.0
Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0
2025-12-17 21:12:24 +01:00
dependabot[bot]
2b87c5f5c2
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] <support@github.com>
2025-12-17 20:01:55 +00:00
dependabot[bot]
8ae1e4eafd
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] <support@github.com>
2025-12-17 20:01:50 +00:00
Joachim Bauch
9f5ce75454
Merge pull request #1161 from strukturag/ci-govuln-latest
CI: Always use latest patch release for govuln checks.
2025-12-16 15:36:42 +01:00
Joachim Bauch
ff753051f7
CI: Always use latest patch release for govuln checks. 2025-12-16 15:34:48 +01:00
Joachim Bauch
0f23c2c4b1
Merge pull request #1160 from strukturag/ci-modernize-go-1.25
CI: Run "modernize" with Go 1.25
2025-12-16 13:29:43 +01:00
Joachim Bauch
b22332e1d7
CI: Skip some modernize checks that fail on GRPC stubs. 2025-12-16 13:10:39 +01:00
Joachim Bauch
a620ecca8b
CI: Run "modernize" with Go 1.25 2025-12-16 13:10:39 +01:00
Joachim Bauch
61d84d0107
Merge pull request #1159 from strukturag/docker-quay-io
CI: Also upload images to quay.io/strukturag/nextcloud-spreed-signaling
2025-12-16 10:20:58 +01:00
Joachim Bauch
f975ce1494
CI: Also upload images to quay.io/strukturag/nextcloud-spreed-signaling 2025-12-16 10:18:11 +01:00
Joachim Bauch
b68a109591
Merge pull request #1154 from strukturag/dependabot/github_actions/artifacts-c2e7f7cad0
Bump the artifacts group with 2 updates
2025-12-15 09:03:59 +01:00
Joachim Bauch
b74f8fc349
Merge pull request #1155 from strukturag/dependabot/go_modules/github.com/pion/ice/v4-4.1.0
Bump github.com/pion/ice/v4 from 4.0.13 to 4.1.0
2025-12-15 08:30:20 +01:00
Joachim Bauch
cb21ff6b6a
Merge pull request #1156 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.11
Bump google.golang.org/protobuf from 1.36.10 to 1.36.11
2025-12-15 08:29:57 +01:00
Joachim Bauch
c917bae050
Merge pull request #1157 from strukturag/dependabot/github_actions/actions/cache-5
Bump actions/cache from 4 to 5
2025-12-15 08:29:07 +01:00
dependabot[bot]
5dd4c91fff
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] <support@github.com>
2025-12-12 20:01:49 +00:00
dependabot[bot]
f0b2fc6c4f
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] <support@github.com>
2025-12-12 20:01:48 +00:00
dependabot[bot]
2f5af4d4a1
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] <support@github.com>
2025-12-12 20:01:43 +00:00
dependabot[bot]
47b51e804f
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] <support@github.com>
2025-12-12 20:01:40 +00:00
Joachim Bauch
20d09941b9
Merge pull request #1153 from strukturag/fix-flaky-tests
Fix flaky tests that fail under load.
2025-12-11 11:21:36 +01:00
Joachim Bauch
550e40f322
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.
2025-12-11 11:17:20 +01:00
Joachim Bauch
18e41f243a
Also ignore "participants" events sent before room was joined.
Fix flaky "TestVirtualSessionCustomInCall" under load.
2025-12-11 11:02:36 +01:00
Joachim Bauch
78d74ea3ee
Start timer for anonymous sessions to join room before sending response.
Fixes flaky "TestExpectAnonymousJoinRoomAfterLeave" under load.
2025-12-11 11:02:32 +01:00
Joachim Bauch
8ed1f15b95
Merge pull request #1152 from strukturag/fix-subscriber-close-on-error
Close subscriber synchronously on errors.
2025-12-11 08:58:15 +01:00
Joachim Bauch
8371fbe9bf
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).
2025-12-11 08:51:58 +01:00
Joachim Bauch
d5edd53536
Merge pull request #1150 from strukturag/split-logging
Move logging code to separate package.
2025-12-10 14:32:32 +01:00
Joachim Bauch
62587796ce
Move logging code to separate package. 2025-12-09 15:26:47 +01:00
Joachim Bauch
67b557349d
Merge pull request #1149 from strukturag/parallelize-tests
Parallelize more tests.
2025-12-09 14:12:29 +01:00
Joachim Bauch
f52da04859
Use testing/synctest so Test_TransientData is not timing-dependent. 2025-12-09 13:31:51 +01:00
Joachim Bauch
16c37cb0ed
Ensure all client connections are closed when test ends. 2025-12-09 13:31:51 +01:00
Joachim Bauch
adb391ab5a
Stop transaction goroutine when removing. 2025-12-09 11:57:36 +01:00
Joachim Bauch
e13bca696b
Close client connections and wait for server before terminating test. 2025-12-09 11:42:02 +01:00
Joachim Bauch
65edf5c03a
Wait for hub housekeeping to finish before continuing tests. 2025-12-09 11:25:07 +01:00
Joachim Bauch
c533b039b2
"checkStatsValue" is now unused, but keep if needed in future. 2025-12-09 10:55:29 +01:00
Joachim Bauch
6f35e021f9
Use "t.Chdir" to directory is restored after test. 2025-12-09 10:55:12 +01:00
Joachim Bauch
f7b9224bda
The loopback NATS client can not leak goroutines. 2025-12-09 10:55:11 +01:00
Joachim Bauch
892dae6842
Run Janus subscriber stats tests in parallel. 2025-12-09 10:55:10 +01:00
Joachim Bauch
0960a714aa
Use "InDelta" to compare values where expected could be "0". 2025-12-09 10:55:10 +01:00
Joachim Bauch
415a49e04b
Run Janus bandwidth tests in parallel. 2025-12-09 10:55:09 +01:00
Joachim Bauch
964e9d2343
Allow running publisher stats tests in parallel. 2025-12-09 10:55:08 +01:00
Joachim Bauch
958f50cec3
Allow running backend configuration tests in parallel. 2025-12-09 10:55:07 +01:00
Joachim Bauch
b86d05de08
Allow running file watcher tests to run in parallel. 2025-12-05 12:15:26 +01:00
Joachim Bauch
9363049e0f
Allow running tests with mock DNS discovery to run in parallel. 2025-12-05 12:02:33 +01:00
Joachim Bauch
4243276698
Merge pull request #1145 from strukturag/linter-updates
Enable more linters
2025-12-05 11:42:33 +01:00
Joachim Bauch
efb90b4216
Add test for "internal.MakePtr". 2025-12-05 11:38:40 +01:00
Joachim Bauch
3178e0ee08
Move TestStorage to separate class for easier reuse. 2025-12-05 11:38:39 +01:00
Joachim Bauch
8510ce45f3
lint: Enable "testifylint" linter. 2025-12-05 11:13:10 +01:00
Joachim Bauch
d8d17734cb
Improve use of testify assertions. 2025-12-05 11:13:09 +01:00
Joachim Bauch
b8e0e5c2c1
lint: Enable "perfsprint" linter. 2025-12-05 11:13:09 +01:00
Joachim Bauch
f338a7b91e
Don't use string formatting without parameters, optimize simple cases. 2025-12-05 11:13:08 +01:00
Joachim Bauch
643c430e36
Use "testStorage" to store ping requests for parallel access. 2025-12-05 11:13:07 +01:00
Joachim Bauch
e0da0529ec
Don't call "t.Setenv" in synctest backport. 2025-12-05 11:09:02 +01:00
Joachim Bauch
f1781719e1
lint: Enable "paralleltest" linter. 2025-12-05 11:09:01 +01:00
Joachim Bauch
4986122493
Run tests in parallel where possible. 2025-12-05 11:09:01 +01:00
Joachim Bauch
c3c3f0bf75
lint: Enable "modernize" linter. 2025-12-05 11:09:00 +01:00
Joachim Bauch
e761ea071b
Use strings.Builder instead of looped string concatenation. 2025-12-05 11:09:00 +01:00
Joachim Bauch
697f659083
lint: Enable "gocritic" linter. 2025-12-05 11:08:59 +01:00
Joachim Bauch
6d3ff0c5ba
Silence error about previous defer not running after "log.Fatal". 2025-12-05 11:08:59 +01:00
Joachim Bauch
bcdf9af5eb
Remove trailing whitespace from key. 2025-12-05 11:08:58 +01:00
Joachim Bauch
5a6dfa0516
Fix comment format. 2025-12-05 11:08:58 +01:00
Joachim Bauch
f237458b35
Don't capitalize variable name. 2025-12-05 11:08:57 +01:00
Joachim Bauch
dd01d98553
Simplify and use range over integer. 2025-12-05 11:08:57 +01:00
Joachim Bauch
57b6b326c0
Simplify string concatenation. 2025-12-05 11:08:56 +01:00
Joachim Bauch
9d07a852a9
Rewrite long if-else chains as switch statements. 2025-12-05 11:08:56 +01:00
Joachim Bauch
694297a6f4
Fix ordering of case statements (found by "gocritic"). 2025-12-05 11:08:55 +01:00
Joachim Bauch
f795bf303d
lint: Enable "exptostd" to detect future std library features. 2025-12-05 11:08:54 +01:00
Joachim Bauch
c581bc14d5
lint: Enable "errchkjson" and update json.Marshal error handling. 2025-12-05 11:08:54 +01:00
Joachim Bauch
98060d48cb
lint: Enable "nilness" check in govet. 2025-12-05 11:08:53 +01:00
Joachim Bauch
55d776d110
Remove unnecessary nil check. 2025-12-05 11:08:52 +01:00
Joachim Bauch
b1c18c7207
lint: Check spelling using "misspell". 2025-12-05 11:08:51 +01:00
Joachim Bauch
55bafac6b7
Fix spelling errors. 2025-12-05 11:08:51 +01:00
Joachim Bauch
66b3049cfc
Merge pull request #1146 from strukturag/dependabot/go_modules/github.com/pion/ice/v4-4.0.13
Bump github.com/pion/ice/v4 from 4.0.12 to 4.0.13
2025-12-04 22:11:22 +01:00
dependabot[bot]
5afa838ee8
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] <support@github.com>
2025-12-04 20:01:43 +00:00
Joachim Bauch
cc934c8b85
Merge pull request #1144 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-9.2.0
Bump golangci/golangci-lint-action from 9.1.0 to 9.2.0
2025-12-04 08:17:00 +01:00
dependabot[bot]
5893fedef4
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] <support@github.com>
2025-12-03 20:01:54 +00:00
Joachim Bauch
628d34d7ce
Merge pull request #1143 from strukturag/client-version
client: Include version, optimize JSON processing.
2025-12-03 09:56:51 +01:00
Joachim Bauch
4586775afc
client: Include version, optimize JSON processing. 2025-12-03 09:52:08 +01:00
Joachim Bauch
5921830423
Merge pull request #1140 from strukturag/shorter-session-ids
Generate shorter session ids.
2025-12-03 09:48:14 +01:00
Joachim Bauch
093555dc8d
Merge pull request #1142 from strukturag/tarball-version
Include "version.txt" in tarball.
2025-12-03 09:37:28 +01:00
Joachim Bauch
5f58e335c8
make: Include "version.txt" in tarball. 2025-12-03 09:31:32 +01:00
Joachim Bauch
ad15055515
CI: Check that version of tarball builds is not "unknown". 2025-12-03 09:31:14 +01:00
Joachim Bauch
a0b64b30e0
Session ids are unique, no need to include type in cache key. 2025-12-03 07:59:35 +01:00
Joachim Bauch
f3a81c23c3
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.
2025-12-02 16:55:43 +01:00
Joachim Bauch
f4fca4f52b
Use microseconds instead of timestamppb object for session creation. 2025-12-02 10:06:47 +01:00
Joachim Bauch
2d729c436d
Merge pull request #1120 from strukturag/transient-sessiondata
Introduce transient session data.
2025-12-02 09:52:23 +01:00
Joachim Bauch
3fe8de1167
Implement transient session data. 2025-12-02 09:47:11 +01:00
Joachim Bauch
805c4cdc81
Describe transient session data. 2025-12-02 09:43:03 +01:00
Joachim Bauch
938b21c359
Merge pull request #1138 from strukturag/dependabot/go_modules/github.com/pion/ice/v4-4.0.12
Bump github.com/pion/ice/v4 from 4.0.11 to 4.0.12
2025-12-02 08:31:50 +01:00
dependabot[bot]
4f4d673e5a
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] <support@github.com>
2025-12-01 21:20:31 +00:00
Joachim Bauch
7a37c0ccbd
Merge pull request #1134 from strukturag/metrics-bandwidth
Add metrics about client bytes/messages sent/received.
2025-12-01 16:48:52 +01:00
Joachim Bauch
9ee64b8c66
Add metrics about client bytes/messages sent/received. 2025-12-01 16:44:15 +01:00
Joachim Bauch
87345118d8
client: Send "bye" before closing connection. 2025-12-01 16:44:15 +01:00
Joachim Bauch
aee0e6d866
client: Log bandwidth used (incoming / outgoing). 2025-12-01 16:44:14 +01:00
Joachim Bauch
5238966385
Merge pull request #1136 from strukturag/dependabot/go_modules/google.golang.org/grpc/cmd/protoc-gen-go-grpc-1.6.0
Bump google.golang.org/grpc/cmd/protoc-gen-go-grpc from 1.5.1 to 1.6.0
2025-12-01 15:19:39 +01:00
dependabot[bot]
595a23ca0a Update generated files from 49d9f873dd 2025-11-27 20:03:34 +00:00
dependabot[bot]
49d9f873dd
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] <support@github.com>
2025-11-27 20:01:27 +00:00
Joachim Bauch
139b24d11b
Merge pull request #1135 from strukturag/dependabot/go_modules/github.com/pion/ice/v4-4.0.11
Bump github.com/pion/ice/v4 from 4.0.10 to 4.0.11
2025-11-26 22:33:18 +01:00
dependabot[bot]
a042d00d25
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] <support@github.com>
2025-11-26 20:01:44 +00:00
Joachim Bauch
a13ca9c9dd
Merge pull request #1133 from strukturag/unnecessary-pointer
No need to use list of pointers, use objects directly.
2025-11-24 15:22:53 +01:00
Joachim Bauch
aefa5c9e36
Break loop when looking for joined events if no hello matched. 2025-11-24 15:07:00 +01:00
Joachim Bauch
535a36042c
Update generated files. 2025-11-24 15:06:21 +01:00
Joachim Bauch
3d4a16bdad
No need to use list of pointers, use objects directly. 2025-11-24 15:06:07 +01:00
Joachim Bauch
263aa418e2
Merge pull request #1132 from strukturag/embed-logger-tests
Use test-related logger for embedded etcd.
2025-11-24 14:08:45 +01:00
Joachim Bauch
0e835c8130
Merge pull request #1131 from strukturag/improve-sprintf-performance
Don't use fmt.Sprintf where not necessary.
2025-11-24 14:07:10 +01:00
Joachim Bauch
e90760a2f1
Use test-related logger for embedded etcd. 2025-11-24 14:04:15 +01:00
Joachim Bauch
855d2c8231
CI: Run benchmarks. 2025-11-24 12:36:01 +01:00
Joachim Bauch
4250d912ae
Add benchmarks for string concatenation. 2025-11-24 12:36:00 +01:00
Joachim Bauch
f412ef533a
Use "UnmarshalJSON" instead of "json.Unmarshal" for some speedup. 2025-11-24 12:35:59 +01:00
Joachim Bauch
0a669b067e
Use string concatenation instead of simple fmt.Sprintf. 2025-11-24 12:30:20 +01:00
Joachim Bauch
96423be5b3
Merge pull request #1130 from strukturag/client-updates
Update client code
2025-11-24 12:28:41 +01:00
Joachim Bauch
a31e3c4c53
Update client code for changed backend auth url, fix overflow in delta calculations. 2025-11-24 11:19:25 +01:00
Joachim Bauch
3ff47ea71a
Merge pull request #1129 from strukturag/ci-split-tarball
CI: Split tarball jobs to speed up total actions time.
2025-11-24 10:20:10 +01:00
Joachim Bauch
51a6162514
CI: Split tarball jobs to speed up total actions time. 2025-11-24 10:07:48 +01:00
Joachim Bauch
f8e9fcabd0
Merge pull request #1128 from strukturag/initial-update-clustered
Fix storing initial data when clustered.
2025-11-24 10:06:16 +01:00
Joachim Bauch
9e98b7bf13
Fix storing initial data when clustered. 2025-11-24 10:01:26 +01:00
Joachim Bauch
c587489765
Merge pull request #1126 from SystemKeeper/fix/noid/already-joined-docs
fix(docs): already_joined error response
2025-11-24 09:46:07 +01:00
Joachim Bauch
ba1af553e0
Merge pull request #1127 from strukturag/initial-transient-data-clustered
Fix initial transient data in clustered setups
2025-11-24 09:41:28 +01:00
Joachim Bauch
8532428ec1
Add note that initial data may be sent using multiple events. 2025-11-24 09:30:15 +01:00
Joachim Bauch
2d8fbda85d
Update generated files. 2025-11-24 09:30:14 +01:00
Joachim Bauch
c1618155b7
Get transient data from other nodes on first room join. 2025-11-24 09:30:14 +01:00
Joachim Bauch
bae71b08f1
Merge pull request #1125 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-9.1.0
Bump golangci/golangci-lint-action from 9.0.0 to 9.1.0
2025-11-24 08:49:47 +01:00
Marcel Müller
8935965df6 fix(docs): already_joined error response
Signed-off-by: Marcel Müller <marcel-mueller@gmx.de>
2025-11-23 12:24:32 +01:00
dependabot[bot]
a8b46d59a6
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] <support@github.com>
2025-11-21 20:01:42 +00:00
Joachim Bauch
9a213a7caf
Merge pull request #1124 from strukturag/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-20 21:09:48 +01:00
Joachim Bauch
89b1cb79d0
Merge pull request #1123 from strukturag/dependabot/go_modules/go.uber.org/zap-1.27.1
Bump go.uber.org/zap from 1.27.0 to 1.27.1
2025-11-20 21:09:33 +01:00
dependabot[bot]
48c6a783ce
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] <support@github.com>
2025-11-20 20:01:50 +00:00
dependabot[bot]
5e291e4cce
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] <support@github.com>
2025-11-20 20:01:32 +00:00
Joachim Bauch
672120990e
Merge pull request #1121 from strukturag/clustered-transient-data
Fix transient data for clustered setups.
2025-11-20 16:57:26 +01:00
Joachim Bauch
73fc0d747e
Add test for transient data with federation. 2025-11-20 16:52:16 +01:00
Joachim Bauch
2881ca98dc
Fix transient data for clustered setups. 2025-11-20 16:46:56 +01:00
Joachim Bauch
90723676a0
Merge pull request #1117 from strukturag/logging-context
Stop using global logger
2025-11-20 10:19:52 +01:00
Joachim Bauch
bfcabaa2fc
Wait for NATS client to be closed. 2025-11-20 10:14:48 +01:00
Joachim Bauch
5863cdd795
Fix remote connection still logging after close.
Was triggered by TestProxyRemoteSubscriber.
2025-11-20 09:42:40 +01:00
Joachim Bauch
6ca41dee61
Don't use global logger. 2025-11-20 09:42:40 +01:00
Joachim Bauch
0e4c4c775b
Add method to create new test logger. 2025-11-20 09:22:26 +01:00
Joachim Bauch
ec5a34f926
Support storing loggers in contexts. 2025-11-20 09:22:25 +01:00
Joachim Bauch
ed94aeacec
Merge pull request #1119 from strukturag/dependabot/go_modules/golang.org/x/crypto-0.45.0
Bump golang.org/x/crypto from 0.43.0 to 0.45.0
2025-11-20 08:14:37 +01:00
dependabot[bot]
e80dd3740d
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] <support@github.com>
2025-11-20 02:59:28 +00:00
Joachim Bauch
f7e545f0b5
Merge pull request #1118 from strukturag/ci-split-test
CI: Split test jobs to speed up total actions time.
2025-11-19 20:17:49 +01:00
Joachim Bauch
c021de4957
CI: Split test jobs to speed up total actions time. 2025-11-19 20:13:45 +01:00
Joachim Bauch
cca5da3cc2
Merge pull request #1116 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.77.0
Bump google.golang.org/grpc from 1.76.0 to 1.77.0
2025-11-18 21:08:27 +01:00
dependabot[bot]
b14ec35679
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] <support@github.com>
2025-11-18 20:02:09 +00:00
Joachim Bauch
bd34a617df
Merge pull request #1115 from strukturag/zero-bandwidth
Don't format zero bandwidth as "unlimited".
2025-11-18 15:55:16 +01:00
Joachim Bauch
dca35b46d4
Don't format zero bandwidth as "unlimited". 2025-11-18 15:35:06 +01:00
Joachim Bauch
ea55d5508b
Merge pull request #1114 from strukturag/format-bandwidth
Add formatting to bandwidth values.
2025-11-18 09:46:01 +01:00
Joachim Bauch
14d1c9bf59
Log formatted bandwidths. 2025-11-18 09:39:24 +01:00
Joachim Bauch
4e87170f0e
Add formatting to bandwidth values. 2025-11-18 09:39:23 +01:00
Joachim Bauch
75ea5e710c
Merge pull request #1109 from strukturag/webrtc-metrics
Add more WebRTC-related metrics
2025-11-13 23:27:36 +01:00
Joachim Bauch
10e55ff241
Lint Janus metrics. 2025-11-13 23:17:46 +01:00
Joachim Bauch
2d5379b61d
Add more media-related metrics. 2025-11-13 23:08:45 +01:00
Joachim Bauch
14db1a60e4
Merge pull request #1113 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.2
Bump github.com/nats-io/nats-server/v2 from 2.12.1 to 2.12.2
2025-11-13 22:18:14 +01:00
Joachim Bauch
4f7ec2fc11
Merge pull request #1112 from strukturag/rewrite-federated-comment-token
Also rewrite token in comment for federated chat relay.
2025-11-13 22:08:43 +01:00
dependabot[bot]
09850d2ce7
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] <support@github.com>
2025-11-13 20:02:00 +00:00
Joachim Bauch
826d6244f3
Use single metrics for selected candidates. 2025-11-13 14:43:15 +01:00
Joachim Bauch
1ad460cee6
Add metrics for media RTT / jitter. 2025-11-13 14:39:57 +01:00
Joachim Bauch
1f0ed8005a
Add metrics for Janus slow link events. 2025-11-13 14:20:28 +01:00
Joachim Bauch
694af62b3d
Merge pull request #1111 from strukturag/dependabot/go_modules/etcd-52df49c9ec
Bump the etcd group with 4 updates
2025-11-13 14:19:53 +01:00
Joachim Bauch
1785e7b42e
Also rewrite token in comment for federated chat relay. 2025-11-13 13:20:10 +01:00
dependabot[bot]
2a17128743
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] <support@github.com>
2025-11-12 20:02:15 +00:00
Joachim Bauch
13db400356
Merge pull request #1110 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-9.0.0
Bump golangci/golangci-lint-action from 8.0.0 to 9.0.0
2025-11-12 08:20:20 +01:00
dependabot[bot]
42d691ce4a
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] <support@github.com>
2025-11-10 20:01:47 +00:00
Joachim Bauch
a5c7ad272f
Need events of type "webrtc". 2025-11-06 20:44:48 +01:00
Joachim Bauch
fa900132b4
Add metrics for candidates and ICE, DTLS and PeerConnection states. 2025-11-06 20:41:53 +01:00
Joachim Bauch
71fda2f258
Add metric for RTT of WebSocket ping messages. 2025-11-06 20:41:52 +01:00
Joachim Bauch
40ff197bd0
Merge pull request #1108 from strukturag/bandwidth-type
Add type to store bandwidths.
2025-11-06 11:04:16 +01:00
Joachim Bauch
315fba975b
Update generated files. 2025-11-06 10:57:25 +01:00
Joachim Bauch
3aacca1ff7
Switch to new Bandwidth type. 2025-11-06 10:57:24 +01:00
Joachim Bauch
dc1c166fd1
Add type to store bandwidths. 2025-11-06 10:57:23 +01:00
Joachim Bauch
9e2633e99c
Merge pull request #1102 from strukturag/janus-events
Expose real bandwidth usage through metrics.
2025-11-05 12:25:38 +01:00
Joachim Bauch
f11fc4008a
Update generated files. 2025-11-05 12:11:20 +01:00
Joachim Bauch
889ec056f2
Also expose bandwidth usage of backend servers through metrics. 2025-11-05 12:11:13 +01:00
Joachim Bauch
6d0d317252
Add tests for different event types. 2025-11-05 11:52:55 +01:00
Joachim Bauch
9b79aac1cf
Add tests for Janus events handler. 2025-11-05 11:52:54 +01:00
Joachim Bauch
f2ef566acc
Add note on how to configure Janus events. 2025-11-05 11:52:53 +01:00
Joachim Bauch
184a41ae4f
Update generated files. 2025-11-05 11:52:53 +01:00
Joachim Bauch
8e784b9616
Document new bandwidth / usage related metrics. 2025-11-05 11:52:52 +01:00
Joachim Bauch
395f2a951b
Expose real bandwidth usage through metrics.
Data is fetched from Janus websocket events handler that sends messages for
various events like media stats.
2025-11-04 16:16:44 +01:00
Joachim Bauch
d778e54f3a
Merge pull request #1106 from strukturag/no-test-env
Don't use environment to keep per-test properties.
2025-11-04 16:16:26 +01:00
Joachim Bauch
7494bb6318
Don't use environment to keep per-test properties. 2025-11-04 16:11:35 +01:00
Joachim Bauch
d7c79c2141
Merge pull request #1105 from strukturag/cleanup-federation
Federation cleanup fixes.
2025-11-04 15:56:00 +01:00
Joachim Bauch
3fd89f7113
Fix race condition where closeOnLeave could be set after leave was received.
With that also add checklocks annotations to federation client.
2025-11-04 15:46:19 +01:00
Joachim Bauch
91b29d4103
Remove federated session from internal map on cleanup. 2025-11-04 14:12:59 +01:00
Joachim Bauch
474686a1b2
Merge pull request #1104 from strukturag/dependabot/pip/docs/markdown-3.10
Bump markdown from 3.9 to 3.10 in /docs
2025-11-04 08:13:17 +01:00
dependabot[bot]
c54133ffb7
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] <support@github.com>
2025-11-03 20:03:54 +00:00
Joachim Bauch
999cc4d095
Merge pull request #1099 from strukturag/room-bandwidths
Return bandwidth information in room responses.
2025-10-28 08:43:20 +01:00
Joachim Bauch
b9ccb419e6
Update generated files. 2025-10-28 08:25:44 +01:00
Joachim Bauch
c7dcfa765c
Return bandwidth information in room responses. 2025-10-28 08:24:48 +01:00
Joachim Bauch
05589f5b04
Document the "bandwidth" field in room join responses. 2025-10-28 08:24:48 +01:00
Joachim Bauch
4b15117894
Support maximum bandwidths for compatibility backend. 2025-10-28 08:24:47 +01:00
Joachim Bauch
3b2c6606de
Merge pull request #868 from strukturag/chat-relay
Support relaying of chat messages.
2025-10-28 08:14:03 +01:00
Joachim Bauch
4367572005
Merge pull request #1101 from strukturag/dependabot/github_actions/artifacts-3249c11fdc
Bump the artifacts group with 2 updates
2025-10-26 20:44:48 +01:00
dependabot[bot]
01f9fb934f
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] <support@github.com>
2025-10-24 20:01:43 +00:00
Joachim Bauch
b6ebb96378
Merge pull request #1100 from strukturag/reconnect-if-shutdown-scheduled
Reconnect proxy connection even if shutdown was scheduled before.
2025-10-23 14:36:59 +02:00
Joachim Bauch
6956df67c8
Reconnect proxy connection even if shutdown was scheduled before. 2025-10-23 14:28:50 +02:00
Joachim Bauch
e027c484bd
Merge pull request #1078 from strukturag/gvisor-checklocks
Use gvisor checklocks for static lock analysis.
2025-10-23 11:35:58 +02:00
Joachim Bauch
00980208d2
Merge pull request #1095 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.1
Bump github.com/nats-io/nats-server/v2 from 2.12.0 to 2.12.1
2025-10-15 17:11:04 +02:00
dependabot[bot]
2d3cb91833
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] <support@github.com>
2025-10-15 11:33:45 +00:00
Joachim Bauch
88e42a05fe
Merge pull request #1096 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.47.0
Bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0
2025-10-15 13:32:24 +02:00
dependabot[bot]
9429119198
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] <support@github.com>
2025-10-14 20:02:33 +00:00
Joachim Bauch
0980939f1a
Merge pull request #1094 from strukturag/protect-debug-endpoint
Protect access to the debug pprof handlers.
2025-10-09 09:08:26 +02:00
Joachim Bauch
41c18d2531
Protect access to the debug pprof handlers. 2025-10-08 10:46:06 +02:00
Joachim Bauch
ac2063d484
Also allow access from ::1 by default. 2025-10-08 10:43:32 +02:00
Joachim Bauch
e5f23c4081
Merge pull request #1089 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.10
Bump google.golang.org/protobuf from 1.36.9 to 1.36.10
2025-10-08 08:47:26 +02:00
dependabot[bot]
28dfc3f532
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] <support@github.com>
2025-10-08 06:01:48 +00:00
Joachim Bauch
bc281128fd
Merge pull request #1090 from strukturag/dependabot/github_actions/peter-evans/create-or-update-comment-5
Bump peter-evans/create-or-update-comment from 4 to 5
2025-10-08 08:00:00 +02:00
Joachim Bauch
a418753dae
Merge pull request #1093 from strukturag/dependabot/github_actions/github/codeql-action-4
Bump github/codeql-action from 3 to 4
2025-10-08 07:59:08 +02:00
Joachim Bauch
5c32a14aaf
Merge pull request #1092 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.76.0
Bump google.golang.org/grpc from 1.75.1 to 1.76.0
2025-10-08 07:58:27 +02:00
dependabot[bot]
4f5b4e807f
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] <support@github.com>
2025-10-07 20:01:51 +00:00
dependabot[bot]
c2d5facce2
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] <support@github.com>
2025-10-07 20:01:42 +00:00
dependabot[bot]
d83eece45c
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] <support@github.com>
2025-10-02 20:01:55 +00:00
Joachim Bauch
a697c40686
Add more subscriber tests. 2025-10-02 16:06:28 +02:00
Joachim Bauch
d80b9072a7
Test subscriber closing on all-inactive streams. 2025-10-01 17:12:03 +02:00
Joachim Bauch
d295ebdf81
Add test for remote publishers. 2025-10-01 16:39:25 +02:00
Joachim Bauch
9dfcf3c75a
Add more proxy tests. 2025-10-01 13:49:34 +02:00
Joachim Bauch
b34b8acb6d
Test and fix reconnections in remote proxy connections. 2025-10-01 13:49:33 +02:00
Joachim Bauch
cfd8cf6718
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.
2025-10-01 13:49:32 +02:00
Joachim Bauch
56f67097a9
Merge pull request #1088 from strukturag/readme-docker-build
Add commands to the readme on how to build Docker images locally.
2025-10-01 10:59:49 +02:00
Joachim Bauch
c5055a8916
Add commands to the readme on how to build Docker images locally. 2025-10-01 10:57:14 +02:00
Joachim Bauch
f3e7599d3c
Merge pull request #1087 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.46.1
Bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1
2025-10-01 09:17:03 +02:00
dependabot[bot]
e749c7038a
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] <support@github.com>
2025-09-30 20:01:56 +00:00
Joachim Bauch
66183084d5
Merge pull request #1086 from strukturag/register-stats
Add missing stats registration.
2025-09-30 16:33:39 +02:00
Joachim Bauch
1c1805e342
Merge pull request #1085 from strukturag/typed-lrucache
Make LruCache typed through generics.
2025-09-30 16:23:43 +02:00
Joachim Bauch
178503fef7
Make LruCache typed through generics. 2025-09-30 09:57:04 +02:00
Joachim Bauch
030e20f5e4
Register "statsHubSessionsResumedTotal" so it gets included in metrics. 2025-09-30 09:40:33 +02:00
Joachim Bauch
1b0ed17460
Merge pull request #1084 from strukturag/etcd-tests
Add etcd TLS tests.
2025-09-29 23:52:40 +02:00
Joachim Bauch
78347e8491
Add etcd TLS tests. 2025-09-29 23:46:36 +02:00
Joachim Bauch
a2a7cf0476
Add "localhost" to generated self-signed test certificates. 2025-09-29 23:46:22 +02:00
Joachim Bauch
9f9c1ae131
Merge pull request #1082 from strukturag/internal-package
Introduce "internal" package
2025-09-29 21:49:55 +02:00
Joachim Bauch
8613bc85cb
codecov: Add fallback "root" module. 2025-09-29 21:44:30 +02:00
Joachim Bauch
7e3be8f8a4
Merge pull request #1083 from strukturag/getincall-interface
Add interface for method "GetInCall".
2025-09-29 21:41:19 +02:00
Joachim Bauch
962f0254b1
Rewrite chat room mentions for federation. 2025-09-29 21:40:41 +02:00
Joachim Bauch
63b658574a
Rewrite chat actor information for federation. 2025-09-29 21:40:40 +02:00
Joachim Bauch
89c71e4a3c
Add interface for method "GetInCall". 2025-09-29 21:18:38 +02:00
Joachim Bauch
628aedef9e
Move "MakePtr" function to internal package. 2025-09-29 21:10:09 +02:00
Joachim Bauch
426df7e083
Move functions to canonicalize urls to internal package. 2025-09-29 21:06:45 +02:00
Joachim Bauch
e6dd45b2cc
Add test for chat relay with federated clients. 2025-09-29 14:19:02 +02:00
Joachim Bauch
b7989d6aa8
Improve test coverage. 2025-09-29 14:19:01 +02:00
Joachim Bauch
9d47022075
Update generated files. 2025-09-29 14:19:00 +02:00
Joachim Bauch
b6e25424a3
Implement relaying of chat messages.
Also fix combining chat refresh messages which was checking the wrong message type.
2025-09-29 14:19:00 +02:00
Joachim Bauch
9a992f365b
Document chat relay. 2025-09-29 14:18:58 +02:00
Joachim Bauch
c8968e5171
Merge pull request #1081 from strukturag/federation-ping-room
Fix URL to send federated ping requests.
2025-09-29 14:18:32 +02:00
Joachim Bauch
105518b1ee
Test that backend URLs are containing the full OCS path. 2025-09-29 14:11:03 +02:00
Joachim Bauch
70e01b4816
Fix URL to send federated ping requests. 2025-09-29 14:10:42 +02:00
Joachim Bauch
5b041b795c
Merge pull request #1080 from strukturag/codecov-components
CI: Use codecov components.
2025-09-27 20:18:02 +02:00
Joachim Bauch
89522b05cf
CI: Use codecov components. 2025-09-27 20:12:39 +02:00
Joachim Bauch
88179b23a5
Merge pull request #1075 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.0
Bump github.com/nats-io/nats-server/v2 from 2.11.9 to 2.12.0
2025-09-26 21:29:46 +02:00
dependabot[bot]
a068a49527
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] <support@github.com>
2025-09-26 19:24:47 +00:00
Joachim Bauch
72d34230d8
Merge pull request #1076 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.46.0
Bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0
2025-09-26 21:23:42 +02:00
dependabot[bot]
40bd1d71ce
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] <support@github.com>
2025-09-26 19:15:33 +00:00
Joachim Bauch
6a145bd893
Merge pull request #1079 from strukturag/disable-lint-stdversion
CI: Disable "stdversion" check of govet.
2025-09-26 21:14:19 +02:00
Joachim Bauch
c06fe02c61
CI: Disable "stdversion" check of govet.
Using "synctest.Wait" is fine as we set GOEXPERIMENT=synctest when running
affected code.
2025-09-26 21:10:51 +02:00
Joachim Bauch
dd34b817f2
Merge pull request #1077 from strukturag/move-stringmap
Move "StringMap" class to api module.
2025-09-26 14:29:47 +02:00
Joachim Bauch
12c16bce61
make: Always test all packages. 2025-09-26 13:51:44 +02:00
Joachim Bauch
41728572fe
Move "StringMap" class to api module. 2025-09-26 13:50:18 +02:00
Joachim Bauch
91220431be
Merge pull request #1073 from strukturag/dependabot/go_modules/etcd-9569061235
Bump the etcd group with 4 updates
2025-09-22 11:12:37 +02:00
Joachim Bauch
32ea6c9942
Merge pull request #1072 from strukturag/dependabot/go_modules/github.com/mailru/easyjson-0.9.1
Bump github.com/mailru/easyjson from 0.9.0 to 0.9.1
2025-09-22 11:12:22 +02:00
dependabot[bot]
374476a419
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] <support@github.com>
2025-09-19 20:01:50 +00:00
dependabot[bot]
d745f14f5c Update generated files from bee1175198 2025-09-15 20:04:04 +00:00
dependabot[bot]
bee1175198
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] <support@github.com>
2025-09-15 20:01:56 +00:00
Joachim Bauch
ad83000fe7
Merge pull request #1071 from strukturag/connected-after-hello
A proxy connection is only connected after a hello has been processed.
2025-09-11 15:21:30 +02:00
Joachim Bauch
697e6242d6
A proxy connection is only connected after a hello has been processed.
Fixes flaky test "Test_ProxyResumeFail".
2025-09-11 15:15:01 +02:00
Joachim Bauch
b3ab49f22f
Merge pull request #1068 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.9
Bump github.com/nats-io/nats-server/v2 from 2.11.8 to 2.11.9
2025-09-11 08:23:31 +02:00
dependabot[bot]
0c9d4970c4
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] <support@github.com>
2025-09-11 05:46:49 +00:00
Joachim Bauch
f6cc1b867a
Merge pull request #1069 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.9
Bump google.golang.org/protobuf from 1.36.8 to 1.36.9
2025-09-11 07:45:10 +02:00
Joachim Bauch
e78a730a13
Merge pull request #1070 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.75.1
Bump google.golang.org/grpc from 1.75.0 to 1.75.1
2025-09-11 07:44:45 +02:00
dependabot[bot]
c132ea25fa
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] <support@github.com>
2025-09-10 20:01:50 +00:00
dependabot[bot]
32b99b6a52
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] <support@github.com>
2025-09-09 20:02:36 +00:00
Joachim Bauch
6d2ee56ada
Fix typo in error message on failed test condition. 2025-09-09 12:11:15 +02:00
Joachim Bauch
9c284a4426
Merge pull request #1066 from strukturag/custom-type-sessionids
Add dedicated types for different session ids.
2025-09-09 12:09:34 +02:00
Joachim Bauch
4409f91e28
Update generated files. 2025-09-09 12:00:50 +02:00
Joachim Bauch
c00fc82777
Use dedicated type for "ClientType". 2025-09-09 12:00:34 +02:00
Joachim Bauch
e39886369f
Add test for proxy client reconnect code. 2025-09-09 11:38:49 +02:00
Joachim Bauch
5892baa3bb
Use interfaces to detect support for remote publishing / streams. 2025-09-09 11:38:48 +02:00
Joachim Bauch
1a74ea87bd
Update generated files. 2025-09-09 11:38:47 +02:00
Joachim Bauch
51326069e2
Add dedicated types for different session ids. 2025-09-09 11:38:47 +02:00
Joachim Bauch
2c5a83b175
Merge pull request #1067 from strukturag/use-synctest
Use "testing/synctest" to simplify timing-dependent tests.
2025-09-09 11:38:22 +02:00
Joachim Bauch
8ee19e3bcf
Use "testing/synctest" to simplify timing-dependent tests. 2025-09-09 11:33:32 +02:00
Joachim Bauch
be868fd4af
Merge pull request #1062 from strukturag/dependabot/github_actions/actions/setup-go-6
Bump actions/setup-go from 5 to 6
2025-09-08 06:56:46 +02:00
Joachim Bauch
8c6c725dd9
Merge pull request #1064 from strukturag/dependabot/go_modules/github.com/prometheus/client_golang-1.23.2
Bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2
2025-09-08 06:56:27 +02:00
Joachim Bauch
add8341680
Merge pull request #1061 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.16
Bump github.com/pion/sdp/v3 from 3.0.15 to 3.0.16
2025-09-08 06:55:58 +02:00
Joachim Bauch
ba4bb26067
Merge pull request #1065 from strukturag/dependabot/pip/docs/markdown-3.9
Bump markdown from 3.8.2 to 3.9 in /docs
2025-09-08 06:55:38 +02:00
dependabot[bot]
521e5c0ff8
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] <support@github.com>
2025-09-05 20:03:20 +00:00
dependabot[bot]
11340aa436
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] <support@github.com>
2025-09-05 20:01:32 +00:00
dependabot[bot]
2c21aeed3a
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] <support@github.com>
2025-09-04 20:12:31 +00:00
dependabot[bot]
23513e9159
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] <support@github.com>
2025-09-02 13:08:04 +00:00
Joachim Bauch
b1451dd447
Merge pull request #1060 from strukturag/dependabot/go_modules/github.com/stretchr/testify-1.11.1
Bump github.com/stretchr/testify from 1.11.0 to 1.11.1
2025-09-01 08:15:33 +02:00
dependabot[bot]
560b795d58
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] <support@github.com>
2025-08-28 00:41:52 +00:00
Joachim Bauch
62e7c48b2c
Merge pull request #1059 from strukturag/dependabot/go_modules/github.com/stretchr/testify-1.11.0
Bump github.com/stretchr/testify from 1.10.0 to 1.11.0
2025-08-27 20:21:37 +02:00
dependabot[bot]
259ebd877a
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] <support@github.com>
2025-08-26 11:24:48 +00:00
Joachim Bauch
fe34c5b469
Merge pull request #1058 from leo9800/master
dockerfile: create system user (uid <= 999) instead of normal user (uid >= 1000), avoid home directory
2025-08-21 13:27:52 +02:00
Leo
bb996a7571
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 <i@hardrain980.com>
2025-08-21 19:12:19 +08:00
Joachim Bauch
9e18a6bdd3
Merge pull request #1056 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.8
Bump google.golang.org/protobuf from 1.36.7 to 1.36.8
2025-08-21 07:09:54 +02:00
dependabot[bot]
f20f0a3ccb
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] <support@github.com>
2025-08-21 05:00:51 +00:00
Joachim Bauch
b46c76c6d0
Merge pull request #1057 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.45.0
Bump github.com/nats-io/nats.go from 1.44.0 to 1.45.0
2025-08-21 07:00:23 +02:00
Joachim Bauch
bb8099228c
Merge pull request #1054 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.75.0
Bump google.golang.org/grpc from 1.74.2 to 1.75.0
2025-08-21 06:59:35 +02:00
dependabot[bot]
d084ea0e56
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] <support@github.com>
2025-08-20 20:53:50 +00:00
dependabot[bot]
d97832a5f8
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] <support@github.com>
2025-08-20 00:02:10 +00:00
Joachim Bauch
46ae7de9a6
Update changelog for 2.0.4 2025-08-18 10:59:59 +02:00
Joachim Bauch
a6376a706c
Merge pull request #1053 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.8
Bump github.com/nats-io/nats-server/v2 from 2.11.7 to 2.11.8
2025-08-18 10:01:11 +02:00
Joachim Bauch
764101c192
Merge pull request #1052 from strukturag/use-standard-library
Use standard library where possible.
2025-08-18 10:00:32 +02:00
Joachim Bauch
b7fdde761c
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.
2025-08-18 09:53:01 +02:00
Joachim Bauch
11dd532502
Not allowed to run tests with mock dns lookup in parallel. 2025-08-18 09:52:48 +02:00
Joachim Bauch
dd45d0e0d8
Add testcase for adding/removing proxy connections. 2025-08-18 08:52:14 +02:00
Joachim Bauch
efb9771f47
Use "slices" / "iter" module where possible. 2025-08-18 08:29:09 +02:00
dependabot[bot]
73434ac0cf
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] <support@github.com>
2025-08-14 20:23:41 +00:00
Joachim Bauch
210aec62db
Simlify splitting string into non-empty entries. 2025-08-14 16:54:28 +02:00
Joachim Bauch
3d0db426fa
Use methods on atomic class instead of custom implementation. 2025-08-14 16:54:27 +02:00
Joachim Bauch
d1a57b34af
Use "maps.Equal" instead of custom implementation. 2025-08-14 16:54:27 +02:00
Joachim Bauch
78c957a607
Merge pull request #1051 from strukturag/test-hasanypermission
Test "HasAnyPermission" method.
2025-08-14 09:58:27 +02:00
Joachim Bauch
c8444b4ecd
Test "HasAnyPermission" method. 2025-08-14 09:41:57 +02:00
Joachim Bauch
0b4fc5f7c7
Fix name of modernize CI job. 2025-08-14 09:30:51 +02:00
Joachim Bauch
87e42caeee
Merge pull request #1050 from strukturag/modernize
Modernize Go code and check from CI.
2025-08-14 09:30:11 +02:00
Joachim Bauch
639588f550
Modernize Go code and check from CI. 2025-08-14 09:23:25 +02:00
Joachim Bauch
7e73f0e290
Merge pull request #1049 from strukturag/remove-go1.23
Drop support for Go 1.23
2025-08-14 08:23:50 +02:00
Joachim Bauch
9769d4fdda
Drop support for Go 1.23 2025-08-14 08:17:09 +02:00
Joachim Bauch
f329a58334
Merge pull request #1046 from strukturag/dependabot/docker/docker/proxy/golang-1.25-alpine
Bump golang from 1.24-alpine to 1.25-alpine in /docker/proxy
2025-08-14 08:11:25 +02:00
Joachim Bauch
4aabe7febf
Merge pull request #1047 from strukturag/dependabot/docker/docker/server/golang-1.25-alpine
Bump golang from 1.24-alpine to 1.25-alpine in /docker/server
2025-08-14 08:11:13 +02:00
Joachim Bauch
bc1b81e23a
Merge pull request #1048 from strukturag/golang-1.25
CI: Test with Golang 1.25
2025-08-14 08:10:45 +02:00
Joachim Bauch
67b52f4f18
CI: Test with Golang 1.25 2025-08-14 08:05:28 +02:00
dependabot[bot]
1a12fca8dd
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] <support@github.com>
2025-08-13 21:00:19 +00:00
dependabot[bot]
90d69a727a
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] <support@github.com>
2025-08-13 20:47:27 +00:00
Joachim Bauch
8c475737e1
Merge pull request #1042 from strukturag/dependabot/github_actions/artifacts-c836e40089
Bump actions/download-artifact from 4 to 5 in the artifacts group
2025-08-12 16:37:46 +02:00
Joachim Bauch
cc8064a08e
Merge pull request #1044 from strukturag/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-12 16:33:30 +02:00
dependabot[bot]
1300d8d970
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] <support@github.com>
2025-08-12 06:54:34 +00:00
dependabot[bot]
42408e5b34
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] <support@github.com>
2025-08-11 21:33:03 +00:00
Joachim Bauch
fb948069d2
Merge pull request #1043 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.7
Bump google.golang.org/protobuf from 1.36.6 to 1.36.7
2025-08-11 08:06:05 +02:00
dependabot[bot]
8d4fac7181
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] <support@github.com>
2025-08-07 20:09:26 +00:00
Joachim Bauch
8bd940e75c
Merge pull request #1040 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.7
Bump github.com/nats-io/nats-server/v2 from 2.11.6 to 2.11.7
2025-08-04 07:58:44 +02:00
dependabot[bot]
c444a740ba
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] <support@github.com>
2025-08-01 20:40:16 +00:00
Joachim Bauch
0ca882562a
Merge pull request #1039 from strukturag/dependabot/go_modules/github.com/prometheus/client_golang-1.23.0
Bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0
2025-07-31 22:23:24 +02:00
dependabot[bot]
81b0e1a8dd
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] <support@github.com>
2025-07-31 20:17:49 +00:00
Joachim Bauch
cae964e601
Merge pull request #1035 from strukturag/improve-testify-usage
Use testify assertions to check expected fields / values internally.
2025-07-31 10:01:09 +02:00
Joachim Bauch
d3798a3174
Use testify assertions to check expected fields / values internally. 2025-07-31 09:56:57 +02:00
Joachim Bauch
1e02834c48
Merge pull request #1038 from strukturag/codecov-config
CI: Add codecov configuration.
2025-07-31 09:27:49 +02:00
Joachim Bauch
7d571ed73a
Set flags for codecov uploads. 2025-07-31 09:20:58 +02:00
Joachim Bauch
595628dcf4
Update codecov comment layout. 2025-07-31 09:20:58 +02:00
Joachim Bauch
542c764d25
CI: Run tests if codecov configuration has changed. 2025-07-31 08:57:05 +02:00
Joachim Bauch
ac69fa3a0c
CI: Add codecov configuration. 2025-07-31 08:55:45 +02:00
Joachim Bauch
d55d9097dc
Merge pull request #1037 from strukturag/migrate-codecov
CI: Migrate to coveralls.
2025-07-31 08:31:06 +02:00
Joachim Bauch
4b2bc57e25
CI: Migrate to coveralls. 2025-07-31 08:24:56 +02:00
Joachim Bauch
536b47e4a3
Merge pull request #1036 from strukturag/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.3.0
Bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0
2025-07-31 08:16:24 +02:00
dependabot[bot]
fcf912bd15
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] <support@github.com>
2025-07-30 20:34:09 +00:00
Joachim Bauch
1c1ede2d71
Merge pull request #1034 from strukturag/stringmap
Add type for string maps.
2025-07-30 10:53:45 +02:00
Joachim Bauch
e36c21d046
Update generated files. 2025-07-30 10:48:40 +02:00
Joachim Bauch
3e6428d72f
Add type for string maps. 2025-07-30 10:48:31 +02:00
Joachim Bauch
0942c05dbd
Merge pull request #1033 from strukturag/interface-any
modernize: Replace "interface{}" with "any".
2025-07-30 10:01:07 +02:00
Joachim Bauch
613806be14
modernize: Replace "interface{}" with "any". 2025-07-30 09:44:18 +02:00
Joachim Bauch
3b66fcea80
Merge pull request #1032 from strukturag/delete-unused-publisher-subscriber
Delete (unused) proxy publisher/subscriber created after local timeout.
2025-07-30 08:58:57 +02:00
Joachim Bauch
0d1cee1b6b
Merge pull request #1031 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.44.0
Bump github.com/nats-io/nats.go from 1.43.0 to 1.44.0
2025-07-30 08:52:53 +02:00
Joachim Bauch
f07a42529b
Delete (unused) proxy publisher/subscriber created after local timeout.
Also fixes using the configured proxy timeout when creating subscribers.
2025-07-30 08:50:25 +02:00
dependabot[bot]
ad78c817b9
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] <support@github.com>
2025-07-29 20:41:27 +00:00
Joachim Bauch
6a512839b7
Merge pull request #1028 from strukturag/dependabot/go_modules/etcd-dcb5e073a1
Bump the etcd group with 4 updates
2025-07-28 08:13:34 +02:00
dependabot[bot]
5bc0c30c1f
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] <support@github.com>
2025-07-25 20:31:58 +00:00
Joachim Bauch
9f07a09877
Merge pull request #1027 from strukturag/subscriber-metrics-errorcases
Fix updating metric "signaling_mcu_subscribers" in various error cases.
2025-07-24 13:11:27 +02:00
Joachim Bauch
bcc5bccbf0
Fix updating metric "signaling_mcu_subscribers" in various error cases. 2025-07-24 12:10:48 +02:00
Joachim Bauch
d363620120
Add helper method to wait for error message. 2025-07-24 12:08:37 +02:00
Joachim Bauch
fd1580998e
Merge pull request #1026 from strukturag/leavevirtual-check-actor
Only forward actor details in leave virtual sessions request if both are given.
2025-07-24 10:22:12 +02:00
Joachim Bauch
85b85feeb8
Add test to check passing of virtual actor information. 2025-07-24 09:58:15 +02:00
Joachim Bauch
3af360944a
Only forward actor details in leave virtual sessions request if both are given.
Missing from previous change in #1009
2025-07-24 09:47:27 +02:00
Joachim Bauch
0c134a0a6f
Merge pull request #1025 from strukturag/remote-token
Return connection / publisher tokens for remote publishers.
2025-07-24 09:34:19 +02:00
Joachim Bauch
9c7b3b9547
Update generated files. 2025-07-24 09:22:47 +02:00
Joachim Bauch
9235b80125
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.
2025-07-24 09:22:46 +02:00
Joachim Bauch
cdd751b0e0
Merge pull request #1024 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.74.2
Bump google.golang.org/grpc from 1.74.0 to 1.74.2
2025-07-23 08:05:09 +02:00
Joachim Bauch
a7c4d55912
Merge pull request #1023 from strukturag/dependabot/go_modules/etcd-311f0223a2
Bump the etcd group with 4 updates
2025-07-23 08:04:49 +02:00
Joachim Bauch
238e56e39c
Merge pull request #1022 from strukturag/remove-debug
Remove debug output.
2025-07-23 08:04:18 +02:00
dependabot[bot]
46a20e5041
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] <support@github.com>
2025-07-22 20:50:48 +00:00
dependabot[bot]
f72e606628
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] <support@github.com>
2025-07-22 20:50:42 +00:00
Joachim Bauch
80e3463ab3
Remove debug output. 2025-07-22 16:58:25 +02:00
Joachim Bauch
6b92af898d
Merge pull request #1021 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.15
Bump github.com/pion/sdp/v3 from 3.0.14 to 3.0.15
2025-07-21 08:21:45 +02:00
dependabot[bot]
5c0ebc4435
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] <support@github.com>
2025-07-18 20:04:52 +00:00
Joachim Bauch
0a14067460
Merge pull request #770 from strukturag/multiple-backend-urls
Support multiple urls per backend
2025-07-17 16:33:27 +02:00
Joachim Bauch
ac900616a5
Fix counting of backends for metrics. 2025-07-17 16:13:44 +02:00
Joachim Bauch
ddad70b4c5
Update default configuration and Docker scripts for backend urls. 2025-07-17 14:36:32 +02:00
Joachim Bauch
8375b985e8
Improve detecting duplicate backend URLs in etcd configuration. 2025-07-17 14:36:32 +02:00
Joachim Bauch
9fea05769f
Add more tests for configuring / using multiple URLs. 2025-07-17 14:36:31 +02:00
Joachim Bauch
762ed8fe59
Update generated files. 2025-07-17 14:36:31 +02:00
Joachim Bauch
f537711e14
Add support for multiple URLs per backend. 2025-07-17 14:36:30 +02:00
Joachim Bauch
5da0a5d4b0
Prepare internal APIs for multiple backend urls. 2025-07-17 08:41:37 +02:00
Joachim Bauch
6f30f0268e
Log request data in error cases. 2025-07-17 08:37:09 +02:00
Joachim Bauch
e1bd64c156
Merge pull request #1019 from strukturag/dialout-caller-number
Describe how to pass caller information for outgoing calls.
2025-07-17 08:20:22 +02:00
Joachim Bauch
4a28c99635
Merge pull request #1020 from strukturag/fix-backend-client-stats-ids
Use backend id in backend client stats to match other stats.
2025-07-17 08:20:03 +02:00
Joachim Bauch
6133563fe2
Use backend id in backend client stats to match other stats. 2025-07-17 08:11:24 +02:00
Joachim Bauch
899a9b6a61
Describe how to pass caller information for outgoing calls. 2025-07-17 08:04:04 +02:00
Joachim Bauch
f1d5b3b5bd
Merge pull request #1018 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.74.0
Bump google.golang.org/grpc from 1.73.0 to 1.74.0
2025-07-17 07:37:19 +02:00
Joachim Bauch
8c2e314b31
Merge pull request #1017 from strukturag/file-watcher-fixes
Fixes for file watcher special cases
2025-07-17 07:31:06 +02:00
dependabot[bot]
667f29507a
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] <support@github.com>
2025-07-16 20:42:45 +00:00
Joachim Bauch
bdb2a816f8
Handle case where watched file is in a symlinked versioned subfolder.
This is what k8s is doing for mounted Secrets / ConfigMaps.
2025-07-16 16:50:47 +02:00
Joachim Bauch
7b909f4ec6
Avoid duplicate wakeups if file in current folder is watched and modified. 2025-07-16 16:14:45 +02:00
Joachim Bauch
e3045caeb0
Merge pull request #1016 from strukturag/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.2.3
Bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3
2025-07-16 08:14:20 +02:00
dependabot[bot]
d2d411a7ca
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] <support@github.com>
2025-07-15 20:46:52 +00:00
Joachim Bauch
58cd75be3c
Merge pull request #1015 from strukturag/dependabot/go_modules/etcd-03ea4b16e4
Bump the etcd group with 4 updates
2025-07-14 13:14:40 +02:00
dependabot[bot]
29a40bf4b9
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] <support@github.com>
2025-07-10 21:00:12 +00:00
Joachim Bauch
bb5a2a5cf7
Merge pull request #1011 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.6
Bump github.com/nats-io/nats-server/v2 from 2.11.5 to 2.11.6
2025-07-02 08:30:59 +02:00
dependabot[bot]
ec9755d10f
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] <support@github.com>
2025-07-01 20:05:21 +00:00
Joachim Bauch
29fa2b5705
Merge pull request #1010 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.5
Bump github.com/nats-io/nats-server/v2 from 2.11.4 to 2.11.5
2025-07-01 16:23:13 +02:00
dependabot[bot]
6dfd31a030
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] <support@github.com>
2025-06-26 20:10:52 +00:00
Joachim Bauch
0c77c67353
Merge pull request #1006 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.14
Bump github.com/pion/sdp/v3 from 3.0.13 to 3.0.14
2025-06-26 21:21:56 +02:00
Joachim Bauch
283919fef6
Merge pull request #1009 from strukturag/addvirtual-check-actor
Only forward actor id / -type in "addsession" request if both are given.
2025-06-26 21:10:15 +02:00
dependabot[bot]
4ab2fc0227
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] <support@github.com>
2025-06-23 11:52:42 +00:00
Joachim Bauch
10ca421026
Merge pull request #1000 from strukturag/filter-candidates
Support filtering candidates received by clients.
2025-06-23 13:51:08 +02:00
Joachim Bauch
f161048fe7
Merge pull request #1008 from strukturag/dependabot/pip/docs/markdown-3.8.2
Bump markdown from 3.8 to 3.8.2 in /docs
2025-06-23 13:48:48 +02:00
Joachim Bauch
af87f11f3c
Only forward actor id / -type in "addsession" request if both are given. 2025-06-23 13:47:41 +02:00
dependabot[bot]
1ec2d9d83b
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] <support@github.com>
2025-06-19 20:07:51 +00:00
Joachim Bauch
91b03d3d25
Increase test coverage for "requestoffer". 2025-06-12 14:25:47 +02:00
Joachim Bauch
dbd13da119
Increase test coverage on publisher "GetStreams". 2025-06-12 14:25:47 +02:00
Joachim Bauch
47a7a9591a
docker: Make candidates lists configurable. 2025-06-12 14:25:46 +02:00
Joachim Bauch
c7cbfcdce2
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.
2025-06-12 14:25:45 +02:00
Joachim Bauch
b35d7e9c57
make: Remove (empty) easyjson files from optimization step. 2025-06-12 12:11:21 +02:00
Joachim Bauch
1bfec37047
Merge pull request #1005 from strukturag/multiple-dialout-sessions
Support multiple sessions for dialout.
2025-06-12 12:10:56 +02:00
Joachim Bauch
ac35a0449d
Support multiple sessions for dialout.
Will try to start request with all of them until one succeeds. Returns first
received error if all failed.
2025-06-12 11:58:07 +02:00
Joachim Bauch
df757be9cf
Add "String()" method to BackendRoomDialoutRequest. 2025-06-12 11:58:06 +02:00
Joachim Bauch
ad63fb1c54
Merge pull request #1004 from strukturag/document-errors
Comment / document possible error responses.
2025-06-12 11:41:33 +02:00
Joachim Bauch
6b04363faf
Comment / document possible error responses. 2025-06-12 08:46:48 +02:00
Joachim Bauch
a03be515d2
Merge pull request #1002 from strukturag/dependabot/go_modules/etcd-3024f05fc4
Bump the etcd group with 4 updates
2025-06-12 08:27:31 +02:00
Joachim Bauch
1a1d2ad708
Merge pull request #1001 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.73.0
Bump google.golang.org/grpc from 1.72.2 to 1.73.0
2025-06-12 08:27:19 +02:00
dependabot[bot]
4c33de41a3
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] <support@github.com>
2025-06-06 20:38:03 +00:00
dependabot[bot]
cdb3472c13
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] <support@github.com>
2025-06-05 20:37:02 +00:00
Joachim Bauch
36af1e86f7
Merge pull request #999 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.43.0
Bump github.com/nats-io/nats.go from 1.42.0 to 1.43.0
2025-06-04 08:02:31 +02:00
dependabot[bot]
865ddc308a
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] <support@github.com>
2025-06-03 20:50:45 +00:00
Joachim Bauch
b74690defb
Merge pull request #995 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.72.2
Bump google.golang.org/grpc from 1.72.1 to 1.72.2
2025-05-27 13:42:03 +02:00
Joachim Bauch
ad11b53932
Merge pull request #994 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.13
Bump github.com/pion/sdp/v3 from 3.0.12 to 3.0.13
2025-05-27 13:41:45 +02:00
Joachim Bauch
257c8736c6
Merge pull request #993 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.4
Bump github.com/nats-io/nats-server/v2 from 2.11.3 to 2.11.4
2025-05-27 13:41:33 +02:00
dependabot[bot]
eced2ffdd7
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] <support@github.com>
2025-05-26 20:47:57 +00:00
dependabot[bot]
6be0fb6828
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] <support@github.com>
2025-05-23 20:16:54 +00:00
dependabot[bot]
b1162cc2da
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] <support@github.com>
2025-05-22 20:06:17 +00:00
Joachim Bauch
0fb022e751
Merge pull request #992 from strukturag/transient-data-deadlock
Fix deadlock when setting transient data while removing listener.
2025-05-22 09:43:22 +02:00
Joachim Bauch
b952cb58de
Fix deadlock when setting transient data while removing listener. 2025-05-22 09:38:36 +02:00
Joachim Bauch
73b225c5a7
Merge pull request #990 from strukturag/dependabot/go_modules/etcd-7c01212e26
Bump the etcd group with 4 updates
2025-05-21 08:21:16 +02:00
dependabot[bot]
dfdfe1b62a
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] <support@github.com>
2025-05-21 05:51:36 +00:00
Joachim Bauch
2e2af9b9ff
Merge pull request #991 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.12
Bump github.com/pion/sdp/v3 from 3.0.11 to 3.0.12
2025-05-21 07:50:04 +02:00
dependabot[bot]
8814fc811b
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] <support@github.com>
2025-05-20 20:46:10 +00:00
Joachim Bauch
ece824d67b
Merge pull request #989 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.72.1
Bump google.golang.org/grpc from 1.72.0 to 1.72.1
2025-05-14 23:09:20 +02:00
dependabot[bot]
38f403f88c
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] <support@github.com>
2025-05-14 20:32:56 +00:00
Joachim Bauch
af69bbf1e8
Update changelog for 2.0.3 2025-05-07 11:46:11 +02:00
Joachim Bauch
ffa316b5e2
Merge pull request #988 from strukturag/jitter-reconnect
Add jitter to reconnect intervals.
2025-05-07 11:26:49 +02:00
Joachim Bauch
e0e6cb6e9d
Add jitter to reconnect intervals.
This prevents all servers from reconnecting at the same time in case of network
interruptions or service restarts.
2025-05-07 11:18:52 +02:00
Joachim Bauch
9ef23270b4
Merge pull request #987 from strukturag/detect-close-on-remote-publishers
Close subscriber if remote publisher was closed.
2025-05-07 11:12:42 +02:00
Joachim Bauch
420f7eb0ba
Close connection if last remote publisher is removed. 2025-05-07 10:28:49 +02:00
Joachim Bauch
6fa6dcc533
Handle "bye" in remote connections. 2025-05-07 09:55:01 +02:00
Joachim Bauch
5c4ffdc7a2
Close subscriber if remote publisher was closed. 2025-05-06 17:01:18 +02:00
Joachim Bauch
e8c4a02945
Provide access to public id of publishers. 2025-05-06 16:54:38 +02:00
Joachim Bauch
8ceab4f8ce
Merge branch 'danxuliu-fix-subscribers-not-closed-when-publisher-is-closed-in-janus-1.x' 2025-05-06 15:08:36 +02:00
Daniel Calviño Sánchez
47f05c9163
Fix subscribers not closed when publisher is closed in Janus 1.x
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 <danxuliu@gmail.com>
2025-05-06 15:07:39 +02:00
Joachim Bauch
93fdf689c2
Merge pull request #985 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-8.0.0
Bump golangci/golangci-lint-action from 7.0.0 to 8.0.0
2025-05-06 07:59:18 +02:00
dependabot[bot]
5e7a1df2b6
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] <support@github.com>
2025-05-05 20:53:06 +00:00
Joachim Bauch
702de28ceb
Merge pull request #983 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.42.0
Bump github.com/nats-io/nats.go from 1.41.2 to 1.42.0
2025-05-05 08:56:41 +02:00
dependabot[bot]
722c7e077c
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] <support@github.com>
2025-05-05 06:49:08 +00:00
Joachim Bauch
6bb8b3fb5f
Merge pull request #982 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.3
Bump github.com/nats-io/nats-server/v2 from 2.11.1 to 2.11.3
2025-05-05 08:47:49 +02:00
dependabot[bot]
01c5ec131c
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] <support@github.com>
2025-05-01 20:28:52 +00:00
Joachim Bauch
5a975a1177
Merge pull request #980 from strukturag/docker-server-config
docker: Make more settings configurable
2025-04-24 08:54:13 +02:00
Joachim Bauch
d6e5975f94
Fix shellcheck error. 2025-04-24 08:37:30 +02:00
Joachim Bauch
d6ce4adaa6
docker: Make proxy timeout configurable. 2025-04-24 08:33:55 +02:00
Joachim Bauch
1ce202f987
docker: Make connections per host configurable. 2025-04-24 08:33:55 +02:00
Joachim Bauch
0daf48ae2b
docker: Make timeout for backend requests configurable. 2025-04-24 08:33:54 +02:00
Joachim Bauch
7aad88e192
docker: Make federation timeout configurable. 2025-04-24 08:33:54 +02:00
Joachim Bauch
403a4417cb
docker: Allow configuring read/write timeouts. 2025-04-24 08:33:53 +02:00
Joachim Bauch
ea5eb424d6
docker: Support configuring compatibility backends. 2025-04-24 08:33:36 +02:00
Joachim Bauch
fa4b532a98
Merge pull request #977 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.41.2
Bump github.com/nats-io/nats.go from 1.41.1 to 1.41.2
2025-04-22 09:13:43 +02:00
Joachim Bauch
151ebeb45f
Merge pull request #978 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.72.0
Bump google.golang.org/grpc from 1.71.1 to 1.72.0
2025-04-22 09:13:08 +02:00
dependabot[bot]
40be134746
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] <support@github.com>
2025-04-21 20:19:08 +00:00
dependabot[bot]
f2ce33478b
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] <support@github.com>
2025-04-17 20:28:09 +00:00
Joachim Bauch
902911b850
Merge pull request #976 from strukturag/fix-flaky-dnsdiscovery
Fix flaky test "Test_GrpcClients_DnsDiscovery".
2025-04-17 09:52:24 +02:00
Joachim Bauch
ed11ef775c
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.
2025-04-17 09:47:03 +02:00
Joachim Bauch
53f736cb2e
Merge pull request #973 from strukturag/backend-client-stats
Add metrics for backend client requests.
2025-04-17 08:59:14 +02:00
Joachim Bauch
3435287cac
Merge pull request #975 from strukturag/dependabot/go_modules/github.com/fsnotify/fsnotify-1.9.0
Bump github.com/fsnotify/fsnotify from 1.8.0 to 1.9.0
2025-04-17 08:40:32 +02:00
Joachim Bauch
0fedc6828f
Merge pull request #974 from strukturag/dependabot/go_modules/github.com/prometheus/client_golang-1.22.0
Bump github.com/prometheus/client_golang from 1.21.1 to 1.22.0
2025-04-17 08:40:11 +02:00
Joachim Bauch
ca2d6eaf3d
Add metrics for backend client requests. 2025-04-17 08:28:41 +02:00
dependabot[bot]
0a351256f0
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] <support@github.com>
2025-04-16 20:28:53 +00:00
dependabot[bot]
63ab3ccffc
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] <support@github.com>
2025-04-16 20:28:51 +00:00
Joachim Bauch
1212c6021f
Describe new backend client metrics. 2025-04-16 17:14:25 +02:00
Joachim Bauch
498ebb333e
Merge pull request #972 from strukturag/tests-speedup
Speedup tests
2025-04-16 16:20:22 +02:00
Joachim Bauch
7bd4bf96f5
Only simulate sleeping in "TestBackoff_Exponential".
Add another test that (shortly) sleeps.
2025-04-16 16:07:37 +02:00
Joachim Bauch
271b45c963
Don't hardcode sleep before running leak checked test. 2025-04-16 15:54:21 +02:00
Joachim Bauch
6c9bfd7b84
Reduce timeout and ensure it is actually triggered. 2025-04-16 15:29:10 +02:00
Joachim Bauch
6206678e74
Run clients in "TestSessionIdsUnordered" concurrently. 2025-04-16 15:20:05 +02:00
Joachim Bauch
83e50d030e
Don't use hardcoded sleeps when waiting for NATS disconnect / reconnect. 2025-04-16 15:09:56 +02:00
Joachim Bauch
953dc28e94
Use RWMutex for ConcurrentStringStringMap and improve test performance. 2025-04-16 15:09:56 +02:00
Joachim Bauch
a5ce4aa8f4
Merge pull request #937 from strukturag/serverinfo-api
Add serverinfo API
2025-04-16 15:09:36 +02:00
Joachim Bauch
c830403cd7
Update generated files. 2025-04-16 14:29:57 +02:00
Joachim Bauch
3e9bace0ad
Move serverinfo result generation to source, add nats, gprc and etcd. 2025-04-16 14:29:56 +02:00
Joachim Bauch
957d930613
Add "version" to dialin sessions. 2025-04-16 14:29:55 +02:00
Joachim Bauch
6bb2582b61
Document dialout serverinfo. 2025-04-16 14:29:54 +02:00
Joachim Bauch
a5e41e4822
Include information on dialout sessions. 2025-04-16 14:29:54 +02:00
Joachim Bauch
7cd55c741d
Document welcome and serverinfo APIs. 2025-04-16 14:29:53 +02:00
Joachim Bauch
864fc6b46b
Add serverinfo feature id. 2025-04-16 14:29:53 +02:00
Joachim Bauch
d10469e1a6
Add serverinfo API. 2025-04-16 14:29:52 +02:00
Joachim Bauch
52ed2f0243
Provide access to Janus Info message. 2025-04-16 14:29:52 +02:00
Joachim Bauch
00a9a6ee44
Provide access to proxy version and features. 2025-04-16 14:29:51 +02:00
Joachim Bauch
3e9d02be00
proxy: Include "X-Spreed-Signaling-Features" header in all responses. 2025-04-16 14:29:50 +02:00
Joachim Bauch
3cbfe2f77c
Merge pull request #971 from strukturag/fix-flaky-pool-reload-tests
Fix race condition in flaky certificate/CA reload tests.
2025-04-16 14:05:19 +02:00
Joachim Bauch
951532d3b3
Fix race condition in flaky certificate/CA reload tests. 2025-04-16 13:57:13 +02:00
Joachim Bauch
bfc153c2e6
Merge pull request #870 from strukturag/improve-memory
Improve memory allocations
2025-04-16 13:24:13 +02:00
Joachim Bauch
36f2f5026f
Use buffer pool to marshal request body. 2025-04-16 10:22:58 +02:00
Joachim Bauch
411cf34437
Use buffer pool for reading data. 2025-04-16 10:22:57 +02:00
Joachim Bauch
bfc4d7facf
Set WriteBufferPool for websocket connections. 2025-04-16 10:22:56 +02:00
Joachim Bauch
53ff3d39e7
Add buffer pool helper class. 2025-04-16 10:22:56 +02:00
Joachim Bauch
6c5eb78cc2
Use a single random string per concurrency level. 2025-04-16 10:22:55 +02:00
Joachim Bauch
1736199650
Merge pull request #970 from strukturag/fix-assert-fail-calls
Fix formatting of errors in "assert.Fail" calls.
2025-04-16 10:21:49 +02:00
Joachim Bauch
75d311c67c
Fix formatting of errors in "assert.Fail" calls. 2025-04-16 10:09:22 +02:00
Joachim Bauch
447bdb827c
Merge pull request #962 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.11.1
Bump github.com/nats-io/nats-server/v2 from 2.10.25 to 2.11.1
2025-04-16 09:48:23 +02:00
dependabot[bot]
a7a5e889c9
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] <support@github.com>
2025-04-16 07:39:36 +00:00
Joachim Bauch
b49e3d01fa
Merge pull request #936 from strukturag/dependabot/go_modules/etcd-cde8d436fb
build(deps): bump the etcd group with 4 updates
2025-04-16 09:38:24 +02:00
dependabot[bot]
594e764560
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] <support@github.com>
2025-04-16 07:25:37 +00:00
Joachim Bauch
2a5c7ff90b
Merge pull request #941 from strukturag/dependabot/go_modules/golang.org/x/net-0.36.0
build(deps): bump golang.org/x/net from 0.34.0 to 0.36.0
2025-04-16 09:23:47 +02:00
dependabot[bot]
a15cfde3d7
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] <support@github.com>
2025-04-16 07:16:21 +00:00
Joachim Bauch
08e0e1a1bf
Merge pull request #957 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.71.1
Bump google.golang.org/grpc from 1.71.0 to 1.71.1
2025-04-16 09:11:17 +02:00
Joachim Bauch
6839f624fb
Merge pull request #964 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.41.1
Bump github.com/nats-io/nats.go from 1.39.1 to 1.41.1
2025-04-16 09:10:40 +02:00
Joachim Bauch
9fe4fd7911
Merge pull request #967 from strukturag/dependabot/go_modules/golang.org/x/crypto-0.35.0
Bump golang.org/x/crypto from 0.32.0 to 0.35.0
2025-04-16 09:07:57 +02:00
dependabot[bot]
ea3f2af2fb
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] <support@github.com>
2025-04-16 07:01:46 +00:00
dependabot[bot]
00d26fdee1
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] <support@github.com>
2025-04-16 06:59:34 +00:00
dependabot[bot]
730a0c8709
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] <support@github.com>
2025-04-16 06:59:30 +00:00
Joachim Bauch
ec9cd3f209
Merge pull request #969 from strukturag/remove-go1.22
Drop support for Go 1.22
2025-04-16 08:58:17 +02:00
Joachim Bauch
f99977718b
Drop support for Go 1.22 2025-04-16 08:44:54 +02:00
Thomas Anderson
4bcd4f9d3a
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
2025-04-16 08:39:15 +02:00
Joachim Bauch
6369cc8f3e
Merge pull request #966 from strukturag/dependabot/pip/docs/markdown-3.8
Bump markdown from 3.7 to 3.8 in /docs
2025-04-12 20:43:46 +02:00
dependabot[bot]
2827506ac5
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] <support@github.com>
2025-04-11 20:48:08 +00:00
Joachim Bauch
99fe6686e2
Merge pull request #959 from strukturag/subscriber-close-on-error
Close subscribers on errors during initial connection.
2025-04-09 17:03:58 +02:00
Joachim Bauch
01af3085bb
Merge pull request #963 from strukturag/systemd-execpaths
Add "/usr/lib64" to systemd ExecPath
2025-04-09 16:59:23 +02:00
Joachim Bauch
c4d4aacc4e
Add "/usr/lib64" to systemd ExecPath
Fixes "Permission denied" errors on Fedora / RedHat systems.
2025-04-09 16:57:37 +02:00
Joachim Bauch
088c809c6f
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.
2025-04-09 16:53:40 +02:00
Joachim Bauch
1606a80ee1
Merge pull request #956 from hosting-de-labs/feature/fix-makefile-tmp-noexec
Explicitly set TMPDIR to ensure that it is a path where go-installed utils (pe. easyjson-bootstrap) can be executed
2025-04-01 16:15:28 +02:00
Jean Kahrs
934422d05e Explicitly set TMPDIR to ensure that it is a path where go-installed utils (pe. easyjson-bootstrap) can be executed. Fixes #955 2025-04-01 15:43:59 +02:00
Joachim Bauch
46ca53dad7
Merge pull request #952 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.6
build(deps): bump google.golang.org/protobuf from 1.36.5 to 1.36.6
2025-03-27 09:51:33 +01:00
Joachim Bauch
0900b4c85c
Merge pull request #951 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-7.0.0
build(deps): bump golangci/golangci-lint-action from 6.5.2 to 7.0.0
2025-03-26 14:56:00 +01:00
Joachim Bauch
1cee8ebbfd
Merge conditional assignment into variable declaration 2025-03-26 14:51:33 +01:00
Joachim Bauch
0f9cee7773
Generate GRPC server id directly in hash. 2025-03-26 14:51:14 +01:00
Joachim Bauch
24dd3f08ef
Use tagged switch instead of comparison where sensible. 2025-03-26 14:08:35 +01:00
Joachim Bauch
32a86fdd76
Don't capitalize error messages. 2025-03-26 14:03:29 +01:00
Joachim Bauch
3ea226c890
Better use "string.ReplaceAll". 2025-03-26 13:41:12 +01:00
Joachim Bauch
62141f97f6
Migrate config file of golangci-lint. 2025-03-26 13:40:06 +01:00
dependabot[bot]
1f0a61a032 Update generated files from d4cdc059bf 2025-03-25 20:51:34 +00:00
dependabot[bot]
d4cdc059bf
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] <support@github.com>
2025-03-25 20:49:55 +00:00
Joachim Bauch
077a11d86b
Merge pull request #949 from strukturag/dependabot/go_modules/github.com/golang-jwt/jwt/v4-4.5.2
build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2
2025-03-25 09:43:55 +01:00
dependabot[bot]
e1470068ff
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] <support@github.com>
2025-03-24 21:54:08 +00:00
dependabot[bot]
b4ace199f2
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] <support@github.com>
2025-03-24 15:05:49 +00:00
Joachim Bauch
c24d5fd055
Merge pull request #948 from strukturag/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.2.2
build(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2
2025-03-24 16:01:38 +01:00
Joachim Bauch
d3fcabaf43
Merge pull request #946 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-6.5.2
build(deps): bump golangci/golangci-lint-action from 6.5.1 to 6.5.2
2025-03-24 16:00:22 +01:00
dependabot[bot]
d25c8dd8ab
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] <support@github.com>
2025-03-21 20:57:12 +00:00
dependabot[bot]
08669dc6ea
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] <support@github.com>
2025-03-18 20:55:33 +00:00
Joachim Bauch
39ea15c9b3
Merge pull request #940 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-6.5.1
build(deps): bump golangci/golangci-lint-action from 6.5.0 to 6.5.1
2025-03-13 08:31:43 +01:00
Joachim Bauch
d577358df3
Merge pull request #939 from strukturag/dependabot/go_modules/github.com/pion/sdp/v3-3.0.11
build(deps): bump github.com/pion/sdp/v3 from 3.0.10 to 3.0.11
2025-03-13 08:31:16 +01:00
dependabot[bot]
b68c6842f4
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] <support@github.com>
2025-03-12 20:47:49 +00:00
dependabot[bot]
85bf1fa398
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] <support@github.com>
2025-03-11 20:31:40 +00:00
Joachim Bauch
0fbbe0cbd9
Merge pull request #938 from strukturag/dependabot/pip/docs/jinja2-3.1.6
build(deps): bump jinja2 from 3.1.5 to 3.1.6 in /docs
2025-03-06 13:11:54 +01:00
dependabot[bot]
396da6ea28
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] <support@github.com>
2025-03-05 20:59:02 +00:00
Joachim Bauch
4690be3e3c
Merge pull request #935 from strukturag/nats-reconnects
nats: Reconnect client indefinitely.
2025-03-05 17:00:19 +01:00
Joachim Bauch
1232bfb3b3
nats: Reconnect client indefinitely. 2025-03-05 16:41:11 +01:00
Joachim Bauch
287491fc1c
Merge pull request #925 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-6.5.0
build(deps): bump golangci/golangci-lint-action from 6.3.3 to 6.5.0
2025-03-05 15:48:22 +01:00
Joachim Bauch
864ac6d38a
CI: Fix option to ignore generated headers in linter. 2025-03-05 15:46:14 +01:00
Joachim Bauch
064e489424
Merge pull request #934 from strukturag/dependabot/go_modules/github.com/prometheus/client_golang-1.21.1
build(deps): bump github.com/prometheus/client_golang from 1.21.0 to 1.21.1
2025-03-05 08:42:05 +01:00
Joachim Bauch
93df9293c6
Merge pull request #933 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.71.0
build(deps): bump google.golang.org/grpc from 1.70.0 to 1.71.0
2025-03-05 08:41:22 +01:00
dependabot[bot]
5328036969
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] <support@github.com>
2025-03-04 20:54:55 +00:00
dependabot[bot]
cffcf084f8
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] <support@github.com>
2025-03-04 20:54:50 +00:00
Joachim Bauch
0ee43b5a8a
Merge pull request #932 from strukturag/dependabot/pip/docs/sphinx-8.2.3
build(deps): bump sphinx from 8.2.1 to 8.2.3 in /docs
2025-03-04 08:12:06 +01:00
dependabot[bot]
0ff6455601
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] <support@github.com>
2025-03-03 20:56:40 +00:00
Joachim Bauch
afacaa72c6
Merge pull request #926 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.39.1
build(deps): bump github.com/nats-io/nats.go from 1.39.0 to 1.39.1
2025-02-26 08:32:56 +01:00
Joachim Bauch
f3f7346f62
Merge pull request #929 from strukturag/dependabot/pip/docs/sphinx-8.2.1
build(deps): bump sphinx from 8.2.0 to 8.2.1 in /docs
2025-02-26 08:32:36 +01:00
dependabot[bot]
03a4d27d82
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] <support@github.com>
2025-02-24 21:24:23 +00:00
Joachim Bauch
f7bdb431c5
Merge pull request #927 from strukturag/dependabot/go_modules/github.com/prometheus/client_golang-1.21.0
build(deps): bump github.com/prometheus/client_golang from 1.20.5 to 1.21.0
2025-02-24 08:46:13 +01:00
Joachim Bauch
896b77438c
Merge pull request #928 from strukturag/dependabot/pip/docs/sphinx-8.2.0
build(deps): bump sphinx from 8.1.3 to 8.2.0 in /docs
2025-02-24 08:45:19 +01:00
dependabot[bot]
397f645b08
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] <support@github.com>
2025-02-19 20:53:42 +00:00
dependabot[bot]
c0d640dbd9
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] <support@github.com>
2025-02-19 20:50:18 +00:00
dependabot[bot]
752c3cffd5
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] <support@github.com>
2025-02-19 20:50:15 +00:00
dependabot[bot]
29cb0d7a4b
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] <support@github.com>
2025-02-17 20:58:16 +00:00
Joachim Bauch
5c459239bf
Merge pull request #919 from strukturag/dependabot/docker/docker/server/golang-1.24-alpine
build(deps): bump golang from 1.23-alpine to 1.24-alpine in /docker/server
2025-02-14 08:47:55 +01:00
Joachim Bauch
34fc59cbe5
Merge pull request #921 from strukturag/dependabot/docker/docker/proxy/golang-1.24-alpine
build(deps): bump golang from 1.23-alpine to 1.24-alpine in /docker/proxy
2025-02-14 08:47:40 +01:00
Joachim Bauch
c210156054
Merge pull request #922 from strukturag/golang-1.24
CI: Test with Golang 1.24
2025-02-14 08:47:05 +01:00
Joachim Bauch
a2baa3e4d8
Merge pull request #920 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-6.3.3
build(deps): bump golangci/golangci-lint-action from 6.3.2 to 6.3.3
2025-02-14 08:42:45 +01:00
Joachim Bauch
ca07f41e78
CI: Test with Golang 1.24 2025-02-14 08:40:11 +01:00
dependabot[bot]
972f0db360
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] <support@github.com>
2025-02-13 20:21:19 +00:00
dependabot[bot]
06119ce07b
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] <support@github.com>
2025-02-13 20:11:29 +00:00
dependabot[bot]
a747190d55
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] <support@github.com>
2025-02-12 20:41:24 +00:00
Joachim Bauch
fa55bc77b0
Merge pull request #917 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.5
Bump google.golang.org/protobuf from 1.36.4 to 1.36.5
2025-02-10 21:14:00 +01:00
Joachim Bauch
0fba4e5920
Merge pull request #918 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-6.3.2
Bump golangci/golangci-lint-action from 6.3.0 to 6.3.2
2025-02-10 21:13:34 +01:00
dependabot[bot]
8ddeedce8b
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] <support@github.com>
2025-02-10 20:08:00 +00:00
dependabot[bot]
f7efc3f155
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] <support@github.com>
2025-02-06 20:37:40 +00:00
Joachim Bauch
97452ae8d5
Merge pull request #915 from strukturag/dependabot/go_modules/github.com/nats-io/nats.go-1.39.0
Bump github.com/nats-io/nats.go from 1.38.0 to 1.39.0
2025-02-06 11:38:58 +01:00
dependabot[bot]
de315082b8
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] <support@github.com>
2025-02-06 10:11:28 +00:00
Joachim Bauch
dd461d5f48
Merge pull request #916 from strukturag/migrate-cachecontrol
Migrate cache-control parsing to https://github.com/pquerna/cachecontrol
2025-02-06 11:10:22 +01:00
Joachim Bauch
eadded9aa2
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.
2025-02-06 10:59:53 +01:00
Joachim Bauch
45ef6fd143
Merge pull request #912 from linka-cloud/backend-secrets-env
Allow using environment variables for backend secrets
2025-02-06 10:33:42 +01:00
Joachim Bauch
70eccc6f6e
Merge pull request #913 from strukturag/dependabot/github_actions/golangci/golangci-lint-action-6.3.0
Bump golangci/golangci-lint-action from 6.2.0 to 6.3.0
2025-02-05 07:42:51 +01:00
dependabot[bot]
5d44577394
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] <support@github.com>
2025-02-04 20:50:21 +00:00
Adphi
dcda0984fa
backend secrets: read with environment override
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-01-28 19:21:53 +01:00
Joachim Bauch
b719a5602a
Merge pull request #911 from linka-cloud/do-not-leak-nats-credentials
Do not log nats url credentials
2025-01-28 14:16:03 +01:00
Joachim Bauch
ca62fb55f6
Merge pull request #903 from strukturag/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.10.25
Bump github.com/nats-io/nats-server/v2 from 2.10.24 to 2.10.25
2025-01-28 14:14:52 +01:00
Adphi
471469f1c8
nats: do not log url credentials
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-01-28 14:01:03 +01:00
dependabot[bot]
9747e4d8e0
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] <support@github.com>
2025-01-28 12:59:16 +00:00
Joachim Bauch
ae796fc1bc
Merge pull request #908 from strukturag/dependabot/go_modules/google.golang.org/protobuf-1.36.4
Bump google.golang.org/protobuf from 1.36.3 to 1.36.4
2025-01-28 13:58:28 +01:00
Joachim Bauch
d6b52ab86e
Merge pull request #909 from strukturag/dependabot/github_actions/coverallsapp/github-action-2.3.6
Bump coverallsapp/github-action from 2.3.4 to 2.3.6
2025-01-28 13:57:47 +01:00
Joachim Bauch
cbf3a3d86f
Merge pull request #907 from strukturag/dependabot/go_modules/etcd-8488209b6a
Bump the etcd group with 4 updates
2025-01-28 13:57:27 +01:00
Joachim Bauch
8b61ebe132
Merge pull request #904 from strukturag/dependabot/go_modules/google.golang.org/grpc-1.70.0
Bump google.golang.org/grpc from 1.69.4 to 1.70.0
2025-01-28 13:57:01 +01:00
Joachim Bauch
805b6da760
Merge pull request #910 from linka-cloud/sessions-secrets-env
Allow using environment variables for sessions and clients secrets
2025-01-28 13:51:35 +01:00
Adphi
bcac66cd7c
sessions secrets: read with environment override
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-01-28 13:30:42 +01:00
dependabot[bot]
a730417b3b
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] <support@github.com>
2025-01-27 20:33:53 +00:00
dependabot[bot]
9606c212b0 Update generated files from 82a73659b7 2025-01-24 21:02:32 +00:00
dependabot[bot]
82a73659b7
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] <support@github.com>
2025-01-24 20:59:29 +00:00
dependabot[bot]
d4b2e76575
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] <support@github.com>
2025-01-24 20:59:25 +00:00
dependabot[bot]
b8afbd0366
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] <support@github.com>
2025-01-23 20:32:31 +00:00
328 changed files with 56623 additions and 30006 deletions

122
.codecov.yml Normal file
View file

@ -0,0 +1,122 @@
coverage:
status:
project:
default:
threshold: 2%
comment:
layout: "header, diff, flags, components, files"
after_n_builds: 2
ignore:
- "*_easyjson.go"
- "**/*_easyjson.go"
- "*.pb.go"
- "**/*.pb.go"
component_management:
individual_components:
- component_id: module_root
name: root
paths:
- "*.go"
- component_id: module_api
name: api
paths:
- api/**
- component_id: module_async
name: async
paths:
- async/**
- component_id: module_client
name: client
paths:
- client/**
- component_id: module_cmd_client
name: cmd/client
paths:
- 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:
- config/**
- component_id: module_container
name: container
paths:
- container/**
- component_id: module_dns
name: dns
paths:
- dns/**
- component_id: module_etcd
name: etcd
paths:
- etcd/**
- component_id: module_geoip
name: geoip
paths:
- geoip/**
- component_id: module_grpc
name: grpc
paths:
- grpc/**
- component_id: module_internal
name: internal
paths:
- internal/**
- component_id: module_log
name: log
paths:
- log/**
- component_id: module_metrics
name: metrics
paths:
- metrics/**
- component_id: module_mock
name: mock
paths:
- mock/**
- component_id: module_nats
name: nats
paths:
- nats/**
- component_id: module_pool
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_session
name: session
paths:
- session/**
- component_id: module_sfu
name: sfu
paths:
- sfu/**
- component_id: module_talk
name: talk
paths:
- talk/**
- component_id: module_test
name: test
paths:
- test/**

View file

@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Check continentmap
run: make check-continentmap

View file

@ -36,15 +36,15 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- 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

View file

@ -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 }}
@ -31,7 +31,7 @@ jobs:
reaction-type: "+1"
- name: Checkout the latest code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
@ -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 }}

View file

@ -1,4 +1,4 @@
name: Deploy to Docker Hub / GHCR
name: Deploy to Docker Hub / GHCR / quay.io
on:
pull_request:
@ -27,18 +27,19 @@ jobs:
steps:
- name: Check Out Repo
uses: actions/checkout@v4
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
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
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}}
@ -46,7 +47,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 }}
@ -54,26 +55,34 @@ 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 }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to quay.io
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
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
uses: docker/setup-buildx-action@v4
- 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
@ -92,18 +101,19 @@ jobs:
steps:
- name: Check Out Repo
uses: actions/checkout@v4
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
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
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.
@ -116,7 +126,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 }}
@ -124,26 +134,34 @@ 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 }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to quay.io
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
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
uses: docker/setup-buildx-action@v4
- 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

View file

@ -22,26 +22,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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
- uses: actions/checkout@v6
- 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
steps:
- uses: actions/checkout@v4
- 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
- uses: actions/checkout@v6
- name: Build Docker images
run: ./docker-compose -f docker/docker-compose.yml build
run: docker compose -f docker/docker-compose.yml build

View file

@ -23,13 +23,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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
uses: docker/build-push-action@v7
with:
context: docker/janus
load: true

View file

@ -33,16 +33,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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
uses: docker/setup-buildx-action@v4
- name: Build Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: docker/server/Dockerfile
@ -52,16 +52,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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
uses: docker/setup-buildx-action@v4
- name: Build Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: docker/proxy/Dockerfile

View file

@ -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
@ -53,12 +57,12 @@ jobs:
contents: write
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
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"
@ -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
@ -97,8 +100,8 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "stable"
@ -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

View file

@ -24,13 +24,14 @@ jobs:
strategy:
matrix:
go-version:
- "1.22"
- "1.23"
- "1.25"
- "1.26"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
check-latest: true
- run: date

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install licensecheck
run: |
@ -33,7 +33,7 @@ jobs:
run: |
{
echo 'CHECK_RESULT<<EOF'
licensecheck *.go */*.go
find -name "*.go" | sort | xargs licensecheck
echo EOF
} >> "$GITHUB_ENV"

View file

@ -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
@ -25,31 +27,60 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.22"
go-version: "1.25"
- name: lint
uses: golangci/golangci-lint-action@v6.2.0
uses: golangci/golangci-lint-action@v9.2.0
with:
version: latest
args: --timeout=2m0s
skip-cache: true
modernize:
name: modernize
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: moderize
run: |
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -any=false -reflecttypefor=false -test ./...
checklocks:
name: checklocks
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
check-latest: true
- name: checklocks
run: |
make checklocks
dependencies:
name: dependencies
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "stable"
- name: Check minimum supported version of Go
run: |
go mod tidy -go=1.22.0 -compat=1.22.0
go mod tidy -go=1.25.0 -compat=1.25.0
- name: Check go.mod / go.sum
run: |

View file

@ -20,7 +20,7 @@ jobs:
name: shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: shellcheck
run: |

View file

@ -24,12 +24,12 @@ jobs:
strategy:
matrix:
go-version:
- "1.22"
- "1.23"
- "1.25"
- "1.26"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
@ -39,26 +39,71 @@ jobs:
make tarball
- name: Upload tarball
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: tarball-${{ matrix.go-version }}
path: nextcloud-spreed-signaling*.tar.gz
build:
strategy:
matrix:
go-version:
- "1.25"
- "1.26"
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@v8
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
[ -f "tmp/version.txt" ] || exit 1
- name: Build
run: |
echo "Building with $(nproc) threads"
make -C tmp build client -j$(nproc)
UNKNOWN=$(./tmp/bin/signaling -version | grep unknown || true)
if [ -n "$UNKNOWN" ]; then \
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
UNKNOWN=$(./tmp/bin/client -version | grep unknown || true)
if [ -n "$UNKNOWN" ]; then \
echo "Found unknown version: $UNKNOWN"; \
exit 1; \
fi
test:
strategy:
matrix:
go-version:
- "1.22"
- "1.23"
- "1.25"
- "1.26"
runs-on: ubuntu-latest
needs: [create]
steps:
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- name: Download tarball
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: tarball-${{ matrix.go-version }}
@ -68,11 +113,6 @@ jobs:
tar xvf nextcloud-spreed-signaling*.tar.gz --strip-components=1 -C tmp
[ -d "tmp/vendor" ] || exit 1
- name: Build
run: |
echo "Building with $(nproc) threads"
make -C tmp build -j$(nproc)
- name: Run tests
env:
USE_DB_IP_GEOIP_DATABASE: "1"

View file

@ -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'
@ -20,19 +22,16 @@ permissions:
contents: read
jobs:
go:
env:
MAXMIND_GEOLITE2_LICENSE: ${{ secrets.MAXMIND_GEOLITE2_LICENSE }}
USE_DB_IP_GEOIP_DATABASE: "1"
build:
strategy:
matrix:
go-version:
- "1.22"
- "1.23"
- "1.25"
- "1.26"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
@ -43,38 +42,69 @@ 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.25"
- "1.26"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- name: Run tests
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.25"
- "1.26"
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 }}
USE_DB_IP_GEOIP_DATABASE: "1"
strategy:
matrix:
go-version:
- "1.25"
- "1.26"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- 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.4
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.4
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
token: ${{ secrets.CODECOV_TOKEN }}
files: ./cover.out
flags: go-${{ matrix.go-version }}

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
bin/
tmp/
vendor/
*.pem

View file

@ -1,34 +1,80 @@
version: "2"
linters:
enable:
- gofmt
- errchkjson
- exptostd
- gocritic
- misspell
- modernize
- paralleltest
- perfsprint
- revive
linters-settings:
revive:
ignoreGeneratedHeader: 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
- testifylint
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
disable:
- stdversion
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: use-any
- name: redefines-builtin-id
testifylint:
disable:
- require-error
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$

View file

@ -2,6 +2,565 @@
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
- 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
- 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
- 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

View file

@ -6,28 +6,28 @@ 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"
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))
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))
EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.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 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)
@ -51,6 +51,10 @@ ifeq ($(TIMEOUT),)
TIMEOUT := 60s
endif
ifeq ($(BENCHMARK),)
BENCHMARK := .
endif
ifneq ($(TEST),)
TESTARGS := $(TESTARGS) -run "$(TEST)"
endif
@ -73,10 +77,12 @@ else
GOPATHBIN := $(GOPATH)/bin/$(GOOS)_$(GOARCH)
endif
GOEXPERIMENT :=
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
@ -85,7 +91,10 @@ $(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
continentmap.go:
$(GOPATHBIN)/checklocks: go.mod go.sum
$(GO) install gvisor.dev/gvisor/tools/checklocks/cmd/checklocks@go
geoip/continentmap.go:
$(CURDIR)/scripts/get_continent_map.py $@
check-continentmap:
@ -93,38 +102,41 @@ 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:
$(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:
$(GO) vet $(ALL_PACKAGES)
GOEXPERIMENT=$(GOEXPERIMENT) $(GO) vet ./...
test: vet
$(GO) test -timeout $(TIMEOUT) $(TESTARGS) $(ALL_PACKAGES)
GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -timeout $(TIMEOUT) $(TESTARGS) ./...
benchmark:
GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -bench=$(BENCHMARK) -benchmem -run=^$$ -timeout $(TIMEOUT) $(TESTARGS) ./...
checklocks: $(GOPATHBIN)/checklocks
GOEXPERIMENT=$(GOEXPERIMENT) $(GOPATHBIN)/checklocks ./...
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
GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test -timeout $(TIMEOUT) -coverprofile cover.out ./...
coverhtml: vet
rm -f cover.out && \
$(GO) test -timeout $(TIMEOUT) -coverprofile cover.out $(ALL_PACKAGES) && \
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
%_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,32 +154,37 @@ 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; \
rm -f *_easyjson_easyjson.go; \
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)
$(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
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)
@ -179,7 +196,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 \
@ -189,11 +206,17 @@ 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
.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:

View file

@ -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)
@ -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.25
- make
Usually the last two versions of Go are supported. This follows the release
@ -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
@ -131,14 +139,30 @@ 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 (`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.

111
api/bandwidth.go Normal file
View file

@ -0,0 +1,111 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package api
import (
"fmt"
"math"
"sync/atomic"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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 {
switch {
case b >= Gigabit:
return formatWithRemainder(b.Bits(), Gigabit.Bits(), "Gbps")
case b >= Megabit:
return formatWithRemainder(b.Bits(), Megabit.Bits(), "Mbps")
case b >= Kilobit:
return formatWithRemainder(b.Bits(), Kilobit.Bits(), "Kbps")
default:
return fmt.Sprintf("%d bps", b)
}
}
// 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) * Megabit
}
// 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
}

109
api/bandwidth_test.go Normal file
View file

@ -0,0 +1,109 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
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())
}
func TestBandwidthString(t *testing.T) {
t.Parallel()
assert := assert.New(t)
testcases := []struct {
value Bandwidth
expected string
}{
{
0,
"0 bps",
},
{
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())
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1081
api/signaling_test.go Normal file

File diff suppressed because it is too large Load diff

78
api/stringmap.go Normal file
View file

@ -0,0 +1,78 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
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
}
switch ob := ob.(type) {
case map[string]any:
return StringMap(ob), true
case StringMap:
return ob, true
default:
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
}
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 string:
return T(v), true
case T:
return v, true
default:
return defaultValue, false
}
}

118
api/stringmap_test.go Normal file
View file

@ -0,0 +1,118 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package api
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestConvertStringMap(t *testing.T) {
t.Parallel()
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)
}
func TestGetStringMapString(t *testing.T) {
t.Parallel()
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)
}
func TestGetStringMapStringMap(t *testing.T) {
t.Parallel()
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)
}

405
api/transient_data.go Normal file
View file

@ -0,0 +1,405 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package api
import (
"encoding/json"
"fmt"
"reflect"
"sync"
"time"
)
const (
TransientSessionDataPrefix = "sd:"
)
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 TransientDataEntries
// +checklocks:mu
listeners map[TransientListener]bool
// +checklocks:mu
timers map[string]*time.Timer
}
// NewTransientData creates a new transient data container.
func NewTransientData() *TransientData {
return &TransientData{}
}
// +checklocks:t.mu
func (t *TransientData) sendMessageToListener(listener TransientListener, message *ServerMessage) {
t.mu.Unlock()
defer t.mu.Lock()
listener.SendMessage(message)
}
// +checklocks:t.mu
func (t *TransientData) notifySet(key string, prev, value any) {
msg := &ServerMessage{
Type: "transient",
TransientData: &TransientDataServerMessage{
Type: "set",
Key: key,
Value: value,
OldValue: prev,
},
}
for listener := range t.listeners {
t.sendMessageToListener(listener, msg)
}
}
// +checklocks:t.mu
func (t *TransientData) notifyDeleted(key string, prev *TransientDataEntry) {
msg := &ServerMessage{
Type: "transient",
TransientData: &TransientDataServerMessage{
Type: "remove",
Key: key,
},
}
if prev != nil {
msg.TransientData.OldValue = prev.Value
}
for listener := range t.listeners {
t.sendMessageToListener(listener, msg)
}
}
// AddListener adds a new listener to be notified about changes.
func (t *TransientData) AddListener(listener TransientListener) {
t.mu.Lock()
defer t.mu.Unlock()
if t.listeners == nil {
t.listeners = make(map[TransientListener]bool)
}
t.listeners[listener] = true
if len(t.data) > 0 {
data := make(StringMap, len(t.data))
for k, v := range t.data {
data[k] = v.Value
}
msg := &ServerMessage{
Type: "transient",
TransientData: &TransientDataServerMessage{
Type: "initial",
Data: data,
},
}
t.sendMessageToListener(listener, msg)
}
}
// RemoveListener removes a previously registered listener.
func (t *TransientData) RemoveListener(listener TransientListener) {
t.mu.Lock()
defer t.mu.Unlock()
delete(t.listeners, listener)
}
// +checklocks:t.mu
func (t *TransientData) updateTTL(key string, value any, ttl time.Duration) {
if ttl <= 0 {
if old, found := t.timers[key]; found {
old.Stop()
delete(t.timers, key)
}
} else {
t.removeAfterTTL(key, value, ttl)
}
}
// +checklocks:t.mu
func (t *TransientData) removeAfterTTL(key string, value any, ttl time.Duration) {
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.timers == nil {
t.timers = make(map[string]*time.Timer)
}
t.timers[key] = timer
}
// +checklocks:t.mu
func (t *TransientData) doSet(key string, value any, prev *TransientDataEntry, ttl time.Duration) {
if t.data == nil {
t.data = make(TransientDataEntries)
}
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)
}
// 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 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 any, ttl time.Duration) bool {
if value == nil {
return t.Remove(key)
}
t.mu.Lock()
defer t.mu.Unlock()
prev, found := t.data[key]
if found && reflect.DeepEqual(prev.Value, value) {
t.updateTTL(key, value, ttl)
return false
}
t.doSet(key, value, prev, ttl)
return true
}
// 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 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 any, ttl time.Duration) bool {
if value == nil {
return t.CompareAndRemove(key, old)
}
t.mu.Lock()
defer t.mu.Unlock()
prev, found := t.data[key]
if old != nil && (!found || !reflect.DeepEqual(prev.Value, old)) {
return false
} else if old == nil && found {
return false
}
t.doSet(key, value, prev, ttl)
return true
}
// +checklocks:t.mu
func (t *TransientData) doRemove(key string, prev *TransientDataEntry) {
delete(t.data, key)
if old, found := t.timers[key]; found {
old.Stop()
delete(t.timers, key)
}
t.notifyDeleted(key, prev)
}
// Remove deletes the value with the given key and notifies listeners
// if the key was removed.
func (t *TransientData) Remove(key string) bool {
t.mu.Lock()
defer t.mu.Unlock()
prev, found := t.data[key]
if !found {
return false
}
t.doRemove(key, prev)
return true
}
// 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 any) bool {
t.mu.Lock()
defer t.mu.Unlock()
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.Value, old) {
return false
}
t.doRemove(key, prev)
return true
}
// GetData returns a copy of the internal data.
func (t *TransientData) GetData() StringMap {
t.mu.Lock()
defer t.mu.Unlock()
if len(t.data) == 0 {
return nil
}
result := make(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)
}
now := time.Now()
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).
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
}
if len(msgData) == 0 {
return
}
msg := &ServerMessage{
Type: "transient",
TransientData: &TransientDataServerMessage{
Type: "initial",
Data: msgData,
},
}
for listener := range t.listeners {
t.sendMessageToListener(listener, msg)
}
}

234
api/transient_data_test.go Normal file
View file

@ -0,0 +1,234 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package api
import (
"sync"
"sync/atomic"
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
)
func Test_TransientData(t *testing.T) {
t.Parallel()
synctest.Test(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
}
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()
synctest.Test(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())
})
}

File diff suppressed because it is too large Load diff

View file

@ -1,426 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2017 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"encoding/json"
"fmt"
"sort"
"testing"
"github.com/stretchr/testify/assert"
)
type testCheckValid interface {
CheckValid() error
}
func wrapMessage(messageType string, msg testCheckValid) *ClientMessage {
wrapped := &ClientMessage{
Type: messageType,
}
switch messageType {
case "hello":
wrapped.Hello = msg.(*HelloClientMessage)
case "message":
wrapped.Message = msg.(*MessageClientMessage)
case "bye":
wrapped.Bye = msg.(*ByeClientMessage)
case "room":
wrapped.Room = msg.(*RoomClientMessage)
default:
return nil
}
return wrapped
}
func testMessages(t *testing.T, messageType string, valid_messages []testCheckValid, invalid_messages []testCheckValid) {
t.Helper()
assert := assert.New(t)
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)
}
}
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)
}
}
}
func TestClientMessage(t *testing.T) {
t.Parallel()
assert := assert.New(t)
// The message needs a type.
msg := ClientMessage{}
assert.Error(msg.CheckValid())
}
func TestHelloClientMessage(t *testing.T) {
t.Parallel()
internalAuthParams := []byte("{\"backend\":\"https://domain.invalid\"}")
tokenAuthParams := []byte("{\"token\":\"invalid-token\"}")
valid_messages := []testCheckValid{
// Hello version 1
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Params: json.RawMessage("{}"),
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Type: "client",
Params: json.RawMessage("{}"),
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Type: "internal",
Params: internalAuthParams,
},
},
&HelloClientMessage{
Version: HelloVersionV1,
ResumeId: "the-resume-id",
},
// Hello version 2
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Params: tokenAuthParams,
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Type: "client",
Params: tokenAuthParams,
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV2,
ResumeId: "the-resume-id",
},
}
invalid_messages := []testCheckValid{
// Hello version 1
&HelloClientMessage{},
&HelloClientMessage{Version: "0.0"},
&HelloClientMessage{Version: HelloVersionV1},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Params: json.RawMessage("{}"),
Type: "invalid-type",
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Params: json.RawMessage("{}"),
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Params: json.RawMessage("{}"),
Url: "invalid-url",
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Type: "internal",
Params: json.RawMessage("{}"),
},
},
&HelloClientMessage{
Version: HelloVersionV1,
Auth: &HelloClientMessageAuth{
Type: "internal",
Params: json.RawMessage("xyz"), // Invalid JSON.
},
},
// Hello version 2
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Params: tokenAuthParams,
},
},
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Params: tokenAuthParams,
Url: "invalid-url",
},
},
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Params: internalAuthParams,
Url: "https://domain.invalid",
},
},
&HelloClientMessage{
Version: HelloVersionV2,
Auth: &HelloClientMessageAuth{
Params: json.RawMessage("xyz"), // Invalid JSON.
Url: "https://domain.invalid",
},
},
}
testMessages(t, "hello", valid_messages, invalid_messages)
// A "hello" message must be present
msg := ClientMessage{
Type: "hello",
}
assert := assert.New(t)
assert.Error(msg.CheckValid())
}
func TestMessageClientMessage(t *testing.T) {
t.Parallel()
valid_messages := []testCheckValid{
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "session",
SessionId: "the-session-id",
},
Data: json.RawMessage("{}"),
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "user",
UserId: "the-user-id",
},
Data: json.RawMessage("{}"),
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "room",
},
Data: json.RawMessage("{}"),
},
}
invalid_messages := []testCheckValid{
&MessageClientMessage{},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "session",
SessionId: "the-session-id",
},
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "session",
},
Data: json.RawMessage("{}"),
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "session",
UserId: "the-user-id",
},
Data: json.RawMessage("{}"),
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "user",
},
Data: json.RawMessage("{}"),
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "user",
UserId: "the-user-id",
},
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "user",
SessionId: "the-user-id",
},
Data: json.RawMessage("{}"),
},
&MessageClientMessage{
Recipient: MessageClientMessageRecipient{
Type: "unknown-type",
},
Data: json.RawMessage("{}"),
},
}
testMessages(t, "message", valid_messages, invalid_messages)
// A "message" message must be present
msg := ClientMessage{
Type: "message",
}
assert := assert.New(t)
assert.Error(msg.CheckValid())
}
func TestByeClientMessage(t *testing.T) {
t.Parallel()
// Any "bye" message is valid.
valid_messages := []testCheckValid{
&ByeClientMessage{},
}
invalid_messages := []testCheckValid{}
testMessages(t, "bye", valid_messages, invalid_messages)
// The "bye" message is optional.
msg := ClientMessage{
Type: "bye",
}
assert := assert.New(t)
assert.NoError(msg.CheckValid())
}
func TestRoomClientMessage(t *testing.T) {
t.Parallel()
// Any "room" message is valid.
valid_messages := []testCheckValid{
&RoomClientMessage{},
}
invalid_messages := []testCheckValid{}
testMessages(t, "room", valid_messages, invalid_messages)
// But a "room" message must be present
msg := ClientMessage{
Type: "room",
}
assert := assert.New(t)
assert.Error(msg.CheckValid())
}
func TestErrorMessages(t *testing.T) {
t.Parallel()
assert := assert.New(t)
id := "request-id"
msg := ClientMessage{
Id: id,
}
err1 := msg.NewErrorServerMessage(&Error{})
assert.Equal(id, err1.Id, "%+v", err1)
assert.Equal("error", err1.Type, "%+v", err1)
assert.NotNil(err1.Error, "%+v", err1)
err2 := msg.NewWrappedErrorServerMessage(fmt.Errorf("test-error"))
assert.Equal(id, err2.Id, "%+v", err2)
assert.Equal("error", err2.Type, "%+v", err2)
if assert.NotNil(err2.Error, "%+v", err2) {
assert.Equal("internal_error", err2.Error.Code, "%+v", err2)
assert.Equal("test-error", err2.Error.Message, "%+v", err2)
}
// Test "error" interface
assert.Equal("test-error", err2.Error.Error(), "%+v", err2)
}
func TestIsChatRefresh(t *testing.T) {
t.Parallel()
var msg ServerMessage
data_true := []byte("{\"type\":\"chat\",\"chat\":{\"refresh\":true}}")
msg = ServerMessage{
Type: "message",
Message: &MessageServerMessage{
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,
},
}
assert.False(t, msg.IsChatRefresh())
}
func assertEqualStrings(t *testing.T, expected, result []string) {
t.Helper()
if expected == nil {
expected = make([]string, 0)
} else {
sort.Strings(expected)
}
if result == nil {
result = make([]string, 0)
} else {
sort.Strings(result)
}
assert.Equal(t, expected, result)
}
func Test_Welcome_AddRemoveFeature(t *testing.T) {
t.Parallel()
assert := assert.New(t)
var msg WelcomeServerMessage
assertEqualStrings(t, []string{}, msg.Features)
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)
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)
msg.RemoveFeature("three", "one")
assertEqualStrings(t, []string{"two"}, msg.Features)
}

View file

@ -19,11 +19,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
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{
@ -67,10 +67,6 @@ func (b *exponentialBackoff) Wait(ctx context.Context) {
waiter, cancel := context.WithTimeout(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()
}

View file

@ -19,11 +19,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"context"
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
@ -32,31 +33,32 @@ import (
func TestBackoff_Exponential(t *testing.T) {
t.Parallel()
assert := assert.New(t)
minWait := 100 * time.Millisecond
backoff, err := NewExponentialBackoff(minWait, 500*time.Millisecond)
require.NoError(t, err)
synctest.Test(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,
}
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())
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())
b := time.Now()
assert.GreaterOrEqual(b.Sub(a), wait)
}
backoff.Reset()
a := time.Now()
backoff.Wait(context.Background())
b := time.Now()
assert.GreaterOrEqual(b.Sub(a), minWait)
assert.Equal(b.Sub(a), minWait)
})
}

View file

@ -19,29 +19,32 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"log"
"reflect"
"runtime"
"runtime/debug"
"sync"
"github.com/strukturag/nextcloud-spreed-signaling/v2/log"
)
// DeferredExecutor will asynchronously execute functions while maintaining
// their order.
type DeferredExecutor struct {
logger log.Logger
queue chan func()
closed chan struct{}
closeOnce sync.Once
}
func NewDeferredExecutor(queueSize int) *DeferredExecutor {
func NewDeferredExecutor(logger log.Logger, queueSize int) *DeferredExecutor {
if queueSize < 0 {
queueSize = 0
}
result := &DeferredExecutor{
logger: logger,
queue: make(chan func(), queueSize),
closed: make(chan struct{}),
}
@ -62,15 +65,15 @@ func (e *DeferredExecutor) run() {
}
}
func getFunctionName(i interface{}) string {
func getFunctionName(i any) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
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()))
}
}()

View file

@ -19,17 +19,22 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test"
)
func TestDeferredExecutor_MultiClose(t *testing.T) {
e := NewDeferredExecutor(0)
t.Parallel()
logger := logtest.NewLoggerForTest(t)
e := NewDeferredExecutor(logger, 0)
defer e.waitForStop()
e.Close()
@ -38,28 +43,32 @@ func TestDeferredExecutor_MultiClose(t *testing.T) {
func TestDeferredExecutor_QueueSize(t *testing.T) {
t.Parallel()
e := NewDeferredExecutor(0)
defer e.waitForStop()
defer e.Close()
synctest.Test(t, func(t *testing.T) {
logger := logtest.NewLoggerForTest(t)
e := NewDeferredExecutor(logger, 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) {
e := NewDeferredExecutor(64)
t.Parallel()
logger := logtest.NewLoggerForTest(t)
e := NewDeferredExecutor(logger, 64)
defer e.waitForStop()
defer e.Close()
@ -71,7 +80,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,13 +89,15 @@ 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)
}
}
func TestDeferredExecutor_CloseFromFunc(t *testing.T) {
e := NewDeferredExecutor(64)
t.Parallel()
logger := logtest.NewLoggerForTest(t)
e := NewDeferredExecutor(logger, 64)
defer e.waitForStop()
done := make(chan struct{})
@ -99,8 +110,9 @@ func TestDeferredExecutor_CloseFromFunc(t *testing.T) {
}
func TestDeferredExecutor_DeferAfterClose(t *testing.T) {
CatchLogForTest(t)
e := NewDeferredExecutor(64)
t.Parallel()
logger := logtest.NewLoggerForTest(t)
e := NewDeferredExecutor(logger, 64)
defer e.waitForStop()
e.Close()
@ -111,7 +123,9 @@ func TestDeferredExecutor_DeferAfterClose(t *testing.T) {
}
func TestDeferredExecutor_WaitForStopTwice(t *testing.T) {
e := NewDeferredExecutor(64)
t.Parallel()
logger := logtest.NewLoggerForTest(t)
e := NewDeferredExecutor(logger, 64)
defer e.waitForStop()
e.Close()

View file

@ -19,12 +19,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package events
import (
"encoding/json"
"fmt"
"time"
"github.com/strukturag/nextcloud-spreed-signaling/v2/api"
"github.com/strukturag/nextcloud-spreed-signaling/v2/talk"
)
type AsyncMessage struct {
@ -32,11 +35,11 @@ type AsyncMessage struct {
Type string `json:"type"`
Message *ServerMessage `json:"message,omitempty"`
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"`
@ -56,12 +59,12 @@ func (m *AsyncMessage) String() string {
type AsyncRoomMessage struct {
Type string `json:"type"`
SessionId string `json:"sessionid,omitempty"`
ClientType string `json:"clienttype,omitempty"`
SessionId api.PublicSessionId `json:"sessionid,omitempty"`
ClientType api.ClientType `json:"clienttype,omitempty"`
}
type SendOfferMessage struct {
MessageId string `json:"messageid,omitempty"`
SessionId string `json:"sessionid"`
Data *MessageClientMessageData `json:"data"`
MessageId string `json:"messageid,omitempty"`
SessionId api.PublicSessionId `json:"sessionid"`
Data *api.MessageClientMessageData `json:"data"`
}

View file

@ -1,12 +1,14 @@
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package signaling
package events
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/v2/api"
talk "github.com/strukturag/nextcloud-spreed-signaling/v2/talk"
)
// suppress unused package warning
@ -17,7 +19,7 @@ var (
_ easyjson.Marshaler
)
func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexer.Lexer, out *SendOfferMessage) {
func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(in *jlexer.Lexer, out *SendOfferMessage) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -30,25 +32,32 @@ 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 = string(in.String())
if in.IsNull() {
in.Skip()
} else {
out.SessionId = api.PublicSessionId(in.String())
}
case "data":
if in.IsNull() {
in.Skip()
out.Data = nil
} else {
if out.Data == nil {
out.Data = new(MessageClientMessageData)
out.Data = new(api.MessageClientMessageData)
}
if in.IsNull() {
in.Skip()
} else {
(*out.Data).UnmarshalEasyJSON(in)
}
(*out.Data).UnmarshalEasyJSON(in)
}
default:
in.SkipRecursive()
@ -60,7 +69,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(in *jlexe
in.Consumed()
}
}
func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwriter.Writer, in SendOfferMessage) {
func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(out *jwriter.Writer, in SendOfferMessage) {
out.RawByte('{')
first := true
_ = first
@ -95,27 +104,27 @@ func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(out *jwri
// MarshalJSON supports json.Marshaler interface
func (v SendOfferMessage) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(&w, v)
easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v SendOfferMessage) MarshalEasyJSON(w *jwriter.Writer) {
easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling(w, v)
easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *SendOfferMessage) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(&r, v)
easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *SendOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling(l, v)
easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents(l, v)
}
func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlexer.Lexer, out *AsyncRoomMessage) {
func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(in *jlexer.Lexer, out *AsyncRoomMessage) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -128,18 +137,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 = string(in.String())
if in.IsNull() {
in.Skip()
} else {
out.SessionId = api.PublicSessionId(in.String())
}
case "clienttype":
out.ClientType = string(in.String())
if in.IsNull() {
in.Skip()
} else {
out.ClientType = api.ClientType(in.String())
}
default:
in.SkipRecursive()
}
@ -150,7 +166,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(in *jlex
in.Consumed()
}
}
func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwriter.Writer, in AsyncRoomMessage) {
func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(out *jwriter.Writer, in AsyncRoomMessage) {
out.RawByte('{')
first := true
_ = first
@ -175,27 +191,27 @@ func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(out *jwr
// MarshalJSON supports json.Marshaler interface
func (v AsyncRoomMessage) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(&w, v)
easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v AsyncRoomMessage) MarshalEasyJSON(w *jwriter.Writer) {
easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling1(w, v)
easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *AsyncRoomMessage) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(&r, v)
easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *AsyncRoomMessage) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling1(l, v)
easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents1(l, v)
}
func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlexer.Lexer, out *AsyncMessage) {
func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(in *jlexer.Lexer, out *AsyncMessage) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -208,27 +224,34 @@ 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()
out.Message = nil
} else {
if out.Message == nil {
out.Message = new(ServerMessage)
out.Message = new(api.ServerMessage)
}
if in.IsNull() {
in.Skip()
} else {
(*out.Message).UnmarshalEasyJSON(in)
}
(*out.Message).UnmarshalEasyJSON(in)
}
case "room":
if in.IsNull() {
@ -236,9 +259,13 @@ 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()
} else {
(*out.Room).UnmarshalEasyJSON(in)
}
(*out.Room).UnmarshalEasyJSON(in)
}
case "permissions":
if in.IsNull() {
@ -248,16 +275,20 @@ 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
v1 = Permission(in.String())
var v1 api.Permission
if in.IsNull() {
in.Skip()
} else {
v1 = api.Permission(in.String())
}
out.Permissions = append(out.Permissions, v1)
in.WantComma()
}
@ -271,7 +302,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 +316,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()
}
@ -295,7 +338,7 @@ func easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(in *jlex
in.Consumed()
}
}
func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwriter.Writer, in AsyncMessage) {
func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(out *jwriter.Writer, in AsyncMessage) {
out.RawByte('{')
first := true
_ = first
@ -354,23 +397,23 @@ func easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(out *jwr
// MarshalJSON supports json.Marshaler interface
func (v AsyncMessage) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(&w, v)
easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v AsyncMessage) MarshalEasyJSON(w *jwriter.Writer) {
easyjson9289e183EncodeGithubComStrukturagNextcloudSpreedSignaling2(w, v)
easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *AsyncMessage) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(&r, v)
easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *AsyncMessage) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson9289e183DecodeGithubComStrukturagNextcloudSpreedSignaling2(l, v)
easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingV2AsyncEvents2(l, v)
}

View file

@ -0,0 +1,76 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2022 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package events
import (
"context"
"errors"
"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 (
ErrAlreadyRegistered = errors.New("already registered") // +checklocksignore: Global readonly variable.
)
const (
DefaultAsyncChannelSize = 64
)
type AsyncChannel chan *nats.Msg
type AsyncEventListener interface {
AsyncChannel() AsyncChannel
}
type AsyncEvents interface {
Close(ctx context.Context) error
RegisterBackendRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error
UnregisterBackendRoomListener(roomId string, backend *talk.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 *talk.Backend, listener AsyncEventListener) error
UnregisterUserListener(userId string, backend *talk.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 *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) {
client, err := nats.NewClient(ctx, url)
if err != nil {
return nil, err
}
return NewAsyncEventsNats(log.LoggerFromContext(ctx), client)
}

View file

@ -0,0 +1,292 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2022 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package events
import (
"context"
"errors"
"sync"
"time"
"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 {
if backend == nil || backend.IsCompat() {
return nats.GetEncodedSubject("backend.room", roomId)
}
return nats.GetEncodedSubject("backend.room", roomId+"|"+backend.Id())
}
func GetSubjectForRoomId(roomId string, backend *talk.Backend) string {
if backend == nil || backend.IsCompat() {
return nats.GetEncodedSubject("room", roomId)
}
return nats.GetEncodedSubject("room", roomId+"|"+backend.Id())
}
func GetSubjectForUserId(userId string, backend *talk.Backend) string {
if backend == nil || backend.IsCompat() {
return nats.GetEncodedSubject("user", userId)
}
return nats.GetEncodedSubject("user", userId+"|"+backend.Id())
}
func GetSubjectForSessionId(sessionId api.PublicSessionId, backend *talk.Backend) string {
return string("session." + sessionId)
}
type asyncEventsNatsSubscriptions map[string]map[AsyncEventListener]nats.Subscription
type asyncEventsNats struct {
mu sync.Mutex
client nats.Client
logger log.Logger // +checklocksignore
// +checklocks:mu
backendRoomSubscriptions asyncEventsNatsSubscriptions
// +checklocks:mu
roomSubscriptions asyncEventsNatsSubscriptions
// +checklocks:mu
userSubscriptions asyncEventsNatsSubscriptions
// +checklocks:mu
sessionSubscriptions asyncEventsNatsSubscriptions
}
func NewAsyncEventsNats(logger log.Logger, client nats.Client) (AsyncEvents, error) {
events := &asyncEventsNats{
client: client,
logger: logger,
backendRoomSubscriptions: make(asyncEventsNatsSubscriptions),
roomSubscriptions: make(asyncEventsNatsSubscriptions),
userSubscriptions: make(asyncEventsNatsSubscriptions),
sessionSubscriptions: make(asyncEventsNatsSubscriptions),
}
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
switch n := e.client.(type) {
case *nats.NativeClient:
result = &talk.BackendServerInfoNats{
Urls: n.URLs(),
}
if n.IsConnected() {
result.Connected = true
result.ServerUrl = n.ConnectedUrl()
result.ServerID = n.ConnectedServerId()
result.ServerVersion = n.ConnectedServerVersion()
result.ClusterName = n.ConnectedClusterName()
}
case *nats.LoopbackClient:
result = &talk.BackendServerInfoNats{
Urls: []string{nats.LoopbackUrl},
Connected: true,
ServerUrl: nats.LoopbackUrl,
}
}
return result
}
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 closeSubscriptions(e.logger, &wg, e.backendRoomSubscriptions)
wg.Add(1)
go closeSubscriptions(e.logger, &wg, e.roomSubscriptions)
wg.Add(1)
go closeSubscriptions(e.logger, &wg, e.userSubscriptions)
wg.Add(1)
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(asyncEventsNatsSubscriptions)
e.roomSubscriptions = make(asyncEventsNatsSubscriptions)
e.userSubscriptions = make(asyncEventsNatsSubscriptions)
e.sessionSubscriptions = make(asyncEventsNatsSubscriptions)
wg.Wait()
return e.client.Close(ctx)
}
// +checklocks:e.mu
func (e *asyncEventsNats) registerListener(key string, subscriptions asyncEventsNatsSubscriptions, listener AsyncEventListener) error {
subs, found := subscriptions[key]
if !found {
subs = make(map[AsyncEventListener]nats.Subscription)
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 *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForBackendRoomId(roomId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.registerListener(key, e.backendRoomSubscriptions, listener)
}
func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForBackendRoomId(roomId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.unregisterListener(key, e.backendRoomSubscriptions, listener)
}
func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForRoomId(roomId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.registerListener(key, e.roomSubscriptions, listener)
}
func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForRoomId(roomId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.unregisterListener(key, e.roomSubscriptions, listener)
}
func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForUserId(roomId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.registerListener(key, e.userSubscriptions, listener)
}
func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForUserId(roomId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.unregisterListener(key, e.userSubscriptions, listener)
}
func (e *asyncEventsNats) RegisterSessionListener(sessionId api.PublicSessionId, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForSessionId(sessionId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.registerListener(key, e.sessionSubscriptions, listener)
}
func (e *asyncEventsNats) UnregisterSessionListener(sessionId api.PublicSessionId, backend *talk.Backend, listener AsyncEventListener) error {
key := GetSubjectForSessionId(sessionId, backend)
e.mu.Lock()
defer e.mu.Unlock()
return e.unregisterListener(key, e.sessionSubscriptions, listener)
}
func (e *asyncEventsNats) publish(subject string, message *AsyncMessage) error {
message.SendTime = time.Now().Truncate(time.Microsecond)
return e.client.Publish(subject, message)
}
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 *talk.Backend, message *AsyncMessage) error {
subject := GetSubjectForRoomId(roomId, backend)
return e.publish(subject, message)
}
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 *talk.Backend, message *AsyncMessage) error {
subject := GetSubjectForSessionId(sessionId, backend)
return e.publish(subject, message)
}

View file

@ -0,0 +1,38 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package events
import (
"testing"
"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) {
backend := talk.NewCompatBackend(nil)
sid := api.PublicSessionId(internal.RandomString(256))
for b.Loop() {
GetSubjectForSessionId(sid, backend)
}
}

View file

@ -0,0 +1,170 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package events
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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 {
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 := logtest.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, _ := natstest.StartLocalServer(t)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
events, err := NewAsyncEvents(ctx, server.ClientURL())
require.NoError(t, err)
testAsyncEvents(t, events)
}

114
async/events/test/events.go Normal file
View file

@ -0,0 +1,114 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package test
import (
"context"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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 (
testTimeout = 10 * time.Second
EventBackendsForTest = []string{
"loopback",
"nats",
}
)
func GetAsyncEventsForTest(t *testing.T) events.AsyncEvents {
var events events.AsyncEvents
if strings.HasSuffix(t.Name(), "/nats") {
events = getRealAsyncEventsForTest(t)
} else {
events = getLoopbackAsyncEventsForTest(t)
}
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
assert.NoError(t, events.Close(ctx))
})
return events
}
func getRealAsyncEventsForTest(t *testing.T) events.AsyncEvents {
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
server, _ := natstest.StartLocalServer(t)
events, err := events.NewAsyncEvents(ctx, server.ClientURL())
require.NoError(t, err)
return events
}
type natsEvents interface {
GetNatsClient() nats.Client
}
func getLoopbackAsyncEventsForTest(t *testing.T) events.AsyncEvents {
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
events, err := events.NewAsyncEvents(ctx, nats.LoopbackUrl)
require.NoError(t, err)
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
e, ok := (events.(natsEvents))
if !ok {
// Only can wait for NATS events.
return
}
natstest.WaitForSubscriptionsEmpty(ctx, t, e.GetNatsClient())
})
return events
}
func WaitForAsyncEventsFlushed(ctx context.Context, t *testing.T, events events.AsyncEvents) {
t.Helper()
e, ok := (events.(natsEvents))
if !ok {
// Only can wait for NATS events.
return
}
client, ok := e.GetNatsClient().(*nats.NativeClient)
if !ok {
// The loopback NATS clients is executing all events synchronously.
return
}
assert.NoError(t, client.FlushWithContext(ctx))
}

View file

@ -0,0 +1,94 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2026 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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 {
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)
})
}
}

View file

@ -19,60 +19,79 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"context"
"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
waiters map[string]*Waiter
// +checklocks:Mutex
waiters map[string]*rootWaiter
// +checklocks:Mutex
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()
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)
n.waiterMap[key][w] = true
releaseFunc := func() {
n.release(w)
}
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
return w, releaseFunc
}
func (n *Notifier) Reset() {
@ -80,13 +99,13 @@ func (n *Notifier) Reset() {
defer n.Unlock()
for _, w := range n.waiters {
w.sw.cancel()
w.notify()
}
n.waiters = nil
n.waiterMap = nil
}
func (n *Notifier) Release(w *Waiter) {
func (n *Notifier) release(w *Waiter) {
n.Lock()
defer n.Unlock()
@ -94,8 +113,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()
}
}
}
}
@ -106,7 +127,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)
}

View file

@ -19,49 +19,76 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"context"
"sync"
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
)
func TestNotifierNoWaiter(t *testing.T) {
t.Parallel()
var notifier Notifier
// Notifications can be sent even if no waiter exists.
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, release := notifier.NewWaiter("foo")
defer release()
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()
var notifier Notifier
waiter, release := notifier.NewWaiter("foo")
defer release()
var wg sync.WaitGroup
wg.Add(1)
waiter := notifier.NewWaiter("foo")
defer notifier.Release(waiter)
go func() {
defer wg.Done()
wg.Go(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
assert.NoError(t, waiter.Wait(ctx))
}()
})
notifier.Notify("foo")
wg.Wait()
}
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.
@ -69,41 +96,41 @@ func TestNotifierMultiNotify(t *testing.T) {
}
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()))
}
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()))
}
func TestNotifierResetWillNotify(t *testing.T) {
t.Parallel()
var notifier Notifier
waiter, release := notifier.NewWaiter("foo")
defer release()
var wg sync.WaitGroup
wg.Add(1)
waiter := notifier.NewWaiter("foo")
defer notifier.Release(waiter)
go func() {
defer wg.Done()
wg.Go(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
assert.NoError(t, waiter.Wait(ctx))
}()
})
notifier.Reset()
wg.Wait()
@ -111,31 +138,24 @@ func TestNotifierResetWillNotify(t *testing.T) {
func TestNotifierDuplicate(t *testing.T) {
t.Parallel()
var notifier Notifier
var wgStart sync.WaitGroup
var wgEnd sync.WaitGroup
synctest.Test(t, func(t *testing.T) {
var notifier Notifier
var done sync.WaitGroup
for i := 0; i < 2; i++ {
wgStart.Add(1)
wgEnd.Add(1)
for range 2 {
done.Go(func() {
waiter, release := notifier.NewWaiter("foo")
defer release()
go func() {
defer wgEnd.Done()
waiter := notifier.NewWaiter("foo")
defer notifier.Release(waiter)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
assert.NoError(t, waiter.Wait(ctx))
})
}
// Goroutine has created the waiter and is ready.
wgStart.Done()
synctest.Wait()
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("foo")
wgEnd.Wait()
notifier.Notify("foo")
done.Wait()
})
}

View file

@ -19,16 +19,18 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"context"
"errors"
"log"
"net"
"strconv"
"sync"
"time"
"github.com/strukturag/nextcloud-spreed-signaling/v2/internal"
"github.com/strukturag/nextcloud-spreed-signaling/v2/log"
)
const (
@ -90,25 +92,33 @@ type throttleEntry struct {
ts time.Time
}
type memoryThrottler struct {
getNow func() time.Time
doDelay func(context.Context, time.Duration)
type GetTimeFunc func() time.Time
type ThrottleDelayFunc func(context.Context, time.Duration)
mu sync.RWMutex
type memoryThrottler struct {
getNow GetTimeFunc
doDelay ThrottleDelayFunc
mu sync.RWMutex
// +checklocks:mu
clients map[string]map[string][]throttleEntry
closer *Closer
closer *internal.Closer
}
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: NewCloser(),
closer: internal.NewCloser(),
}
result.doDelay = result.delay
go result.housekeeping()
return result, nil
}
@ -257,10 +267,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
}
@ -279,7 +286,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 := log.LoggerFromContext(ctx)
logger.Printf("Detected bruteforce attempt on \"%s\" from %s", action, client)
statsThrottleBruteforceTotal.WithLabelValues(action).Inc()
return doThrottle, ErrBruteforceDetected
}
@ -303,12 +311,13 @@ 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 := 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)
}
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()

View file

@ -19,10 +19,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package async
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/strukturag/nextcloud-spreed-signaling/v2/metrics"
)
var (
@ -47,5 +49,5 @@ var (
)
func RegisterThrottleStats() {
registerAll(throttleStats...)
metrics.RegisterAll(throttleStats...)
}

303
async/throttle_test.go Normal file
View file

@ -0,0 +1,303 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2024 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package async
import (
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/strukturag/nextcloud-spreed-signaling/v2/log"
logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test"
)
func newMemoryThrottlerForTest(t *testing.T) Throttler {
t.Helper()
result, err := NewMemoryThrottler()
require.NoError(t, err)
t.Cleanup(func() {
result.Close()
})
return result
}
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) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
assert := assert.New(t)
th := newMemoryThrottlerForTest(t)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
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.1", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle2(ctx)
}, 200*time.Millisecond)
throttle3, err := th.CheckBruteforce(ctx, "192.168.0.2", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle3(ctx)
}, 100*time.Millisecond)
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) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
assert := assert.New(t)
th := newMemoryThrottlerForTest(t)
logger := logtest.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")
assert.NoError(err)
expectDelay(t, func() {
throttle1(ctx)
}, 100*time.Millisecond)
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.
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.
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) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
assert := assert.New(t)
th := newMemoryThrottlerForTest(t)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
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
}
}
_, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1")
assert.ErrorIs(err, ErrBruteforceDetected)
})
}
func TestThrottler_Cleanup(t *testing.T) {
t.Parallel()
synctest.Test(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)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
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)
expectDelay(t, func() {
throttle2(ctx)
}, 100*time.Millisecond)
time.Sleep(time.Hour)
throttle3, err := th.CheckBruteforce(ctx, "192.168.0.1", "action2")
assert.NoError(err)
expectDelay(t, func() {
throttle3(ctx)
}, 100*time.Millisecond)
throttle4, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle4(ctx)
}, 200*time.Millisecond)
cleanupNow := time.Now().Add(-time.Hour).Add(maxBruteforceAge).Add(time.Second)
th.cleanup(cleanupNow)
assert.Len(th.getEntries("192.168.0.1", "action1"), 1)
assert.Len(th.getEntries("192.168.0.1", "action2"), 1)
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) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
assert := assert.New(t)
th := newMemoryThrottlerForTest(t)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle1(ctx)
}, 100*time.Millisecond)
time.Sleep(time.Minute)
throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle2(ctx)
}, 200*time.Millisecond)
time.Sleep(maxBruteforceAge - time.Minute + time.Second)
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) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
assert := assert.New(t)
th := newMemoryThrottlerForTest(t)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
throttle1, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle1(ctx)
}, 100*time.Millisecond)
time.Sleep(time.Millisecond)
throttle2, err := th.CheckBruteforce(ctx, "192.168.0.1", "action1")
assert.NoError(err)
expectDelay(t, func() {
throttle2(ctx)
}, 200*time.Millisecond)
time.Sleep(maxBruteforceAge + time.Second)
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) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
assert := assert.New(t)
th := newMemoryThrottlerForTest(t)
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
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
}
}
})
}

View file

@ -1,210 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2022 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package signaling
import "sync"
type AsyncBackendRoomEventListener interface {
ProcessBackendRoomRequest(message *AsyncMessage)
}
type AsyncRoomEventListener interface {
ProcessAsyncRoomMessage(message *AsyncMessage)
}
type AsyncUserEventListener interface {
ProcessAsyncUserMessage(message *AsyncMessage)
}
type AsyncSessionEventListener interface {
ProcessAsyncSessionMessage(message *AsyncMessage)
}
type AsyncEvents interface {
Close()
RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) error
UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener)
RegisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) error
UnregisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener)
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)
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
}
func NewAsyncEvents(url string) (AsyncEvents, error) {
client, err := NewNatsClient(url)
if err != nil {
return nil, err
}
return NewAsyncEventsNats(client)
}
type asyncBackendRoomSubscriber struct {
mu sync.Mutex
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
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
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
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
}

View file

@ -1,452 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2022 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"log"
"sync"
"time"
"github.com/nats-io/nats.go"
)
func GetSubjectForBackendRoomId(roomId string, backend *Backend) string {
if backend == nil || backend.IsCompat() {
return GetEncodedSubject("backend.room", roomId)
}
return GetEncodedSubject("backend.room", roomId+"|"+backend.Id())
}
func GetSubjectForRoomId(roomId string, backend *Backend) string {
if backend == nil || backend.IsCompat() {
return GetEncodedSubject("room", roomId)
}
return GetEncodedSubject("room", roomId+"|"+backend.Id())
}
func GetSubjectForUserId(userId string, backend *Backend) string {
if backend == nil || backend.IsCompat() {
return GetEncodedSubject("user", userId)
}
return GetEncodedSubject("user", userId+"|"+backend.Id())
}
func GetSubjectForSessionId(sessionId string, backend *Backend) string {
return "session." + sessionId
}
type asyncSubscriberNats struct {
key string
client NatsClient
receiver chan *nats.Msg
closeChan chan struct{}
subscription NatsSubscription
processMessage func(*nats.Msg)
}
func newAsyncSubscriberNats(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,
receiver: receiver,
closeChan: make(chan struct{}),
subscription: sub,
}
return result, nil
}
func (s *asyncSubscriberNats) run() {
defer func() {
if err := s.subscription.Unsubscribe(); err != nil {
log.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(key string, client NatsClient) (*asyncBackendRoomSubscriberNats, error) {
sub, err := newAsyncSubscriberNats(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 {
log.Printf("Could not decode NATS message %+v, %s", msg, err)
return
}
s.processBackendRoomRequest(&message)
}
type asyncRoomSubscriberNats struct {
asyncRoomSubscriber
*asyncSubscriberNats
}
func newAsyncRoomSubscriberNats(key string, client NatsClient) (*asyncRoomSubscriberNats, error) {
sub, err := newAsyncSubscriberNats(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 {
log.Printf("Could not decode nats message %+v, %s", msg, err)
return
}
s.processAsyncRoomMessage(&message)
}
type asyncUserSubscriberNats struct {
*asyncSubscriberNats
asyncUserSubscriber
}
func newAsyncUserSubscriberNats(key string, client NatsClient) (*asyncUserSubscriberNats, error) {
sub, err := newAsyncSubscriberNats(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 {
log.Printf("Could not decode nats message %+v, %s", msg, err)
return
}
s.processAsyncUserMessage(&message)
}
type asyncSessionSubscriberNats struct {
*asyncSubscriberNats
asyncSessionSubscriber
}
func newAsyncSessionSubscriberNats(key string, client NatsClient) (*asyncSessionSubscriberNats, error) {
sub, err := newAsyncSubscriberNats(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 {
log.Printf("Could not decode nats message %+v, %s", msg, err)
return
}
s.processAsyncSessionMessage(&message)
}
type asyncEventsNats struct {
mu sync.Mutex
client NatsClient
backendRoomSubscriptions map[string]*asyncBackendRoomSubscriberNats
roomSubscriptions map[string]*asyncRoomSubscriberNats
userSubscriptions map[string]*asyncUserSubscriberNats
sessionSubscriptions map[string]*asyncSessionSubscriberNats
}
func NewAsyncEventsNats(client NatsClient) (AsyncEvents, error) {
events := &asyncEventsNats{
client: client,
backendRoomSubscriptions: make(map[string]*asyncBackendRoomSubscriberNats),
roomSubscriptions: make(map[string]*asyncRoomSubscriberNats),
userSubscriptions: make(map[string]*asyncUserSubscriberNats),
sessionSubscriptions: make(map[string]*asyncSessionSubscriberNats),
}
return events, nil
}
func (e *asyncEventsNats) Close() {
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)
wg.Add(1)
go func(subscriptions map[string]*asyncRoomSubscriberNats) {
defer wg.Done()
for _, sub := range subscriptions {
sub.close()
}
}(e.roomSubscriptions)
wg.Add(1)
go func(subscriptions map[string]*asyncUserSubscriberNats) {
defer wg.Done()
for _, sub := range subscriptions {
sub.close()
}
}(e.userSubscriptions)
wg.Add(1)
go func(subscriptions map[string]*asyncSessionSubscriberNats) {
defer wg.Done()
for _, sub := range subscriptions {
sub.close()
}
}(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)
wg.Wait()
e.client.Close()
}
func (e *asyncEventsNats) RegisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) 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(key, e.client); err != nil {
return err
}
e.backendRoomSubscriptions[key] = sub
}
sub.addListener(listener)
return nil
}
func (e *asyncEventsNats) UnregisterBackendRoomListener(roomId string, backend *Backend, listener AsyncBackendRoomEventListener) {
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()
}
}
func (e *asyncEventsNats) RegisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) 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(key, e.client); err != nil {
return err
}
e.roomSubscriptions[key] = sub
}
sub.addListener(listener)
return nil
}
func (e *asyncEventsNats) UnregisterRoomListener(roomId string, backend *Backend, listener AsyncRoomEventListener) {
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()
}
}
func (e *asyncEventsNats) RegisterUserListener(roomId string, backend *Backend, listener AsyncUserEventListener) 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(key, e.client); err != nil {
return err
}
e.userSubscriptions[key] = sub
}
sub.addListener(listener)
return nil
}
func (e *asyncEventsNats) UnregisterUserListener(roomId string, backend *Backend, listener AsyncUserEventListener) {
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()
}
}
func (e *asyncEventsNats) RegisterSessionListener(sessionId string, backend *Backend, listener AsyncSessionEventListener) 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(key, e.client); err != nil {
return err
}
e.sessionSubscriptions[key] = sub
}
sub.addListener(listener)
return nil
}
func (e *asyncEventsNats) UnregisterSessionListener(sessionId string, backend *Backend, listener AsyncSessionEventListener) {
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()
}
}
func (e *asyncEventsNats) publish(subject string, message *AsyncMessage) error {
message.SendTime = time.Now()
return e.client.Publish(subject, message)
}
func (e *asyncEventsNats) PublishBackendRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error {
subject := GetSubjectForBackendRoomId(roomId, backend)
return e.publish(subject, message)
}
func (e *asyncEventsNats) PublishRoomMessage(roomId string, backend *Backend, message *AsyncMessage) error {
subject := GetSubjectForRoomId(roomId, backend)
return e.publish(subject, message)
}
func (e *asyncEventsNats) PublishUserMessage(userId string, backend *Backend, message *AsyncMessage) error {
subject := GetSubjectForUserId(userId, backend)
return e.publish(subject, message)
}
func (e *asyncEventsNats) PublishSessionMessage(sessionId string, backend *Backend, message *AsyncMessage) error {
subject := GetSubjectForSessionId(sessionId, backend)
return e.publish(subject, message)
}

View file

@ -1,75 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2022 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var (
eventBackendsForTest = []string{
"loopback",
"nats",
}
)
func getAsyncEventsForTest(t *testing.T) AsyncEvents {
var events AsyncEvents
if strings.HasSuffix(t.Name(), "/nats") {
events = getRealAsyncEventsForTest(t)
} else {
events = getLoopbackAsyncEventsForTest(t)
}
t.Cleanup(func() {
events.Close()
})
return events
}
func getRealAsyncEventsForTest(t *testing.T) AsyncEvents {
url := startLocalNatsServer(t)
events, err := NewAsyncEvents(url)
if err != nil {
require.NoError(t, err)
}
return events
}
func getLoopbackAsyncEventsForTest(t *testing.T) AsyncEvents {
events, err := NewAsyncEvents(NatsLoopbackUrl)
if err != nil {
require.NoError(t, err)
}
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
nats := (events.(*asyncEventsNats)).client
(nats).(*LoopbackNatsClient).waitForSubscriptionsEmpty(ctx, t)
})
return events
}

View file

@ -1,217 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2017 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"github.com/dlintw/goconf"
)
var (
ErrNotRedirecting = errors.New("not redirecting to different host")
ErrUnsupportedContentType = errors.New("unsupported_content_type")
ErrIncompleteResponse = errors.New("incomplete OCS response")
ErrThrottledResponse = errors.New("throttled OCS response")
)
type BackendClient struct {
hub *Hub
version string
backends *BackendConfiguration
pool *HttpClientPool
capabilities *Capabilities
}
func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string, etcdClient *EtcdClient) (*BackendClient, error) {
backends, err := NewBackendConfiguration(config, etcdClient)
if err != nil {
return nil, err
}
skipverify, _ := config.GetBool("backend", "skipverify")
if skipverify {
log.Println("WARNING: Backend verification is disabled!")
}
pool, err := NewHttpClientPool(maxConcurrentRequestsPerHost, skipverify)
if err != nil {
return nil, err
}
capabilities, err := NewCapabilities(version, pool)
if err != nil {
return nil, err
}
return &BackendClient{
version: version,
backends: backends,
pool: pool,
capabilities: capabilities,
}, nil
}
func (b *BackendClient) Close() {
b.backends.Close()
}
func (b *BackendClient) Reload(config *goconf.ConfigFile) {
b.backends.Reload(config)
}
func (b *BackendClient) GetCompatBackend() *Backend {
return b.backends.GetCompatBackend()
}
func (b *BackendClient) GetBackend(u *url.URL) *Backend {
return b.backends.GetBackend(u)
}
func (b *BackendClient) GetBackends() []*Backend {
return b.backends.GetBackends()
}
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 interface{}, response interface{}) error {
if u == nil {
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)
}
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)
requestUrl = &newUrl
} else {
requestUrl = u
}
c, pool, err := b.pool.Get(ctx, u)
if err != nil {
log.Printf("Could not get client for host %s: %s", u.Host, err)
return err
}
defer pool.Put(c)
data, err := json.Marshal(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))
if err != nil {
log.Printf("Could not create request to %s: %s", requestUrl, err)
return err
}
req.Header.Set("Content-Type", "application/json")
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, ", "))
}
// Add checksum so the backend can validate the request.
AddBackendChecksum(req, data, secret)
resp, err := c.Do(req)
if err != nil {
log.Printf("Could not send request %s to %s: %s", string(data), 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: %s (%s)", req.URL, ct, resp.Status)
return ErrUnsupportedContentType
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Could not read response body from %s: %s", req.URL, err)
return err
}
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:
// {
// "ocs": {
// "meta": { ... },
// "data": { ... }
// }
// }
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)
return err
} else if ocs.Ocs == nil || len(ocs.Ocs.Data) == 0 {
log.Printf("Incomplete OCS response %s from %s", string(body), req.URL)
return ErrIncompleteResponse
}
switch ocs.Ocs.Meta.StatusCode {
case http.StatusTooManyRequests:
log.Printf("Throttled OCS response %s from %s", string(body), req.URL)
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)
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)
return err
}
return nil
}

View file

@ -1,321 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2022 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"log"
"net/url"
"reflect"
"strings"
"github.com/dlintw/goconf"
)
type backendStorageStatic struct {
backendStorageCommon
// Deprecated
allowAll bool
commonSecret []byte
compatBackend *Backend
}
func NewBackendStorageStatic(config *goconf.ConfigFile) (BackendStorage, error) {
allowAll, _ := config.GetBool("backend", "allowall")
allowHttp, _ := config.GetBool("backend", "allowhttp")
commonSecret, _ := config.GetString("backend", "secret")
sessionLimit, err := config.GetInt("backend", "sessionlimit")
if err != nil || sessionLimit < 0 {
sessionLimit = 0
}
backends := make(map[string][]*Backend)
var compatBackend *Backend
numBackends := 0
if allowAll {
log.Println("WARNING: All backend hostnames are allowed, only use for development!")
compatBackend = &Backend{
id: "compat",
secret: []byte(commonSecret),
compat: true,
allowHttp: allowHttp,
sessionLimit: uint64(sessionLimit),
}
if sessionLimit > 0 {
log.Printf("Allow a maximum of %d sessions", sessionLimit)
}
updateBackendStats(compatBackend)
numBackends++
} else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
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)
updateBackendStats(be)
}
numBackends += len(configuredBackends)
}
} 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, ",") {
u = strings.TrimSpace(u)
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 len(allowMap) == 0 {
log.Println("WARNING: No backend hostnames are allowed, check your configuration!")
} else {
compatBackend = &Backend{
id: "compat",
secret: []byte(commonSecret),
compat: true,
allowHttp: allowHttp,
sessionLimit: uint64(sessionLimit),
}
hosts := make([]string, 0, len(allowMap))
for host := range allowMap {
hosts = append(hosts, host)
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.")
}
log.Printf("Allowed backend hostnames: %s", hosts)
if sessionLimit > 0 {
log.Printf("Allow a maximum of %d sessions", sessionLimit)
}
updateBackendStats(compatBackend)
numBackends++
}
}
if numBackends == 0 {
log.Printf("WARNING: No backends configured, client connections will not be possible.")
}
statsBackendsCurrent.Add(float64(numBackends))
return &backendStorageStatic{
backendStorageCommon: backendStorageCommon{
backends: backends,
},
allowAll: allowAll,
commonSecret: []byte(commonSecret),
compatBackend: compatBackend,
}, nil
}
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)
deleteBackendStats(backend)
}
statsBackendsCurrent.Sub(float64(len(oldBackends)))
}
delete(s.backends, host)
}
func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend) {
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
found = true
backends = append(backends[:index], backends[index+1:]...)
break
} else if newBackend.id == existingBackend.id {
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)
updateBackendStats(newBackend)
break
}
index++
}
if !found {
removed := s.backends[host][existingIndex]
log.Printf("Backend %s removed for %s", removed.id, removed.url)
s.backends[host] = append(s.backends[host][:existingIndex], s.backends[host][existingIndex+1:]...)
deleteBackendStats(removed)
statsBackendsCurrent.Dec()
}
}
s.backends[host] = append(s.backends[host], backends...)
for _, added := range backends {
log.Printf("Backend %s added for %s", added.id, added.url)
updateBackendStats(added)
}
statsBackendsCurrent.Add(float64(len(backends)))
}
func getConfiguredBackendIDs(backendIds string) (ids []string) {
seen := make(map[string]bool)
for _, id := range strings.Split(backendIds, ",") {
id = strings.TrimSpace(id)
if id == "" {
continue
}
if seen[id] {
continue
}
ids = append(ids, id)
seen[id] = true
}
return ids
}
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")
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, _ := config.GetString(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 == "" {
log.Printf("Backend %s is missing or incomplete, skipping", id)
continue
}
sessionLimit, err := config.GetInt(id, "sessionlimit")
if err != nil || sessionLimit < 0 {
sessionLimit = 0
}
if sessionLimit > 0 {
log.Printf("Backend %s allows a maximum of %d sessions", id, sessionLimit)
}
maxStreamBitrate, err := config.GetInt(id, "maxstreambitrate")
if err != nil || maxStreamBitrate < 0 {
maxStreamBitrate = 0
}
maxScreenBitrate, err := config.GetInt(id, "maxscreenbitrate")
if err != nil || maxScreenBitrate < 0 {
maxScreenBitrate = 0
}
hosts[parsed.Host] = append(hosts[parsed.Host], &Backend{
id: id,
url: u,
parsedUrl: parsed,
secret: []byte(secret),
allowHttp: parsed.Scheme == "http",
maxStreamBitrate: maxStreamBitrate,
maxScreenBitrate: maxScreenBitrate,
sessionLimit: uint64(sessionLimit),
})
}
return hosts
}
func (s *backendStorageStatic) Reload(config *goconf.ConfigFile) {
s.mu.Lock()
defer s.mu.Unlock()
if s.compatBackend != nil {
log.Println("Old-style configuration active, reload is not supported")
return
}
commonSecret, _ := config.GetString("backend", "secret")
if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
configuredHosts := getConfiguredHosts(backendIds, config, commonSecret)
// remove backends that are no longer configured
for hostname := range s.backends {
if _, ok := configuredHosts[hostname]; !ok {
s.RemoveBackendsForHost(hostname)
}
}
// rewrite backends adding newly configured ones and rewriting existing ones
for hostname, configuredBackends := range configuredHosts {
s.UpsertHost(hostname, configuredBackends)
}
}
}
func (s *backendStorageStatic) GetCompatBackend() *Backend {
s.mu.RLock()
defer s.mu.RUnlock()
return s.compatBackend
}
func (s *backendStorageStatic) GetBackend(u *url.URL) *Backend {
s.mu.RLock()
defer s.mu.RUnlock()
if _, found := s.backends[u.Host]; !found {
if s.allowAll {
return s.compatBackend
}
return nil
}
return s.getBackendLocked(u)
}

View file

@ -19,14 +19,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"log"
"io"
"net"
"strconv"
"strings"
@ -36,6 +36,12 @@ import (
"github.com/gorilla/websocket"
"github.com/mailru/easyjson"
"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 (
@ -52,136 +58,112 @@ 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 = NewError("invalid_format", "Invalid data format.")
InvalidFormat = api.NewError("invalid_format", "Invalid data format.")
bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
bufferPool pool.BufferPool
)
type WritableClientMessage interface {
json.Marshaler
CloseAfterSend(session Session) bool
CloseAfterSend(session api.RoomAware) bool
}
type HandlerClient interface {
Context() context.Context
RemoteAddr() string
Country() string
Country() geoip.Country
UserAgent() string
IsConnected() bool
IsAuthenticated() bool
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()
}
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) string
type GeoIpHandler interface {
OnLookupCountry(addr string) geoip.Country
}
type InRoomHandler interface {
IsInRoom(string) bool
}
type SessionCloserHandler interface {
CloseSession()
}
type Client struct {
ctx context.Context
logger log.Logger
ctx context.Context
// +checklocks:mu
conn *websocket.Conn
addr string
agent string
closed atomic.Int32
country *string
country *geoip.Country
logRTT bool
handlerMu sync.RWMutex
handler ClientHandler
// +checklocks:handlerMu
handler Handler
session atomic.Pointer[Session]
sessionId atomic.Pointer[string]
sessionId atomic.Pointer[api.PublicSessionId]
mu sync.Mutex
closer *Closer
closer *internal.Closer
closeOnce sync.Once
messagesDone chan 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 = NewCloser()
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
@ -195,40 +177,19 @@ 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 string) {
func (c *Client) SetSessionId(sessionId api.PublicSessionId) {
c.sessionId.Store(&sessionId)
}
func (c *Client) GetSessionId() string {
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
@ -242,13 +203,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
if handler, ok := c.getHandler().(ClientGeoIpHandler); ok {
country = handler.OnLookupCountry(c)
var country geoip.Country
if handler, ok := c.getHandler().(GeoIpHandler); ok {
country = handler.OnLookupCountry(c.addr)
} else {
country = unknownCountry
country = geoip.UnknownCountry
}
c.country = &country
}
@ -256,6 +217,14 @@ func (c *Client) Country() string {
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
@ -271,7 +240,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,30 +249,29 @@ 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
c.getHandler().OnClosed(c)
c.SetSession(nil)
c.getHandler().OnClosed()
}
}
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 {
@ -310,7 +279,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
}
@ -334,7 +303,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
}
@ -345,17 +314,19 @@ 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 {
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)
}
}
c.getHandler().OnRTTReceived(c, rtt)
statsClientRTT.Observe(float64(rtt.Milliseconds()))
c.getHandler().OnRTTReceived(rtt)
}
return nil
})
@ -372,9 +343,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
@ -382,22 +353,20 @@ 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
}
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)
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
}
@ -408,6 +377,8 @@ func (c *Client) ReadPump() {
break
}
statsClientBytesTotal.WithLabelValues("incoming").Add(float64(decodeBuffer.Len()))
statsClientMessagesTotal.WithLabelValues("incoming").Inc()
c.messageChan <- decodeBuffer
}
}
@ -419,7 +390,7 @@ func (c *Client) processMessages() {
break
}
c.getHandler().OnMessageReceived(c, buffer.Bytes())
c.getHandler().OnMessageReceived(buffer.Bytes())
bufferPool.Put(buffer)
}
@ -427,16 +398,34 @@ 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
}
// +checklocks:c.mu
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 := (interface{}(message)).(easyjson.Marshaler); ok {
_, err = easyjson.MarshalToWriter(m, writer)
if m, ok := (any(message)).(easyjson.Marshaler); ok {
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 {
@ -449,49 +438,25 @@ 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
}
statsClientBytesTotal.WithLabelValues("outgoing").Add(float64(written))
statsClientMessagesTotal.WithLabelValues("outgoing").Inc()
return true
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)
}
}
return false
}
func (c *Client) writeError(e error) bool { // nolint
message := &ServerMessage{
Type: "error",
Error: 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 != "" {
log.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
@ -507,19 +472,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
@ -537,13 +502,14 @@ 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
}
statsClientBytesTotal.WithLabelValues("outgoing").Add(float64(len(msg)))
return true
}

339
client/client_test.go Normal file
View file

@ -0,0 +1,339 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
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/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) {
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 := logtest.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.Close()
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.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{
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)
closed := make(chan struct{})
context.AfterFunc(client.Context(), func() {
close(closed)
})
go client.WritePump()
client.ReadPump()
<-closed
}
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) Write(v []byte) error {
return c.conn.WriteMessage(websocket.BinaryMessage, 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.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) &&
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())
}
}

93
client/ip.go Normal file
View file

@ -0,0 +1,93 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2026 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package client
import (
"net"
"net/http"
"slices"
"strings"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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
}

278
client/ip_test.go Normal file
View file

@ -0,0 +1,278 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2026 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package client
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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)
}
}

View file

@ -0,0 +1,60 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package client
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/strukturag/nextcloud-spreed-signaling/v2/metrics"
)
var (
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),
})
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{
statsClientRTT,
statsClientBytesTotal,
statsClientMessagesTotal,
}
)
func RegisterClientStats() {
metrics.RegisterAll(clientStats...)
}

File diff suppressed because it is too large Load diff

View file

@ -1,267 +0,0 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2019 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
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 {
test := test
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)
require := require.New(t)
assert := assert.New(t)
hub, _, _, server := CreateHubForTest(t)
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
mcu, err := NewTestMCU()
require.NoError(err)
require.NoError(mcu.Start(ctx))
defer mcu.Stop()
hub.SetMcu(mcu)
client := NewTestClient(t, server, hub)
defer client.CloseWithBye()
require.NoError(client.SendHello(testDefaultUserId))
hello, err := client.RunUntilHello(ctx)
require.NoError(err)
// Join room by id.
roomId := "test-room"
roomMsg, err := client.JoinRoom(ctx, roomId)
require.NoError(err)
require.Equal(roomId, roomMsg.Room.RoomId)
// We will receive a "joined" event.
assert.NoError(client.RunUntilJoined(ctx, hello.Hello))
// Client may not send an offer with audio and video.
bitrate := 10000
require.NoError(client.SendMessage(MessageClientMessageRecipient{
Type: "session",
SessionId: hello.Hello.SessionId,
}, MessageClientMessageData{
Type: "offer",
Sid: "54321",
RoomType: "video",
Bitrate: bitrate,
Payload: map[string]interface{}{
"sdp": MockSdpOfferAudioAndVideo,
},
}))
require.NoError(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo))
pub := mcu.GetPublisher(hello.Hello.SessionId)
require.NotNil(pub)
assert.Equal(bitrate, pub.settings.Bitrate)
}
func TestBandwidth_Backend(t *testing.T) {
t.Parallel()
CatchLogForTest(t)
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, err := NewTestMCU()
require.NoError(t, err)
require.NoError(t, mcu.Start(ctx))
defer mcu.Stop()
hub.SetMcu(mcu)
streamTypes := []StreamType{
StreamTypeVideo,
StreamTypeScreen,
}
for _, streamType := range streamTypes {
t.Run(string(streamType), func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
client := NewTestClient(t, server, hub)
defer client.CloseWithBye()
params := TestBackendClientAuthParams{
UserId: testDefaultUserId,
}
require.NoError(client.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params))
hello, err := client.RunUntilHello(ctx)
require.NoError(err)
// Join room by id.
roomId := "test-room"
roomMsg, err := client.JoinRoom(ctx, roomId)
require.NoError(err)
require.Equal(roomId, roomMsg.Room.RoomId)
// We will receive a "joined" event.
require.NoError(client.RunUntilJoined(ctx, hello.Hello))
// Client may not send an offer with audio and video.
bitrate := 10000
require.NoError(client.SendMessage(MessageClientMessageRecipient{
Type: "session",
SessionId: hello.Hello.SessionId,
}, MessageClientMessageData{
Type: "offer",
Sid: "54321",
RoomType: string(streamType),
Bitrate: bitrate,
Payload: map[string]interface{}{
"sdp": MockSdpOfferAudioAndVideo,
},
}))
require.NoError(client.RunUntilAnswer(ctx, MockSdpAnswerAudioAndVideo))
pub := mcu.GetPublisher(hello.Hello.SessionId)
require.NotNil(pub, "Could not find publisher")
var expectBitrate int
if streamType == StreamTypeVideo {
expectBitrate = backend.maxStreamBitrate
} else {
expectBitrate = backend.maxScreenBitrate
}
assert.Equal(expectBitrate, pub.settings.Bitrate)
})
}
}

View file

@ -35,7 +35,7 @@ import (
"os"
"os/signal"
"runtime"
"strings"
"runtime/pprof"
"sync"
"sync/atomic"
"time"
@ -44,14 +44,27 @@ 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"
"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 (
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")
configFlag = 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")
@ -75,47 +88,47 @@ const (
maxMessageSize = 64 * 1024
)
type Stats struct {
numRecvMessages atomic.Uint64
numSentMessages atomic.Uint64
resetRecvMessages uint64
resetSentMessages uint64
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 := uint64(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"`
}
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
cookie *signaling.SessionIdCodec
readyWg *sync.WaitGroup // +checklocksignore: Only written to from constructor.
conn *websocket.Conn
@ -124,13 +137,16 @@ type SignalingClient struct {
stopChan chan struct{}
lock sync.Mutex
privateSessionId string
publicSessionId string
userId string
lock sync.Mutex
// +checklocks:lock
privateSessionId api.PrivateSessionId
// +checklocks:lock
publicSessionId api.PublicSessionId
// +checklocks:lock
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
@ -138,7 +154,6 @@ func NewSignalingClient(cookie *signaling.SessionIdCodec, url string, stats *Sta
client := &SignalingClient{
readyWg: readyWg,
cookie: cookie,
conn: conn,
@ -146,15 +161,8 @@ func NewSignalingClient(cookie *signaling.SessionIdCodec, url string, stats *Sta
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
}
@ -169,6 +177,10 @@ func (c *SignalingClient) Close() {
c.lock.Lock()
c.publicSessionId = ""
c.privateSessionId = ""
c.writeInternal(&api.ClientMessage{
Type: "bye",
Bye: &api.ByeClientMessage{},
})
c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint
c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) // nolint
c.conn.Close()
@ -176,7 +188,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()
@ -191,9 +203,11 @@ 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":
// Ignore welcome message.
case "hello":
c.processHelloMessage(message)
case "message":
@ -209,37 +223,25 @@ func (c *SignalingClient) processMessage(message *signaling.ServerMessage) {
}
}
func (c *SignalingClient) privateToPublicSessionId(privateId string) string {
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) {
func (c *SignalingClient) processHelloMessage(message *api.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()
}
func (c *SignalingClient) PublicSessionId() string {
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 := json.Unmarshal(message.Message.Data, &msg); err != nil {
if err := msg.UnmarshalJSON(message.Message.Data); err != nil {
log.Println("Error in unmarshal", err)
return
}
@ -294,7 +296,9 @@ func (c *SignalingClient) readPump() {
break
}
var message signaling.ServerMessage
c.stats.numRecvBytes.Add(uint64(decodeBuffer.Len()))
var message api.ServerMessage
if err := message.UnmarshalJSON(decodeBuffer.Bytes()); err != nil {
log.Printf("Error: %v", err)
break
@ -304,13 +308,14 @@ 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
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 {
@ -326,6 +331,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:
@ -369,7 +377,7 @@ func (c *SignalingClient) writePump() {
}
func (c *SignalingClient) SendMessages(clients []*SignalingClient) {
sessionIds := make(map[*SignalingClient]string)
sessionIds := make(map[*SignalingClient]api.PublicSessionId)
for _, c := range clients {
sessionIds[c] = c.PublicSessionId()
}
@ -387,11 +395,11 @@ func (c *SignalingClient) SendMessages(clients []*SignalingClient) {
msgdata := MessagePayload{
Now: now,
}
data, _ := json.Marshal(msgdata)
msg := &signaling.ClientMessage{
data, _ := msgdata.MarshalJSON()
msg := &api.ClientMessage{
Type: "message",
Message: &signaling.MessageClientMessage{
Recipient: signaling.MessageClientMessageRecipient{
Message: &api.MessageClientMessage{
Recipient: api.MessageClientMessageRecipient{
Type: "session",
SessionId: sessionIds[recipient],
},
@ -405,35 +413,35 @@ 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)
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",
},
}
@ -445,9 +453,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),
@ -488,38 +496,48 @@ func main() {
flag.Parse()
log.SetFlags(0)
config, err := goconf.ReadConfigFile(*config)
if *showVersion {
fmt.Printf("nextcloud-spreed-signaling-client version %s/%s\n", version, runtime.Version())
os.Exit(0)
}
cfg, err := goconf.ReadConfigFile(*configFlag)
if err != nil {
log.Fatal("Could not read configuration: ", err)
}
secret, _ := config.GetString("backend", "secret")
secret, _ := config.GetStringOptionWithEnv(cfg, "backend", "secret")
backendSecret = []byte(secret)
hashKey, _ := config.GetString("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))
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()
}
blockKey, _ := config.GetString("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)
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal(err) // nolint (defer pprof.StopCPUProfile() will not run which is ok in case of errors)
}
cpus := runtime.NumCPU()
runtime.GOMAXPROCS(cpus)
log.Printf("Using a maximum of %d CPUs", cpus)
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)
@ -544,7 +562,7 @@ func main() {
urls := make([]url.URL, 0)
urlstrings := make([]string, 0)
for _, host := range strings.Split(*addr, ",") {
for host := range internal.SplitEntries(*addr, ",") {
u := url.URL{
Scheme: "ws",
Host: host,
@ -568,19 +586,19 @@ 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)
}
defer client.Close()
readyWg.Add(1)
request := &signaling.ClientMessage{
request := &api.ClientMessage{
Type: "hello",
Hello: &signaling.HelloClientMessage{
Version: signaling.HelloVersionV1,
Auth: &signaling.HelloClientMessageAuth{
Url: backendUrl + "/auth",
Hello: &api.HelloClientMessage{
Version: api.HelloVersionV1,
Auth: &api.HelloClientMessageAuth{
Url: backendUrl,
Params: json.RawMessage("{}"),
},
},
@ -596,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()

97
cmd/client/stats.go Normal file
View file

@ -0,0 +1,97 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package main
import (
"log"
"sync/atomic"
"time"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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)
}
}

80
cmd/client/stats_test.go Normal file
View file

@ -0,0 +1,80 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package main
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/strukturag/nextcloud-spreed-signaling/v2/api"
)
func TestStats(t *testing.T) {
t.Parallel()
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)
}
}

View file

@ -22,6 +22,7 @@
package main
import (
"context"
"flag"
"fmt"
"log"
@ -30,14 +31,15 @@ import (
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/dlintw/goconf"
"github.com/gorilla/mux"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"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 (
@ -65,49 +67,52 @@ 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()
config, err := goconf.ReadConfigFile(*configFlag)
logger := log.Default()
stopCtx = signalinglog.NewLoggerContext(stopCtx, logger)
logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid())
cfg, err := goconf.ReadConfigFile(*configFlag)
if err != nil {
log.Fatal("Could not read configuration: ", err)
logger.Fatal("Could not read configuration: ", err)
}
cpus := runtime.NumCPU()
runtime.GOMAXPROCS(cpus)
log.Printf("Using a maximum of %d CPUs", cpus)
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, cfg)
if err != nil {
log.Fatal(err)
logger.Fatal(err)
}
if err := proxy.Start(config); err != nil {
log.Fatal(err)
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
}
for _, address := range strings.Split(addr, " ") {
for address := range internal.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,
@ -117,7 +122,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)
}
@ -126,24 +131,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
}
}

View file

@ -27,25 +27,35 @@ import (
"time"
"github.com/gorilla/websocket"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"github.com/strukturag/nextcloud-spreed-signaling/v2/api"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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()
}

View file

@ -27,7 +27,8 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"log"
"math/rand/v2"
"net"
"net/http"
"net/url"
"strconv"
@ -38,12 +39,15 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/gorilla/websocket"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"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 (
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
@ -56,41 +60,56 @@ const (
)
var (
ErrNotConnected = errors.New("not connected")
ErrNotConnected = errors.New("not connected") // +checklocksignore: Global readonly variable.
)
type RemoteConnection struct {
logger log.Logger
mu sync.Mutex
p *ProxyServer
url *url.URL
conn *websocket.Conn
closer *signaling.Closer
closed atomic.Bool
// +checklocks:mu
conn *websocket.Conn
closeCtx context.Context
closeFunc context.CancelFunc // +checklocksignore: Only written to from constructor.
tokenId string
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 string
// +checklocks:mu
sessionId api.PublicSessionId
// +checklocks:mu
helloReceived bool
pendingMessages []*signaling.ProxyClientMessage
messageCallbacks map[string]chan *signaling.ProxyServerMessage
// +checklocks:mu
pendingMessages []*proxy.ClientMessage
// +checklocks:mu
messageCallbacks map[string]chan *proxy.ServerMessage
}
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
}
closeCtx, closeFunc := context.WithCancel(context.Background())
result := &RemoteConnection{
url: u,
closer: signaling.NewCloser(),
logger: p.logger,
p: p,
url: u,
closeCtx: closeCtx,
closeFunc: closeFunc,
tokenId: tokenId,
tokenKey: tokenKey,
@ -98,7 +117,7 @@ func NewRemoteConnection(proxyUrl string, tokenId string, tokenKey *rsa.PrivateK
reconnectTimer: time.NewTimer(0),
messageCallbacks: make(map[string]chan *signaling.ProxyServerMessage),
messageCallbacks: make(map[string]chan *proxy.ServerMessage),
}
result.reconnectInterval.Store(int64(initialReconnectInterval))
@ -111,16 +130,23 @@ func (c *RemoteConnection) String() string {
return c.url.String()
}
func (c *RemoteConnection) SessionId() api.PublicSessionId {
c.mu.Lock()
defer c.mu.Unlock()
return c.sessionId
}
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
}
if u.Scheme == "http" {
switch u.Scheme {
case "http":
u.Scheme = "ws"
} else if u.Scheme == "https" {
case "https":
u.Scheme = "wss"
}
@ -129,58 +155,81 @@ 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 {
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.closed.Store(false)
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()
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) scheduleReconnect() {
if err := c.sendClose(); err != nil && err != ErrNotConnected {
log.Printf("Could not send close message to %s: %s", c, err)
func (c *RemoteConnection) sendReconnectHello() bool {
c.mu.Lock()
defer c.mu.Unlock()
if err := c.sendHello(c.closeCtx); err != nil {
c.logger.Printf("Error sending hello request to proxy at %s: %s", c, err)
return false
}
c.close()
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.sendCloseLocked(); err != nil && err != ErrNotConnected {
c.logger.Printf("Could not send close message to %s: %s", c, err)
}
c.closeLocked()
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) {
interval = int64(maxReconnectInterval)
}
interval = min(interval*2, int64(maxReconnectInterval))
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{
msg := &proxy.ClientMessage{
Id: c.helloMsgId,
Type: "hello",
Hello: &signaling.HelloProxyClientMessage{
Hello: &proxy.HelloClientMessage{
Version: "1.0",
},
}
@ -195,13 +244,11 @@ func (c *RemoteConnection) sendHello() error {
msg.Hello.Token = tokenString
}
return c.SendMessage(msg)
return c.sendMessageLocked(ctx, msg)
}
func (c *RemoteConnection) sendClose() error {
c.mu.Lock()
defer c.mu.Unlock()
// +checklocks:c.mu
func (c *RemoteConnection) sendCloseLocked() error {
if c.conn == nil {
return ErrNotConnected
}
@ -214,24 +261,39 @@ 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 {
c.mu.Lock()
defer c.mu.Unlock()
c.reconnectTimer.Stop()
if c.conn == nil {
if c.closeCtx.Err() != nil {
// Already closed
return nil
}
c.sendClose()
err1 := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{})
err2 := c.conn.Close()
c.conn = nil
c.closeFunc()
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 {
return err1
}
@ -239,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,
@ -255,14 +317,15 @@ 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()
return c.sendMessageLocked(context.Background(), msg)
return c.sendMessageLocked(c.closeCtx, msg)
}
func (c *RemoteConnection) deferMessage(ctx context.Context, msg *signaling.ProxyClientMessage) {
// +checklocks:c.mu
func (c *RemoteConnection) deferMessage(ctx context.Context, msg *proxy.ClientMessage) {
c.pendingMessages = append(c.pendingMessages, msg)
if ctx.Done() != nil {
go func() {
@ -280,7 +343,8 @@ func (c *RemoteConnection) deferMessage(ctx context.Context, msg *signaling.Prox
}
}
func (c *RemoteConnection) sendMessageLocked(ctx context.Context, msg *signaling.ProxyClientMessage) error {
// +checklocks:c.mu
func (c *RemoteConnection) sendMessageLocked(ctx context.Context, msg *proxy.ClientMessage) error {
if c.conn == nil {
// Defer until connected.
c.deferMessage(ctx, msg)
@ -299,7 +363,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()
}
}()
@ -314,19 +378,21 @@ 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.closeCtx.Err() == nil {
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
var message proxy.ServerMessage
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
}
@ -353,7 +419,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
}
@ -374,44 +440,48 @@ func (c *RemoteConnection) writePump() {
c.reconnect()
case <-ticker.C:
c.sendPing()
case <-c.closer.C:
case <-c.closeCtx.Done():
return
}
}
}
func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) {
func (c *RemoteConnection) processHello(msg *proxy.ServerMessage) {
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.logger.Printf("Session %s could not be resumed on %s, registering new", c.sessionId, c)
c.sessionId = ""
if err := c.sendHello(); err != nil {
log.Printf("Could not send hello request to %s: %s", c, err)
c.scheduleReconnect()
if err := c.sendHello(c.closeCtx); err != nil {
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.scheduleReconnect()
c.logger.Printf("Hello connection to %s failed with %+v, reconnecting", c, msg.Error)
c.scheduleReconnectLocked()
case "hello":
resumed := c.sessionId == msg.Hello.SessionId
c.sessionId = msg.Hello.SessionId
country := ""
c.helloReceived = true
var country geoip.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)
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 = ""
}
}
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
@ -421,60 +491,94 @@ func (c *RemoteConnection) processHello(msg *signaling.ProxyServerMessage) {
continue
}
if err := c.sendMessageLocked(context.Background(), m); err != nil {
log.Printf("Could not send pending message %+v to %s: %s", m, c, err)
if err := c.sendMessageLocked(c.closeCtx, m); err != nil {
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.scheduleReconnect()
c.logger.Printf("Received unsupported hello response %+v from %s, reconnecting", msg, c)
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 *proxy.ServerMessage) 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 *proxy.ServerMessage) {
if c.handleCallback(msg) {
return
}
switch msg.Type {
case "event":
c.processEvent(msg)
case "bye":
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()
c.sessionId = ""
c.mu.Unlock()
}
c.scheduleReconnect()
default:
log.Printf("Received unsupported message %+v from %s", msg, c)
c.logger.Printf("Received unsupported message %+v from %s", msg, c)
}
}
func (c *RemoteConnection) processEvent(msg *signaling.ProxyServerMessage) {
func (c *RemoteConnection) processEvent(msg *proxy.ServerMessage) {
switch msg.Event.Type {
case "update-load":
// Ignore
case "publisher-closed":
c.logger.Printf("Remote publisher %s was closed on %s", msg.Event.ClientId, c)
c.p.RemotePublisherDeleted(api.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)
}
}
func (c *RemoteConnection) RequestMessage(ctx context.Context, msg *signaling.ProxyClientMessage) (*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()
defer c.mu.Unlock()
if err := c.sendMessageLocked(ctx, msg); err != nil {
msg.Id = ""
return "", nil, err
}
ch := make(chan *proxy.ServerMessage, 1)
c.messageCallbacks[msg.Id] = ch
return msg.Id, ch, nil
}
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
}
ch := make(chan *signaling.ProxyServerMessage, 1)
c.messageCallbacks[msg.Id] = ch
c.mu.Unlock()
defer func() {
c.mu.Lock()
delete(c.messageCallbacks, msg.Id)
defer c.mu.Unlock()
delete(c.messageCallbacks, id)
}()
select {
@ -488,3 +592,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, &proxy.ClientMessage{
Type: "bye",
})
}

View file

@ -0,0 +1,216 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package main
import (
"context"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/strukturag/nextcloud-spreed-signaling/v2/proxy"
)
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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())
}
func Test_ProxyRemoteConnectionCreatePublisher(t *testing.T) {
t.Parallel()
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, &proxy.ClientMessage{
Type: "command",
Command: &proxy.CommandClientMessage{
Type: "publish-remote",
ClientId: publisherId,
Hostname: hostname,
Port: port,
RtcpPort: rtcpPort,
},
})
assert.ErrorContains(err, UnknownClient.Error())
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -24,12 +24,14 @@ package main
import (
"context"
"fmt"
"log"
"sync"
"sync/atomic"
"time"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"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 (
@ -38,49 +40,59 @@ const (
)
type remotePublisherData struct {
id api.PublicSessionId
hostname string
port int
rtcpPort int
}
type ProxySession struct {
logger log.Logger
proxy *ProxyServer
id string
id api.PublicSessionId
sid uint64
lastUsed atomic.Int64
ctx context.Context
closeFunc context.CancelFunc
clientLock sync.Mutex
client *ProxyClient
pendingMessages []*signaling.ProxyServerMessage
clientLock sync.Mutex
// +checklocks:clientLock
client *ProxyClient
// +checklocks:clientLock
pendingMessages []*proxy.ServerMessage
publishersLock sync.Mutex
publishers map[string]signaling.McuPublisher
publisherIds map[signaling.McuPublisher]string
// +checklocks:publishersLock
publishers map[string]sfu.Publisher
// +checklocks:publishersLock
publisherIds map[sfu.Publisher]string
subscribersLock sync.Mutex
subscribers map[string]signaling.McuSubscriber
subscriberIds map[signaling.McuSubscriber]string
// +checklocks:subscribersLock
subscribers map[string]sfu.Subscriber
// +checklocks:subscribersLock
subscriberIds map[sfu.Subscriber]string
remotePublishersLock sync.Mutex
remotePublishers map[signaling.McuPublisher]map[string]*remotePublisherData
// +checklocks:remotePublishersLock
remotePublishers map[sfu.RemoteAwarePublisher]map[string]*remotePublisherData
}
func NewProxySession(proxy *ProxyServer, sid uint64, id string) *ProxySession {
func NewProxySession(proxy *ProxyServer, sid uint64, id api.PublicSessionId) *ProxySession {
ctx, closeFunc := context.WithCancel(context.Background())
result := &ProxySession{
logger: proxy.logger,
proxy: proxy,
id: id,
sid: sid,
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
@ -90,7 +102,7 @@ func (s *ProxySession) Context() context.Context {
return s.ctx
}
func (s *ProxySession) PublicId() string {
func (s *ProxySession) PublicId() api.PublicSessionId {
return s.id
}
@ -105,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() {
@ -120,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,
},
})
@ -139,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
}
@ -157,19 +169,19 @@ 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 sfu.Client, 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
}
msg := &signaling.ProxyServerMessage{
msg := &proxy.ServerMessage{
Type: "payload",
Payload: &signaling.PayloadProxyServerMessage{
Payload: &proxy.PayloadServerMessage{
Type: "offer",
ClientId: id,
Payload: map[string]interface{}{
Payload: api.StringMap{
"offer": offer,
},
},
@ -177,19 +189,19 @@ 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 sfu.Client, 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
}
msg := &signaling.ProxyServerMessage{
msg := &proxy.ServerMessage{
Type: "payload",
Payload: &signaling.PayloadProxyServerMessage{
Payload: &proxy.PayloadServerMessage{
Type: "candidate",
ClientId: id,
Payload: map[string]interface{}{
Payload: api.StringMap{
"candidate": candidate,
},
},
@ -197,7 +209,7 @@ func (s *ProxySession) OnIceCandidate(client signaling.McuClient, candidate inte
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
@ -210,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 == "" {
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
}
msg := &signaling.ProxyServerMessage{
msg := &proxy.ServerMessage{
Type: "event",
Event: &signaling.EventProxyServerMessage{
Event: &proxy.EventServerMessage{
Type: "ice-completed",
ClientId: id,
},
@ -227,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 == "" {
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
}
msg := &signaling.ProxyServerMessage{
msg := &proxy.ServerMessage{
Type: "event",
Event: &signaling.EventProxyServerMessage{
Event: &proxy.EventServerMessage{
Type: "subscriber-sid-updated",
ClientId: id,
Sid: subscriber.Sid(),
@ -245,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,
},
@ -262,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,
},
@ -279,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()
@ -287,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()
@ -298,12 +310,16 @@ func (s *ProxySession) DeletePublisher(publisher signaling.McuPublisher) string
delete(s.publishers, id)
delete(s.publisherIds, publisher)
delete(s.remotePublishers, publisher)
if rp, ok := publisher.(sfu.RemoteAwarePublisher); ok {
s.remotePublishersLock.Lock()
defer s.remotePublishersLock.Unlock()
delete(s.remotePublishers, rp)
}
go s.proxy.PublisherDeleted(publisher)
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()
@ -311,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()
@ -329,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()
@ -338,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)
}
@ -346,11 +362,11 @@ func (s *ProxySession) clearRemotePublishers() {
s.remotePublishersLock.Lock()
defer s.remotePublishersLock.Unlock()
go func(remotePublishers map[signaling.McuPublisher]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 {
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)
}
}
}
@ -359,10 +375,10 @@ 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) {
go func(subscribers map[string]sfu.Subscriber) {
for id, subscriber := range subscribers {
if s.proxy.DeleteClient(id, subscriber) {
statsSubscribersCurrent.WithLabelValues(string(subscriber.StreamType())).Dec()
@ -371,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)
}
@ -381,7 +397,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 sfu.RemoteAwarePublisher, hostname string, port int, rtcpPort int) bool {
s.remotePublishersLock.Lock()
defer s.remotePublishersLock.Unlock()
@ -389,7 +405,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[sfu.RemoteAwarePublisher]map[string]*remotePublisherData)
}
s.remotePublishers[publisher] = remote
}
@ -400,6 +416,7 @@ func (s *ProxySession) AddRemotePublisher(publisher signaling.McuPublisher, host
}
data := &remotePublisherData{
id: publisher.PublisherId(),
hostname: hostname,
port: port,
rtcpPort: rtcpPort,
@ -408,7 +425,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 sfu.RemoteAwarePublisher, hostname string, port int, rtcpPort int) {
s.remotePublishersLock.Lock()
defer s.remotePublishersLock.Unlock()
@ -427,9 +444,43 @@ func (s *ProxySession) RemoveRemotePublisher(publisher signaling.McuPublisher, h
}
}
func (s *ProxySession) OnPublisherDeleted(publisher signaling.McuPublisher) {
func (s *ProxySession) OnPublisherDeleted(publisher sfu.Publisher) {
if publisher, ok := publisher.(sfu.RemoteAwarePublisher); ok {
s.OnRemoteAwarePublisherDeleted(publisher)
}
}
func (s *ProxySession) OnRemoteAwarePublisherDeleted(publisher sfu.RemoteAwarePublisher) {
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 := &proxy.ServerMessage{
Type: "event",
Event: &proxy.EventServerMessage{
Type: "publisher-closed",
ClientId: string(entry.id),
},
}
s.sendMessage(msg)
}
}
}
func (s *ProxySession) OnRemotePublisherDeleted(publisherId api.PublicSessionId) {
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)
s.logger.Printf("Remote subscriber %s was closed, closing %s subscriber %s", publisherId, sub.StreamType(), sub.Id())
go sub.Close(context.Background())
}
}
}

View file

@ -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)
}

View file

@ -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/v2/api"
"github.com/strukturag/nextcloud-spreed-signaling/v2/proxy"
)
var (
@ -43,15 +45,16 @@ 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
sessionId string
sessionId api.PublicSessionId
}
func NewProxyTestClient(ctx context.Context, t *testing.T, url string) *ProxyTestClient {
@ -117,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 interface{}) error {
if msg, ok := data.(*signaling.ProxyClientMessage); ok {
func (c *ProxyTestClient) WriteJSON(data any) error {
if msg, ok := data.(*proxy.ClientMessage); ok {
if err := msg.CheckValid(); err != nil {
return err
}
@ -137,11 +140,11 @@ func (c *ProxyTestClient) WriteJSON(data interface{}) 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
}
@ -162,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
}
@ -188,8 +191,8 @@ func checkMessageType(message *signaling.ProxyServerMessage, expectedType string
return nil
}
func (c *ProxyTestClient) SendHello(key interface{}) error {
claims := &signaling.TokenClaims{
func (c *ProxyTestClient) SendHello(key any) error {
claims := &proxy.TokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now().Add(-maxTokenAge / 2)),
Issuer: TokenIdForTest,
@ -199,10 +202,10 @@ func (c *ProxyTestClient) SendHello(key interface{}) 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,
@ -211,7 +214,7 @@ func (c *ProxyTestClient) SendHello(key interface{}) 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
}
@ -225,7 +228,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 *proxy.ServerMessage, err error) {
if message, err = c.RunUntilMessage(ctx); err != nil {
return nil, err
}
@ -244,8 +247,8 @@ func (c *ProxyTestClient) RunUntilLoad(ctx context.Context, load int64) (message
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,

View file

@ -24,8 +24,8 @@ package main
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"strings"
"sync/atomic"
"time"
@ -33,7 +33,9 @@ import (
"github.com/dlintw/goconf"
"github.com/golang-jwt/jwt/v5"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"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 (
@ -46,25 +48,27 @@ type tokenCacheEntry struct {
}
type tokensEtcd struct {
client *signaling.EtcdClient
logger log.Logger
client etcd.Client
tokenFormats atomic.Value
tokenCache *signaling.LruCache
tokenCache *container.LruCache[*tokenCacheEntry]
}
func NewProxyTokensEtcd(config *goconf.ConfigFile) (ProxyTokens, error) {
client, err := signaling.NewEtcdClient(config, "tokens")
func NewProxyTokensEtcd(logger log.Logger, config *goconf.ConfigFile) (ProxyTokens, error) {
client, err := etcd.NewClient(logger, config, "tokens")
if err != nil {
return nil, err
}
if !client.IsConfigured() {
return nil, fmt.Errorf("No etcd endpoints configured")
return nil, errors.New("no etcd endpoints configured")
}
result := &tokensEtcd{
logger: logger,
client: client,
tokenCache: signaling.NewLruCache(tokenCacheSize),
tokenCache: container.NewLruCache[*tokenCacheEntry](tokenCacheSize),
}
if err := result.load(config, false); err != nil {
return nil, err
@ -94,11 +98,11 @@ 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
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)
@ -123,7 +127,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 +155,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)
}
}

View file

@ -27,13 +27,10 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"net"
"net/url"
"os"
"runtime"
"strconv"
"syscall"
"testing"
"github.com/dlintw/goconf"
@ -41,38 +38,23 @@ 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"
logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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()
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)
@ -89,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
}
@ -115,7 +97,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 := logtest.NewLoggerForTest(t)
tokens, err := NewProxyTokensEtcd(logger, cfg)
require.NoError(t, err)
t.Cleanup(func() {
tokens.Close()
@ -132,7 +115,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{
@ -155,7 +138,7 @@ func generateAndSaveKey(t *testing.T, etcd *embed.Etcd, name string) *rsa.Privat
}
func TestProxyTokensEtcd(t *testing.T) {
signaling.CatchLogForTest(t)
t.Parallel()
assert := assert.New(t)
tokens, etcd := newTokensEtcdForTesting(t)
@ -170,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))
}
}

View file

@ -23,23 +23,26 @@ package main
import (
"fmt"
"log"
"os"
"sort"
"slices"
"sync/atomic"
"github.com/dlintw/goconf"
"github.com/golang-jwt/jwt/v5"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"github.com/strukturag/nextcloud-spreed-signaling/v2/config"
"github.com/strukturag/nextcloud-spreed-signaling/v2/log"
)
type tokensStatic struct {
logger log.Logger
tokenKeys atomic.Value
}
func NewProxyTokensStatic(config *goconf.ConfigFile) (ProxyTokens, error) {
result := &tokensStatic{}
func NewProxyTokensStatic(logger log.Logger, config *goconf.ConfigFile) (ProxyTokens, error) {
result := &tokensStatic{
logger: logger,
}
if err := result.load(config, false); err != nil {
return nil, err
}
@ -61,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
}
@ -71,29 +74,29 @@ 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)
t.logger.Printf("No filename given for token %s, ignoring", id)
continue
}
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)
}
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)
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)
}
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 +107,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)
}
sort.Strings(keyIds)
log.Printf("Enabled token keys: %v", keyIds)
slices.Sort(keyIds)
t.logger.Printf("Enabled token keys: %v", keyIds)
}
t.setTokenKeys(tokenKeys)
return nil
@ -119,7 +122,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)
}
}

View file

@ -0,0 +1,185 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2026 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
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/v2/internal"
logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/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)
}

437
cmd/server/main.go Normal file
View file

@ -0,0 +1,437 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2017 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package main
import (
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"runtime"
runtimepprof "runtime/pprof"
"sync"
"syscall"
"time"
"github.com/dlintw/goconf"
"github.com/gorilla/mux"
"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 (
version = "unreleased"
configFlag = 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")
showVersion = flag.Bool("version", false, "show version and quit")
)
const (
defaultReadTimeout = 15
defaultWriteTimeout = 30
initialMcuRetry = time.Second
maxMcuRetry = time.Second * 16
dnsMonitorInterval = time.Second
)
func createListener(addr string) (net.Listener, error) {
if addr[0] == '/' {
os.Remove(addr)
return net.Listen("unix", addr)
}
return net.Listen("tcp", addr)
}
func createTLSListener(addr string, certFile, keyFile string) (net.Listener, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
config := tls.Config{
Certificates: []tls.Certificate{cert},
}
if addr[0] == '/' {
os.Remove(addr)
return tls.Listen("unix", addr, &config)
}
return tls.Listen("tcp", addr, &config)
}
type Listeners struct {
logger signalinglog.Logger // +checklocksignore
mu sync.Mutex
// +checklocks:mu
listeners []net.Listener
}
func (l *Listeners) Add(listener net.Listener) {
l.mu.Lock()
defer l.mu.Unlock()
l.listeners = append(l.listeners, listener)
}
func (l *Listeners) Close() {
l.mu.Lock()
defer l.mu.Unlock()
for _, listener := range l.listeners {
if err := listener.Close(); err != nil {
l.logger.Printf("Error closing listener %s: %s", listener.Addr(), err)
}
}
}
func main() {
log.SetFlags(log.Lshortfile)
flag.Parse()
if *showVersion {
fmt.Printf("nextcloud-spreed-signaling version %s/%s\n", version, runtime.Version())
os.Exit(0)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGHUP)
signal.Notify(sigChan, syscall.SIGUSR1)
stopCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
logger := log.Default()
stopCtx = signalinglog.NewLoggerContext(stopCtx, logger)
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
logger.Fatal(err)
}
if err := runtimepprof.StartCPUProfile(f); err != nil {
logger.Fatalf("Error writing CPU profile to %s: %s", *cpuprofile, err)
}
logger.Printf("Writing CPU profile to %s ...", *cpuprofile)
defer runtimepprof.StopCPUProfile()
}
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
logger.Fatal(err)
}
defer func() {
logger.Printf("Writing Memory profile to %s ...", *memprofile)
runtime.GC()
if err := runtimepprof.WriteHeapProfile(f); err != nil {
logger.Printf("Error writing Memory profile to %s: %s", *memprofile, err)
}
}()
}
logger.Printf("Starting up version %s/%s as pid %d", version, runtime.Version(), os.Getpid())
cfg, err := goconf.ReadConfigFile(*configFlag)
if err != nil {
logger.Fatal("Could not read configuration: ", err)
}
logger.Printf("Using a maximum of %d CPUs", runtime.GOMAXPROCS(0))
server.RegisterStats()
natsUrl, _ := config.GetStringOptionWithEnv(cfg, "nats", "url")
if natsUrl == "" {
natsUrl = nats.DefaultURL
}
events, err := events.NewAsyncEvents(stopCtx, natsUrl)
if err != nil {
logger.Fatal("Could not create async events client: ", err)
}
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 := dns.NewMonitor(logger, dnsMonitorInterval, nil)
if err != nil {
logger.Fatal("Could not create DNS monitor: ", err)
}
if err := dnsMonitor.Start(); err != nil {
logger.Fatal("Could not start DNS monitor: ", err)
}
defer dnsMonitor.Stop()
etcdClient, err := etcd.NewClient(logger, cfg, "mcu")
if err != nil {
logger.Fatalf("Could not create etcd client: %s", err)
}
defer func() {
if err := etcdClient.Close(); err != nil {
logger.Printf("Error while closing etcd client: %s", err)
}
}()
rpcServer, err := grpc.NewServer(stopCtx, cfg, version)
if err != nil {
logger.Fatalf("Could not create RPC server: %s", err)
}
go func() {
if err := rpcServer.Run(); err != nil {
logger.Fatalf("Could not start RPC server: %s", err)
}
}()
defer rpcServer.Close()
rpcClients, err := grpc.NewClients(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 := server.NewHub(stopCtx, cfg, events, rpcServer, rpcClients, etcdClient, r, version)
if err != nil {
logger.Fatal("Could not create hub: ", err)
}
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", 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 sfu.SFU
mcuRetry := initialMcuRetry
mcuRetryTimer := time.NewTimer(mcuRetry)
mcuTypeLoop:
for {
// Context should be cancelled on signals but need a way to differentiate later.
ctx := context.TODO()
switch mcuType {
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)
}
if err == nil {
err = mcu.Start(ctx)
if err != nil {
logger.Printf("Could not create %s MCU: %s", mcuType, err)
}
}
if err == nil {
break
}
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 syscall.SIGHUP:
logger.Printf("Received SIGHUP, reloading %s", *configFlag)
if cfg, err = goconf.ReadConfigFile(*configFlag); err != nil {
logger.Printf("Could not read configuration from %s: %s", *configFlag, err)
} else {
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", 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
}
}
}
case <-mcuRetryTimer.C:
// Retry connection
mcuRetry = min(mcuRetry*2, maxMcuRetry)
}
}
if mcu != nil {
defer mcu.Stop()
logger.Printf("Using %s MCU", mcuType)
hub.SetMcu(mcu)
}
}
go hub.Run()
defer hub.Stop()
server, err := server.NewBackendServer(stopCtx, cfg, hub, version)
if err != nil {
logger.Fatal("Could not create backend server: ", err)
}
if err := server.Start(r); err != nil {
logger.Fatal("Could not start backend server: ", err)
}
listeners := Listeners{
logger: logger,
}
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, _ := cfg.GetInt("https", "readtimeout")
if readTimeout <= 0 {
readTimeout = defaultReadTimeout
}
writeTimeout, _ := cfg.GetInt("https", "writetimeout")
if writeTimeout <= 0 {
writeTimeout = defaultWriteTimeout
}
for address := range internal.SplitEntries(saddr, " ") {
go func(address string) {
logger.Println("Listening on", address)
listener, err := createTLSListener(address, cert, key)
if err != nil {
logger.Fatal("Could not start listening: ", err)
}
srv := &http.Server{
Handler: r,
ReadTimeout: time.Duration(readTimeout) * time.Second,
WriteTimeout: time.Duration(writeTimeout) * time.Second,
}
listeners.Add(listener)
if err := srv.Serve(listener); err != nil {
if !hub.IsShutdownScheduled() || !errors.Is(err, net.ErrClosed) {
logger.Fatal("Could not start server: ", err)
}
}
}(address)
}
}
if addr, _ := config.GetStringOptionWithEnv(cfg, "http", "listen"); addr != "" {
readTimeout, _ := cfg.GetInt("http", "readtimeout")
if readTimeout <= 0 {
readTimeout = defaultReadTimeout
}
writeTimeout, _ := cfg.GetInt("http", "writetimeout")
if writeTimeout <= 0 {
writeTimeout = defaultWriteTimeout
}
for address := range internal.SplitEntries(addr, " ") {
go func(address string) {
logger.Println("Listening on", address)
listener, err := createListener(address)
if err != nil {
logger.Fatal("Could not start listening: ", err)
}
srv := &http.Server{
Handler: r,
Addr: addr,
ReadTimeout: time.Duration(readTimeout) * time.Second,
WriteTimeout: time.Duration(writeTimeout) * time.Second,
}
listeners.Add(listener)
if err := srv.Serve(listener); err != nil {
if !hub.IsShutdownScheduled() || !errors.Is(err, net.ErrClosed) {
logger.Fatal("Could not start server: ", err)
}
}
}(address)
}
}
loop:
for {
select {
case <-stopCtx.Done():
logger.Println("Interrupted")
break loop
case sig := <-sigChan:
switch sig {
case syscall.SIGHUP:
logger.Printf("Received SIGHUP, reloading %s", *configFlag)
if config, err := goconf.ReadConfigFile(*configFlag); err != nil {
logger.Printf("Could not read configuration from %s: %s", *configFlag, err)
} else {
hub.Reload(stopCtx, config)
server.Reload(config)
}
case syscall.SIGUSR1:
logger.Printf("Received SIGUSR1, scheduling server to shutdown")
hub.ScheduleShutdown()
listeners.Close()
}
case <-hub.ShutdownChannel():
logger.Printf("All clients disconnected, shutting down")
break loop
}
}
}

View file

@ -19,14 +19,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package config
import (
"errors"
"os"
"regexp"
"github.com/dlintw/goconf"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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
}

View file

@ -19,7 +19,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package config
import (
"testing"

View file

@ -19,47 +19,48 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package container
import (
"sync"
)
type ConcurrentStringStringMap struct {
sync.Mutex
d map[string]string
type ConcurrentMap[K comparable, V any] struct {
mu sync.RWMutex
// +checklocks:mu
d map[K]V
}
func (m *ConcurrentStringStringMap) Set(key, value string) {
m.Lock()
defer m.Unlock()
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) {
m.Lock()
defer m.Unlock()
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) {
m.Lock()
defer m.Unlock()
func (m *ConcurrentMap[K, V]) Del(key K) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.d, key)
}
func (m *ConcurrentStringStringMap) Len() int {
m.Lock()
defer m.Unlock()
func (m *ConcurrentMap[K, V]) Len() int {
m.mu.RLock()
defer m.mu.RUnlock()
return len(m.d)
}
func (m *ConcurrentStringStringMap) Clear() {
m.Lock()
defer m.Unlock()
func (m *ConcurrentMap[K, V]) Clear() {
m.mu.Lock()
defer m.mu.Unlock()
m.d = nil
}

View file

@ -19,9 +19,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package container
import (
"crypto/rand"
"strconv"
"sync"
"testing"
@ -30,8 +31,9 @@ import (
)
func TestConcurrentStringStringMap(t *testing.T) {
t.Parallel()
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)
@ -76,21 +78,22 @@ func TestConcurrentStringStringMap(t *testing.T) {
var wg sync.WaitGroup
concurrency := 100
count := 1000
for x := 0; x < concurrency; x++ {
wg.Add(1)
go func(x int) {
defer wg.Done()
for x := range concurrency {
wg.Go(func() {
key := "key-" + strconv.Itoa(x)
for y := 0; y < count; y = y + 1 {
value := newRandomString(32)
rnd := rand.Text()
for y := range count {
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
}
}
}(x)
})
}
wg.Wait()
assert.Equal(concurrency, m.Len())

View file

@ -19,23 +19,26 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package container
import (
"bytes"
"fmt"
"net"
"slices"
"strings"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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(", ")
}
@ -45,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) {
@ -81,35 +80,36 @@ 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 strings.Split(allowed, ",") {
ip = strings.TrimSpace(ip)
if ip != "" {
i, err := parseIPNet(ip)
if err != nil {
return nil, err
}
allowedIps = append(allowedIps, i)
for ip := range internal.SplitEntries(allowed, ",") {
i, err := parseIPNet(ip)
if err != nil {
return nil, err
}
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"),
Mask: net.CIDRMask(32, 32),
},
{
IP: net.ParseIP("::1"),
Mask: net.CIDRMask(128, 128),
},
}
result := &AllowedIps{
allowed: allowedIps,
result := &IPList{
ips: allowedIps,
}
return result
}
@ -118,6 +118,7 @@ var (
privateIpNets = []string{
// Loopback addresses.
"127.0.0.0/8",
"::1",
// Private addresses.
"10.0.0.0/8",
"172.16.0.0/12",
@ -125,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))
}

View file

@ -19,7 +19,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package container
import (
"net"
@ -29,39 +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")))
}

View file

@ -19,43 +19,45 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package container
import (
"container/list"
"sync"
)
type cacheEntry struct {
type cacheEntry[T any] struct {
key string
value interface{}
value T
}
type LruCache struct {
size int
mu sync.Mutex
type LruCache[T any] struct {
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(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 interface{}) {
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 +68,21 @@ func (c *LruCache) Set(key string, value interface{}) {
c.mu.Unlock()
}
func (c *LruCache) Get(key string) interface{} {
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 +90,28 @@ func (c *LruCache) Remove(key string) {
c.mu.Unlock()
}
func (c *LruCache) removeOldestLocked() {
// +checklocks:c.mu
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) {
// +checklocks:c.mu
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()

View file

@ -19,109 +19,101 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package container
import (
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLruUnbound(t *testing.T) {
t.Parallel()
assert := assert.New(t)
lru := NewLruCache(0)
lru := NewLruCache[int](0)
count := 10
for i := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
for i := range count {
key := strconv.Itoa(i)
lru.Set(key, i)
}
assert.Equal(count, lru.Len())
for i := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
if value := lru.Get(key); assert.NotNil(value, "No value found for %s", key) {
assert.EqualValues(i, value)
}
for i := range count {
key := strconv.Itoa(i)
value := lru.Get(key)
assert.Equal(i, value, "Failed for %s", key)
}
// The first key ("0") is now the oldest.
lru.RemoveOldest()
assert.Equal(count-1, lru.Len())
for i := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
for i := range count {
key := strconv.Itoa(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.Equal(i, value, "Failed for %s", key)
}
// NOTE: Key "0" no longer exists below, so make sure to not set it again.
// 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)
if value := lru.Get(key); assert.NotNil(value, "No value found for %s", key) {
assert.EqualValues(i, value)
}
key := strconv.Itoa(i)
value := lru.Get(key)
assert.Equal(i, value, "Failed for %s", key)
}
// The last key ("9") is now the oldest.
lru.RemoveOldest()
assert.Equal(count-2, lru.Len())
for i := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
for i := range count {
key := strconv.Itoa(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.Equal(0, value, "The value for key %s should have been removed", key)
} else {
assert.Equal(i, value, "Failed for %s", key)
}
}
// 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 := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
for i := range count {
key := strconv.Itoa(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.Equal(0, value, "The value for key %s should have been removed", key)
} else {
assert.Equal(i, value, "Failed for %s", key)
}
}
}
func TestLruBound(t *testing.T) {
t.Parallel()
assert := assert.New(t)
size := 2
lru := NewLruCache(size)
lru := NewLruCache[int](size)
count := 10
for i := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
for i := range count {
key := strconv.Itoa(i)
lru.Set(key, i)
}
assert.Equal(size, lru.Len())
// Only the last "size" entries have been stored.
for i := 0; i < count; i++ {
key := fmt.Sprintf("%d", i)
for i := range count {
key := strconv.Itoa(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.Equal(0, value, "The value for key %s should have been removed", key)
} else {
assert.Equal(i, value, "Failed for %s", key)
}
}
}

View file

@ -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=/

View file

@ -0,0 +1,71 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package internal
import (
"net"
"sync"
)
type MockLookup struct {
sync.RWMutex
// +checklocks:RWMutex
ips map[string][]net.IP
}
func NewMockLookup() *MockLookup {
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
}

View file

@ -1,6 +1,6 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2023 struktur AG
* Copyright (C) 2026 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
@ -19,50 +19,40 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package internal
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestChannelWaiters(t *testing.T) {
var waiters ChannelWaiters
func TestMockLookup(t *testing.T) {
t.Parallel()
ch1 := make(chan struct{}, 1)
id1 := waiters.Add(ch1)
defer waiters.Remove(id1)
assert := assert.New(t)
ch2 := make(chan struct{}, 1)
id2 := waiters.Add(ch2)
defer waiters.Remove(id2)
host1 := "domain1.invalid"
host2 := "domain2.invalid"
waiters.Wakeup()
<-ch1
<-ch2
lookup := NewMockLookup()
assert.Empty(lookup.Get(host1))
assert.Empty(lookup.Get(host2))
select {
case <-ch1:
assert.Fail(t, "should have not received another event")
case <-ch2:
assert.Fail(t, "should have not received another event")
default:
ips := []net.IP{
net.ParseIP("1.2.3.4"),
}
lookup.Set(host1, ips)
assert.Equal(ips, lookup.Get(host1))
assert.Empty(lookup.Get(host2))
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:
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)
}
}

View file

@ -19,49 +19,64 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package dns
import (
"context"
"log"
"net"
"net/url"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
)
var (
lookupDnsMonitorIP = net.LookupIP
"github.com/strukturag/nextcloud-spreed-signaling/v2/log"
)
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
mu sync.Mutex
ips []net.IP
entries map[*DnsMonitorEntry]bool
mu sync.Mutex
// +checklocks:mu
ips []net.IP
// +checklocks:mu
entries map[*MonitorEntry]bool
}
func (e *dnsMonitorEntry) setIPs(ips []net.IP, fromIP bool) {
func (e *monitorEntry) 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 *monitorEntry) setIPs(ips []net.IP, fromIP bool) {
e.mu.Lock()
defer e.mu.Unlock()
@ -94,7 +109,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)
@ -118,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()
@ -133,14 +148,19 @@ func (e *dnsMonitorEntry) removeEntry(entry *DnsMonitorEntry) bool {
return len(e.entries) == 0
}
func (e *dnsMonitorEntry) runCallbacks(all []net.IP, add []net.IP, keep []net.IP, remove []net.IP) {
// +checklocks:e.mu
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 DnsMonitor struct {
interval time.Duration
type MonitorLookupFunc func(hostname string) ([]net.IP, error)
type Monitor struct {
logger log.Logger
interval time.Duration
lookupFunc MonitorLookupFunc
stopCtx context.Context
stopFunc func()
@ -148,46 +168,48 @@ type DnsMonitor struct {
mu sync.RWMutex
cond *sync.Cond
hostnames map[string]*dnsMonitorEntry
hostnames map[string]*monitorEntry
hasRemoved atomic.Bool
// Can be overwritten from tests.
checkHostnames func()
tickerWaiting atomic.Bool
hasRemoved atomic.Bool
}
func NewDnsMonitor(interval time.Duration) (*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{
interval: interval,
monitor := &Monitor{
logger: logger,
interval: interval,
lookupFunc: lookupFunc,
stopCtx: stopCtx,
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.
@ -207,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
}
@ -227,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.
@ -245,7 +267,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 {
@ -257,7 +279,7 @@ func (m *DnsMonitor) Remove(entry *DnsMonitorEntry) {
}
}
func (m *DnsMonitor) clearRemoved() {
func (m *Monitor) clearRemoved() {
if !m.hasRemoved.CompareAndSwap(true, false) {
return
}
@ -266,21 +288,13 @@ 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)
}
}
}
func (m *DnsMonitor) waitForEntries() (waited bool) {
func (m *Monitor) waitForEntries() (waited bool) {
m.mu.Lock()
defer m.mu.Unlock()
@ -291,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)
@ -302,21 +316,22 @@ 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
}
}
m.tickerWaiting.Store(true)
select {
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()
@ -327,17 +342,27 @@ 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
}
ips, err := lookupDnsMonitorIP(entry.hostname)
ips, err := m.lookupFunc(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
}
entry.setIPs(ips, false)
}
func (m *Monitor) WaitForTicker(ctx context.Context) error {
for !m.tickerWaiting.Load() {
time.Sleep(time.Millisecond)
if err := ctx.Err(); err != nil {
return err
}
}
return nil
}

View file

@ -19,7 +19,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package dns
import (
"context"
@ -33,61 +33,20 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/strukturag/nextcloud-spreed-signaling/v2/dns/internal"
logtest "github.com/strukturag/nextcloud-spreed-signaling/v2/log/test"
)
type mockDnsLookup struct {
sync.RWMutex
ips map[string][]net.IP
}
func newMockDnsLookupForTest(t *testing.T) *mockDnsLookup {
mock := &mockDnsLookup{
ips: make(map[string][]net.IP),
}
prev := lookupDnsMonitorIP
t.Cleanup(func() {
lookupDnsMonitorIP = prev
})
lookupDnsMonitorIP = mock.lookup
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: fmt.Sprintf("could not resolve %s", host),
Name: host,
IsNotFound: true,
}
}
return append([]net.IP{}, ips...), nil
}
func newDnsMonitorForTest(t *testing.T, interval time.Duration) *DnsMonitor {
func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *internal.MockLookup) *Monitor {
t.Helper()
require := require.New(t)
monitor, err := NewDnsMonitor(interval)
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() {
@ -98,46 +57,48 @@ func newDnsMonitorForTest(t *testing.T, interval time.Duration) *DnsMonitor {
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{}
expectNone = &monitorReceiverRecord{} // +checklocksignore: Global readonly variable.
)
type dnsMonitorReceiver struct {
type monitorReceiver struct {
sync.Mutex
t *testing.T
expected *dnsMonitorReceiverRecord
received *dnsMonitorReceiverRecord
t *testing.T
// +checklocks:Mutex
expected *monitorReceiverRecord
// +checklocks:Mutex
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,
@ -147,13 +108,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
}
@ -163,7 +124,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()
@ -182,16 +143,16 @@ 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()
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{
expected := &monitorReceiverRecord{
all: all,
add: add,
keep: keep,
@ -205,25 +166,26 @@ 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()
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
}
func TestDnsMonitor(t *testing.T) {
lookup := newMockDnsLookupForTest(t)
func TestMonitor(t *testing.T) {
t.Parallel()
lookup := internal.NewMockLookup()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
interval := time.Millisecond
monitor := newDnsMonitorForTest(t, interval)
monitor := NewMonitorForTest(t, interval, lookup)
ip1 := net.ParseIP("192.168.0.1")
ip2 := net.ParseIP("192.168.1.1")
@ -234,7 +196,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)
@ -285,19 +247,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)
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)
@ -310,14 +273,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)
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)
@ -326,18 +290,20 @@ func TestDnsMonitorNoLookupIfEmpty(t *testing.T) {
type deadlockMonitorReceiver struct {
t *testing.T
monitor *DnsMonitor
monitor *Monitor // +checklocksignore: Only written to from constructor.
mu sync.RWMutex
wg sync.WaitGroup
wg sync.WaitGroup // +checklocksignore: Only written to from constructor.
entry *DnsMonitorEntry
started chan struct{}
// +checklocks:mu
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,
@ -345,7 +311,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
}
@ -358,16 +324,13 @@ func (r *deadlockMonitorReceiver) OnLookup(entry *DnsMonitorEntry, all []net.IP,
}
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() {
@ -393,14 +356,15 @@ func (r *deadlockMonitorReceiver) Close() {
r.wg.Wait()
}
func TestDnsMonitorDeadlock(t *testing.T) {
lookup := newMockDnsLookupForTest(t)
func TestMonitorDeadlock(t *testing.T) {
t.Parallel()
lookup := internal.NewMockLookup()
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 := NewMonitorForTest(t, interval, lookup)
r := newDeadlockMonitorReceiver(t, monitor)
r.Start()

View file

@ -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 <bauch@struktur.de>
*
@ -19,44 +19,41 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
package test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"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"
)
func UpdateCertificateCheckIntervalForTest(t *testing.T, interval time.Duration) {
type MockLookup = internal.MockLookup
func NewMockLookup() *MockLookup {
return internal.NewMockLookup()
}
func NewMonitorForTest(t *testing.T, interval time.Duration, lookup *MockLookup) *dns.Monitor {
t.Helper()
// Make sure test is not executed with "t.Parallel()"
t.Setenv("PARALLEL_CHECK", "1")
old := deduplicateWatchEvents.Load()
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() {
deduplicateWatchEvents.Store(old)
monitor.Stop()
})
deduplicateWatchEvents.Store(int64(interval))
}
func (r *CertificateReloader) WaitForReload(ctx context.Context) error {
counter := r.GetReloadCounter()
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) error {
counter := r.GetReloadCounter()
for counter == r.GetReloadCounter() {
if err := ctx.Err(); err != nil {
return err
}
time.Sleep(time.Millisecond)
}
return nil
require.NoError(monitor.Start())
return monitor
}

68
dns/test/dns_test.go Normal file
View file

@ -0,0 +1,68 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2026 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
package test
import (
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/strukturag/nextcloud-spreed-signaling/v2/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
}

View file

@ -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).
@ -24,11 +28,15 @@ 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.
- `BACKEND_<ID>_URL`: Url of backend `ID` (where `ID` is the uppercase backend id).
- `BACKENDS_TIMEOUT`: Timeout in seconds for requests to backends.
- `CONNECTIONS_PER_HOST`: Maximum number of concurrent backend connections per host.
- `BACKEND_<ID>_URLS`: Comma-separated list of urls of backend `ID` (where `ID` is the uppercase backend id).
- `BACKEND_<ID>_URL`: Url of backend `ID` (where `ID` is the uppercase backend id, deprecated).
- `BACKEND_<ID>_SHARED_SECRET`: Shared secret for backend `ID` (where `ID` is the uppercase backend id).
- `BACKEND_<ID>_SESSION_LIMIT`: Optional session limit for backend `ID` (where `ID` is the uppercase backend id).
- `BACKEND_<ID>_MAX_STREAM_BITRATE`: Optional maximum bitrate for audio/video streams in backend `ID` (where `ID` is the uppercase backend id).
- `BACKEND_<ID>_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).
@ -41,12 +49,15 @@ 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.
- `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.
@ -109,6 +120,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).

View file

@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS builder
FROM --platform=${BUILDPLATFORM} golang:1.26-alpine AS builder
ARG TARGETARCH
ARG TARGETOS
@ -12,7 +12,8 @@ RUN touch /.dockerenv && \
FROM alpine:3
ENV CONFIG=/config/proxy.conf
RUN adduser -D 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

View file

@ -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

View file

@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS builder
FROM --platform=${BUILDPLATFORM} golang:1.26-alpine AS builder
ARG TARGETARCH
ARG TARGETOS
@ -12,7 +12,8 @@ RUN touch /.dockerenv && \
FROM alpine:3
ENV CONFIG=/config/server.conf
RUN adduser -D 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

View file

@ -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"
@ -64,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
@ -103,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
@ -131,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"
@ -232,6 +257,14 @@ 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 "$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"
@ -240,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"
@ -266,6 +305,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

View file

@ -52,3 +52,28 @@ 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` |
| `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` |

View file

@ -1,6 +1,6 @@
jinja2==3.1.5
markdown==3.7
jinja2==3.1.6
markdown==3.10.2
mkdocs==1.6.1
readthedocs-sphinx-search==0.3.2
sphinx==8.1.3
sphinx_rtd_theme==3.0.2
sphinx==9.1.0
sphinx_rtd_theme==3.1.0

Some files were not shown because too many files have changed in this diff Show more