diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..92bfd0151 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,75 @@ +# This file is adapted from the same file in element-hq/synapse: +# https://github.com/element-hq/synapse/blob/2e9b8202f0a1a8ceba9f02bb5ec227498d51dcbd/.github/dependabot.yml +# But updated to remove `pip` and `cargo` ecosystems, and introduce `gomod`. +version: 2 +# As dependabot is currently only run on a weekly basis, we raise the +# open-pull-requests-limit to 10 (from the default of 5) to better ensure we +# don't continuously grow a backlog of updates. +updates: + - package-ecosystem: "gomod" + directory: "/" + open-pull-requests-limit: 10 + schedule: + interval: "weekly" + # Group patch updates to packages together into a single PR, as they rarely + # if ever contain breaking changes that need to be reviewed separately. + # + # Less PRs means a streamlined review process. + # + # The Go ecosystem is special in that breaking changes are often introduced + # in minor version bumps, as packages typically stay pre-1.0 for a long time. + # Thus we specifically keep minor version bumps separate in their own PRs. + groups: + minor-and-patches: + applies-to: version-updates + patterns: + - "*" + update-types: + - "minor" + - "patch" + # Prevent pulling packages that were recently updated to help mitigate + # supply chain attacks. 14 days was taken from the recommendation at + # https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns + # where the author noted that 9/10 attacks would have been mitigated by a + # two week cooldown. + # + # The cooldown only applies to general updates; security updates will still + # be pulled in as soon as possible. + cooldown: + default-days: 14 + + - package-ecosystem: "docker" + directory: "/complement/cmd/homerunner" + open-pull-requests-limit: 10 + schedule: + interval: "weekly" + # For container versions, breaking changes are also typically only introduced in major + # package bumps. + groups: + minor-and-patches: + applies-to: version-updates + patterns: + - "*" + update-types: + - "minor" + - "patch" + cooldown: + default-days: 14 + + - package-ecosystem: "github-actions" + directory: "/" + open-pull-requests-limit: 10 + schedule: + interval: "weekly" + # Similarly for GitHub Actions, breaking changes are typically only introduced in major + # package bumps. + groups: + minor-and-patches: + applies-to: version-updates + patterns: + - "*" + update-types: + - "minor" + - "patch" + cooldown: + default-days: 14 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d1dcb1ee3..6909dd465 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,8 +10,8 @@ jobs: complement-internal: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 # Checkout complement - - uses: actions/setup-go@v4 + - uses: actions/checkout@v6 # Checkout complement + - uses: actions/setup-go@v6 with: go-version-file: go.mod - name: "Run internal Complement tests" @@ -50,9 +50,9 @@ jobs: timeout: 10m steps: - - uses: actions/checkout@v3 # Checkout complement + - uses: actions/checkout@v6 # Checkout complement - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/client/sync.go b/client/sync.go index 701f73af1..b3f713107 100644 --- a/client/sync.go +++ b/client/sync.go @@ -215,10 +215,11 @@ func SyncTimelineHasEventID(roomID string, eventID string) SyncCheckOpt { }) } -// Check that the state section for `roomID` has an event which passes the check function. -// Note that the state section of a sync response only contains the change in state up to the start -// of the timeline and will not contain the entire state of the room for incremental or -// `lazy_load_members` syncs. +// Check that the `state` section for `roomID` has an event which passes the check function. +// +// Note that the `state` section of a sync response only contains the change in state up +// to the start of the `timeline` and will not contain the entire state of the room for +// incremental or `lazy_load_members` syncs. func SyncStateHas(roomID string, check func(gjson.Result) bool) SyncCheckOpt { return func(clientUserID string, topLevelSyncJSON gjson.Result) error { err := checkArrayElements( @@ -231,6 +232,31 @@ func SyncStateHas(roomID string, check func(gjson.Result) bool) SyncCheckOpt { } } +// Check that the `state_after` section for `roomID` has an event which passes the check function. +// +// Note that the `state_after` section of a sync response will not contain the entire +// state of the room for incremental or `lazy_load_members` syncs. +func SyncStateAfterHas(roomID string, check func(gjson.Result) bool) SyncCheckOpt { + return func(clientUserID string, topLevelSyncJSON gjson.Result) error { + // Check the stable field + errStable := checkArrayElements( + topLevelSyncJSON, "rooms.join."+GjsonEscape(roomID)+".state_after.events", check, + ) + // Check the unstable field + // + // FIXME: Some implementations haven't stabilized yet (Synapse) so we'll keep this + // here until then. + errUnstable := checkArrayElements( + topLevelSyncJSON, "rooms.join."+GjsonEscape(roomID)+"."+GjsonEscape("org.matrix.msc4222.state_after")+".events", check, + ) + // Valid to find it in either place + if errStable == nil || errUnstable == nil { + return nil + } + return fmt.Errorf("SyncStateAfterHas(%s): Tried to check in the stable field: %s - and unstable field: %s", roomID, errStable, errUnstable) + } +} + func SyncEphemeralHas(roomID string, check func(gjson.Result) bool) SyncCheckOpt { return func(clientUserID string, topLevelSyncJSON gjson.Result) error { err := checkArrayElements( diff --git a/federation/server_room.go b/federation/server_room.go index 61c40ed37..fd02e720c 100644 --- a/federation/server_room.go +++ b/federation/server_room.go @@ -333,15 +333,20 @@ func InitialRoomEvents(roomVer gomatrixserverlib.RoomVersion, creator string) [] Type: "m.room.create", StateKey: b.Ptr(""), Sender: creator, - Content: map[string]interface{}{ - "creator": creator, - "room_version": roomVer, - // We have to add randomness to the create event, else if you create 2x v12+ rooms in the same millisecond - // they will get the same room ID, clobbering internal data structures and causing extremely confusing - // behaviour. By adding this entropy, we ensure that even if rooms are created in the same millisecond, their - // hashes will not be the same. - "complement_entropy": util.RandomString(18), - }, + Content: func() map[string]interface{} { + content := map[string]interface{}{ + "room_version": roomVer, + // We have to add randomness to the create event, else if you create 2x v12+ rooms in the same millisecond + // they will get the same room ID, clobbering internal data structures and causing extremely confusing + // behaviour. By adding this entropy, we ensure that even if rooms are created in the same millisecond, their + // hashes will not be the same. + "complement_entropy": util.RandomString(18), + } + if gomatrixserverlib.MustGetRoomVersion(gomatrixserverlib.RoomVersion(roomVer)).CreatorInCreateEvent() { + content["creator"] = creator + } + return content + }(), }, { Type: "m.room.member", diff --git a/go.mod b/go.mod index ab6cd5b9e..6766c1272 100644 --- a/go.mod +++ b/go.mod @@ -1,49 +1,51 @@ module github.com/matrix-org/complement -go 1.24.0 - -toolchain go1.24.3 +go 1.25.0 require ( - github.com/docker/docker v28.0.4+incompatible - github.com/docker/go-connections v0.4.0 - github.com/gorilla/mux v1.8.0 + github.com/docker/docker v28.5.2+incompatible + github.com/docker/go-connections v0.6.0 + github.com/gorilla/mux v1.8.1 github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 - github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744 + github.com/matrix-org/gomatrixserverlib v0.0.0-20260506075950-c9c468727353 github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 - github.com/sirupsen/logrus v1.9.3 + github.com/sirupsen/logrus v1.9.4 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 - golang.org/x/crypto v0.45.0 + golang.org/x/crypto v0.49.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - gonum.org/v1/plot v0.11.0 + gonum.org/v1/plot v0.16.0 ) require ( - git.sr.ht/~sbinet/gg v0.3.1 // indirect + codeberg.org/go-fonts/liberation v0.5.0 // indirect + codeberg.org/go-latex/latex v0.1.0 // indirect + codeberg.org/go-pdf/fpdf v0.10.0 // indirect + git.sr.ht/~sbinet/gg v0.6.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect + github.com/campoy/embedmd v1.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-fonts/liberation v0.2.0 // indirect - github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-pdf/fpdf v0.6.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/hashicorp/go-set/v3 v3.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/oleiade/lane/v2 v2.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -53,10 +55,9 @@ require ( go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect - golang.org/x/image v0.18.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/image v0.25.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect gotest.tools/v3 v3.0.3 // indirect ) diff --git a/go.sum b/go.sum index 7d61f914e..76a6631ee 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,17 @@ -git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +codeberg.org/go-fonts/dejavu v0.4.0 h1:2yn58Vkh4CFK3ipacWUAIE3XVBGNa0y1bc95Bmfx91I= +codeberg.org/go-fonts/dejavu v0.4.0/go.mod h1:abni088lmhQJvso2Lsb7azCKzwkfcnttl6tL1UTWKzg= +codeberg.org/go-fonts/latin-modern v0.4.0 h1:vkRCc1y3whKA7iL9Ep0fSGVuJfqjix0ica9UflHORO8= +codeberg.org/go-fonts/latin-modern v0.4.0/go.mod h1:BF68mZznJ9QHn+hic9ks2DaFl4sR5YhfM6xTYaP9vNw= +codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOLfQ5Bo0= +codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= +codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= +codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= +codeberg.org/go-pdf/fpdf v0.10.0 h1:u+w669foDDx5Ds43mpiiayp40Ov6sZalgcPMDBcZRd4= +codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= +git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= +git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= +git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= +git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= @@ -10,48 +22,36 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= -github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0 h1:5/Tv1Ek/QCr20C6ZOz15vw3g7GELYL98KWr8Hgo+3vk= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.2.0 h1:jAkAWJP4S+OsrPLZM4/eC9iW7CtHy+HBXrEwZXWo5VM= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -60,27 +60,31 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/go-set/v3 v3.0.0 h1:CaJBQvQCOWoftrBcDt7Nwgo0kdpmrKxar/x2o6pV9JA= github.com/hashicorp/go-set/v3 v3.0.0/go.mod h1:IEghM2MpE5IaNvL+D7X480dfNtxjRXZ6VMpK3C8s2ok= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U= github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744 h1:5GvC2FD9O/PhuyY95iJQdNYHbDioEhMWdeMP9maDUL8= github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= +github.com/matrix-org/gomatrixserverlib v0.0.0-20260506075950-c9c468727353 h1:BdGU/8sF4ZaqGhfYN+eFEzc/whU4LpKvEDdM6i0Oqqs= +github.com/matrix-org/gomatrixserverlib v0.0.0-20260506075950-c9c468727353/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66/go.mod h1:iBI1foelCqA09JJgPV0FYz4qA5dUXYOxMi57FxKBdd4= github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -91,25 +95,18 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ= github.com/shoenig/test v1.11.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -122,7 +119,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= @@ -145,35 +141,25 @@ go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -182,46 +168,40 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/plot v0.11.0 h1:z2ZkgNqW34d0oYUzd80RRlc0L9kWtenqK4kflZG1lGc= -gonum.org/v1/plot v0.11.0/go.mod h1:fH9YnKnDKax0u5EzHVXvhN5HJwtMFWIOLNuhgUahbCQ= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/plot v0.16.0 h1:dK28Qx/Ky4VmPUN/2zeW0ELyM6ucDnBAj5yun7M9n1g= +gonum.org/v1/plot v0.16.0/go.mod h1:Xz6U1yDMi6Ni6aaXILqmVIb6Vro8E+K7Q/GeeH+Pn0c= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= -google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/tests/csapi/rooms_state_test.go b/tests/csapi/rooms_state_test.go index 2158cae1f..4f2a54510 100644 --- a/tests/csapi/rooms_state_test.go +++ b/tests/csapi/rooms_state_test.go @@ -15,6 +15,7 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/gomatrixserverlib" ) func TestRoomCreationReportsEventsToMyself(t *testing.T) { @@ -26,6 +27,7 @@ func TestRoomCreationReportsEventsToMyself(t *testing.T) { LocalpartSuffix: "bob", Password: "bobpassword", }) + defaultVer := alice.GetDefaultRoomVersion(t) roomID := alice.MustCreateRoom(t, map[string]interface{}{}) t.Run("parallel", func(t *testing.T) { @@ -38,7 +40,10 @@ func TestRoomCreationReportsEventsToMyself(t *testing.T) { return false } must.Equal(t, ev.Get("sender").Str, alice.UserID, "wrong sender") - must.Equal(t, ev.Get("content").Get("creator").Str, alice.UserID, "wrong content.creator") + // The creator field was removed in room version 11 (MSC4239). + if gomatrixserverlib.MustGetRoomVersion(defaultVer).CreatorInCreateEvent() { + must.Equal(t, ev.Get("content").Get("creator").Str, alice.UserID, "wrong content.creator") + } return true })) }) diff --git a/tests/federation_acl_test.go b/tests/federation_acl_test.go index c7f4dae5d..9810c5efc 100644 --- a/tests/federation_acl_test.go +++ b/tests/federation_acl_test.go @@ -12,12 +12,15 @@ import ( "github.com/matrix-org/complement/runtime" "github.com/matrix-org/complement/should" "github.com/matrix-org/gomatrixserverlib/spec" + "github.com/tidwall/gjson" ) // Test for https://github.com/matrix-org/dendrite/issues/3004 func TestACLs(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // needs https://github.com/matrix-org/dendrite/pull/3008 - // 1. Prepare 3 or more servers. 1st will be room host, 2nd will be blocked with m.room.server_acl and 3rd server will be affected by this issue. 1st and 2nd servers don't have to be powered by dendrite. + // 1. Prepare 3 or more servers. 1st will be room host, 2nd will be blocked with + // m.room.server_acl and 3rd server will be affected by this issue. 1st and 2nd + // servers don't have to be powered by dendrite. deployment := complement.Deploy(t, 3) defer deployment.Destroy(t) @@ -74,7 +77,8 @@ func TestACLs(t *testing.T) { aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(charlie.UserID, roomID)) charlieSince := charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(charlie.UserID, roomID)) - // 6. Any events sent by 2nd server will not appear for users from 1st server, but will be visible for users from 3rd server + // 6. Any events sent by 2nd server will not appear for users from 1st or 3rd server, as + // m.room.server_acl blocks everyone from accepting hs2's events eventID = bob.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Sender: bob.UserID, @@ -120,3 +124,119 @@ func TestACLs(t *testing.T) { ) } } + +// MSC4163: TestACLsForEDUs checks that ACLs are applied to EDUs (typing notifications and read receipts) +func TestACLsForEDUs(t *testing.T) { + runtime.SkipIf(t, runtime.Dendrite) + // 1. Prepare 3 or more servers. 1st will be room host, 2nd will be blocked with + // m.room.server_acl and 3rd server will be affected by this issue. 1st and 2nd + // servers don't have to be powered by dendrite. + deployment := complement.Deploy(t, 3) + defer deployment.Destroy(t) + + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{}) + charlie := deployment.Register(t, "hs3", helpers.RegistrationOpts{}) + + // Create a room where hs2 will be blocked by `m.room.server_acl` later on + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + aliceSince := alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + + bob.MustJoinRoom(t, roomID, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(bob.UserID, roomID)) + bobSince := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) + + // Create a sentinel room without ACLs to confirm federation is working + sentinelRoom := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(alice.UserID, sentinelRoom)) + bob.MustJoinRoom(t, sentinelRoom, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + charlie.MustJoinRoom(t, sentinelRoom, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, + client.SyncJoinedTo(bob.UserID, sentinelRoom), + client.SyncJoinedTo(charlie.UserID, sentinelRoom), + ) + + // Block hs2 from participating in the roomID via `m.room.server_acl` rules + stateKey := "" + aclEventID := alice.SendEventSynced(t, roomID, b.Event{ + Type: "m.room.server_acl", + Sender: alice.UserID, + StateKey: &stateKey, + Content: map[string]interface{}{ + "allow": []string{"*"}, + "allow_ip_literals": true, + "deny": []string{ + string(deployment.GetFullyQualifiedHomeserverName(t, "hs2")), + }, + }, + }) + // Wait for the ACL to reach hs2 before sending EDUs + bob.MustSyncUntil(t, client.SyncReq{Since: bobSince}, client.SyncTimelineHasEventID(roomID, aclEventID)) + + // Join charlie to roomID after the ACL is set up + charlie.MustJoinRoom(t, roomID, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(charlie.UserID, roomID)) + charlieSince := charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(charlie.UserID, roomID)) + + // Bob starts typing (kind of EDU) in both rooms; typing in roomID should be dropped by ACLs + bob.SendTyping(t, roomID, true, 10000) + bob.SendTyping(t, sentinelRoom, true, 10000) + + // Bob sets read receipts (another kind of EDU) on both rooms; receipts in roomID should be dropped by ACLs + bob.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "read_markers"}, + client.WithJSONBody(t, map[string]interface{}{"m.read": aclEventID})) + + // Send a sentinel message to sentinelRoom to use as a receipt anchor + sentinelEventID := bob.SendEventSynced(t, sentinelRoom, b.Event{ + Type: "m.room.message", + Sender: bob.UserID, + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "sentinel", + }, + }) + bob.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", sentinelRoom, "read_markers"}, + client.WithJSONBody(t, map[string]interface{}{"m.read": sentinelEventID})) + + // Wait for the sentinel message and EDUs in sentinelRoom to arrive, proving the federation + // transaction containing hs2's EDUs was processed + alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, + client.SyncTimelineHasEventID(sentinelRoom, sentinelEventID), + client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool { + return result.Get("type").Str == "m.receipt" + }), + client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool { + return result.Get("type").Str == "m.typing" + }), + ) + charlie.MustSyncUntil(t, client.SyncReq{Since: charlieSince}, + client.SyncTimelineHasEventID(sentinelRoom, sentinelEventID), + client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool { + return result.Get("type").Str == "m.receipt" + }), + client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool { + return result.Get("type").Str == "m.typing" + }), + ) + + // Verify with alice (hs1) and charlie (hs3) that we never received EDU's from hs2 + for _, user := range []*client.CSAPI{alice, charlie} { + syncResp, _ := user.MustSync(t, client.SyncReq{}) + + // No typing or read receipts from the blocked server should appear in room with id roomID + ephemerals := syncResp.Get("rooms.join." + client.GjsonEscape(roomID) + ".ephemeral") + must.MatchGJSON(t, ephemerals, match.JSONKeyArrayOfSize("events", 0)) + + // sentinelRoom should have received the read receipt and typing notification + ephemerals = syncResp.Get("rooms.join." + client.GjsonEscape(sentinelRoom) + ".ephemeral") + must.MatchGJSON(t, ephemerals, match.JSONKeyArrayOfSize("events", 2)) + } +} diff --git a/tests/federation_room_get_missing_events_test.go b/tests/federation_room_get_missing_events_test.go index 9899f88bd..9743e4325 100644 --- a/tests/federation_room_get_missing_events_test.go +++ b/tests/federation_room_get_missing_events_test.go @@ -497,6 +497,16 @@ func TestOutboundFederationEventSizeGetMissingEvents(t *testing.T) { }).Methods("POST") ver := alice.GetDefaultRoomVersion(t) + // This test crafts a "bad" event which state_key is 280 bytes but only 70 + // codepoints. + // + // Room version 11 in Synapse switched from using codepoints to using + // bytes. Which means the 280-byte state_key would be rejected immediately. + // Use room version 10 in that case so the codepoint-based limit is in effect. + // + if gomatrixserverlib.MustGetRoomVersion(ver).StrictEventByteLimits() { + ver = gomatrixserverlib.RoomVersion("10") + } charlie := srv.UserID("charlie") room := srv.MustMakeRoom(t, ver, federation.InitialRoomEvents(ver, charlie)) roomAlias := srv.MakeAliasMapping("flibble", room.RoomID) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 4384411b8..6c93ffb53 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -62,6 +62,7 @@ func TestDelayedEvents(t *testing.T) { }) }) + // FIXME: Too much mixing of tests that should be more independent t.Run("delayed message events are sent on timeout", func(t *testing.T) { var res *http.Response var countExpected uint64 @@ -139,6 +140,7 @@ func TestDelayedEvents(t *testing.T) { stateKey := "to_send_on_timeout" + // Schedule a delayed event setterKey := "setter" setterExpected := "on_timeout" user.MustDo( @@ -151,8 +153,11 @@ func TestDelayedEvents(t *testing.T) { getDelayQueryParam("900"), ) + // Ensure that a delayed event is now scheduled matchDelayedEvents(t, user, delayedEventsNumberEqual(1)) - + // And includes the correct content + // + // FIXME: This assertion seems superfluous to this test and should be it's own test res = getDelayedEvents(t, user) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ @@ -168,19 +173,30 @@ func TestDelayedEvents(t *testing.T) { }), }, }) + + // Sanity check that the room state hasn't changed yet res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 404, }) + // Wait one second which will cause the delayed state event to be sent time.Sleep(1 * time.Second) - matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) + + // Check for the state change from the delayed state event (using `MustSyncUntil` to + // account for any processing or worker replication delays) + user.MustSyncUntil(t, client.SyncReq{UseStateAfter: true}, client.SyncStateAfterHas(roomID, func(ev gjson.Result) bool { + return ev.Get("type").Str == eventType && ev.Get("state_key").Str == stateKey + })) + // Make sure the state looks as expected after res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ match.JSONKeyEqual(setterKey, setterExpected), }, }) + // No more delayed events + matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) }) t.Run("cannot update a delayed event without an action", func(t *testing.T) { @@ -232,6 +248,7 @@ func TestDelayedEvents(t *testing.T) { stateKey := "to_never_send" + // Schedule a delayed event setterKey := "setter" setterExpected := "none" res = user.MustDo( @@ -245,22 +262,33 @@ func TestDelayedEvents(t *testing.T) { ) delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + // Wait a bit but not long enough for the delayed state event to be sent time.Sleep(1 * time.Second) + // We should still see the scheduled delayed event (hasn't been sent yet) matchDelayedEvents(t, user, delayedEventsNumberEqual(1)) + + // Sanity check that the room state hasn't changed res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 404, }) + // Cancel the delayed event unauthedClient.MustDo( t, "POST", getPathForUpdateDelayedEvent(delayID, DelayedEventActionCancel), client.WithJSONBody(t, map[string]interface{}{}), ) + // No more delayed events matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) + // Sanity check that the previously scheduled delayed event doesn't end up being sent anyway + // + // Wait another second which would cause the previously scheduled delayed to be sent + // as we've waited a total of 2s now (> 1.5s delay) time.Sleep(1 * time.Second) + // Sanity check that the room state hasn't changed res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 404, @@ -274,6 +302,7 @@ func TestDelayedEvents(t *testing.T) { stateKey := "to_send_on_request" + // Schedule a delayed event setterKey := "setter" setterExpected := "on_send" res = user.MustDo( @@ -287,26 +316,39 @@ func TestDelayedEvents(t *testing.T) { ) delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + // Wait a bit but not long enough for the delayed state event to be sent time.Sleep(1 * time.Second) + // We should still see the scheduled delayed event (hasn't been sent yet) matchDelayedEvents(t, user, delayedEventsNumberEqual(1)) + + // Sanity check that the room state hasn't changed yet res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 404, }) + // Force the delayed event to be sent immediately unauthedClient.MustDo( t, "POST", getPathForUpdateDelayedEvent(delayID, DelayedEventActionSend), client.WithJSONBody(t, map[string]interface{}{}), ) - matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) - res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + + // Check for the state change from the delayed state event (using `MustSyncUntil` to + // account for any processing or worker replication delays) + user.MustSyncUntil(t, client.SyncReq{UseStateAfter: true}, client.SyncStateAfterHas(roomID, func(ev gjson.Result) bool { + return ev.Get("type").Str == eventType && ev.Get("state_key").Str == stateKey + })) + // Make sure the state looks as expected after + res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ match.JSONKeyEqual(setterKey, setterExpected), }, }) + // No more delayed events + matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) }) t.Run("delayed state events can be restarted", func(t *testing.T) { @@ -316,6 +358,7 @@ func TestDelayedEvents(t *testing.T) { defer cleanupDelayedEvents(t, user) + // Schedule a delayed event setterKey := "setter" setterExpected := "on_timeout" res = user.MustDo( @@ -329,13 +372,18 @@ func TestDelayedEvents(t *testing.T) { ) delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + // Wait a bit but not long enough for the delayed state event to be sent time.Sleep(1 * time.Second) + // We should still see the scheduled delayed event (hasn't been sent yet) matchDelayedEvents(t, user, delayedEventsNumberEqual(1)) + + // Sanity check that the room state hasn't changed yet res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 404, }) + // Restart the timer on the delayed event unauthedClient.MustDo( t, "POST", @@ -343,21 +391,34 @@ func TestDelayedEvents(t *testing.T) { client.WithJSONBody(t, map[string]interface{}{}), ) + // Wait a bit but not long enough for the delayed state event to be sent time.Sleep(1 * time.Second) + // We should still see the scheduled delayed event (hasn't been sent yet) matchDelayedEvents(t, user, delayedEventsNumberEqual(1)) + + // Sanity check that the room state hasn't changed yet res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 404, }) + // Wait one second which will cause the delayed state event to be sent time.Sleep(1 * time.Second) - matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) + + // Check for the state change from the delayed state event (using `MustSyncUntil` to + // account for any processing or worker replication delays) + user.MustSyncUntil(t, client.SyncReq{UseStateAfter: true}, client.SyncStateAfterHas(roomID, func(ev gjson.Result) bool { + return ev.Get("type").Str == eventType && ev.Get("state_key").Str == stateKey + })) + // Make sure the state looks as expected after res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ match.JSONKeyEqual(setterKey, setterExpected), }, }) + // No more delayed events + matchDelayedEvents(t, user, delayedEventsNumberEqual(0)) }) t.Run("delayed state events are kept on server restart", func(t *testing.T) { @@ -439,8 +500,11 @@ func TestDelayedEvents(t *testing.T) { // Capture whatever number of delayed events are remaining after the server restart. remainingDelayedEventCount := countDelayedEvents(t, delayedEventResponse) // Sanity check that the room state was updated correctly with the delayed events - // that were sent. - user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey1)) + // that were sent. (using `MustSyncUntil` to account for any processing or worker + // replication delays) + user.MustSyncUntil(t, client.SyncReq{UseStateAfter: true}, client.SyncStateAfterHas(roomID, func(ev gjson.Result) bool { + return ev.Get("type").Str == eventType && ev.Get("state_key").Str == stateKey1 + })) // Wait until we see another delayed event being sent (ensure things resumed and are continuing). time.Sleep(10 * time.Second) @@ -448,11 +512,15 @@ func TestDelayedEvents(t *testing.T) { delayedEventsNumberLessThan(remainingDelayedEventCount), ) // Sanity check that the other delayed events also updated the room state correctly. + // (using `MustSyncUntil` to account for any processing or worker replication + // delays) // // FIXME: Ideally, we'd check specifically for the last one that was sent but it // will be a bit of a juggle and fiddly to get this right so for now we just check // one. - user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey2)) + user.MustSyncUntil(t, client.SyncReq{UseStateAfter: true}, client.SyncStateAfterHas(roomID, func(ev gjson.Result) bool { + return ev.Get("type").Str == eventType && ev.Get("state_key").Str == stateKey2 + })) }) } @@ -577,6 +645,8 @@ func matchDelayedEvents(t *testing.T, user *client.CSAPI, checks ...delayedEvent ) } +// FIXME: Instead of using `cleanupDelayedEvents`, each test should just use their own +// room func cleanupDelayedEvents(t *testing.T, user *client.CSAPI) { t.Helper() res := getDelayedEvents(t, user) diff --git a/tests/room_timestamp_to_event_test.go b/tests/room_timestamp_to_event_test.go index 9a0749fc2..125f66067 100644 --- a/tests/room_timestamp_to_event_test.go +++ b/tests/room_timestamp_to_event_test.go @@ -9,6 +9,7 @@ package tests import ( "fmt" + "net/http" "net/url" "strconv" "testing" @@ -204,19 +205,88 @@ func TestJumpToDateEndpoint(t *testing.T) { mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, timeBeforeRoomCreation, "b", importedEventID) }) - t.Run("can paginate after getting remote event from timestamp to event endpoint", func(t *testing.T) { + t.Run("can paginate backwards after getting remote event from timestamp to event endpoint (start)", func(t *testing.T) { t.Parallel() roomID, eventA, eventB := createTestRoom(t, alice) remoteCharlie.MustJoinRoom(t, roomID, []spec.ServerName{ deployment.GetFullyQualifiedHomeserverName(t, "hs1"), }) + // After Charlie's homeserver finds the event, it "should try to backfill this + // event" (per the spec, + // https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1timestamp_to_eventroomid) mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID) - // Get a pagination token from eventB + // And then "clients can call /rooms/{roomId}/context/{eventId} to obtain a + // pagination token to retrieve the events around the returned event." (per the + // spec, https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event). + // + // Get a pagination token that represents the position just *before* eventB contextRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "context", eventB.EventID}, client.WithContentType("application/json"), client.WithQueries(url.Values{ "limit": []string{"0"}, }), + // Retry as the worker backfilling and persisting the event isn't necessarily + // the same as the worker serving `/context` + client.WithRetryUntil(remoteCharlie.SyncUntilTimeout, func(res *http.Response) bool { + return res.StatusCode == 200 + }), + ) + contextResResBody := client.ParseJSON(t, contextRes) + // Remember: Tokens are positions between events. + // + // start end + // | | + // [A] <-- ▼ [B] ▼ <--- [remoteCharlie join] + // + // "start" is the token that represents the position just *before* eventB + paginationToken := client.GetJSONFieldStr(t, contextResResBody, "start") + + // Paginate backwards seamlessly from the `/context` request (start, point + // before eventB) + messagesRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"}, + client.WithContentType("application/json"), + client.WithQueries(url.Values{ + "dir": []string{"b"}, + "limit": []string{"100"}, + "from": []string{paginationToken}, + }), + ) + + // Make sure A is visible + must.MatchResponse(t, messagesRes, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONCheckOff("chunk", []interface{}{eventA.EventID}, match.CheckOffMapper(func(r gjson.Result) interface{} { + return r.Get("event_id").Str + }), match.CheckOffAllowUnwanted()), + }, + }) + }) + + t.Run("can paginate backwards after getting remote event from timestamp to event endpoint (end)", func(t *testing.T) { + t.Parallel() + roomID, eventA, eventB := createTestRoom(t, alice) + remoteCharlie.MustJoinRoom(t, roomID, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + // After Charlie's homeserver finds the event, it "should try to backfill this + // event" (per the spec, + // https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1timestamp_to_eventroomid) + mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID) + + // And then "clients can call /rooms/{roomId}/context/{eventId} to obtain a + // pagination token to retrieve the events around the returned event." (per the + // spec, https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event). + // + // Get a pagination token that represents the position just *after* eventB + contextRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "context", eventB.EventID}, + client.WithContentType("application/json"), client.WithQueries(url.Values{ + "limit": []string{"0"}, + }), + // Retry as the worker backfilling and persisting the event isn't necessarily + // the same as the worker serving `/context` + client.WithRetryUntil(remoteCharlie.SyncUntilTimeout, func(res *http.Response) bool { + return res.StatusCode == 200 + }), ) contextResResBody := client.ParseJSON(t, contextRes) // Remember: Tokens are positions between events. Normally, you would use the @@ -227,16 +297,12 @@ func TestJumpToDateEndpoint(t *testing.T) { // start end // | | // [A] <-- ▼ [B] ▼ <--- [remoteCharlie join] + // + // "end" is the token that represents the position just *after* eventB paginationToken := client.GetJSONFieldStr(t, contextResResBody, "end") - // Hit `/messages` until `eventA` has been backfilled and replicated across - // workers (the worker persisting events isn't necessarily the same as the worker - // serving `/messages`) - fetchUntilMessagesResponseHas(t, remoteCharlie, roomID, func(ev gjson.Result) bool { - return ev.Get("event_id").Str == eventA.EventID - }) - - // Paginate backwards from the point after eventB + // Paginate backwards seamlessly from the `/context` request (end, point after + // eventB) messagesRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ @@ -357,42 +423,6 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin } } -func fetchUntilMessagesResponseHas(t *testing.T, c *client.CSAPI, roomID string, check func(gjson.Result) bool) { - t.Helper() - start := time.Now() - checkCounter := 0 - for { - if time.Since(start) > c.SyncUntilTimeout { - t.Fatalf("fetchUntilMessagesResponseHas timed out. Called check function %d times", checkCounter) - } - - messagesRes := c.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ - "dir": []string{"b"}, - "limit": []string{"100"}, - })) - messsageResBody := client.ParseJSON(t, messagesRes) - wantKey := "chunk" - keyRes := gjson.GetBytes(messsageResBody, wantKey) - if !keyRes.Exists() { - t.Fatalf("missing key '%s'", wantKey) - } - if !keyRes.IsArray() { - t.Fatalf("key '%s' is not an array (was %s)", wantKey, keyRes.Type) - } - - events := keyRes.Array() - for _, ev := range events { - if check(ev) { - return - } - } - - checkCounter++ - // Add a slight delay so we don't hammmer the messages endpoint - time.Sleep(500 * time.Millisecond) - } -} - func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, roomID string, expectedEventId string, actualEventId string, givenTimestamp int64) string { t.Helper()