mirror of
https://github.com/hoernschen/dendrite.git
synced 2024-12-27 07:28:27 +00:00
Merge branch 'master' into neilalexander/onoldroomevent
This commit is contained in:
commit
125d63ffe6
121 changed files with 3172 additions and 648 deletions
71
.github/workflows/docker-hub.yml
vendored
Normal file
71
.github/workflows/docker-hub.yml
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Based on https://github.com/docker/build-push-action
|
||||
|
||||
name: "Docker Hub"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
DOCKER_NAMESPACE: matrixdotorg
|
||||
DOCKER_HUB_USER: dendritegithub
|
||||
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
jobs:
|
||||
Monolith:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Get release tag
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ env.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build monolith image
|
||||
id: docker_build_monolith
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./build/docker/Dockerfile.monolith
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:latest
|
||||
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:${{ env.RELEASE_VERSION }}
|
||||
|
||||
Polylith:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Get release tag
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ env.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build polylith image
|
||||
id: docker_build_polylith
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./build/docker/Dockerfile.polylith
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:latest
|
||||
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:${{ env.RELEASE_VERSION }}
|
41
CHANGES.md
41
CHANGES.md
|
@ -1,12 +1,51 @@
|
|||
# Changelog
|
||||
|
||||
## Dendrite 0.3.8 (2021-01-28)
|
||||
|
||||
### Fixes
|
||||
|
||||
* A well-known lookup regression in version 0.3.7 has been fixed
|
||||
|
||||
## Dendrite 0.3.7 (2021-01-26)
|
||||
|
||||
### Features
|
||||
|
||||
* Sync filtering support (for event types, senders and limits)
|
||||
* In-process DNS caching support for deployments where a local DNS caching resolver is not available (disabled by default)
|
||||
* Experimental support for MSC2444 (Peeking over Federation) has been merged
|
||||
* Experimental federation support for MSC2946 (Spaces Summary) has been merged
|
||||
|
||||
### Fixes
|
||||
|
||||
* Dendrite will no longer load a given event more than once for state resolution, which may help to reduce memory usage and database I/O slightly in some cases
|
||||
* Large well-known responses will no longer use significant amounts of memory
|
||||
|
||||
## Dendrite 0.3.6 (2021-01-18)
|
||||
|
||||
### Features
|
||||
|
||||
* Experimental support for MSC2946 (Spaces Summary) has been merged
|
||||
* Send-to-device messages have been refactored and now take advantage of having their own stream position, making delivery more reliable
|
||||
* Unstable features and MSCs are now listed in `/versions` (contributed by [sumitks866](https://github.com/sumitks866))
|
||||
* Well-known and DNS SRV record results for federated servers are now cached properly, improving outbound federation performance and reducing traffic
|
||||
|
||||
### Fixes
|
||||
|
||||
* Updating forward extremities will no longer result in so many unnecessary state snapshots, reducing on-going disk usage in the roomserver database
|
||||
* Pagination tokens for `/messages` have been fixed, which should improve the reliability of scrollback/pagination
|
||||
* Dendrite now avoids returning `null`s in fields of the `/sync` response, and omitting some fields altogether when not needed, which should fix sync issues with Element Android
|
||||
* Requests for user device lists now time out quicker, which prevents federated `/send` requests from also timing out in many cases
|
||||
* Empty push rules are no longer sent over and over again in `/sync`
|
||||
* An integer overflow in the device list updater which could result in panics on 32-bit platforms has been fixed (contributed by [Lesterpig](https://github.com/Lesterpig))
|
||||
* Event IDs are now logged properly in federation sender and sync API consumer errors
|
||||
|
||||
## Dendrite 0.3.5 (2021-01-11)
|
||||
|
||||
### Features
|
||||
|
||||
* All `/sync` streams are now logically separate after a refactoring exercise
|
||||
|
||||
## Fixes
|
||||
### Fixes
|
||||
|
||||
* Event references are now deeply checked properly when calculating forward extremities, reducing the amount of forward extremities in most cases, which improves RAM utilisation and reduces the work done by state resolution
|
||||
* Sync no longer sends incorrect `next_batch` tokens with old stream positions, reducing flashbacks of old messages in clients
|
||||
|
|
|
@ -89,7 +89,7 @@ func NewInternalAPI(
|
|||
// We can't add ASes at runtime so this is safe to do.
|
||||
if len(workerStates) > 0 {
|
||||
consumer := consumers.NewOutputRoomEventConsumer(
|
||||
base.Cfg, consumer, appserviceDB,
|
||||
base.ProcessContext, base.Cfg, consumer, appserviceDB,
|
||||
rsAPI, workerStates,
|
||||
)
|
||||
if err := consumer.Start(); err != nil {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
"github.com/Shopify/sarama"
|
||||
|
@ -41,6 +42,7 @@ type OutputRoomEventConsumer struct {
|
|||
// NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call
|
||||
// Start() to begin consuming from room servers.
|
||||
func NewOutputRoomEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.Dendrite,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
appserviceDB storage.Database,
|
||||
|
@ -48,6 +50,7 @@ func NewOutputRoomEventConsumer(
|
|||
workerStates []types.ApplicationServiceWorkerState,
|
||||
) *OutputRoomEventConsumer {
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "appservice/roomserver",
|
||||
Topic: cfg.Global.Kafka.TopicFor(config.TopicOutputRoomEvent),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
6
build.sh
6
build.sh
|
@ -17,6 +17,8 @@ else
|
|||
export FLAGS=""
|
||||
fi
|
||||
|
||||
go install -trimpath -ldflags "$FLAGS" -v $PWD/`dirname $0`/cmd/...
|
||||
mkdir -p bin
|
||||
|
||||
GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs
|
||||
CGO_ENABLED=1 go build -trimpath -ldflags "$FLAGS" -v -o "bin/" ./cmd/...
|
||||
|
||||
CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
FROM docker.io/golang:1.15-alpine AS builder
|
||||
|
||||
RUN apk --update --no-cache add bash build-base
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY . /build
|
||||
|
||||
RUN mkdir -p bin
|
||||
RUN sh ./build.sh
|
|
@ -1,11 +1,20 @@
|
|||
FROM matrixdotorg/dendrite:latest AS base
|
||||
FROM docker.io/golang:1.15-alpine AS base
|
||||
|
||||
RUN apk --update --no-cache add bash build-base
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY . /build
|
||||
|
||||
RUN mkdir -p bin
|
||||
RUN go build -trimpath -o bin/ ./cmd/dendrite-monolith-server
|
||||
RUN go build -trimpath -o bin/ ./cmd/goose
|
||||
RUN go build -trimpath -o bin/ ./cmd/create-account
|
||||
RUN go build -trimpath -o bin/ ./cmd/generate-keys
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=base /build/bin/dendrite-monolith-server /usr/bin
|
||||
COPY --from=base /build/bin/goose /usr/bin
|
||||
COPY --from=base /build/bin/create-account /usr/bin
|
||||
COPY --from=base /build/bin/generate-keys /usr/bin
|
||||
COPY --from=base /build/bin/* /usr/bin
|
||||
|
||||
VOLUME /etc/dendrite
|
||||
WORKDIR /etc/dendrite
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
FROM matrixdotorg/dendrite:latest AS base
|
||||
FROM docker.io/golang:1.15-alpine AS base
|
||||
|
||||
RUN apk --update --no-cache add bash build-base
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY . /build
|
||||
|
||||
RUN mkdir -p bin
|
||||
RUN go build -trimpath -o bin/ ./cmd/dendrite-polylith-multi
|
||||
RUN go build -trimpath -o bin/ ./cmd/goose
|
||||
RUN go build -trimpath -o bin/ ./cmd/create-account
|
||||
RUN go build -trimpath -o bin/ ./cmd/generate-keys
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=base /build/bin/dendrite-polylith-multi /usr/bin
|
||||
COPY --from=base /build/bin/goose /usr/bin
|
||||
COPY --from=base /build/bin/create-account /usr/bin
|
||||
COPY --from=base /build/bin/generate-keys /usr/bin
|
||||
COPY --from=base /build/bin/* /usr/bin
|
||||
|
||||
VOLUME /etc/dendrite
|
||||
WORKDIR /etc/dendrite
|
||||
|
|
|
@ -91,6 +91,17 @@ global:
|
|||
username: metrics
|
||||
password: metrics
|
||||
|
||||
# DNS cache options. The DNS cache may reduce the load on DNS servers
|
||||
# if there is no local caching resolver available for use.
|
||||
dns_cache:
|
||||
# Whether or not the DNS cache is enabled.
|
||||
enabled: false
|
||||
|
||||
# Maximum number of entries to hold in the DNS cache, and
|
||||
# for how long those items should be considered valid in seconds.
|
||||
cache_size: 256
|
||||
cache_lifetime: 300
|
||||
|
||||
# Configuration for the Appservice API.
|
||||
app_service_api:
|
||||
internal_api:
|
||||
|
|
|
@ -6,7 +6,5 @@ TAG=${1:-latest}
|
|||
|
||||
echo "Building tag '${TAG}'"
|
||||
|
||||
docker build -f build/docker/Dockerfile -t matrixdotorg/dendrite:${TAG} .
|
||||
|
||||
docker build -t matrixdotorg/dendrite-monolith:${TAG} -f build/docker/Dockerfile.monolith .
|
||||
docker build -t matrixdotorg/dendrite-polylith:${TAG} -f build/docker/Dockerfile.polylith .
|
|
@ -166,6 +166,7 @@ func (m *DendriteMonolith) Start() {
|
|||
),
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
base.PublicClientAPIMux,
|
||||
base.PublicFederationAPIMux,
|
||||
base.PublicKeyAPIMux,
|
||||
|
|
|
@ -46,6 +46,7 @@ func AddPublicRoutes(
|
|||
userAPI userapi.UserInternalAPI,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||
mscCfg *config.MSCs,
|
||||
) {
|
||||
_, producer := kafka.SetupConsumerProducer(&cfg.Matrix.Kafka)
|
||||
|
||||
|
@ -57,6 +58,6 @@ func AddPublicRoutes(
|
|||
routing.Setup(
|
||||
router, cfg, eduInputAPI, rsAPI, asAPI,
|
||||
accountsDB, userAPI, federation,
|
||||
syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider,
|
||||
syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, mscCfg,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -58,17 +58,24 @@ func Setup(
|
|||
federationSender federationSenderAPI.FederationSenderInternalAPI,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||
mscCfg *config.MSCs,
|
||||
) {
|
||||
rateLimits := newRateLimits(&cfg.RateLimiting)
|
||||
userInteractiveAuth := auth.NewUserInteractive(accountDB.GetAccountByPassword, cfg)
|
||||
|
||||
unstableFeatures := make(map[string]bool)
|
||||
for _, msc := range cfg.MSCs.MSCs {
|
||||
unstableFeatures["org.matrix."+msc] = true
|
||||
}
|
||||
|
||||
publicAPIMux.Handle("/versions",
|
||||
httputil.MakeExternalAPI("versions", func(req *http.Request) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct {
|
||||
Versions []string `json:"versions"`
|
||||
}{[]string{
|
||||
Versions []string `json:"versions"`
|
||||
UnstableFeatures map[string]bool `json:"unstable_features"`
|
||||
}{Versions: []string{
|
||||
"r0.0.1",
|
||||
"r0.1.0",
|
||||
"r0.2.0",
|
||||
|
@ -76,7 +83,7 @@ func Setup(
|
|||
"r0.4.0",
|
||||
"r0.5.0",
|
||||
"r0.6.1",
|
||||
}},
|
||||
}, UnstableFeatures: unstableFeatures},
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
@ -104,20 +111,23 @@ func Setup(
|
|||
)
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
r0mux.Handle("/peek/{roomIDOrAlias}",
|
||||
httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
if r := rateLimits.rateLimit(req); r != nil {
|
||||
return *r
|
||||
}
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return PeekRoomByIDOrAlias(
|
||||
req, device, rsAPI, accountDB, vars["roomIDOrAlias"],
|
||||
)
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
if mscCfg.Enabled("msc2753") {
|
||||
r0mux.Handle("/peek/{roomIDOrAlias}",
|
||||
httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
if r := rateLimits.rateLimit(req); r != nil {
|
||||
return *r
|
||||
}
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return PeekRoomByIDOrAlias(
|
||||
req, device, rsAPI, accountDB, vars["roomIDOrAlias"],
|
||||
)
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
}
|
||||
r0mux.Handle("/joined_rooms",
|
||||
httputil.MakeAuthAPI("joined_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return GetJoinedRooms(req, device, rsAPI)
|
||||
|
|
|
@ -76,9 +76,10 @@ func createFederationClient(
|
|||
"matrix",
|
||||
p2phttp.NewTransport(base.LibP2P, p2phttp.ProtocolOption("/matrix")),
|
||||
)
|
||||
return gomatrixserverlib.NewFederationClientWithTransport(
|
||||
return gomatrixserverlib.NewFederationClient(
|
||||
base.Base.Cfg.Global.ServerName, base.Base.Cfg.Global.KeyID,
|
||||
base.Base.Cfg.Global.PrivateKey, true, tr,
|
||||
base.Base.Cfg.Global.PrivateKey,
|
||||
gomatrixserverlib.WithTransport(tr),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -90,7 +91,9 @@ func createClient(
|
|||
"matrix",
|
||||
p2phttp.NewTransport(base.LibP2P, p2phttp.ProtocolOption("/matrix")),
|
||||
)
|
||||
return gomatrixserverlib.NewClientWithTransport(tr)
|
||||
return gomatrixserverlib.NewClient(
|
||||
gomatrixserverlib.WithTransport(tr),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -189,6 +192,7 @@ func main() {
|
|||
ExtPublicRoomsProvider: provider,
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.Base.ProcessContext,
|
||||
base.Base.PublicClientAPIMux,
|
||||
base.Base.PublicFederationAPIMux,
|
||||
base.Base.PublicKeyAPIMux,
|
||||
|
@ -231,5 +235,5 @@ func main() {
|
|||
}
|
||||
|
||||
// We want to block forever to let the HTTP and HTTPS handler serve the APIs
|
||||
select {}
|
||||
base.Base.WaitForShutdown()
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@ func main() {
|
|||
),
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
base.PublicClientAPIMux,
|
||||
base.PublicFederationAPIMux,
|
||||
base.PublicKeyAPIMux,
|
||||
|
@ -200,5 +201,6 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
select {}
|
||||
// We want to block forever to let the HTTP and HTTPS handler serve the APIs
|
||||
base.WaitForShutdown()
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@ func (n *Node) CreateClient(
|
|||
},
|
||||
},
|
||||
)
|
||||
return gomatrixserverlib.NewClientWithTransport(tr)
|
||||
return gomatrixserverlib.NewClient(
|
||||
gomatrixserverlib.WithTransport(tr),
|
||||
)
|
||||
}
|
||||
|
||||
func (n *Node) CreateFederationClient(
|
||||
|
@ -53,8 +55,9 @@ func (n *Node) CreateFederationClient(
|
|||
},
|
||||
},
|
||||
)
|
||||
return gomatrixserverlib.NewFederationClientWithTransport(
|
||||
return gomatrixserverlib.NewFederationClient(
|
||||
base.Cfg.Global.ServerName, base.Cfg.Global.KeyID,
|
||||
base.Cfg.Global.PrivateKey, true, tr,
|
||||
base.Cfg.Global.PrivateKey,
|
||||
gomatrixserverlib.WithTransport(tr),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -144,6 +144,7 @@ func main() {
|
|||
KeyAPI: keyAPI,
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
base.PublicClientAPIMux,
|
||||
base.PublicFederationAPIMux,
|
||||
base.PublicKeyAPIMux,
|
||||
|
@ -176,5 +177,5 @@ func main() {
|
|||
}
|
||||
|
||||
// We want to block forever to let the HTTP and HTTPS handler serve the APIs
|
||||
select {}
|
||||
base.WaitForShutdown()
|
||||
}
|
||||
|
|
|
@ -74,5 +74,6 @@ func main() {
|
|||
base := setup.NewBaseDendrite(cfg, component, false) // TODO
|
||||
defer base.Close() // nolint: errcheck
|
||||
|
||||
start(base, cfg)
|
||||
go start(base, cfg)
|
||||
base.WaitForShutdown()
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ func ClientAPI(base *setup.BaseDendrite, cfg *config.Dendrite) {
|
|||
clientapi.AddPublicRoutes(
|
||||
base.PublicClientAPIMux, &base.Cfg.ClientAPI, accountDB, federation,
|
||||
rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI, keyAPI, nil,
|
||||
&cfg.MSCs,
|
||||
)
|
||||
|
||||
base.SetupAndServeHTTP(
|
||||
|
|
|
@ -33,6 +33,7 @@ func FederationAPI(base *setup.BaseDendrite, cfg *config.Dendrite) {
|
|||
base.PublicFederationAPIMux, base.PublicKeyAPIMux,
|
||||
&base.Cfg.FederationAPI, userAPI, federation, keyRing,
|
||||
rsAPI, fsAPI, base.EDUServerClient(), keyAPI,
|
||||
&base.Cfg.MSCs,
|
||||
)
|
||||
|
||||
base.SetupAndServeHTTP(
|
||||
|
|
|
@ -27,6 +27,7 @@ func SyncAPI(base *setup.BaseDendrite, cfg *config.Dendrite) {
|
|||
rsAPI := base.RoomserverHTTPClient()
|
||||
|
||||
syncapi.AddPublicRoutes(
|
||||
base.ProcessContext,
|
||||
base.PublicClientAPIMux, userAPI, rsAPI,
|
||||
base.KeyServerHTTPClient(),
|
||||
federation, &cfg.SyncAPI,
|
||||
|
|
|
@ -139,16 +139,18 @@ func createFederationClient(cfg *config.Dendrite, node *go_http_js_libp2p.P2pLoc
|
|||
tr := go_http_js_libp2p.NewP2pTransport(node)
|
||||
|
||||
fed := gomatrixserverlib.NewFederationClient(
|
||||
cfg.Global.ServerName, cfg.Global.KeyID, cfg.Global.PrivateKey, true,
|
||||
cfg.Global.ServerName, cfg.Global.KeyID, cfg.Global.PrivateKey,
|
||||
gomatrixserverlib.WithTransport(tr),
|
||||
)
|
||||
fed.Client = *gomatrixserverlib.NewClientWithTransport(tr)
|
||||
|
||||
return fed
|
||||
}
|
||||
|
||||
func createClient(node *go_http_js_libp2p.P2pLocalNode) *gomatrixserverlib.Client {
|
||||
tr := go_http_js_libp2p.NewP2pTransport(node)
|
||||
return gomatrixserverlib.NewClientWithTransport(tr)
|
||||
return gomatrixserverlib.NewClient(
|
||||
gomatrixserverlib.WithTransport(tr),
|
||||
)
|
||||
}
|
||||
|
||||
func createP2PNode(privKey ed25519.PrivateKey) (serverName string, node *go_http_js_libp2p.P2pLocalNode) {
|
||||
|
@ -229,6 +231,7 @@ func main() {
|
|||
ExtPublicRoomsProvider: p2pPublicRoomProvider,
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
base.PublicClientAPIMux,
|
||||
base.PublicFederationAPIMux,
|
||||
base.PublicKeyAPIMux,
|
||||
|
|
|
@ -54,7 +54,6 @@ func main() {
|
|||
gomatrixserverlib.ServerName(*requestFrom),
|
||||
gomatrixserverlib.KeyID(keyBlock.Headers["Key-ID"]),
|
||||
privateKey,
|
||||
false,
|
||||
)
|
||||
|
||||
u, err := url.Parse(flag.Arg(0))
|
||||
|
|
|
@ -63,7 +63,7 @@ func main() {
|
|||
if *defaultsForCI {
|
||||
cfg.ClientAPI.RateLimiting.Enabled = false
|
||||
cfg.FederationSender.DisableTLSValidation = true
|
||||
cfg.MSCs.MSCs = []string{"msc2836"}
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946", "msc2444", "msc2753"}
|
||||
cfg.Logging[0].Level = "trace"
|
||||
// don't hit matrix.org when running tests!!!
|
||||
cfg.SigningKeyServer.KeyPerspectives = config.KeyPerspectives{}
|
||||
|
|
|
@ -103,6 +103,17 @@ global:
|
|||
username: metrics
|
||||
password: metrics
|
||||
|
||||
# DNS cache options. The DNS cache may reduce the load on DNS servers
|
||||
# if there is no local caching resolver available for use.
|
||||
dns_cache:
|
||||
# Whether or not the DNS cache is enabled.
|
||||
enabled: false
|
||||
|
||||
# Maximum number of entries to hold in the DNS cache, and
|
||||
# for how long those items should be considered valid in seconds.
|
||||
cache_size: 256
|
||||
cache_lifetime: 300
|
||||
|
||||
# Configuration for the Appservice API.
|
||||
app_service_api:
|
||||
internal_api:
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
## Peeking
|
||||
|
||||
Peeking is implemented as per [MSC2753](https://github.com/matrix-org/matrix-doc/pull/2753).
|
||||
Local peeking is implemented as per [MSC2753](https://github.com/matrix-org/matrix-doc/pull/2753).
|
||||
|
||||
Implementationwise, this means:
|
||||
* Users call `/peek` and `/unpeek` on the clientapi from a given device.
|
||||
* The clientapi delegates these via HTTP to the roomserver, which coordinates peeking in general for a given room
|
||||
* The roomserver writes an NewPeek event into the kafka log headed to the syncserver
|
||||
* The syncserver tracks the existence of the local peek in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peek` section of the /sync response.
|
||||
* The syncserver tracks the existence of the local peek in the syncapi_peeks table in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peek` section of the /sync response.
|
||||
|
||||
Questions (given this is [my](https://github.com/ara4n) first time hacking on Dendrite):
|
||||
* The whole clientapi -> roomserver -> syncapi flow to initiate a peek seems very indirect. Is there a reason not to just let syncapi itself host the implementation of `/peek`?
|
||||
Peeking over federation is implemented as per [MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444).
|
||||
|
||||
In future, peeking over federation will be added as per [MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444).
|
||||
* The `roomserver` will kick the `federationsender` much as it does for a federated `/join` in order to trigger a federated `/peek`
|
||||
* The `federationsender` tracks the existence of the remote peek in question
|
||||
For requests to peek our rooms ("inbound peeks"):
|
||||
* Remote servers call `/peek` on federationapi
|
||||
* The federationapi queries the federationsender to check if this is renewing an inbound peek or not.
|
||||
* If not, it hits the PerformInboundPeek on the roomserver to ask it for the current state of the room.
|
||||
* The roomserver atomically (in theory) adds a NewInboundPeek to its kafka stream to tell the federationserver to start peeking.
|
||||
* The federationsender receives the event, tracks the inbound peek in the federationsender_inbound_peeks table, and starts sending events to the peeking server.
|
||||
* The federationsender evicts stale inbound peeks which haven't been renewed.
|
||||
|
||||
For peeking into other server's rooms ("outbound peeks"):
|
||||
* The `roomserver` will kick the `federationsender` much as it does for a federated `/join` in order to trigger a federated outbound `/peek`
|
||||
* The `federationsender` tracks the existence of the outbound peek in in its federationsender_outbound_peeks table.
|
||||
* The `federationsender` regularly renews the remote peek as long as there are still peeking devices syncing for it.
|
||||
* TBD: how do we tell if there are no devices currently syncing for a given peeked room? The syncserver needs to tell the roomserver
|
||||
somehow who then needs to warn the federationsender.
|
|
@ -38,10 +38,11 @@ func AddPublicRoutes(
|
|||
federationSenderAPI federationSenderAPI.FederationSenderInternalAPI,
|
||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
mscCfg *config.MSCs,
|
||||
) {
|
||||
routing.Setup(
|
||||
fedRouter, keyRouter, cfg, rsAPI,
|
||||
eduAPI, federationSenderAPI, keyRing,
|
||||
federation, userAPI, keyAPI,
|
||||
federation, userAPI, keyAPI, mscCfg,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,12 +31,15 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) {
|
|||
fsAPI := base.FederationSenderHTTPClient()
|
||||
// TODO: This is pretty fragile, as if anything calls anything on these nils this test will break.
|
||||
// Unfortunately, it makes little sense to instantiate these dependencies when we just want to test routing.
|
||||
federationapi.AddPublicRoutes(base.PublicFederationAPIMux, base.PublicKeyAPIMux, &cfg.FederationAPI, nil, nil, keyRing, nil, fsAPI, nil, nil)
|
||||
federationapi.AddPublicRoutes(base.PublicFederationAPIMux, base.PublicKeyAPIMux, &cfg.FederationAPI, nil, nil, keyRing, nil, fsAPI, nil, nil, &cfg.MSCs)
|
||||
baseURL, cancel := test.ListenAndServe(t, base.PublicFederationAPIMux, true)
|
||||
defer cancel()
|
||||
serverName := gomatrixserverlib.ServerName(strings.TrimPrefix(baseURL, "https://"))
|
||||
|
||||
fedCli := gomatrixserverlib.NewFederationClient(serverName, cfg.Global.KeyID, cfg.Global.PrivateKey, true)
|
||||
fedCli := gomatrixserverlib.NewFederationClient(
|
||||
serverName, cfg.Global.KeyID, cfg.Global.PrivateKey,
|
||||
gomatrixserverlib.WithSkipVerify(true),
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
roomVer gomatrixserverlib.RoomVersion
|
||||
|
|
102
federationapi/routing/peek.go
Normal file
102
federationapi/routing/peek.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// Peek implements the SS /peek API, handling inbound peeks
|
||||
func Peek(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
cfg *config.FederationAPI,
|
||||
rsAPI api.RoomserverInternalAPI,
|
||||
roomID, peekID string,
|
||||
remoteVersions []gomatrixserverlib.RoomVersion,
|
||||
) util.JSONResponse {
|
||||
// TODO: check if we're just refreshing an existing peek by querying the federationsender
|
||||
|
||||
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||
if err := rsAPI.QueryRoomVersionForRoom(httpReq.Context(), &verReq, &verRes); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the room that the peeking server is trying to peek is actually
|
||||
// one of the room versions that they listed in their supported ?ver= in
|
||||
// the peek URL.
|
||||
remoteSupportsVersion := false
|
||||
for _, v := range remoteVersions {
|
||||
if v == verRes.RoomVersion {
|
||||
remoteSupportsVersion = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If it isn't, stop trying to peek the room.
|
||||
if !remoteSupportsVersion {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.IncompatibleRoomVersion(verRes.RoomVersion),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check history visibility
|
||||
|
||||
// tell the peeking server to renew every hour
|
||||
renewalInterval := int64(60 * 60 * 1000 * 1000)
|
||||
|
||||
var response api.PerformInboundPeekResponse
|
||||
err := rsAPI.PerformInboundPeek(
|
||||
httpReq.Context(),
|
||||
&api.PerformInboundPeekRequest{
|
||||
RoomID: roomID,
|
||||
PeekID: peekID,
|
||||
ServerName: request.Origin(),
|
||||
RenewalInterval: renewalInterval,
|
||||
},
|
||||
&response,
|
||||
)
|
||||
if err != nil {
|
||||
resErr := util.ErrorResponse(err)
|
||||
return resErr
|
||||
}
|
||||
|
||||
if !response.RoomExists {
|
||||
return util.JSONResponse{Code: http.StatusNotFound, JSON: nil}
|
||||
}
|
||||
|
||||
respPeek := gomatrixserverlib.RespPeek{
|
||||
StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents),
|
||||
AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents),
|
||||
RoomVersion: response.RoomVersion,
|
||||
LatestEvent: response.LatestEvent.Unwrap(),
|
||||
RenewalInterval: renewalInterval,
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: respPeek,
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ func Setup(
|
|||
federation *gomatrixserverlib.FederationClient,
|
||||
userAPI userapi.UserInternalAPI,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
mscCfg *config.MSCs,
|
||||
) {
|
||||
v2keysmux := keyMux.PathPrefix("/v2").Subrouter()
|
||||
v1fedmux := fedMux.PathPrefix("/v1").Subrouter()
|
||||
|
@ -229,7 +230,39 @@ func Setup(
|
|||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/make_join/{roomID}/{eventID}", httputil.MakeFedAPI(
|
||||
if mscCfg.Enabled("msc2444") {
|
||||
v1fedmux.Handle("/peek/{roomID}/{peekID}", httputil.MakeFedAPI(
|
||||
"federation_peek", cfg.Matrix.ServerName, keys, wakeup,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("Forbidden by server ACLs"),
|
||||
}
|
||||
}
|
||||
roomID := vars["roomID"]
|
||||
peekID := vars["peekID"]
|
||||
queryVars := httpReq.URL.Query()
|
||||
remoteVersions := []gomatrixserverlib.RoomVersion{}
|
||||
if vers, ok := queryVars["ver"]; ok {
|
||||
// The remote side supplied a ?ver= so use that to build up the list
|
||||
// of supported room versions
|
||||
for _, v := range vers {
|
||||
remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersion(v))
|
||||
}
|
||||
} else {
|
||||
// The remote side didn't supply a ?ver= so just assume that they only
|
||||
// support room version 1
|
||||
remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersionV1)
|
||||
}
|
||||
return Peek(
|
||||
httpReq, request, cfg, rsAPI, roomID, peekID, remoteVersions,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut, http.MethodDelete)
|
||||
}
|
||||
|
||||
v1fedmux.Handle("/make_join/{roomID}/{userID}", httputil.MakeFedAPI(
|
||||
"federation_make_join", cfg.Matrix.ServerName, keys, wakeup,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||
|
@ -239,11 +272,11 @@ func Setup(
|
|||
}
|
||||
}
|
||||
roomID := vars["roomID"]
|
||||
eventID := vars["eventID"]
|
||||
userID := vars["userID"]
|
||||
queryVars := httpReq.URL.Query()
|
||||
remoteVersions := []gomatrixserverlib.RoomVersion{}
|
||||
if vers, ok := queryVars["ver"]; ok {
|
||||
// The remote side supplied a ?=ver so use that to build up the list
|
||||
// The remote side supplied a ?ver= so use that to build up the list
|
||||
// of supported room versions
|
||||
for _, v := range vers {
|
||||
remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersion(v))
|
||||
|
@ -255,7 +288,7 @@ func Setup(
|
|||
remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersionV1)
|
||||
}
|
||||
return MakeJoin(
|
||||
httpReq, request, cfg, rsAPI, roomID, eventID, remoteVersions,
|
||||
httpReq, request, cfg, rsAPI, roomID, userID, remoteVersions,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
|
|
@ -22,6 +22,7 @@ type FederationClient interface {
|
|||
GetEvent(ctx context.Context, s gomatrixserverlib.ServerName, eventID string) (res gomatrixserverlib.Transaction, err error)
|
||||
GetServerKeys(ctx context.Context, matrixServer gomatrixserverlib.ServerName) (gomatrixserverlib.ServerKeys, error)
|
||||
MSC2836EventRelationships(ctx context.Context, dst gomatrixserverlib.ServerName, r gomatrixserverlib.MSC2836EventRelationshipsRequest, roomVersion gomatrixserverlib.RoomVersion) (res gomatrixserverlib.MSC2836EventRelationshipsResponse, err error)
|
||||
MSC2946Spaces(ctx context.Context, dst gomatrixserverlib.ServerName, roomID string, r gomatrixserverlib.MSC2946SpacesRequest) (res gomatrixserverlib.MSC2946SpacesResponse, err error)
|
||||
LookupServerKeys(ctx context.Context, s gomatrixserverlib.ServerName, keyRequests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) ([]gomatrixserverlib.ServerKeys, error)
|
||||
}
|
||||
|
||||
|
@ -61,6 +62,12 @@ type FederationSenderInternalAPI interface {
|
|||
request *PerformJoinRequest,
|
||||
response *PerformJoinResponse,
|
||||
)
|
||||
// Handle an instruction to peek a room on a remote server.
|
||||
PerformOutboundPeek(
|
||||
ctx context.Context,
|
||||
request *PerformOutboundPeekRequest,
|
||||
response *PerformOutboundPeekResponse,
|
||||
) error
|
||||
// Handle an instruction to make_leave & send_leave with a remote server.
|
||||
PerformLeave(
|
||||
ctx context.Context,
|
||||
|
@ -110,6 +117,16 @@ type PerformJoinResponse struct {
|
|||
LastError *gomatrix.HTTPError
|
||||
}
|
||||
|
||||
type PerformOutboundPeekRequest struct {
|
||||
RoomID string `json:"room_id"`
|
||||
// The sorted list of servers to try. Servers will be tried sequentially, after de-duplication.
|
||||
ServerNames types.ServerNames `json:"server_names"`
|
||||
}
|
||||
|
||||
type PerformOutboundPeekResponse struct {
|
||||
LastError *gomatrix.HTTPError
|
||||
}
|
||||
|
||||
type PerformLeaveRequest struct {
|
||||
RoomID string `json:"room_id"`
|
||||
UserID string `json:"user_id"`
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/federationsender/storage"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -44,6 +45,7 @@ type OutputEDUConsumer struct {
|
|||
|
||||
// NewOutputEDUConsumer creates a new OutputEDUConsumer. Call Start() to begin consuming from EDU servers.
|
||||
func NewOutputEDUConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationSender,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
queues *queue.OutgoingQueues,
|
||||
|
@ -51,18 +53,21 @@ func NewOutputEDUConsumer(
|
|||
) *OutputEDUConsumer {
|
||||
c := &OutputEDUConsumer{
|
||||
typingConsumer: &internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "eduserver/typing",
|
||||
Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputTypingEvent),
|
||||
Consumer: kafkaConsumer,
|
||||
PartitionStore: store,
|
||||
},
|
||||
sendToDeviceConsumer: &internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "eduserver/sendtodevice",
|
||||
Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputSendToDeviceEvent),
|
||||
Consumer: kafkaConsumer,
|
||||
PartitionStore: store,
|
||||
},
|
||||
receiptConsumer: &internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "eduserver/receipt",
|
||||
Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputReceiptEvent),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/keyserver/api"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -41,6 +42,7 @@ type KeyChangeConsumer struct {
|
|||
|
||||
// NewKeyChangeConsumer creates a new KeyChangeConsumer. Call Start() to begin consuming from key servers.
|
||||
func NewKeyChangeConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.KeyServer,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
queues *queue.OutgoingQueues,
|
||||
|
@ -49,6 +51,7 @@ func NewKeyChangeConsumer(
|
|||
) *KeyChangeConsumer {
|
||||
c := &KeyChangeConsumer{
|
||||
consumer: &internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "federationsender/keychange",
|
||||
Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputKeyChangeEvent)),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -41,6 +42,7 @@ type OutputRoomEventConsumer struct {
|
|||
|
||||
// NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers.
|
||||
func NewOutputRoomEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationSender,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
queues *queue.OutgoingQueues,
|
||||
|
@ -48,6 +50,7 @@ func NewOutputRoomEventConsumer(
|
|||
rsAPI api.RoomserverInternalAPI,
|
||||
) *OutputRoomEventConsumer {
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "federationsender/roomserver",
|
||||
Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputRoomEvent)),
|
||||
Consumer: kafkaConsumer,
|
||||
|
@ -102,6 +105,7 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
|||
default:
|
||||
// panic rather than continue with an inconsistent database
|
||||
log.WithFields(log.Fields{
|
||||
"event_id": ev.EventID(),
|
||||
"event": string(ev.JSON()),
|
||||
"add": output.NewRoomEvent.AddsStateEventIDs,
|
||||
"del": output.NewRoomEvent.RemovesStateEventIDs,
|
||||
|
@ -110,6 +114,14 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
case api.OutputTypeNewInboundPeek:
|
||||
if err := s.processInboundPeek(*output.NewInboundPeek); err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"event": output.NewInboundPeek,
|
||||
log.ErrorKey: err,
|
||||
}).Panicf("roomserver output log: remote peek event failure")
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
log.WithField("type", output.Type).Debug(
|
||||
"roomserver output log: ignoring unknown output type",
|
||||
|
@ -120,6 +132,23 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// processInboundPeek starts tracking a new federated inbound peek (replacing the existing one if any)
|
||||
// causing the federationsender to start sending messages to the peeking server
|
||||
func (s *OutputRoomEventConsumer) processInboundPeek(orp api.OutputNewInboundPeek) error {
|
||||
|
||||
// FIXME: there's a race here - we should start /sending new peeked events
|
||||
// atomically after the orp.LatestEventID to ensure there are no gaps between
|
||||
// the peek beginning and the send stream beginning.
|
||||
//
|
||||
// We probably need to track orp.LatestEventID on the inbound peek, but it's
|
||||
// unclear how we then use that to prevent the race when we start the send
|
||||
// stream.
|
||||
//
|
||||
// This is making the tests flakey.
|
||||
|
||||
return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval)
|
||||
}
|
||||
|
||||
// processMessage updates the list of currently joined hosts in the room
|
||||
// and then sends the event to the hosts that were joined before the event.
|
||||
func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) error {
|
||||
|
@ -163,6 +192,10 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err
|
|||
return err
|
||||
}
|
||||
|
||||
// TODO: do housekeeping to evict unrenewed peeking hosts
|
||||
|
||||
// TODO: implement query to let the fedapi check whether a given peek is live or not
|
||||
|
||||
// Send the event.
|
||||
return s.queues.SendEvent(
|
||||
ore.Event, gomatrixserverlib.ServerName(ore.SendAsServer), joinedHostsAtEvent,
|
||||
|
@ -170,7 +203,7 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err
|
|||
}
|
||||
|
||||
// joinedHostsAtEvent works out a list of matrix servers that were joined to
|
||||
// the room at the event.
|
||||
// the room at the event (including peeking ones)
|
||||
// It is important to use the state at the event for sending messages because:
|
||||
// 1) We shouldn't send messages to servers that weren't in the room.
|
||||
// 2) If a server is kicked from the rooms it should still be told about the
|
||||
|
@ -221,6 +254,15 @@ func (s *OutputRoomEventConsumer) joinedHostsAtEvent(
|
|||
joined[joinedHost.ServerName] = true
|
||||
}
|
||||
|
||||
// handle peeking hosts
|
||||
inboundPeeks, err := s.db.GetInboundPeeks(context.TODO(), ore.Event.Event.RoomID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, inboundPeek := range inboundPeeks {
|
||||
joined[inboundPeek.ServerName] = true
|
||||
}
|
||||
|
||||
var result []gomatrixserverlib.ServerName
|
||||
for serverName, include := range joined {
|
||||
if include {
|
||||
|
|
|
@ -59,7 +59,8 @@ func NewInternalAPI(
|
|||
consumer, _ := kafka.SetupConsumerProducer(&cfg.Matrix.Kafka)
|
||||
|
||||
queues := queue.NewOutgoingQueues(
|
||||
federationSenderDB, cfg.Matrix.DisableFederation,
|
||||
federationSenderDB, base.ProcessContext,
|
||||
cfg.Matrix.DisableFederation,
|
||||
cfg.Matrix.ServerName, federation, rsAPI, stats,
|
||||
&queue.SigningInfo{
|
||||
KeyID: cfg.Matrix.KeyID,
|
||||
|
@ -69,7 +70,7 @@ func NewInternalAPI(
|
|||
)
|
||||
|
||||
rsConsumer := consumers.NewOutputRoomEventConsumer(
|
||||
cfg, consumer, queues,
|
||||
base.ProcessContext, cfg, consumer, queues,
|
||||
federationSenderDB, rsAPI,
|
||||
)
|
||||
if err = rsConsumer.Start(); err != nil {
|
||||
|
@ -77,13 +78,13 @@ func NewInternalAPI(
|
|||
}
|
||||
|
||||
tsConsumer := consumers.NewOutputEDUConsumer(
|
||||
cfg, consumer, queues, federationSenderDB,
|
||||
base.ProcessContext, cfg, consumer, queues, federationSenderDB,
|
||||
)
|
||||
if err := tsConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start typing server consumer")
|
||||
}
|
||||
keyConsumer := consumers.NewKeyChangeConsumer(
|
||||
&base.Cfg.KeyServer, consumer, queues, federationSenderDB, rsAPI,
|
||||
base.ProcessContext, &base.Cfg.KeyServer, consumer, queues, federationSenderDB, rsAPI,
|
||||
)
|
||||
if err := keyConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start key server consumer")
|
||||
|
|
|
@ -244,3 +244,17 @@ func (a *FederationSenderInternalAPI) MSC2836EventRelationships(
|
|||
}
|
||||
return ires.(gomatrixserverlib.MSC2836EventRelationshipsResponse), nil
|
||||
}
|
||||
|
||||
func (a *FederationSenderInternalAPI) MSC2946Spaces(
|
||||
ctx context.Context, s gomatrixserverlib.ServerName, roomID string, r gomatrixserverlib.MSC2946SpacesRequest,
|
||||
) (res gomatrixserverlib.MSC2946SpacesResponse, err error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
ires, err := a.doRequest(s, func() (interface{}, error) {
|
||||
return a.federation.MSC2946Spaces(ctx, s, roomID, r)
|
||||
})
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return ires.(gomatrixserverlib.MSC2946SpacesResponse), nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationsender/api"
|
||||
"github.com/matrix-org/dendrite/federationsender/internal/perform"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
|
@ -218,9 +217,9 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer(
|
|||
|
||||
// Sanity-check the join response to ensure that it has a create
|
||||
// event, that the room version is known, etc.
|
||||
if err := sanityCheckSendJoinResponse(respSendJoin); err != nil {
|
||||
if err := sanityCheckAuthChain(respSendJoin.AuthEvents); err != nil {
|
||||
cancel()
|
||||
return fmt.Errorf("sanityCheckSendJoinResponse: %w", err)
|
||||
return fmt.Errorf("sanityCheckAuthChain: %w", err)
|
||||
}
|
||||
|
||||
// Process the join response in a goroutine. The idea here is
|
||||
|
@ -231,11 +230,9 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer(
|
|||
go func() {
|
||||
defer cancel()
|
||||
|
||||
// Check that the send_join response was valid.
|
||||
joinCtx := perform.JoinContext(r.federation, r.keyRing)
|
||||
respState, err := joinCtx.CheckSendJoinResponse(
|
||||
ctx, event, serverName, respMakeJoin, respSendJoin,
|
||||
)
|
||||
// TODO: Can we expand Check here to return a list of missing auth
|
||||
// events rather than failing one at a time?
|
||||
respState, err := respSendJoin.Check(ctx, r.keyRing, event, federatedAuthProvider(ctx, r.federation, r.keyRing, serverName))
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"room_id": roomID,
|
||||
|
@ -266,6 +263,181 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer(
|
|||
return nil
|
||||
}
|
||||
|
||||
// PerformOutboundPeekRequest implements api.FederationSenderInternalAPI
|
||||
func (r *FederationSenderInternalAPI) PerformOutboundPeek(
|
||||
ctx context.Context,
|
||||
request *api.PerformOutboundPeekRequest,
|
||||
response *api.PerformOutboundPeekResponse,
|
||||
) error {
|
||||
// Look up the supported room versions.
|
||||
var supportedVersions []gomatrixserverlib.RoomVersion
|
||||
for version := range version.SupportedRoomVersions() {
|
||||
supportedVersions = append(supportedVersions, version)
|
||||
}
|
||||
|
||||
// Deduplicate the server names we were provided but keep the ordering
|
||||
// as this encodes useful information about which servers are most likely
|
||||
// to respond.
|
||||
seenSet := make(map[gomatrixserverlib.ServerName]bool)
|
||||
var uniqueList []gomatrixserverlib.ServerName
|
||||
for _, srv := range request.ServerNames {
|
||||
if seenSet[srv] {
|
||||
continue
|
||||
}
|
||||
seenSet[srv] = true
|
||||
uniqueList = append(uniqueList, srv)
|
||||
}
|
||||
request.ServerNames = uniqueList
|
||||
|
||||
// See if there's an existing outbound peek for this room ID with
|
||||
// one of the specified servers.
|
||||
if peeks, err := r.db.GetOutboundPeeks(ctx, request.RoomID); err == nil {
|
||||
for _, peek := range peeks {
|
||||
if _, ok := seenSet[peek.ServerName]; ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try each server that we were provided until we land on one that
|
||||
// successfully completes the peek
|
||||
var lastErr error
|
||||
for _, serverName := range request.ServerNames {
|
||||
if err := r.performOutboundPeekUsingServer(
|
||||
ctx,
|
||||
request.RoomID,
|
||||
serverName,
|
||||
supportedVersions,
|
||||
); err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"server_name": serverName,
|
||||
"room_id": request.RoomID,
|
||||
}).Warnf("Failed to peek room through server")
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// We're all good.
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we reach here then we didn't complete a peek for some reason.
|
||||
var httpErr gomatrix.HTTPError
|
||||
if ok := errors.As(lastErr, &httpErr); ok {
|
||||
httpErr.Message = string(httpErr.Contents)
|
||||
// Clear the wrapped error, else serialising to JSON (in polylith mode) will fail
|
||||
httpErr.WrappedError = nil
|
||||
response.LastError = &httpErr
|
||||
} else {
|
||||
response.LastError = &gomatrix.HTTPError{
|
||||
Code: 0,
|
||||
WrappedError: nil,
|
||||
Message: lastErr.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Errorf(
|
||||
"failed to peek room %q through %d server(s): last error %s",
|
||||
request.RoomID, len(request.ServerNames), lastErr,
|
||||
)
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer(
|
||||
ctx context.Context,
|
||||
roomID string,
|
||||
serverName gomatrixserverlib.ServerName,
|
||||
supportedVersions []gomatrixserverlib.RoomVersion,
|
||||
) error {
|
||||
// create a unique ID for this peek.
|
||||
// for now we just use the room ID again. In future, if we ever
|
||||
// support concurrent peeks to the same room with different filters
|
||||
// then we would need to disambiguate further.
|
||||
peekID := roomID
|
||||
|
||||
// check whether we're peeking already to try to avoid needlessly
|
||||
// re-peeking on the server. we don't need a transaction for this,
|
||||
// given this is a nice-to-have.
|
||||
outboundPeek, err := r.db.GetOutboundPeek(ctx, serverName, roomID, peekID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
renewing := false
|
||||
if outboundPeek != nil {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
if nowMilli > outboundPeek.RenewedTimestamp+outboundPeek.RenewalInterval {
|
||||
logrus.Infof("stale outbound peek to %s for %s already exists; renewing", serverName, roomID)
|
||||
renewing = true
|
||||
} else {
|
||||
logrus.Infof("live outbound peek to %s for %s already exists", serverName, roomID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Try to perform an outbound /peek using the information supplied in the
|
||||
// request.
|
||||
respPeek, err := r.federation.Peek(
|
||||
ctx,
|
||||
serverName,
|
||||
roomID,
|
||||
peekID,
|
||||
supportedVersions,
|
||||
)
|
||||
if err != nil {
|
||||
r.statistics.ForServer(serverName).Failure()
|
||||
return fmt.Errorf("r.federation.Peek: %w", err)
|
||||
}
|
||||
r.statistics.ForServer(serverName).Success()
|
||||
|
||||
// Work out if we support the room version that has been supplied in
|
||||
// the peek response.
|
||||
if respPeek.RoomVersion == "" {
|
||||
respPeek.RoomVersion = gomatrixserverlib.RoomVersionV1
|
||||
}
|
||||
if _, err = respPeek.RoomVersion.EventFormat(); err != nil {
|
||||
return fmt.Errorf("respPeek.RoomVersion.EventFormat: %w", err)
|
||||
}
|
||||
|
||||
// we have the peek state now so let's process regardless of whether upstream gives up
|
||||
ctx = context.Background()
|
||||
|
||||
respState := respPeek.ToRespState()
|
||||
// authenticate the state returned (check its auth events etc)
|
||||
// the equivalent of CheckSendJoinResponse()
|
||||
if err = sanityCheckAuthChain(respState.AuthEvents); err != nil {
|
||||
return fmt.Errorf("sanityCheckAuthChain: %w", err)
|
||||
}
|
||||
if err = respState.Check(ctx, r.keyRing, federatedAuthProvider(ctx, r.federation, r.keyRing, serverName)); err != nil {
|
||||
return fmt.Errorf("Error checking state returned from peeking: %w", err)
|
||||
}
|
||||
|
||||
// If we've got this far, the remote server is peeking.
|
||||
if renewing {
|
||||
if err = r.db.RenewOutboundPeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = r.db.AddOutboundPeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// logrus.Warnf("got respPeek %#v", respPeek)
|
||||
// Send the newly returned state to the roomserver to update our local view.
|
||||
if err = roomserverAPI.SendEventWithState(
|
||||
ctx, r.rsAPI,
|
||||
roomserverAPI.KindNew,
|
||||
&respState,
|
||||
respPeek.LatestEvent.Headered(respPeek.RoomVersion),
|
||||
nil,
|
||||
); err != nil {
|
||||
return fmt.Errorf("r.producer.SendEventWithState: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PerformLeaveRequest implements api.FederationSenderInternalAPI
|
||||
func (r *FederationSenderInternalAPI) PerformLeave(
|
||||
ctx context.Context,
|
||||
|
@ -441,9 +613,9 @@ func (r *FederationSenderInternalAPI) PerformBroadcastEDU(
|
|||
return nil
|
||||
}
|
||||
|
||||
func sanityCheckSendJoinResponse(respSendJoin gomatrixserverlib.RespSendJoin) error {
|
||||
func sanityCheckAuthChain(authChain []*gomatrixserverlib.Event) error {
|
||||
// sanity check we have a create event and it has a known room version
|
||||
for _, ev := range respSendJoin.AuthEvents {
|
||||
for _, ev := range authChain {
|
||||
if ev.Type() == gomatrixserverlib.MRoomCreate && ev.StateKeyEquals("") {
|
||||
// make sure the room version is known
|
||||
content := ev.Content()
|
||||
|
@ -461,12 +633,12 @@ func sanityCheckSendJoinResponse(respSendJoin gomatrixserverlib.RespSendJoin) er
|
|||
}
|
||||
knownVersions := gomatrixserverlib.RoomVersions()
|
||||
if _, ok := knownVersions[gomatrixserverlib.RoomVersion(verBody.Version)]; !ok {
|
||||
return fmt.Errorf("send_join m.room.create event has an unknown room version: %s", verBody.Version)
|
||||
return fmt.Errorf("auth chain m.room.create event has an unknown room version: %s", verBody.Version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("send_join response is missing m.room.create event")
|
||||
return fmt.Errorf("auth chain response is missing m.room.create event")
|
||||
}
|
||||
|
||||
func setDefaultRoomVersionFromJoinEvent(joinEvent gomatrixserverlib.EventBuilder) gomatrixserverlib.RoomVersion {
|
||||
|
@ -490,3 +662,71 @@ func setDefaultRoomVersionFromJoinEvent(joinEvent gomatrixserverlib.EventBuilder
|
|||
}
|
||||
return gomatrixserverlib.RoomVersionV4
|
||||
}
|
||||
|
||||
// FederatedAuthProvider is an auth chain provider which fetches events from the server provided
|
||||
func federatedAuthProvider(
|
||||
ctx context.Context, federation *gomatrixserverlib.FederationClient,
|
||||
keyRing gomatrixserverlib.JSONVerifier, server gomatrixserverlib.ServerName,
|
||||
) gomatrixserverlib.AuthChainProvider {
|
||||
// A list of events that we have retried, if they were not included in
|
||||
// the auth events supplied in the send_join.
|
||||
retries := map[string][]*gomatrixserverlib.Event{}
|
||||
|
||||
// Define a function which we can pass to Check to retrieve missing
|
||||
// auth events inline. This greatly increases our chances of not having
|
||||
// to repeat the entire set of checks just for a missing event or two.
|
||||
return func(roomVersion gomatrixserverlib.RoomVersion, eventIDs []string) ([]*gomatrixserverlib.Event, error) {
|
||||
returning := []*gomatrixserverlib.Event{}
|
||||
|
||||
// See if we have retry entries for each of the supplied event IDs.
|
||||
for _, eventID := range eventIDs {
|
||||
// If we've already satisfied a request for this event ID before then
|
||||
// just append the results. We won't retry the request.
|
||||
if retry, ok := retries[eventID]; ok {
|
||||
if retry == nil {
|
||||
return nil, fmt.Errorf("missingAuth: not retrying failed event ID %q", eventID)
|
||||
}
|
||||
returning = append(returning, retry...)
|
||||
continue
|
||||
}
|
||||
|
||||
// Make a note of the fact that we tried to do something with this
|
||||
// event ID, even if we don't succeed.
|
||||
retries[eventID] = nil
|
||||
|
||||
// Try to retrieve the event from the server that sent us the send
|
||||
// join response.
|
||||
tx, txerr := federation.GetEvent(ctx, server, eventID)
|
||||
if txerr != nil {
|
||||
return nil, fmt.Errorf("missingAuth r.federation.GetEvent: %w", txerr)
|
||||
}
|
||||
|
||||
// For each event returned, add it to the set of return events. We
|
||||
// also will populate the retries, in case someone asks for this
|
||||
// event ID again.
|
||||
for _, pdu := range tx.PDUs {
|
||||
// Try to parse the event.
|
||||
ev, everr := gomatrixserverlib.NewEventFromUntrustedJSON(pdu, roomVersion)
|
||||
if everr != nil {
|
||||
return nil, fmt.Errorf("missingAuth gomatrixserverlib.NewEventFromUntrustedJSON: %w", everr)
|
||||
}
|
||||
|
||||
// Check the signatures of the event.
|
||||
if res, err := gomatrixserverlib.VerifyEventSignatures(ctx, []*gomatrixserverlib.Event{ev}, keyRing); err != nil {
|
||||
return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err)
|
||||
} else {
|
||||
for _, err := range res {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the event is OK then add it to the results and the retry map.
|
||||
returning = append(returning, ev)
|
||||
retries[ev.EventID()] = append(retries[ev.EventID()], ev)
|
||||
}
|
||||
}
|
||||
return returning, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
package perform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// This file contains helpers for the PerformJoin function.
|
||||
|
||||
type joinContext struct {
|
||||
federation *gomatrixserverlib.FederationClient
|
||||
keyRing *gomatrixserverlib.KeyRing
|
||||
}
|
||||
|
||||
// Returns a new join context.
|
||||
func JoinContext(f *gomatrixserverlib.FederationClient, k *gomatrixserverlib.KeyRing) *joinContext {
|
||||
return &joinContext{
|
||||
federation: f,
|
||||
keyRing: k,
|
||||
}
|
||||
}
|
||||
|
||||
// checkSendJoinResponse checks that all of the signatures are correct
|
||||
// and that the join is allowed by the supplied state.
|
||||
func (r joinContext) CheckSendJoinResponse(
|
||||
ctx context.Context,
|
||||
event *gomatrixserverlib.Event,
|
||||
server gomatrixserverlib.ServerName,
|
||||
respMakeJoin gomatrixserverlib.RespMakeJoin,
|
||||
respSendJoin gomatrixserverlib.RespSendJoin,
|
||||
) (*gomatrixserverlib.RespState, error) {
|
||||
// A list of events that we have retried, if they were not included in
|
||||
// the auth events supplied in the send_join.
|
||||
retries := map[string][]*gomatrixserverlib.Event{}
|
||||
|
||||
// Define a function which we can pass to Check to retrieve missing
|
||||
// auth events inline. This greatly increases our chances of not having
|
||||
// to repeat the entire set of checks just for a missing event or two.
|
||||
missingAuth := func(roomVersion gomatrixserverlib.RoomVersion, eventIDs []string) ([]*gomatrixserverlib.Event, error) {
|
||||
returning := []*gomatrixserverlib.Event{}
|
||||
|
||||
// See if we have retry entries for each of the supplied event IDs.
|
||||
for _, eventID := range eventIDs {
|
||||
// If we've already satisfied a request for this event ID before then
|
||||
// just append the results. We won't retry the request.
|
||||
if retry, ok := retries[eventID]; ok {
|
||||
if retry == nil {
|
||||
return nil, fmt.Errorf("missingAuth: not retrying failed event ID %q", eventID)
|
||||
}
|
||||
returning = append(returning, retry...)
|
||||
continue
|
||||
}
|
||||
|
||||
// Make a note of the fact that we tried to do something with this
|
||||
// event ID, even if we don't succeed.
|
||||
retries[event.EventID()] = nil
|
||||
|
||||
// Try to retrieve the event from the server that sent us the send
|
||||
// join response.
|
||||
tx, txerr := r.federation.GetEvent(ctx, server, eventID)
|
||||
if txerr != nil {
|
||||
return nil, fmt.Errorf("missingAuth r.federation.GetEvent: %w", txerr)
|
||||
}
|
||||
|
||||
// For each event returned, add it to the set of return events. We
|
||||
// also will populate the retries, in case someone asks for this
|
||||
// event ID again.
|
||||
for _, pdu := range tx.PDUs {
|
||||
// Try to parse the event.
|
||||
ev, everr := gomatrixserverlib.NewEventFromUntrustedJSON(pdu, roomVersion)
|
||||
if everr != nil {
|
||||
return nil, fmt.Errorf("missingAuth gomatrixserverlib.NewEventFromUntrustedJSON: %w", everr)
|
||||
}
|
||||
|
||||
// Check the signatures of the event.
|
||||
if res, err := gomatrixserverlib.VerifyEventSignatures(ctx, []*gomatrixserverlib.Event{ev}, r.keyRing); err != nil {
|
||||
return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err)
|
||||
} else {
|
||||
for _, err := range res {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the event is OK then add it to the results and the retry map.
|
||||
returning = append(returning, ev)
|
||||
retries[event.EventID()] = append(retries[event.EventID()], ev)
|
||||
retries[ev.EventID()] = append(retries[ev.EventID()], ev)
|
||||
}
|
||||
}
|
||||
|
||||
return returning, nil
|
||||
}
|
||||
|
||||
// TODO: Can we expand Check here to return a list of missing auth
|
||||
// events rather than failing one at a time?
|
||||
rs, err := respSendJoin.Check(ctx, r.keyRing, event, missingAuth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("respSendJoin: %w", err)
|
||||
}
|
||||
return rs, nil
|
||||
}
|
|
@ -20,6 +20,7 @@ const (
|
|||
FederationSenderPerformJoinRequestPath = "/federationsender/performJoinRequest"
|
||||
FederationSenderPerformLeaveRequestPath = "/federationsender/performLeaveRequest"
|
||||
FederationSenderPerformInviteRequestPath = "/federationsender/performInviteRequest"
|
||||
FederationSenderPerformOutboundPeekRequestPath = "/federationsender/performOutboundPeekRequest"
|
||||
FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive"
|
||||
FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU"
|
||||
|
||||
|
@ -33,6 +34,7 @@ const (
|
|||
FederationSenderGetServerKeysPath = "/federationsender/client/getServerKeys"
|
||||
FederationSenderLookupServerKeysPath = "/federationsender/client/lookupServerKeys"
|
||||
FederationSenderEventRelationshipsPath = "/federationsender/client/msc2836eventRelationships"
|
||||
FederationSenderSpacesSummaryPath = "/federationsender/client/msc2946spacesSummary"
|
||||
)
|
||||
|
||||
// NewFederationSenderClient creates a FederationSenderInternalAPI implemented by talking to a HTTP POST API.
|
||||
|
@ -75,6 +77,19 @@ func (h *httpFederationSenderInternalAPI) PerformInvite(
|
|||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
}
|
||||
|
||||
// Handle starting a peek on a remote server.
|
||||
func (h *httpFederationSenderInternalAPI) PerformOutboundPeek(
|
||||
ctx context.Context,
|
||||
request *api.PerformOutboundPeekRequest,
|
||||
response *api.PerformOutboundPeekResponse,
|
||||
) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformOutboundPeekRequest")
|
||||
defer span.Finish()
|
||||
|
||||
apiURL := h.federationSenderURL + FederationSenderPerformOutboundPeekRequestPath
|
||||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
}
|
||||
|
||||
func (h *httpFederationSenderInternalAPI) PerformServersAlive(
|
||||
ctx context.Context,
|
||||
request *api.PerformServersAliveRequest,
|
||||
|
@ -449,3 +464,34 @@ func (h *httpFederationSenderInternalAPI) MSC2836EventRelationships(
|
|||
}
|
||||
return response.Res, nil
|
||||
}
|
||||
|
||||
type spacesReq struct {
|
||||
S gomatrixserverlib.ServerName
|
||||
Req gomatrixserverlib.MSC2946SpacesRequest
|
||||
RoomID string
|
||||
Res gomatrixserverlib.MSC2946SpacesResponse
|
||||
Err *api.FederationClientError
|
||||
}
|
||||
|
||||
func (h *httpFederationSenderInternalAPI) MSC2946Spaces(
|
||||
ctx context.Context, dst gomatrixserverlib.ServerName, roomID string, r gomatrixserverlib.MSC2946SpacesRequest,
|
||||
) (res gomatrixserverlib.MSC2946SpacesResponse, err error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "MSC2946Spaces")
|
||||
defer span.Finish()
|
||||
|
||||
request := spacesReq{
|
||||
S: dst,
|
||||
Req: r,
|
||||
RoomID: roomID,
|
||||
}
|
||||
var response spacesReq
|
||||
apiURL := h.federationSenderURL + FederationSenderSpacesSummaryPath
|
||||
err = httputil.PostJSON(ctx, span, h.httpClient, apiURL, &request, &response)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if response.Err != nil {
|
||||
return res, response.Err
|
||||
}
|
||||
return response.Res, nil
|
||||
}
|
||||
|
|
|
@ -329,4 +329,26 @@ func AddRoutes(intAPI api.FederationSenderInternalAPI, internalAPIMux *mux.Route
|
|||
return util.JSONResponse{Code: http.StatusOK, JSON: request}
|
||||
}),
|
||||
)
|
||||
internalAPIMux.Handle(
|
||||
FederationSenderSpacesSummaryPath,
|
||||
httputil.MakeInternalAPI("MSC2946SpacesSummary", func(req *http.Request) util.JSONResponse {
|
||||
var request spacesReq
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
res, err := intAPI.MSC2946Spaces(req.Context(), request.S, request.RoomID, request.Req)
|
||||
if err != nil {
|
||||
ferr, ok := err.(*api.FederationClientError)
|
||||
if ok {
|
||||
request.Err = ferr
|
||||
} else {
|
||||
request.Err = &api.FederationClientError{
|
||||
Err: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
request.Res = res
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: request}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/federationsender/storage"
|
||||
"github.com/matrix-org/dendrite/federationsender/storage/shared"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -46,6 +47,7 @@ const (
|
|||
// at a time.
|
||||
type destinationQueue struct {
|
||||
db storage.Database
|
||||
process *process.ProcessContext
|
||||
signing *SigningInfo
|
||||
rsAPI api.RoomserverInternalAPI
|
||||
client *gomatrixserverlib.FederationClient // federation client
|
||||
|
@ -411,7 +413,7 @@ func (oq *destinationQueue) nextTransaction(
|
|||
// TODO: we should check for 500-ish fails vs 400-ish here,
|
||||
// since we shouldn't queue things indefinitely in response
|
||||
// to a 400-ish error
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
|
||||
ctx, cancel := context.WithTimeout(oq.process.Context(), time.Minute*5)
|
||||
defer cancel()
|
||||
_, err := oq.client.SendTransaction(ctx, t)
|
||||
switch err.(type) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/federationsender/storage"
|
||||
"github.com/matrix-org/dendrite/federationsender/storage/shared"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -36,6 +37,7 @@ import (
|
|||
// matrix servers
|
||||
type OutgoingQueues struct {
|
||||
db storage.Database
|
||||
process *process.ProcessContext
|
||||
disabled bool
|
||||
rsAPI api.RoomserverInternalAPI
|
||||
origin gomatrixserverlib.ServerName
|
||||
|
@ -80,6 +82,7 @@ var destinationQueueBackingOff = prometheus.NewGauge(
|
|||
// NewOutgoingQueues makes a new OutgoingQueues
|
||||
func NewOutgoingQueues(
|
||||
db storage.Database,
|
||||
process *process.ProcessContext,
|
||||
disabled bool,
|
||||
origin gomatrixserverlib.ServerName,
|
||||
client *gomatrixserverlib.FederationClient,
|
||||
|
@ -89,6 +92,7 @@ func NewOutgoingQueues(
|
|||
) *OutgoingQueues {
|
||||
queues := &OutgoingQueues{
|
||||
disabled: disabled,
|
||||
process: process,
|
||||
db: db,
|
||||
rsAPI: rsAPI,
|
||||
origin: origin,
|
||||
|
@ -151,6 +155,7 @@ func (oqs *OutgoingQueues) getQueue(destination gomatrixserverlib.ServerName) *d
|
|||
destinationQueueTotal.Inc()
|
||||
oq = &destinationQueue{
|
||||
db: oqs.db,
|
||||
process: oqs.process,
|
||||
rsAPI: oqs.rsAPI,
|
||||
origin: oqs.origin,
|
||||
destination: destination,
|
||||
|
|
|
@ -51,7 +51,18 @@ type Database interface {
|
|||
GetPendingPDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error)
|
||||
GetPendingEDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error)
|
||||
|
||||
// these don't have contexts passed in as we want things to happen regardless of the request context
|
||||
AddServerToBlacklist(serverName gomatrixserverlib.ServerName) error
|
||||
RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error
|
||||
IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error)
|
||||
|
||||
AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error
|
||||
RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error
|
||||
GetOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.OutboundPeek, error)
|
||||
GetOutboundPeeks(ctx context.Context, roomID string) ([]types.OutboundPeek, error)
|
||||
|
||||
AddInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error
|
||||
RenewInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error
|
||||
GetInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.InboundPeek, error)
|
||||
GetInboundPeeks(ctx context.Context, roomID string) ([]types.InboundPeek, error)
|
||||
}
|
||||
|
|
176
federationsender/storage/postgres/inbound_peeks_table.go
Normal file
176
federationsender/storage/postgres/inbound_peeks_table.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationsender/types"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const inboundPeeksSchema = `
|
||||
CREATE TABLE IF NOT EXISTS federationsender_inbound_peeks (
|
||||
room_id TEXT NOT NULL,
|
||||
server_name TEXT NOT NULL,
|
||||
peek_id TEXT NOT NULL,
|
||||
creation_ts BIGINT NOT NULL,
|
||||
renewed_ts BIGINT NOT NULL,
|
||||
renewal_interval BIGINT NOT NULL,
|
||||
UNIQUE (room_id, server_name, peek_id)
|
||||
);
|
||||
`
|
||||
|
||||
const insertInboundPeekSQL = "" +
|
||||
"INSERT INTO federationsender_inbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)"
|
||||
|
||||
const selectInboundPeekSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3"
|
||||
|
||||
const selectInboundPeeksSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1"
|
||||
|
||||
const renewInboundPeekSQL = "" +
|
||||
"UPDATE federationsender_inbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5"
|
||||
|
||||
const deleteInboundPeekSQL = "" +
|
||||
"DELETE FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2"
|
||||
|
||||
const deleteInboundPeeksSQL = "" +
|
||||
"DELETE FROM federationsender_inbound_peeks WHERE room_id = $1"
|
||||
|
||||
type inboundPeeksStatements struct {
|
||||
db *sql.DB
|
||||
insertInboundPeekStmt *sql.Stmt
|
||||
selectInboundPeekStmt *sql.Stmt
|
||||
selectInboundPeeksStmt *sql.Stmt
|
||||
renewInboundPeekStmt *sql.Stmt
|
||||
deleteInboundPeekStmt *sql.Stmt
|
||||
deleteInboundPeeksStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewPostgresInboundPeeksTable(db *sql.DB) (s *inboundPeeksStatements, err error) {
|
||||
s = &inboundPeeksStatements{
|
||||
db: db,
|
||||
}
|
||||
_, err = db.Exec(inboundPeeksSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.insertInboundPeekStmt, err = db.Prepare(insertInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectInboundPeekStmt, err = db.Prepare(selectInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectInboundPeeksStmt, err = db.Prepare(selectInboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.renewInboundPeekStmt, err = db.Prepare(renewInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteInboundPeeksStmt, err = db.Prepare(deleteInboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteInboundPeekStmt, err = db.Prepare(deleteInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) InsertInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
stmt := sqlutil.TxStmt(txn, s.insertInboundPeekStmt)
|
||||
_, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) RenewInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
_, err = sqlutil.TxStmt(txn, s.renewInboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) SelectInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (*types.InboundPeek, error) {
|
||||
row := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryRowContext(ctx, roomID)
|
||||
inboundPeek := types.InboundPeek{}
|
||||
err := row.Scan(
|
||||
&inboundPeek.RoomID,
|
||||
&inboundPeek.ServerName,
|
||||
&inboundPeek.PeekID,
|
||||
&inboundPeek.CreationTimestamp,
|
||||
&inboundPeek.RenewedTimestamp,
|
||||
&inboundPeek.RenewalInterval,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inboundPeek, nil
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) SelectInboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (inboundPeeks []types.InboundPeek, err error) {
|
||||
rows, err := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryContext(ctx, roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, rows, "SelectInboundPeeks: rows.close() failed")
|
||||
|
||||
for rows.Next() {
|
||||
inboundPeek := types.InboundPeek{}
|
||||
if err = rows.Scan(
|
||||
&inboundPeek.RoomID,
|
||||
&inboundPeek.ServerName,
|
||||
&inboundPeek.PeekID,
|
||||
&inboundPeek.CreationTimestamp,
|
||||
&inboundPeek.RenewedTimestamp,
|
||||
&inboundPeek.RenewalInterval,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
inboundPeeks = append(inboundPeeks, inboundPeek)
|
||||
}
|
||||
|
||||
return inboundPeeks, rows.Err()
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) DeleteInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteInboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) DeleteInboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteInboundPeeksStmt).ExecContext(ctx, roomID)
|
||||
return
|
||||
}
|
176
federationsender/storage/postgres/outbound_peeks_table.go
Normal file
176
federationsender/storage/postgres/outbound_peeks_table.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationsender/types"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const outboundPeeksSchema = `
|
||||
CREATE TABLE IF NOT EXISTS federationsender_outbound_peeks (
|
||||
room_id TEXT NOT NULL,
|
||||
server_name TEXT NOT NULL,
|
||||
peek_id TEXT NOT NULL,
|
||||
creation_ts BIGINT NOT NULL,
|
||||
renewed_ts BIGINT NOT NULL,
|
||||
renewal_interval BIGINT NOT NULL,
|
||||
UNIQUE (room_id, server_name, peek_id)
|
||||
);
|
||||
`
|
||||
|
||||
const insertOutboundPeekSQL = "" +
|
||||
"INSERT INTO federationsender_outbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)"
|
||||
|
||||
const selectOutboundPeekSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3"
|
||||
|
||||
const selectOutboundPeeksSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1"
|
||||
|
||||
const renewOutboundPeekSQL = "" +
|
||||
"UPDATE federationsender_outbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5"
|
||||
|
||||
const deleteOutboundPeekSQL = "" +
|
||||
"DELETE FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2"
|
||||
|
||||
const deleteOutboundPeeksSQL = "" +
|
||||
"DELETE FROM federationsender_outbound_peeks WHERE room_id = $1"
|
||||
|
||||
type outboundPeeksStatements struct {
|
||||
db *sql.DB
|
||||
insertOutboundPeekStmt *sql.Stmt
|
||||
selectOutboundPeekStmt *sql.Stmt
|
||||
selectOutboundPeeksStmt *sql.Stmt
|
||||
renewOutboundPeekStmt *sql.Stmt
|
||||
deleteOutboundPeekStmt *sql.Stmt
|
||||
deleteOutboundPeeksStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewPostgresOutboundPeeksTable(db *sql.DB) (s *outboundPeeksStatements, err error) {
|
||||
s = &outboundPeeksStatements{
|
||||
db: db,
|
||||
}
|
||||
_, err = db.Exec(outboundPeeksSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.insertOutboundPeekStmt, err = db.Prepare(insertOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectOutboundPeekStmt, err = db.Prepare(selectOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectOutboundPeeksStmt, err = db.Prepare(selectOutboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.renewOutboundPeekStmt, err = db.Prepare(renewOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteOutboundPeeksStmt, err = db.Prepare(deleteOutboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteOutboundPeekStmt, err = db.Prepare(deleteOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) InsertOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
stmt := sqlutil.TxStmt(txn, s.insertOutboundPeekStmt)
|
||||
_, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) RenewOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
_, err = sqlutil.TxStmt(txn, s.renewOutboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) SelectOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (*types.OutboundPeek, error) {
|
||||
row := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryRowContext(ctx, roomID)
|
||||
outboundPeek := types.OutboundPeek{}
|
||||
err := row.Scan(
|
||||
&outboundPeek.RoomID,
|
||||
&outboundPeek.ServerName,
|
||||
&outboundPeek.PeekID,
|
||||
&outboundPeek.CreationTimestamp,
|
||||
&outboundPeek.RenewedTimestamp,
|
||||
&outboundPeek.RenewalInterval,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &outboundPeek, nil
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) SelectOutboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (outboundPeeks []types.OutboundPeek, err error) {
|
||||
rows, err := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryContext(ctx, roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, rows, "SelectOutboundPeeks: rows.close() failed")
|
||||
|
||||
for rows.Next() {
|
||||
outboundPeek := types.OutboundPeek{}
|
||||
if err = rows.Scan(
|
||||
&outboundPeek.RoomID,
|
||||
&outboundPeek.ServerName,
|
||||
&outboundPeek.PeekID,
|
||||
&outboundPeek.CreationTimestamp,
|
||||
&outboundPeek.RenewedTimestamp,
|
||||
&outboundPeek.RenewalInterval,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
outboundPeeks = append(outboundPeeks, outboundPeek)
|
||||
}
|
||||
|
||||
return outboundPeeks, rows.Err()
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) DeleteOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteOutboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) DeleteOutboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteOutboundPeeksStmt).ExecContext(ctx, roomID)
|
||||
return
|
||||
}
|
|
@ -64,16 +64,26 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inboundPeeks, err := NewPostgresInboundPeeksTable(d.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outboundPeeks, err := NewPostgresOutboundPeeksTable(d.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Database = shared.Database{
|
||||
DB: d.db,
|
||||
Cache: cache,
|
||||
Writer: d.writer,
|
||||
FederationSenderJoinedHosts: joinedHosts,
|
||||
FederationSenderQueuePDUs: queuePDUs,
|
||||
FederationSenderQueueEDUs: queueEDUs,
|
||||
FederationSenderQueueJSON: queueJSON,
|
||||
FederationSenderRooms: rooms,
|
||||
FederationSenderBlacklist: blacklist,
|
||||
DB: d.db,
|
||||
Cache: cache,
|
||||
Writer: d.writer,
|
||||
FederationSenderJoinedHosts: joinedHosts,
|
||||
FederationSenderQueuePDUs: queuePDUs,
|
||||
FederationSenderQueueEDUs: queueEDUs,
|
||||
FederationSenderQueueJSON: queueJSON,
|
||||
FederationSenderRooms: rooms,
|
||||
FederationSenderBlacklist: blacklist,
|
||||
FederationSenderInboundPeeks: inboundPeeks,
|
||||
FederationSenderOutboundPeeks: outboundPeeks,
|
||||
}
|
||||
if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -27,15 +27,17 @@ import (
|
|||
)
|
||||
|
||||
type Database struct {
|
||||
DB *sql.DB
|
||||
Cache caching.FederationSenderCache
|
||||
Writer sqlutil.Writer
|
||||
FederationSenderQueuePDUs tables.FederationSenderQueuePDUs
|
||||
FederationSenderQueueEDUs tables.FederationSenderQueueEDUs
|
||||
FederationSenderQueueJSON tables.FederationSenderQueueJSON
|
||||
FederationSenderJoinedHosts tables.FederationSenderJoinedHosts
|
||||
FederationSenderRooms tables.FederationSenderRooms
|
||||
FederationSenderBlacklist tables.FederationSenderBlacklist
|
||||
DB *sql.DB
|
||||
Cache caching.FederationSenderCache
|
||||
Writer sqlutil.Writer
|
||||
FederationSenderQueuePDUs tables.FederationSenderQueuePDUs
|
||||
FederationSenderQueueEDUs tables.FederationSenderQueueEDUs
|
||||
FederationSenderQueueJSON tables.FederationSenderQueueJSON
|
||||
FederationSenderJoinedHosts tables.FederationSenderJoinedHosts
|
||||
FederationSenderRooms tables.FederationSenderRooms
|
||||
FederationSenderBlacklist tables.FederationSenderBlacklist
|
||||
FederationSenderOutboundPeeks tables.FederationSenderOutboundPeeks
|
||||
FederationSenderInboundPeeks tables.FederationSenderInboundPeeks
|
||||
}
|
||||
|
||||
// An Receipt contains the NIDs of a call to GetNextTransactionPDUs/EDUs.
|
||||
|
@ -173,3 +175,43 @@ func (d *Database) RemoveServerFromBlacklist(serverName gomatrixserverlib.Server
|
|||
func (d *Database) IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) {
|
||||
return d.FederationSenderBlacklist.SelectBlacklist(context.TODO(), nil, serverName)
|
||||
}
|
||||
|
||||
func (d *Database) AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error {
|
||||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||
return d.FederationSenderOutboundPeeks.InsertOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Database) RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error {
|
||||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||
return d.FederationSenderOutboundPeeks.RenewOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Database) GetOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.OutboundPeek, error) {
|
||||
return d.FederationSenderOutboundPeeks.SelectOutboundPeek(ctx, nil, serverName, roomID, peekID)
|
||||
}
|
||||
|
||||
func (d *Database) GetOutboundPeeks(ctx context.Context, roomID string) ([]types.OutboundPeek, error) {
|
||||
return d.FederationSenderOutboundPeeks.SelectOutboundPeeks(ctx, nil, roomID)
|
||||
}
|
||||
|
||||
func (d *Database) AddInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error {
|
||||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||
return d.FederationSenderInboundPeeks.InsertInboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Database) RenewInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error {
|
||||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||
return d.FederationSenderInboundPeeks.RenewInboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Database) GetInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.InboundPeek, error) {
|
||||
return d.FederationSenderInboundPeeks.SelectInboundPeek(ctx, nil, serverName, roomID, peekID)
|
||||
}
|
||||
|
||||
func (d *Database) GetInboundPeeks(ctx context.Context, roomID string) ([]types.InboundPeek, error) {
|
||||
return d.FederationSenderInboundPeeks.SelectInboundPeeks(ctx, nil, roomID)
|
||||
}
|
||||
|
|
176
federationsender/storage/sqlite3/inbound_peeks_table.go
Normal file
176
federationsender/storage/sqlite3/inbound_peeks_table.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationsender/types"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const inboundPeeksSchema = `
|
||||
CREATE TABLE IF NOT EXISTS federationsender_inbound_peeks (
|
||||
room_id TEXT NOT NULL,
|
||||
server_name TEXT NOT NULL,
|
||||
peek_id TEXT NOT NULL,
|
||||
creation_ts INTEGER NOT NULL,
|
||||
renewed_ts INTEGER NOT NULL,
|
||||
renewal_interval INTEGER NOT NULL,
|
||||
UNIQUE (room_id, server_name, peek_id)
|
||||
);
|
||||
`
|
||||
|
||||
const insertInboundPeekSQL = "" +
|
||||
"INSERT INTO federationsender_inbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)"
|
||||
|
||||
const selectInboundPeekSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3"
|
||||
|
||||
const selectInboundPeeksSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1"
|
||||
|
||||
const renewInboundPeekSQL = "" +
|
||||
"UPDATE federationsender_inbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5"
|
||||
|
||||
const deleteInboundPeekSQL = "" +
|
||||
"DELETE FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2"
|
||||
|
||||
const deleteInboundPeeksSQL = "" +
|
||||
"DELETE FROM federationsender_inbound_peeks WHERE room_id = $1"
|
||||
|
||||
type inboundPeeksStatements struct {
|
||||
db *sql.DB
|
||||
insertInboundPeekStmt *sql.Stmt
|
||||
selectInboundPeekStmt *sql.Stmt
|
||||
selectInboundPeeksStmt *sql.Stmt
|
||||
renewInboundPeekStmt *sql.Stmt
|
||||
deleteInboundPeekStmt *sql.Stmt
|
||||
deleteInboundPeeksStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewSQLiteInboundPeeksTable(db *sql.DB) (s *inboundPeeksStatements, err error) {
|
||||
s = &inboundPeeksStatements{
|
||||
db: db,
|
||||
}
|
||||
_, err = db.Exec(inboundPeeksSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.insertInboundPeekStmt, err = db.Prepare(insertInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectInboundPeekStmt, err = db.Prepare(selectInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectInboundPeeksStmt, err = db.Prepare(selectInboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.renewInboundPeekStmt, err = db.Prepare(renewInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteInboundPeeksStmt, err = db.Prepare(deleteInboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteInboundPeekStmt, err = db.Prepare(deleteInboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) InsertInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
stmt := sqlutil.TxStmt(txn, s.insertInboundPeekStmt)
|
||||
_, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) RenewInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
_, err = sqlutil.TxStmt(txn, s.renewInboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) SelectInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (*types.InboundPeek, error) {
|
||||
row := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryRowContext(ctx, roomID)
|
||||
inboundPeek := types.InboundPeek{}
|
||||
err := row.Scan(
|
||||
&inboundPeek.RoomID,
|
||||
&inboundPeek.ServerName,
|
||||
&inboundPeek.PeekID,
|
||||
&inboundPeek.CreationTimestamp,
|
||||
&inboundPeek.RenewedTimestamp,
|
||||
&inboundPeek.RenewalInterval,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inboundPeek, nil
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) SelectInboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (inboundPeeks []types.InboundPeek, err error) {
|
||||
rows, err := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryContext(ctx, roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, rows, "SelectInboundPeeks: rows.close() failed")
|
||||
|
||||
for rows.Next() {
|
||||
inboundPeek := types.InboundPeek{}
|
||||
if err = rows.Scan(
|
||||
&inboundPeek.RoomID,
|
||||
&inboundPeek.ServerName,
|
||||
&inboundPeek.PeekID,
|
||||
&inboundPeek.CreationTimestamp,
|
||||
&inboundPeek.RenewedTimestamp,
|
||||
&inboundPeek.RenewalInterval,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
inboundPeeks = append(inboundPeeks, inboundPeek)
|
||||
}
|
||||
|
||||
return inboundPeeks, rows.Err()
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) DeleteInboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteInboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inboundPeeksStatements) DeleteInboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteInboundPeeksStmt).ExecContext(ctx, roomID)
|
||||
return
|
||||
}
|
176
federationsender/storage/sqlite3/outbound_peeks_table.go
Normal file
176
federationsender/storage/sqlite3/outbound_peeks_table.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationsender/types"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const outboundPeeksSchema = `
|
||||
CREATE TABLE IF NOT EXISTS federationsender_outbound_peeks (
|
||||
room_id TEXT NOT NULL,
|
||||
server_name TEXT NOT NULL,
|
||||
peek_id TEXT NOT NULL,
|
||||
creation_ts INTEGER NOT NULL,
|
||||
renewed_ts INTEGER NOT NULL,
|
||||
renewal_interval INTEGER NOT NULL,
|
||||
UNIQUE (room_id, server_name, peek_id)
|
||||
);
|
||||
`
|
||||
|
||||
const insertOutboundPeekSQL = "" +
|
||||
"INSERT INTO federationsender_outbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)"
|
||||
|
||||
const selectOutboundPeekSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3"
|
||||
|
||||
const selectOutboundPeeksSQL = "" +
|
||||
"SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1"
|
||||
|
||||
const renewOutboundPeekSQL = "" +
|
||||
"UPDATE federationsender_outbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5"
|
||||
|
||||
const deleteOutboundPeekSQL = "" +
|
||||
"DELETE FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2"
|
||||
|
||||
const deleteOutboundPeeksSQL = "" +
|
||||
"DELETE FROM federationsender_outbound_peeks WHERE room_id = $1"
|
||||
|
||||
type outboundPeeksStatements struct {
|
||||
db *sql.DB
|
||||
insertOutboundPeekStmt *sql.Stmt
|
||||
selectOutboundPeekStmt *sql.Stmt
|
||||
selectOutboundPeeksStmt *sql.Stmt
|
||||
renewOutboundPeekStmt *sql.Stmt
|
||||
deleteOutboundPeekStmt *sql.Stmt
|
||||
deleteOutboundPeeksStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewSQLiteOutboundPeeksTable(db *sql.DB) (s *outboundPeeksStatements, err error) {
|
||||
s = &outboundPeeksStatements{
|
||||
db: db,
|
||||
}
|
||||
_, err = db.Exec(outboundPeeksSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.insertOutboundPeekStmt, err = db.Prepare(insertOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectOutboundPeekStmt, err = db.Prepare(selectOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectOutboundPeeksStmt, err = db.Prepare(selectOutboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.renewOutboundPeekStmt, err = db.Prepare(renewOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteOutboundPeeksStmt, err = db.Prepare(deleteOutboundPeeksSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteOutboundPeekStmt, err = db.Prepare(deleteOutboundPeekSQL); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) InsertOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
stmt := sqlutil.TxStmt(txn, s.insertOutboundPeekStmt)
|
||||
_, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) RenewOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64,
|
||||
) (err error) {
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
_, err = sqlutil.TxStmt(txn, s.renewOutboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) SelectOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (*types.OutboundPeek, error) {
|
||||
row := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryRowContext(ctx, roomID)
|
||||
outboundPeek := types.OutboundPeek{}
|
||||
err := row.Scan(
|
||||
&outboundPeek.RoomID,
|
||||
&outboundPeek.ServerName,
|
||||
&outboundPeek.PeekID,
|
||||
&outboundPeek.CreationTimestamp,
|
||||
&outboundPeek.RenewedTimestamp,
|
||||
&outboundPeek.RenewalInterval,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &outboundPeek, nil
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) SelectOutboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (outboundPeeks []types.OutboundPeek, err error) {
|
||||
rows, err := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryContext(ctx, roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, rows, "SelectOutboundPeeks: rows.close() failed")
|
||||
|
||||
for rows.Next() {
|
||||
outboundPeek := types.OutboundPeek{}
|
||||
if err = rows.Scan(
|
||||
&outboundPeek.RoomID,
|
||||
&outboundPeek.ServerName,
|
||||
&outboundPeek.PeekID,
|
||||
&outboundPeek.CreationTimestamp,
|
||||
&outboundPeek.RenewedTimestamp,
|
||||
&outboundPeek.RenewalInterval,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
outboundPeeks = append(outboundPeeks, outboundPeek)
|
||||
}
|
||||
|
||||
return outboundPeeks, rows.Err()
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) DeleteOutboundPeek(
|
||||
ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteOutboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *outboundPeeksStatements) DeleteOutboundPeeks(
|
||||
ctx context.Context, txn *sql.Tx, roomID string,
|
||||
) (err error) {
|
||||
_, err = sqlutil.TxStmt(txn, s.deleteOutboundPeeksStmt).ExecContext(ctx, roomID)
|
||||
return
|
||||
}
|
|
@ -66,16 +66,26 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outboundPeeks, err := NewSQLiteOutboundPeeksTable(d.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inboundPeeks, err := NewSQLiteInboundPeeksTable(d.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Database = shared.Database{
|
||||
DB: d.db,
|
||||
Cache: cache,
|
||||
Writer: d.writer,
|
||||
FederationSenderJoinedHosts: joinedHosts,
|
||||
FederationSenderQueuePDUs: queuePDUs,
|
||||
FederationSenderQueueEDUs: queueEDUs,
|
||||
FederationSenderQueueJSON: queueJSON,
|
||||
FederationSenderRooms: rooms,
|
||||
FederationSenderBlacklist: blacklist,
|
||||
DB: d.db,
|
||||
Cache: cache,
|
||||
Writer: d.writer,
|
||||
FederationSenderJoinedHosts: joinedHosts,
|
||||
FederationSenderQueuePDUs: queuePDUs,
|
||||
FederationSenderQueueEDUs: queueEDUs,
|
||||
FederationSenderQueueJSON: queueJSON,
|
||||
FederationSenderRooms: rooms,
|
||||
FederationSenderBlacklist: blacklist,
|
||||
FederationSenderOutboundPeeks: outboundPeeks,
|
||||
FederationSenderInboundPeeks: inboundPeeks,
|
||||
}
|
||||
if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -67,3 +67,21 @@ type FederationSenderBlacklist interface {
|
|||
SelectBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) (bool, error)
|
||||
DeleteBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) error
|
||||
}
|
||||
|
||||
type FederationSenderOutboundPeeks interface {
|
||||
InsertOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error)
|
||||
RenewOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error)
|
||||
SelectOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (outboundPeek *types.OutboundPeek, err error)
|
||||
SelectOutboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (outboundPeeks []types.OutboundPeek, err error)
|
||||
DeleteOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error)
|
||||
DeleteOutboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error)
|
||||
}
|
||||
|
||||
type FederationSenderInboundPeeks interface {
|
||||
InsertInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error)
|
||||
RenewInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error)
|
||||
SelectInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (inboundPeek *types.InboundPeek, err error)
|
||||
SelectInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (inboundPeeks []types.InboundPeek, err error)
|
||||
DeleteInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error)
|
||||
DeleteInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error)
|
||||
}
|
||||
|
|
|
@ -49,3 +49,23 @@ func (e EventIDMismatchError) Error() string {
|
|||
e.DatabaseID, e.RoomServerID,
|
||||
)
|
||||
}
|
||||
|
||||
// tracks peeks we're performing on another server over federation
|
||||
type OutboundPeek struct {
|
||||
PeekID string
|
||||
RoomID string
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
CreationTimestamp int64
|
||||
RenewedTimestamp int64
|
||||
RenewalInterval int64
|
||||
}
|
||||
|
||||
// tracks peeks other servers are performing on us over federation
|
||||
type InboundPeek struct {
|
||||
PeekID string
|
||||
RoomID string
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
CreationTimestamp int64
|
||||
RenewedTimestamp int64
|
||||
RenewalInterval int64
|
||||
}
|
||||
|
|
11
go.mod
11
go.mod
|
@ -22,7 +22,7 @@ require (
|
|||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3
|
||||
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20210113173004-b1c67ac867cc
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead
|
||||
github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91
|
||||
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
|
||||
github.com/mattn/go-sqlite3 v1.14.2
|
||||
|
@ -33,16 +33,15 @@ require (
|
|||
github.com/pressly/goose v2.7.0-rc5+incompatible
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/tidwall/gjson v1.6.3
|
||||
github.com/tidwall/match v1.0.2 // indirect
|
||||
github.com/tidwall/sjson v1.1.2
|
||||
github.com/tidwall/gjson v1.6.7
|
||||
github.com/tidwall/sjson v1.1.4
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20201006093556-760d9a7fd5ee
|
||||
go.uber.org/atomic v1.6.0
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect
|
||||
gopkg.in/h2non/bimg.v1 v1.1.4
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
|
25
go.sum
25
go.sum
|
@ -567,8 +567,8 @@ github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bh
|
|||
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd h1:xVrqJK3xHREMNjwjljkAUaadalWc0rRbmVuQatzmgwg=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20210113173004-b1c67ac867cc h1:n2Hnbg8RZ4102Qmxie1riLkIyrqeqShJUILg1miSmDI=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20210113173004-b1c67ac867cc/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead h1:VmGJybKUQin8+NyA9ZkrHJpE8ygXzcON9peQH9LC92c=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU=
|
||||
github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 h1:HJ6U3S3ljJqNffYMcIeAncp5qT/i+ZMiJ2JC2F0aXP4=
|
||||
github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91/go.mod h1:sjyPyRxKM5uw1nD2cJ6O2OxI6GOqyVBfNXqKjBZTBZE=
|
||||
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo=
|
||||
|
@ -810,13 +810,12 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP
|
|||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
|
||||
github.com/tidwall/gjson v1.6.3 h1:aHoiiem0dr7GHkW001T1SMTJ7X5PvyekH5WX0whWGnI=
|
||||
github.com/tidwall/gjson v1.6.3/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
|
||||
github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
|
||||
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/match v1.0.2 h1:uuqvHuBGSedK7awZ2YoAtpnimfwBGFjHuWLuLqQj+bU=
|
||||
github.com/tidwall/match v1.0.2/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
|
||||
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
|
@ -824,8 +823,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
|||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/sjson v1.0.3 h1:DeF+0LZqvIt4fKYw41aPB29ZGlvwVkHKktoXJ1YW9Y8=
|
||||
github.com/tidwall/sjson v1.0.3/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
|
||||
github.com/tidwall/sjson v1.1.2 h1:NC5okI+tQ8OG/oyzchvwXXxRxCV/FVdhODbPKkQ25jQ=
|
||||
github.com/tidwall/sjson v1.1.2/go.mod h1:SEzaDwxiPzKzNfUEO4HbYF/m4UCSJDsGgNqsS1LvdoY=
|
||||
github.com/tidwall/sjson v1.1.4 h1:bTSsPLdAYF5QNLSwYsKfBKKTnlGbIuhqL3CpRsjzGhg=
|
||||
github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg=
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw=
|
||||
|
@ -906,8 +905,8 @@ golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzht
|
|||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -996,8 +995,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
|
||||
"github.com/Shopify/sarama"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// A PartitionStorer has the storage APIs needed by the consumer.
|
||||
|
@ -33,6 +35,9 @@ type PartitionStorer interface {
|
|||
// A ContinualConsumer continually consumes logs even across restarts. It requires a PartitionStorer to
|
||||
// remember the offset it reached.
|
||||
type ContinualConsumer struct {
|
||||
// The parent context for the listener, stop consuming when this context is done
|
||||
Process *process.ProcessContext
|
||||
// The component name
|
||||
ComponentName string
|
||||
// The kafkaesque topic to consume events from.
|
||||
// This is the name used in kafka to identify the stream to consume events from.
|
||||
|
@ -100,6 +105,15 @@ func (c *ContinualConsumer) StartOffsets() ([]sqlutil.PartitionOffset, error) {
|
|||
}
|
||||
for _, pc := range partitionConsumers {
|
||||
go c.consumePartition(pc)
|
||||
if c.Process != nil {
|
||||
c.Process.ComponentStarted()
|
||||
go func(pc sarama.PartitionConsumer) {
|
||||
<-c.Process.WaitForShutdown()
|
||||
_ = pc.Close()
|
||||
c.Process.ComponentFinished()
|
||||
logrus.Infof("Stopped consumer for %q topic %q", c.ComponentName, c.Topic)
|
||||
}(pc)
|
||||
}
|
||||
}
|
||||
|
||||
return storedOffsets, nil
|
||||
|
|
|
@ -17,7 +17,7 @@ var build string
|
|||
const (
|
||||
VersionMajor = 0
|
||||
VersionMinor = 3
|
||||
VersionPatch = 5
|
||||
VersionPatch = 8
|
||||
VersionTag = "" // example: "rc1"
|
||||
)
|
||||
|
||||
|
|
|
@ -245,7 +245,7 @@ func (u *DeviceListUpdater) notifyWorkers(userID string) {
|
|||
}
|
||||
hash := fnv.New32a()
|
||||
_, _ = hash.Write([]byte(remoteServer))
|
||||
index := int(hash.Sum32()) % len(u.workerChans)
|
||||
index := int(int64(hash.Sum32()) % int64(len(u.workerChans)))
|
||||
|
||||
ch := u.assignChannel(userID)
|
||||
u.workerChans[index] <- remoteServer
|
||||
|
|
|
@ -106,9 +106,11 @@ func (t *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
func newFedClient(tripper func(*http.Request) (*http.Response, error)) *gomatrixserverlib.FederationClient {
|
||||
_, pkey, _ := ed25519.GenerateKey(nil)
|
||||
fedClient := gomatrixserverlib.NewFederationClient(
|
||||
gomatrixserverlib.ServerName("example.test"), gomatrixserverlib.KeyID("ed25519:test"), pkey, true,
|
||||
gomatrixserverlib.ServerName("example.test"), gomatrixserverlib.KeyID("ed25519:test"), pkey,
|
||||
)
|
||||
fedClient.Client = *gomatrixserverlib.NewClient(
|
||||
gomatrixserverlib.WithTransport(&roundTripper{tripper}),
|
||||
)
|
||||
fedClient.Client = *gomatrixserverlib.NewClientWithTransport(&roundTripper{tripper})
|
||||
return fedClient
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,12 @@ type RoomserverInternalAPI interface {
|
|||
res *PerformPublishResponse,
|
||||
)
|
||||
|
||||
PerformInboundPeek(
|
||||
ctx context.Context,
|
||||
req *PerformInboundPeekRequest,
|
||||
res *PerformInboundPeekResponse,
|
||||
) error
|
||||
|
||||
QueryPublishedRooms(
|
||||
ctx context.Context,
|
||||
req *QueryPublishedRoomsRequest,
|
||||
|
|
|
@ -88,6 +88,16 @@ func (t *RoomserverInternalAPITrace) PerformPublish(
|
|||
util.GetLogger(ctx).Infof("PerformPublish req=%+v res=%+v", js(req), js(res))
|
||||
}
|
||||
|
||||
func (t *RoomserverInternalAPITrace) PerformInboundPeek(
|
||||
ctx context.Context,
|
||||
req *PerformInboundPeekRequest,
|
||||
res *PerformInboundPeekResponse,
|
||||
) error {
|
||||
err := t.Impl.PerformInboundPeek(ctx, req, res)
|
||||
util.GetLogger(ctx).Infof("PerformInboundPeek req=%+v res=%+v", js(req), js(res))
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *RoomserverInternalAPITrace) QueryPublishedRooms(
|
||||
ctx context.Context,
|
||||
req *QueryPublishedRoomsRequest,
|
||||
|
|
|
@ -51,6 +51,8 @@ const (
|
|||
|
||||
// OutputTypeNewPeek indicates that the kafka event is an OutputNewPeek
|
||||
OutputTypeNewPeek OutputType = "new_peek"
|
||||
// OutputTypeNewInboundPeek indicates that the kafka event is an OutputNewInboundPeek
|
||||
OutputTypeNewInboundPeek OutputType = "new_inbound_peek"
|
||||
// OutputTypeRetirePeek indicates that the kafka event is an OutputRetirePeek
|
||||
OutputTypeRetirePeek OutputType = "retire_peek"
|
||||
)
|
||||
|
@ -72,6 +74,8 @@ type OutputEvent struct {
|
|||
RedactedEvent *OutputRedactedEvent `json:"redacted_event,omitempty"`
|
||||
// The content of event with type OutputTypeNewPeek
|
||||
NewPeek *OutputNewPeek `json:"new_peek,omitempty"`
|
||||
// The content of event with type OutputTypeNewInboundPeek
|
||||
NewInboundPeek *OutputNewInboundPeek `json:"new_inbound_peek,omitempty"`
|
||||
// The content of event with type OutputTypeRetirePeek
|
||||
RetirePeek *OutputRetirePeek `json:"retire_peek,omitempty"`
|
||||
}
|
||||
|
@ -245,6 +249,19 @@ type OutputNewPeek struct {
|
|||
DeviceID string
|
||||
}
|
||||
|
||||
// An OutputNewInboundPeek is written whenever a server starts peeking into a room
|
||||
type OutputNewInboundPeek struct {
|
||||
RoomID string
|
||||
PeekID string
|
||||
// the event ID at which the peek begins (so we can avoid
|
||||
// a race between tracking the state returned by /peek and emitting subsequent
|
||||
// peeked events)
|
||||
LatestEventID string
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
// how often we told the peeking server to renew the peek
|
||||
RenewalInterval int64
|
||||
}
|
||||
|
||||
// An OutputRetirePeek is written whenever a user stops peeking into a room.
|
||||
type OutputRetirePeek struct {
|
||||
RoomID string
|
||||
|
|
|
@ -172,6 +172,28 @@ type PerformPublishResponse struct {
|
|||
Error *PerformError
|
||||
}
|
||||
|
||||
type PerformInboundPeekRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
RoomID string `json:"room_id"`
|
||||
PeekID string `json:"peek_id"`
|
||||
ServerName gomatrixserverlib.ServerName `json:"server_name"`
|
||||
RenewalInterval int64 `json:"renewal_interval"`
|
||||
}
|
||||
|
||||
type PerformInboundPeekResponse struct {
|
||||
// Does the room exist on this roomserver?
|
||||
// If the room doesn't exist this will be false and StateEvents will be empty.
|
||||
RoomExists bool `json:"room_exists"`
|
||||
// The room version of the room.
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
// The current state and auth chain events.
|
||||
// The lists will be in an arbitrary order.
|
||||
StateEvents []*gomatrixserverlib.HeaderedEvent `json:"state_events"`
|
||||
AuthChainEvents []*gomatrixserverlib.HeaderedEvent `json:"auth_chain_events"`
|
||||
// The event at which this state was captured
|
||||
LatestEvent *gomatrixserverlib.HeaderedEvent `json:"latest_event"`
|
||||
}
|
||||
|
||||
// PerformForgetRequest is a request to PerformForget
|
||||
type PerformForgetRequest struct {
|
||||
RoomID string `json:"room_id"`
|
||||
|
|
|
@ -221,7 +221,7 @@ type QueryStateAndAuthChainRequest struct {
|
|||
// The room ID to query the state in.
|
||||
RoomID string `json:"room_id"`
|
||||
// The list of prev events for the event. Used to calculate the state at
|
||||
// the event
|
||||
// the event.
|
||||
PrevEventIDs []string `json:"prev_event_ids"`
|
||||
// The list of auth events for the event. Used to calculate the auth chain
|
||||
AuthEventIDs []string `json:"auth_event_ids"`
|
||||
|
|
|
@ -43,7 +43,7 @@ func SendEvents(
|
|||
|
||||
// SendEventWithState writes an event with the specified kind to the roomserver
|
||||
// with the state at the event as KindOutlier before it. Will not send any event that is
|
||||
// marked as `true` in haveEventIDs
|
||||
// marked as `true` in haveEventIDs.
|
||||
func SendEventWithState(
|
||||
ctx context.Context, rsAPI RoomserverInternalAPI, kind Kind,
|
||||
state *gomatrixserverlib.RespState, event *gomatrixserverlib.HeaderedEvent,
|
||||
|
|
|
@ -24,6 +24,7 @@ type RoomserverInternalAPI struct {
|
|||
*perform.Inviter
|
||||
*perform.Joiner
|
||||
*perform.Peeker
|
||||
*perform.InboundPeeker
|
||||
*perform.Unpeeker
|
||||
*perform.Leaver
|
||||
*perform.Publisher
|
||||
|
@ -97,6 +98,10 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen
|
|||
FSAPI: r.fsAPI,
|
||||
Inputer: r.Inputer,
|
||||
}
|
||||
r.InboundPeeker = &perform.InboundPeeker{
|
||||
DB: r.DB,
|
||||
Inputer: r.Inputer,
|
||||
}
|
||||
r.Unpeeker = &perform.Unpeeker{
|
||||
ServerName: r.Cfg.Matrix.ServerName,
|
||||
Cfg: r.Cfg,
|
||||
|
|
|
@ -100,7 +100,8 @@ type latestEventsUpdater struct {
|
|||
// The eventID of the event that was processed before this one.
|
||||
lastEventIDSent string
|
||||
// The latest events in the room after processing this event.
|
||||
latest []types.StateAtEventAndReference
|
||||
oldLatest []types.StateAtEventAndReference
|
||||
latest []types.StateAtEventAndReference
|
||||
// The state entries removed from and added to the current state of the
|
||||
// room as a result of processing this event. They are sorted lists.
|
||||
removed []types.StateEntry
|
||||
|
@ -123,10 +124,10 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
|||
// state snapshot from somewhere else, e.g. a federated room join,
|
||||
// then start with an empty set - none of the forward extremities
|
||||
// that we knew about before matter anymore.
|
||||
oldLatest := []types.StateAtEventAndReference{}
|
||||
u.oldLatest = []types.StateAtEventAndReference{}
|
||||
if !u.rewritesState {
|
||||
u.oldStateNID = u.updater.CurrentStateSnapshotNID()
|
||||
oldLatest = u.updater.LatestEvents()
|
||||
u.oldLatest = u.updater.LatestEvents()
|
||||
}
|
||||
|
||||
// If the event has already been written to the output log then we
|
||||
|
@ -140,7 +141,7 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
|||
// Work out what the latest events are. This will include the new
|
||||
// event if it is not already referenced.
|
||||
extremitiesChanged, err := u.calculateLatest(
|
||||
oldLatest, u.event,
|
||||
u.oldLatest, u.event,
|
||||
types.StateAtEventAndReference{
|
||||
EventReference: u.event.EventReference(),
|
||||
StateAtEvent: u.stateAtEvent,
|
||||
|
@ -200,6 +201,37 @@ func (u *latestEventsUpdater) latestState() error {
|
|||
var err error
|
||||
roomState := state.NewStateResolution(u.api.DB, *u.roomInfo)
|
||||
|
||||
// Work out if the state at the extremities has actually changed
|
||||
// or not. If they haven't then we won't bother doing all of the
|
||||
// hard work.
|
||||
if u.event.StateKey() == nil {
|
||||
stateChanged := false
|
||||
oldStateNIDs := make([]types.StateSnapshotNID, 0, len(u.oldLatest))
|
||||
newStateNIDs := make([]types.StateSnapshotNID, 0, len(u.latest))
|
||||
for _, old := range u.oldLatest {
|
||||
oldStateNIDs = append(oldStateNIDs, old.BeforeStateSnapshotNID)
|
||||
}
|
||||
for _, new := range u.latest {
|
||||
newStateNIDs = append(newStateNIDs, new.BeforeStateSnapshotNID)
|
||||
}
|
||||
oldStateNIDs = state.UniqueStateSnapshotNIDs(oldStateNIDs)
|
||||
newStateNIDs = state.UniqueStateSnapshotNIDs(newStateNIDs)
|
||||
if len(oldStateNIDs) != len(newStateNIDs) {
|
||||
stateChanged = true
|
||||
} else {
|
||||
for i := range oldStateNIDs {
|
||||
if oldStateNIDs[i] != newStateNIDs[i] {
|
||||
stateChanged = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !stateChanged {
|
||||
u.newStateNID = u.oldStateNID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of the current latest events. This may or may not
|
||||
// include the new event from the input path, depending on whether
|
||||
// it is a forward extremity or not.
|
||||
|
|
|
@ -107,13 +107,6 @@ func (r *Inputer) updateMembership(
|
|||
return updates, nil
|
||||
}
|
||||
|
||||
if add == nil {
|
||||
// This can happen when we have rejoined a room and suddenly we have a
|
||||
// divergence between the former state and the new one. We don't want to
|
||||
// act on removals and apparently there are no adds, so stop here.
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
mu, err := updater.MembershipUpdater(targetUserNID, r.isLocalTarget(add))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
129
roomserver/internal/perform/perform_inbound_peek.go
Normal file
129
roomserver/internal/perform/perform_inbound_peek.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package perform
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/internal/helpers"
|
||||
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
||||
"github.com/matrix-org/dendrite/roomserver/internal/query"
|
||||
"github.com/matrix-org/dendrite/roomserver/state"
|
||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type InboundPeeker struct {
|
||||
DB storage.Database
|
||||
Inputer *input.Inputer
|
||||
}
|
||||
|
||||
// PerformInboundPeek handles peeking into matrix rooms, including over
|
||||
// federation by talking to the federationsender. called when a remote server
|
||||
// initiates a /peek over federation.
|
||||
//
|
||||
// It should atomically figure out the current state of the room (for the
|
||||
// response to /peek) while adding the new inbound peek to the kafka stream so the
|
||||
// fed sender can start sending peeked events without a race between the state
|
||||
// snapshot and the stream of peeked events.
|
||||
func (r *InboundPeeker) PerformInboundPeek(
|
||||
ctx context.Context,
|
||||
request *api.PerformInboundPeekRequest,
|
||||
response *api.PerformInboundPeekResponse,
|
||||
) error {
|
||||
info, err := r.DB.RoomInfo(ctx, request.RoomID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info == nil || info.IsStub {
|
||||
return nil
|
||||
}
|
||||
response.RoomExists = true
|
||||
response.RoomVersion = info.RoomVersion
|
||||
|
||||
var stateEvents []*gomatrixserverlib.Event
|
||||
|
||||
var currentStateSnapshotNID types.StateSnapshotNID
|
||||
latestEventRefs, currentStateSnapshotNID, _, err :=
|
||||
r.DB.LatestEventIDs(ctx, info.RoomNID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
latestEvents, err := r.DB.EventsFromIDs(ctx, []string{latestEventRefs[0].EventID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sortedLatestEvents []*gomatrixserverlib.Event
|
||||
for _, ev := range latestEvents {
|
||||
sortedLatestEvents = append(sortedLatestEvents, ev.Event)
|
||||
}
|
||||
sortedLatestEvents = gomatrixserverlib.ReverseTopologicalOrdering(
|
||||
sortedLatestEvents,
|
||||
gomatrixserverlib.TopologicalOrderByPrevEvents,
|
||||
)
|
||||
response.LatestEvent = sortedLatestEvents[0].Headered(info.RoomVersion)
|
||||
|
||||
// XXX: do we actually need to do a state resolution here?
|
||||
roomState := state.NewStateResolution(r.DB, *info)
|
||||
|
||||
var stateEntries []types.StateEntry
|
||||
stateEntries, err = roomState.LoadStateAtSnapshot(
|
||||
ctx, currentStateSnapshotNID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateEvents, err = helpers.LoadStateEvents(ctx, r.DB, stateEntries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the auth event IDs for the current state events
|
||||
var authEventIDs []string
|
||||
for _, se := range stateEvents {
|
||||
authEventIDs = append(authEventIDs, se.AuthEventIDs()...)
|
||||
}
|
||||
authEventIDs = util.UniqueStrings(authEventIDs) // de-dupe
|
||||
|
||||
authEvents, err := query.GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, event := range stateEvents {
|
||||
response.StateEvents = append(response.StateEvents, event.Headered(info.RoomVersion))
|
||||
}
|
||||
|
||||
for _, event := range authEvents {
|
||||
response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion))
|
||||
}
|
||||
|
||||
err = r.Inputer.WriteOutputEvents(request.RoomID, []api.OutputEvent{
|
||||
{
|
||||
Type: api.OutputTypeNewInboundPeek,
|
||||
NewInboundPeek: &api.OutputNewInboundPeek{
|
||||
RoomID: request.RoomID,
|
||||
PeekID: request.PeekID,
|
||||
LatestEventID: latestEvents[0].EventID(),
|
||||
ServerName: request.ServerName,
|
||||
RenewalInterval: request.RenewalInterval,
|
||||
},
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
|
@ -225,7 +225,7 @@ func buildInviteStrippedState(
|
|||
for _, t := range []string{
|
||||
gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias,
|
||||
gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules,
|
||||
"m.room.avatar", "m.room.encryption",
|
||||
"m.room.avatar", "m.room.encryption", gomatrixserverlib.MRoomCreate,
|
||||
} {
|
||||
stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: t,
|
||||
|
|
|
@ -151,11 +151,28 @@ func (r *Peeker) performPeekRoomByID(
|
|||
}
|
||||
}
|
||||
|
||||
// If the server name in the room ID isn't ours then it's a
|
||||
// possible candidate for finding the room via federation. Add
|
||||
// it to the list of servers to try.
|
||||
// handle federated peeks
|
||||
// FIXME: don't create an outbound peek if we already have one going.
|
||||
if domain != r.Cfg.Matrix.ServerName {
|
||||
// If the server name in the room ID isn't ours then it's a
|
||||
// possible candidate for finding the room via federation. Add
|
||||
// it to the list of servers to try.
|
||||
req.ServerNames = append(req.ServerNames, domain)
|
||||
|
||||
// Try peeking by all of the supplied server names.
|
||||
fedReq := fsAPI.PerformOutboundPeekRequest{
|
||||
RoomID: req.RoomIDOrAlias, // the room ID to try and peek
|
||||
ServerNames: req.ServerNames, // the servers to try peeking via
|
||||
}
|
||||
fedRes := fsAPI.PerformOutboundPeekResponse{}
|
||||
_ = r.FSAPI.PerformOutboundPeek(ctx, &fedReq, &fedRes)
|
||||
if fedRes.LastError != nil {
|
||||
return "", &api.PerformError{
|
||||
Code: api.PerformErrRemote,
|
||||
Msg: fedRes.LastError.Message,
|
||||
RemoteCode: fedRes.LastError.Code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this room isn't world_readable, we reject.
|
||||
|
|
|
@ -107,7 +107,7 @@ func (r *Queryer) QueryStateAfterEvents(
|
|||
}
|
||||
authEventIDs = util.UniqueStrings(authEventIDs)
|
||||
|
||||
authEvents, err := getAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs)
|
||||
authEvents, err := GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAuthChain: %w", err)
|
||||
}
|
||||
|
@ -447,10 +447,12 @@ func (r *Queryer) QueryStateAndAuthChain(
|
|||
response.RoomExists = true
|
||||
response.RoomVersion = info.RoomVersion
|
||||
|
||||
stateEvents, err := r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs)
|
||||
var stateEvents []*gomatrixserverlib.Event
|
||||
stateEvents, err = r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response.PrevEventsExist = true
|
||||
|
||||
// add the auth event IDs for the current state events too
|
||||
|
@ -461,7 +463,7 @@ func (r *Queryer) QueryStateAndAuthChain(
|
|||
}
|
||||
authEventIDs = util.UniqueStrings(authEventIDs) // de-dupe
|
||||
|
||||
authEvents, err := getAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs)
|
||||
authEvents, err := GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -510,11 +512,11 @@ func (r *Queryer) loadStateAtEventIDs(ctx context.Context, roomInfo types.RoomIn
|
|||
|
||||
type eventsFromIDs func(context.Context, []string) ([]types.Event, error)
|
||||
|
||||
// getAuthChain fetches the auth chain for the given auth events. An auth chain
|
||||
// GetAuthChain fetches the auth chain for the given auth events. An auth chain
|
||||
// is the list of all events that are referenced in the auth_events section, and
|
||||
// all their auth_events, recursively. The returned set of events contain the
|
||||
// given events. Will *not* error if we don't have all auth events.
|
||||
func getAuthChain(
|
||||
func GetAuthChain(
|
||||
ctx context.Context, fn eventsFromIDs, authEventIDs []string,
|
||||
) ([]*gomatrixserverlib.Event, error) {
|
||||
// List of event IDs to fetch. On each pass, these events will be requested
|
||||
|
@ -718,7 +720,7 @@ func (r *Queryer) QueryServerBannedFromRoom(ctx context.Context, req *api.QueryS
|
|||
}
|
||||
|
||||
func (r *Queryer) QueryAuthChain(ctx context.Context, req *api.QueryAuthChainRequest, res *api.QueryAuthChainResponse) error {
|
||||
chain, err := getAuthChain(ctx, r.DB.EventsFromIDs, req.EventIDs)
|
||||
chain, err := GetAuthChain(ctx, r.DB.EventsFromIDs, req.EventIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ func TestGetAuthChainSingle(t *testing.T) {
|
|||
t.Fatalf("Failed to add events to db: %v", err)
|
||||
}
|
||||
|
||||
result, err := getAuthChain(context.TODO(), db.EventsFromIDs, []string{"e"})
|
||||
result, err := GetAuthChain(context.TODO(), db.EventsFromIDs, []string{"e"})
|
||||
if err != nil {
|
||||
t.Fatalf("getAuthChain failed: %v", err)
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func TestGetAuthChainMultiple(t *testing.T) {
|
|||
t.Fatalf("Failed to add events to db: %v", err)
|
||||
}
|
||||
|
||||
result, err := getAuthChain(context.TODO(), db.EventsFromIDs, []string{"e", "f"})
|
||||
result, err := GetAuthChain(context.TODO(), db.EventsFromIDs, []string{"e", "f"})
|
||||
if err != nil {
|
||||
t.Fatalf("getAuthChain failed: %v", err)
|
||||
}
|
||||
|
|
|
@ -26,14 +26,15 @@ const (
|
|||
RoomserverInputRoomEventsPath = "/roomserver/inputRoomEvents"
|
||||
|
||||
// Perform operations
|
||||
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
||||
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
||||
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
||||
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
||||
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
||||
RoomserverPerformBackfillPath = "/roomserver/performBackfill"
|
||||
RoomserverPerformPublishPath = "/roomserver/performPublish"
|
||||
RoomserverPerformForgetPath = "/roomserver/performForget"
|
||||
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
||||
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
||||
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
||||
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
||||
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
||||
RoomserverPerformBackfillPath = "/roomserver/performBackfill"
|
||||
RoomserverPerformPublishPath = "/roomserver/performPublish"
|
||||
RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek"
|
||||
RoomserverPerformForgetPath = "/roomserver/performForget"
|
||||
|
||||
// Query operations
|
||||
RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState"
|
||||
|
@ -216,6 +217,18 @@ func (h *httpRoomserverInternalAPI) PerformPeek(
|
|||
}
|
||||
}
|
||||
|
||||
func (h *httpRoomserverInternalAPI) PerformInboundPeek(
|
||||
ctx context.Context,
|
||||
request *api.PerformInboundPeekRequest,
|
||||
response *api.PerformInboundPeekResponse,
|
||||
) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformInboundPeek")
|
||||
defer span.Finish()
|
||||
|
||||
apiURL := h.roomserverURL + RoomserverPerformInboundPeekPath
|
||||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
}
|
||||
|
||||
func (h *httpRoomserverInternalAPI) PerformUnpeek(
|
||||
ctx context.Context,
|
||||
request *api.PerformUnpeekRequest,
|
||||
|
|
|
@ -72,6 +72,19 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
|
|||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||
}),
|
||||
)
|
||||
internalAPIMux.Handle(RoomserverPerformInboundPeekPath,
|
||||
httputil.MakeInternalAPI("performInboundPeek", func(req *http.Request) util.JSONResponse {
|
||||
var request api.PerformInboundPeekRequest
|
||||
var response api.PerformInboundPeekResponse
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if err := r.PerformInboundPeek(req.Context(), &request, &response); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||
}),
|
||||
)
|
||||
internalAPIMux.Handle(RoomserverPerformPeekPath,
|
||||
httputil.MakeInternalAPI("performUnpeek", func(req *http.Request) util.JSONResponse {
|
||||
var request api.PerformUnpeekRequest
|
||||
|
|
|
@ -33,19 +33,21 @@ import (
|
|||
type StateResolution struct {
|
||||
db storage.Database
|
||||
roomInfo types.RoomInfo
|
||||
events map[types.EventNID]*gomatrixserverlib.Event
|
||||
}
|
||||
|
||||
func NewStateResolution(db storage.Database, roomInfo types.RoomInfo) StateResolution {
|
||||
return StateResolution{
|
||||
db: db,
|
||||
roomInfo: roomInfo,
|
||||
events: make(map[types.EventNID]*gomatrixserverlib.Event),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadStateAtSnapshot loads the full state of a room at a particular snapshot.
|
||||
// This is typically the state before an event or the current state of a room.
|
||||
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||
func (v StateResolution) LoadStateAtSnapshot(
|
||||
func (v *StateResolution) LoadStateAtSnapshot(
|
||||
ctx context.Context, stateNID types.StateSnapshotNID,
|
||||
) ([]types.StateEntry, error) {
|
||||
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
||||
|
@ -83,7 +85,7 @@ func (v StateResolution) LoadStateAtSnapshot(
|
|||
}
|
||||
|
||||
// LoadStateAtEvent loads the full state of a room before a particular event.
|
||||
func (v StateResolution) LoadStateAtEvent(
|
||||
func (v *StateResolution) LoadStateAtEvent(
|
||||
ctx context.Context, eventID string,
|
||||
) ([]types.StateEntry, error) {
|
||||
snapshotNID, err := v.db.SnapshotNIDFromEventID(ctx, eventID)
|
||||
|
@ -105,7 +107,7 @@ func (v StateResolution) LoadStateAtEvent(
|
|||
// LoadCombinedStateAfterEvents loads a snapshot of the state after each of the events
|
||||
// and combines those snapshots together into a single list. At this point it is
|
||||
// possible to run into duplicate (type, state key) tuples.
|
||||
func (v StateResolution) LoadCombinedStateAfterEvents(
|
||||
func (v *StateResolution) LoadCombinedStateAfterEvents(
|
||||
ctx context.Context, prevStates []types.StateAtEvent,
|
||||
) ([]types.StateEntry, error) {
|
||||
stateNIDs := make([]types.StateSnapshotNID, len(prevStates))
|
||||
|
@ -116,7 +118,7 @@ func (v StateResolution) LoadCombinedStateAfterEvents(
|
|||
// Deduplicate the IDs before passing them to the database.
|
||||
// There could be duplicates because the events could be state events where
|
||||
// the snapshot of the room state before them was the same.
|
||||
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, uniqueStateSnapshotNIDs(stateNIDs))
|
||||
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, UniqueStateSnapshotNIDs(stateNIDs))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("v.db.StateBlockNIDs: %w", err)
|
||||
}
|
||||
|
@ -177,7 +179,7 @@ func (v StateResolution) LoadCombinedStateAfterEvents(
|
|||
}
|
||||
|
||||
// DifferenceBetweeenStateSnapshots works out which state entries have been added and removed between two snapshots.
|
||||
func (v StateResolution) DifferenceBetweeenStateSnapshots(
|
||||
func (v *StateResolution) DifferenceBetweeenStateSnapshots(
|
||||
ctx context.Context, oldStateNID, newStateNID types.StateSnapshotNID,
|
||||
) (removed, added []types.StateEntry, err error) {
|
||||
if oldStateNID == newStateNID {
|
||||
|
@ -236,7 +238,7 @@ func (v StateResolution) DifferenceBetweeenStateSnapshots(
|
|||
// If there is no entry for a given event type and state key pair then it will be discarded.
|
||||
// This is typically the state before an event or the current state of a room.
|
||||
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||
func (v StateResolution) LoadStateAtSnapshotForStringTuples(
|
||||
func (v *StateResolution) LoadStateAtSnapshotForStringTuples(
|
||||
ctx context.Context,
|
||||
stateNID types.StateSnapshotNID,
|
||||
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
||||
|
@ -251,7 +253,7 @@ func (v StateResolution) LoadStateAtSnapshotForStringTuples(
|
|||
// stringTuplesToNumericTuples converts the string state key tuples into numeric IDs
|
||||
// If there isn't a numeric ID for either the event type or the event state key then the tuple is discarded.
|
||||
// Returns an error if there was a problem talking to the database.
|
||||
func (v StateResolution) stringTuplesToNumericTuples(
|
||||
func (v *StateResolution) stringTuplesToNumericTuples(
|
||||
ctx context.Context,
|
||||
stringTuples []gomatrixserverlib.StateKeyTuple,
|
||||
) ([]types.StateKeyTuple, error) {
|
||||
|
@ -292,7 +294,7 @@ func (v StateResolution) stringTuplesToNumericTuples(
|
|||
// If there is no entry for a given event type and state key pair then it will be discarded.
|
||||
// This is typically the state before an event or the current state of a room.
|
||||
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||
func (v StateResolution) loadStateAtSnapshotForNumericTuples(
|
||||
func (v *StateResolution) loadStateAtSnapshotForNumericTuples(
|
||||
ctx context.Context,
|
||||
stateNID types.StateSnapshotNID,
|
||||
stateKeyTuples []types.StateKeyTuple,
|
||||
|
@ -340,7 +342,7 @@ func (v StateResolution) loadStateAtSnapshotForNumericTuples(
|
|||
// If there is no entry for a given event type and state key pair then it will be discarded.
|
||||
// This is typically the state before an event.
|
||||
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||
func (v StateResolution) LoadStateAfterEventsForStringTuples(
|
||||
func (v *StateResolution) LoadStateAfterEventsForStringTuples(
|
||||
ctx context.Context,
|
||||
prevStates []types.StateAtEvent,
|
||||
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
||||
|
@ -352,7 +354,7 @@ func (v StateResolution) LoadStateAfterEventsForStringTuples(
|
|||
return v.loadStateAfterEventsForNumericTuples(ctx, prevStates, numericTuples)
|
||||
}
|
||||
|
||||
func (v StateResolution) loadStateAfterEventsForNumericTuples(
|
||||
func (v *StateResolution) loadStateAfterEventsForNumericTuples(
|
||||
ctx context.Context,
|
||||
prevStates []types.StateAtEvent,
|
||||
stateKeyTuples []types.StateKeyTuple,
|
||||
|
@ -520,7 +522,7 @@ func init() {
|
|||
// CalculateAndStoreStateBeforeEvent calculates a snapshot of the state of a room before an event.
|
||||
// Stores the snapshot of the state in the database.
|
||||
// Returns a numeric ID for the snapshot of the state before the event.
|
||||
func (v StateResolution) CalculateAndStoreStateBeforeEvent(
|
||||
func (v *StateResolution) CalculateAndStoreStateBeforeEvent(
|
||||
ctx context.Context,
|
||||
event *gomatrixserverlib.Event,
|
||||
isRejected bool,
|
||||
|
@ -537,7 +539,7 @@ func (v StateResolution) CalculateAndStoreStateBeforeEvent(
|
|||
|
||||
// CalculateAndStoreStateAfterEvents finds the room state after the given events.
|
||||
// Stores the resulting state in the database and returns a numeric ID for that snapshot.
|
||||
func (v StateResolution) CalculateAndStoreStateAfterEvents(
|
||||
func (v *StateResolution) CalculateAndStoreStateAfterEvents(
|
||||
ctx context.Context,
|
||||
prevStates []types.StateAtEvent,
|
||||
) (types.StateSnapshotNID, error) {
|
||||
|
@ -607,7 +609,7 @@ const maxStateBlockNIDs = 64
|
|||
// calculateAndStoreStateAfterManyEvents finds the room state after the given events.
|
||||
// This handles the slow path of calculateAndStoreStateAfterEvents for when there is more than one event.
|
||||
// Stores the resulting state and returns a numeric ID for the snapshot.
|
||||
func (v StateResolution) calculateAndStoreStateAfterManyEvents(
|
||||
func (v *StateResolution) calculateAndStoreStateAfterManyEvents(
|
||||
ctx context.Context,
|
||||
roomNID types.RoomNID,
|
||||
prevStates []types.StateAtEvent,
|
||||
|
@ -627,7 +629,7 @@ func (v StateResolution) calculateAndStoreStateAfterManyEvents(
|
|||
return metrics.stop(v.db.AddState(ctx, roomNID, nil, state))
|
||||
}
|
||||
|
||||
func (v StateResolution) calculateStateAfterManyEvents(
|
||||
func (v *StateResolution) calculateStateAfterManyEvents(
|
||||
ctx context.Context, roomVersion gomatrixserverlib.RoomVersion,
|
||||
prevStates []types.StateAtEvent,
|
||||
) (state []types.StateEntry, algorithm string, conflictLength int, err error) {
|
||||
|
@ -754,7 +756,7 @@ func ResolveConflictsAdhoc(
|
|||
return resolved, nil
|
||||
}
|
||||
|
||||
func (v StateResolution) resolveConflicts(
|
||||
func (v *StateResolution) resolveConflicts(
|
||||
ctx context.Context, version gomatrixserverlib.RoomVersion,
|
||||
notConflicted, conflicted []types.StateEntry,
|
||||
) ([]types.StateEntry, error) {
|
||||
|
@ -778,7 +780,7 @@ func (v StateResolution) resolveConflicts(
|
|||
// Returns a list that combines the entries without conflicts with the result of state resolution for the entries with conflicts.
|
||||
// The returned list is sorted by state key tuple.
|
||||
// Returns an error if there was a problem talking to the database.
|
||||
func (v StateResolution) resolveConflictsV1(
|
||||
func (v *StateResolution) resolveConflictsV1(
|
||||
ctx context.Context,
|
||||
notConflicted, conflicted []types.StateEntry,
|
||||
) ([]types.StateEntry, error) {
|
||||
|
@ -842,7 +844,7 @@ func (v StateResolution) resolveConflictsV1(
|
|||
// The returned list is sorted by state key tuple.
|
||||
// Returns an error if there was a problem talking to the database.
|
||||
// nolint:gocyclo
|
||||
func (v StateResolution) resolveConflictsV2(
|
||||
func (v *StateResolution) resolveConflictsV2(
|
||||
ctx context.Context,
|
||||
notConflicted, conflicted []types.StateEntry,
|
||||
) ([]types.StateEntry, error) {
|
||||
|
@ -959,7 +961,7 @@ func (v StateResolution) resolveConflictsV2(
|
|||
}
|
||||
|
||||
// stateKeyTuplesNeeded works out which numeric state key tuples we need to authenticate some events.
|
||||
func (v StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.EventStateKeyNID, stateNeeded gomatrixserverlib.StateNeeded) []types.StateKeyTuple {
|
||||
func (v *StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.EventStateKeyNID, stateNeeded gomatrixserverlib.StateNeeded) []types.StateKeyTuple {
|
||||
var keyTuples []types.StateKeyTuple
|
||||
if stateNeeded.Create {
|
||||
keyTuples = append(keyTuples, types.StateKeyTuple{
|
||||
|
@ -1004,26 +1006,33 @@ func (v StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.Ev
|
|||
// Returns a list of state events in no particular order and a map from string event ID back to state entry.
|
||||
// The map can be used to recover which numeric state entry a given event is for.
|
||||
// Returns an error if there was a problem talking to the database.
|
||||
func (v StateResolution) loadStateEvents(
|
||||
func (v *StateResolution) loadStateEvents(
|
||||
ctx context.Context, entries []types.StateEntry,
|
||||
) ([]*gomatrixserverlib.Event, map[string]types.StateEntry, error) {
|
||||
eventNIDs := make([]types.EventNID, len(entries))
|
||||
for i := range entries {
|
||||
eventNIDs[i] = entries[i].EventNID
|
||||
result := make([]*gomatrixserverlib.Event, 0, len(entries))
|
||||
eventEntries := make([]types.StateEntry, 0, len(entries))
|
||||
eventNIDs := make([]types.EventNID, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if e, ok := v.events[entry.EventNID]; ok {
|
||||
result = append(result, e)
|
||||
} else {
|
||||
eventEntries = append(eventEntries, entry)
|
||||
eventNIDs = append(eventNIDs, entry.EventNID)
|
||||
}
|
||||
}
|
||||
events, err := v.db.Events(ctx, eventNIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
eventIDMap := map[string]types.StateEntry{}
|
||||
result := make([]*gomatrixserverlib.Event, len(entries))
|
||||
for i := range entries {
|
||||
event, ok := eventMap(events).lookup(entries[i].EventNID)
|
||||
for _, entry := range eventEntries {
|
||||
event, ok := eventMap(events).lookup(entry.EventNID)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Corrupt DB: Missing event numeric ID %d", entries[i].EventNID))
|
||||
panic(fmt.Errorf("Corrupt DB: Missing event numeric ID %d", entry.EventNID))
|
||||
}
|
||||
result[i] = event.Event
|
||||
eventIDMap[event.Event.EventID()] = entries[i]
|
||||
result = append(result, event.Event)
|
||||
eventIDMap[event.Event.EventID()] = entry
|
||||
v.events[entry.EventNID] = event.Event
|
||||
}
|
||||
return result, eventIDMap, nil
|
||||
}
|
||||
|
@ -1103,7 +1112,7 @@ func (s stateNIDSorter) Len() int { return len(s) }
|
|||
func (s stateNIDSorter) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s stateNIDSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func uniqueStateSnapshotNIDs(nids []types.StateSnapshotNID) []types.StateSnapshotNID {
|
||||
func UniqueStateSnapshotNIDs(nids []types.StateSnapshotNID) []types.StateSnapshotNID {
|
||||
return nids[:util.SortAndUnique(stateNIDSorter(nids))]
|
||||
}
|
||||
|
||||
|
|
111
setup/base.go
111
setup/base.go
|
@ -15,22 +15,28 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/userapi/storage/accounts"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
@ -61,6 +67,7 @@ import (
|
|||
// should only be used during start up.
|
||||
// Must be closed when shutting down.
|
||||
type BaseDendrite struct {
|
||||
*process.ProcessContext
|
||||
componentName string
|
||||
tracerCloser io.Closer
|
||||
PublicClientAPIMux *mux.Router
|
||||
|
@ -73,6 +80,7 @@ type BaseDendrite struct {
|
|||
httpClient *http.Client
|
||||
Cfg *config.Dendrite
|
||||
Caches *caching.Caches
|
||||
DNSCache *gomatrixserverlib.DNSCache
|
||||
// KafkaConsumer sarama.Consumer
|
||||
// KafkaProducer sarama.SyncProducer
|
||||
}
|
||||
|
@ -111,6 +119,20 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo
|
|||
logrus.WithError(err).Warnf("Failed to create cache")
|
||||
}
|
||||
|
||||
var dnsCache *gomatrixserverlib.DNSCache
|
||||
if cfg.Global.DNSCache.Enabled {
|
||||
lifetime := time.Second * cfg.Global.DNSCache.CacheLifetime
|
||||
dnsCache = gomatrixserverlib.NewDNSCache(
|
||||
cfg.Global.DNSCache.CacheSize,
|
||||
lifetime,
|
||||
)
|
||||
logrus.Infof(
|
||||
"DNS cache enabled (size %d, lifetime %s)",
|
||||
cfg.Global.DNSCache.CacheSize,
|
||||
lifetime,
|
||||
)
|
||||
}
|
||||
|
||||
apiClient := http.Client{
|
||||
Timeout: time.Minute * 10,
|
||||
Transport: &http2.Transport{
|
||||
|
@ -146,12 +168,15 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo
|
|||
// We need to be careful with media APIs if they read from a filesystem to make sure they
|
||||
// are not inadvertently reading paths without cleaning, else this could introduce a
|
||||
// directory traversal attack e.g /../../../etc/passwd
|
||||
|
||||
return &BaseDendrite{
|
||||
ProcessContext: process.NewProcessContext(),
|
||||
componentName: componentName,
|
||||
UseHTTPAPIs: useHTTPAPIs,
|
||||
tracerCloser: closer,
|
||||
Cfg: cfg,
|
||||
Caches: cache,
|
||||
DNSCache: dnsCache,
|
||||
PublicClientAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicClientPathPrefix).Subrouter().UseEncodedPath(),
|
||||
PublicFederationAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicFederationPathPrefix).Subrouter().UseEncodedPath(),
|
||||
PublicKeyAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicKeyPathPrefix).Subrouter().UseEncodedPath(),
|
||||
|
@ -250,11 +275,17 @@ func (b *BaseDendrite) CreateAccountsDB() accounts.Database {
|
|||
// Should only be called once per component.
|
||||
func (b *BaseDendrite) CreateClient() *gomatrixserverlib.Client {
|
||||
if b.Cfg.Global.DisableFederation {
|
||||
return gomatrixserverlib.NewClientWithTransport(noOpHTTPTransport)
|
||||
return gomatrixserverlib.NewClient(
|
||||
gomatrixserverlib.WithTransport(noOpHTTPTransport),
|
||||
)
|
||||
}
|
||||
client := gomatrixserverlib.NewClient(
|
||||
b.Cfg.FederationSender.DisableTLSValidation,
|
||||
)
|
||||
opts := []gomatrixserverlib.ClientOption{
|
||||
gomatrixserverlib.WithSkipVerify(b.Cfg.FederationSender.DisableTLSValidation),
|
||||
}
|
||||
if b.Cfg.Global.DNSCache.Enabled {
|
||||
opts = append(opts, gomatrixserverlib.WithDNSCache(b.DNSCache))
|
||||
}
|
||||
client := gomatrixserverlib.NewClient(opts...)
|
||||
client.SetUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString()))
|
||||
return client
|
||||
}
|
||||
|
@ -263,14 +294,21 @@ func (b *BaseDendrite) CreateClient() *gomatrixserverlib.Client {
|
|||
// once per component.
|
||||
func (b *BaseDendrite) CreateFederationClient() *gomatrixserverlib.FederationClient {
|
||||
if b.Cfg.Global.DisableFederation {
|
||||
return gomatrixserverlib.NewFederationClientWithTransport(
|
||||
return gomatrixserverlib.NewFederationClient(
|
||||
b.Cfg.Global.ServerName, b.Cfg.Global.KeyID, b.Cfg.Global.PrivateKey,
|
||||
b.Cfg.FederationSender.DisableTLSValidation, noOpHTTPTransport,
|
||||
gomatrixserverlib.WithTransport(noOpHTTPTransport),
|
||||
)
|
||||
}
|
||||
client := gomatrixserverlib.NewFederationClientWithTimeout(
|
||||
b.Cfg.Global.ServerName, b.Cfg.Global.KeyID, b.Cfg.Global.PrivateKey,
|
||||
b.Cfg.FederationSender.DisableTLSValidation, time.Minute*5,
|
||||
opts := []gomatrixserverlib.ClientOption{
|
||||
gomatrixserverlib.WithTimeout(time.Minute * 5),
|
||||
gomatrixserverlib.WithSkipVerify(b.Cfg.FederationSender.DisableTLSValidation),
|
||||
}
|
||||
if b.Cfg.Global.DNSCache.Enabled {
|
||||
opts = append(opts, gomatrixserverlib.WithDNSCache(b.DNSCache))
|
||||
}
|
||||
client := gomatrixserverlib.NewFederationClient(
|
||||
b.Cfg.Global.ServerName, b.Cfg.Global.KeyID,
|
||||
b.Cfg.Global.PrivateKey, opts...,
|
||||
)
|
||||
client.SetUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString()))
|
||||
return client
|
||||
|
@ -325,14 +363,26 @@ func (b *BaseDendrite) SetupAndServeHTTP(
|
|||
|
||||
if internalAddr != NoListener && internalAddr != externalAddr {
|
||||
go func() {
|
||||
var internalShutdown atomic.Bool // RegisterOnShutdown can be called more than once
|
||||
logrus.Infof("Starting internal %s listener on %s", b.componentName, internalServ.Addr)
|
||||
b.ProcessContext.ComponentStarted()
|
||||
internalServ.RegisterOnShutdown(func() {
|
||||
if internalShutdown.CAS(false, true) {
|
||||
b.ProcessContext.ComponentFinished()
|
||||
logrus.Infof("Stopped internal HTTP listener")
|
||||
}
|
||||
})
|
||||
if certFile != nil && keyFile != nil {
|
||||
if err := internalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTPS")
|
||||
if err != http.ErrServerClosed {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTPS")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := internalServ.ListenAndServe(); err != nil {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTP")
|
||||
if err != http.ErrServerClosed {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTP")
|
||||
}
|
||||
}
|
||||
}
|
||||
logrus.Infof("Stopped internal %s listener on %s", b.componentName, internalServ.Addr)
|
||||
|
@ -341,19 +391,52 @@ func (b *BaseDendrite) SetupAndServeHTTP(
|
|||
|
||||
if externalAddr != NoListener {
|
||||
go func() {
|
||||
var externalShutdown atomic.Bool // RegisterOnShutdown can be called more than once
|
||||
logrus.Infof("Starting external %s listener on %s", b.componentName, externalServ.Addr)
|
||||
b.ProcessContext.ComponentStarted()
|
||||
externalServ.RegisterOnShutdown(func() {
|
||||
if externalShutdown.CAS(false, true) {
|
||||
b.ProcessContext.ComponentFinished()
|
||||
logrus.Infof("Stopped external HTTP listener")
|
||||
}
|
||||
})
|
||||
if certFile != nil && keyFile != nil {
|
||||
if err := externalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTPS")
|
||||
if err != http.ErrServerClosed {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTPS")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := externalServ.ListenAndServe(); err != nil {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTP")
|
||||
if err != http.ErrServerClosed {
|
||||
logrus.WithError(err).Fatal("failed to serve HTTP")
|
||||
}
|
||||
}
|
||||
}
|
||||
logrus.Infof("Stopped external %s listener on %s", b.componentName, externalServ.Addr)
|
||||
}()
|
||||
}
|
||||
|
||||
select {}
|
||||
<-b.ProcessContext.WaitForShutdown()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_ = internalServ.Shutdown(ctx)
|
||||
_ = externalServ.Shutdown(ctx)
|
||||
logrus.Infof("Stopped HTTP listeners")
|
||||
}
|
||||
|
||||
func (b *BaseDendrite) WaitForShutdown() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
logrus.Warnf("Shutdown signal received")
|
||||
|
||||
b.ProcessContext.ShutdownDendrite()
|
||||
b.ProcessContext.WaitForComponentsToFinish()
|
||||
|
||||
logrus.Warnf("Dendrite is exiting now")
|
||||
}
|
||||
|
|
|
@ -344,6 +344,7 @@ func (c *Dendrite) Wiring() {
|
|||
|
||||
c.ClientAPI.Derived = &c.Derived
|
||||
c.AppServiceAPI.Derived = &c.Derived
|
||||
c.ClientAPI.MSCs = &c.MSCs
|
||||
}
|
||||
|
||||
// Error returns a string detailing how many errors were contained within a
|
||||
|
|
|
@ -37,6 +37,8 @@ type ClientAPI struct {
|
|||
|
||||
// Rate-limiting options
|
||||
RateLimiting RateLimiting `yaml:"rate_limiting"`
|
||||
|
||||
MSCs *MSCs `yaml:"mscs"`
|
||||
}
|
||||
|
||||
func (c *ClientAPI) Defaults() {
|
||||
|
|
|
@ -48,6 +48,9 @@ type Global struct {
|
|||
|
||||
// Metrics configuration
|
||||
Metrics Metrics `yaml:"metrics"`
|
||||
|
||||
// DNS caching options for all outbound HTTP requests
|
||||
DNSCache DNSCacheOptions `yaml:"dns_cache"`
|
||||
}
|
||||
|
||||
func (c *Global) Defaults() {
|
||||
|
@ -59,6 +62,7 @@ func (c *Global) Defaults() {
|
|||
|
||||
c.Kafka.Defaults()
|
||||
c.Metrics.Defaults()
|
||||
c.DNSCache.Defaults()
|
||||
}
|
||||
|
||||
func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
|
@ -67,6 +71,7 @@ func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
|||
|
||||
c.Kafka.Verify(configErrs, isMonolith)
|
||||
c.Metrics.Verify(configErrs, isMonolith)
|
||||
c.DNSCache.Verify(configErrs, isMonolith)
|
||||
}
|
||||
|
||||
type OldVerifyKeys struct {
|
||||
|
@ -140,3 +145,23 @@ func (c DatabaseOptions) MaxOpenConns() int {
|
|||
func (c DatabaseOptions) ConnMaxLifetime() time.Duration {
|
||||
return time.Duration(c.ConnMaxLifetimeSeconds) * time.Second
|
||||
}
|
||||
|
||||
type DNSCacheOptions struct {
|
||||
// Whether the DNS cache is enabled or not
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// How many entries to store in the DNS cache at a given time
|
||||
CacheSize int `yaml:"cache_size"`
|
||||
// How long a cache entry should be considered valid for
|
||||
CacheLifetime time.Duration `yaml:"cache_lifetime"`
|
||||
}
|
||||
|
||||
func (c *DNSCacheOptions) Defaults() {
|
||||
c.Enabled = false
|
||||
c.CacheSize = 256
|
||||
c.CacheLifetime = time.Minute * 5
|
||||
}
|
||||
|
||||
func (c *DNSCacheOptions) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
checkPositive(configErrs, "cache_size", int64(c.CacheSize))
|
||||
checkPositive(configErrs, "cache_lifetime", int64(c.CacheLifetime))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,11 @@ package config
|
|||
type MSCs struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
// The MSCs to enable
|
||||
// The MSCs to enable. Supported MSCs include:
|
||||
// 'msc2444': Peeking over federation - https://github.com/matrix-org/matrix-doc/pull/2444
|
||||
// 'msc2753': Peeking via /sync - https://github.com/matrix-org/matrix-doc/pull/2753
|
||||
// 'msc2836': Threading - https://github.com/matrix-org/matrix-doc/pull/2836
|
||||
// 'msc2946': Spaces Summary - https://github.com/matrix-org/matrix-doc/pull/2946
|
||||
MSCs []string `yaml:"mscs"`
|
||||
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
|
@ -14,6 +18,16 @@ func (c *MSCs) Defaults() {
|
|||
c.Database.ConnectionString = "file:mscs.db"
|
||||
}
|
||||
|
||||
// Enabled returns true if the given msc is enabled. Should in the form 'msc12345'.
|
||||
func (c *MSCs) Enabled(msc string) bool {
|
||||
for _, m := range c.MSCs {
|
||||
if m == msc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *MSCs) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
checkNotEmpty(configErrs, "mscs.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/mediaapi"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
serverKeyAPI "github.com/matrix-org/dendrite/signingkeyserver/api"
|
||||
"github.com/matrix-org/dendrite/syncapi"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
|
@ -56,21 +57,22 @@ type Monolith struct {
|
|||
}
|
||||
|
||||
// AddAllPublicRoutes attaches all public paths to the given router
|
||||
func (m *Monolith) AddAllPublicRoutes(csMux, ssMux, keyMux, mediaMux *mux.Router) {
|
||||
func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, mediaMux *mux.Router) {
|
||||
clientapi.AddPublicRoutes(
|
||||
csMux, &m.Config.ClientAPI, m.AccountDB,
|
||||
m.FedClient, m.RoomserverAPI,
|
||||
m.EDUInternalAPI, m.AppserviceAPI, transactions.New(),
|
||||
m.FederationSenderAPI, m.UserAPI, m.KeyAPI, m.ExtPublicRoomsProvider,
|
||||
&m.Config.MSCs,
|
||||
)
|
||||
federationapi.AddPublicRoutes(
|
||||
ssMux, keyMux, &m.Config.FederationAPI, m.UserAPI, m.FedClient,
|
||||
m.KeyRing, m.RoomserverAPI, m.FederationSenderAPI,
|
||||
m.EDUInternalAPI, m.KeyAPI,
|
||||
m.EDUInternalAPI, m.KeyAPI, &m.Config.MSCs,
|
||||
)
|
||||
mediaapi.AddPublicRoutes(mediaMux, &m.Config.MediaAPI, m.UserAPI, m.Client)
|
||||
syncapi.AddPublicRoutes(
|
||||
csMux, m.UserAPI, m.RoomserverAPI,
|
||||
process, csMux, m.UserAPI, m.RoomserverAPI,
|
||||
m.KeyAPI, m.FedClient, &m.Config.SyncAPI,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,12 +17,17 @@ package msc2946
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
chttputil "github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
fs "github.com/matrix-org/dendrite/federationsender/api"
|
||||
"github.com/matrix-org/dendrite/internal/hooks"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
roomserver "github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
@ -36,41 +41,19 @@ import (
|
|||
const (
|
||||
ConstCreateEventContentKey = "org.matrix.msc1772.type"
|
||||
ConstSpaceChildEventType = "org.matrix.msc1772.space.child"
|
||||
ConstSpaceParentEventType = "org.matrix.msc1772.room.parent"
|
||||
ConstSpaceParentEventType = "org.matrix.msc1772.space.parent"
|
||||
)
|
||||
|
||||
// SpacesRequest is the request body to POST /_matrix/client/r0/rooms/{roomID}/spaces
|
||||
type SpacesRequest struct {
|
||||
MaxRoomsPerSpace int `json:"max_rooms_per_space"`
|
||||
Limit int `json:"limit"`
|
||||
Batch string `json:"batch"`
|
||||
}
|
||||
|
||||
// Defaults sets the request defaults
|
||||
func (r *SpacesRequest) Defaults() {
|
||||
func Defaults(r *gomatrixserverlib.MSC2946SpacesRequest) {
|
||||
r.Limit = 100
|
||||
r.MaxRoomsPerSpace = -1
|
||||
}
|
||||
|
||||
// SpacesResponse is the response body to POST /_matrix/client/r0/rooms/{roomID}/spaces
|
||||
type SpacesResponse struct {
|
||||
NextBatch string `json:"next_batch"`
|
||||
// Rooms are nodes on the space graph.
|
||||
Rooms []Room `json:"rooms"`
|
||||
// Events are edges on the space graph, exclusively m.space.child or m.room.parent events
|
||||
Events []gomatrixserverlib.ClientEvent `json:"events"`
|
||||
}
|
||||
|
||||
// Room is a node on the space graph
|
||||
type Room struct {
|
||||
gomatrixserverlib.PublicRoom
|
||||
NumRefs int `json:"num_refs"`
|
||||
RoomType string `json:"room_type"`
|
||||
}
|
||||
|
||||
// Enable this MSC
|
||||
func Enable(
|
||||
base *setup.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, userAPI userapi.UserInternalAPI,
|
||||
fsAPI fs.FederationSenderInternalAPI, keyRing gomatrixserverlib.JSONVerifier,
|
||||
) error {
|
||||
db, err := NewDatabase(&base.Cfg.MSCs.Database)
|
||||
if err != nil {
|
||||
|
@ -88,12 +71,69 @@ func Enable(
|
|||
})
|
||||
|
||||
base.PublicClientAPIMux.Handle("/unstable/rooms/{roomID}/spaces",
|
||||
httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(db, rsAPI)),
|
||||
httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(db, rsAPI, fsAPI, base.Cfg.Global.ServerName)),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
base.PublicFederationAPIMux.Handle("/unstable/spaces/{roomID}", httputil.MakeExternalAPI(
|
||||
"msc2946_fed_spaces", func(req *http.Request) util.JSONResponse {
|
||||
fedReq, errResp := gomatrixserverlib.VerifyHTTPRequest(
|
||||
req, time.Now(), base.Cfg.Global.ServerName, keyRing,
|
||||
)
|
||||
if fedReq == nil {
|
||||
return errResp
|
||||
}
|
||||
// Extract the room ID from the request. Sanity check request data.
|
||||
params, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
roomID := params["roomID"]
|
||||
return federatedSpacesHandler(req.Context(), fedReq, roomID, db, rsAPI, fsAPI, base.Cfg.Global.ServerName)
|
||||
},
|
||||
)).Methods(http.MethodPost, http.MethodOptions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*http.Request, *userapi.Device) util.JSONResponse {
|
||||
func federatedSpacesHandler(
|
||||
ctx context.Context, fedReq *gomatrixserverlib.FederationRequest, roomID string, db Database,
|
||||
rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI,
|
||||
thisServer gomatrixserverlib.ServerName,
|
||||
) util.JSONResponse {
|
||||
inMemoryBatchCache := make(map[string]set)
|
||||
var r gomatrixserverlib.MSC2946SpacesRequest
|
||||
Defaults(&r)
|
||||
if err := json.Unmarshal(fedReq.Content(), &r); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
if r.Limit > 100 {
|
||||
r.Limit = 100
|
||||
}
|
||||
w := walker{
|
||||
req: &r,
|
||||
rootRoomID: roomID,
|
||||
serverName: fedReq.Origin(),
|
||||
thisServer: thisServer,
|
||||
ctx: ctx,
|
||||
|
||||
db: db,
|
||||
rsAPI: rsAPI,
|
||||
fsAPI: fsAPI,
|
||||
inMemoryBatchCache: inMemoryBatchCache,
|
||||
}
|
||||
res := w.walk()
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: res,
|
||||
}
|
||||
}
|
||||
|
||||
func spacesHandler(
|
||||
db Database, rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI,
|
||||
thisServer gomatrixserverlib.ServerName,
|
||||
) func(*http.Request, *userapi.Device) util.JSONResponse {
|
||||
return func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
inMemoryBatchCache := make(map[string]set)
|
||||
// Extract the room ID from the request. Sanity check request data.
|
||||
|
@ -102,8 +142,8 @@ func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*ht
|
|||
return util.ErrorResponse(err)
|
||||
}
|
||||
roomID := params["roomID"]
|
||||
var r SpacesRequest
|
||||
r.Defaults()
|
||||
var r gomatrixserverlib.MSC2946SpacesRequest
|
||||
Defaults(&r)
|
||||
if resErr := chttputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
@ -114,10 +154,12 @@ func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*ht
|
|||
req: &r,
|
||||
rootRoomID: roomID,
|
||||
caller: device,
|
||||
thisServer: thisServer,
|
||||
ctx: req.Context(),
|
||||
|
||||
db: db,
|
||||
rsAPI: rsAPI,
|
||||
fsAPI: fsAPI,
|
||||
inMemoryBatchCache: inMemoryBatchCache,
|
||||
}
|
||||
res := w.walk()
|
||||
|
@ -129,11 +171,14 @@ func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*ht
|
|||
}
|
||||
|
||||
type walker struct {
|
||||
req *SpacesRequest
|
||||
req *gomatrixserverlib.MSC2946SpacesRequest
|
||||
rootRoomID string
|
||||
caller *userapi.Device
|
||||
serverName gomatrixserverlib.ServerName
|
||||
thisServer gomatrixserverlib.ServerName
|
||||
db Database
|
||||
rsAPI roomserver.RoomserverInternalAPI
|
||||
fsAPI fs.FederationSenderInternalAPI
|
||||
ctx context.Context
|
||||
|
||||
// user ID|device ID|batch_num => event/room IDs sent to client
|
||||
|
@ -141,10 +186,26 @@ type walker struct {
|
|||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *walker) roomIsExcluded(roomID string) bool {
|
||||
for _, exclRoom := range w.req.ExcludeRooms {
|
||||
if exclRoom == roomID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *walker) callerID() string {
|
||||
if w.caller != nil {
|
||||
return w.caller.UserID + "|" + w.caller.ID
|
||||
}
|
||||
return string(w.serverName)
|
||||
}
|
||||
|
||||
func (w *walker) alreadySent(id string) bool {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
m, ok := w.inMemoryBatchCache[w.caller.UserID+"|"+w.caller.ID]
|
||||
m, ok := w.inMemoryBatchCache[w.callerID()]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
@ -154,17 +215,17 @@ func (w *walker) alreadySent(id string) bool {
|
|||
func (w *walker) markSent(id string) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
m := w.inMemoryBatchCache[w.caller.UserID+"|"+w.caller.ID]
|
||||
m := w.inMemoryBatchCache[w.callerID()]
|
||||
if m == nil {
|
||||
m = make(set)
|
||||
}
|
||||
m[id] = true
|
||||
w.inMemoryBatchCache[w.caller.UserID+"|"+w.caller.ID] = m
|
||||
w.inMemoryBatchCache[w.callerID()] = m
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (w *walker) walk() *SpacesResponse {
|
||||
var res SpacesResponse
|
||||
func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse {
|
||||
var res gomatrixserverlib.MSC2946SpacesResponse
|
||||
// Begin walking the graph starting with the room ID in the request in a queue of unvisited rooms
|
||||
unvisited := []string{w.rootRoomID}
|
||||
processed := make(set)
|
||||
|
@ -177,36 +238,58 @@ func (w *walker) walk() *SpacesResponse {
|
|||
}
|
||||
// Mark this room as processed.
|
||||
processed[roomID] = true
|
||||
// Is the caller currently joined to the room or is the room `world_readable`
|
||||
// If no, skip this room. If yes, continue.
|
||||
if !w.authorised(roomID) {
|
||||
continue
|
||||
}
|
||||
// Get all `m.space.child` and `m.room.parent` state events for the room. *In addition*, get
|
||||
// all `m.space.child` and `m.room.parent` state events which *point to* (via `state_key` or `content.room_id`)
|
||||
// this room. This requires servers to store reverse lookups.
|
||||
refs, err := w.references(roomID)
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Error("failed to extract references for room")
|
||||
continue
|
||||
}
|
||||
|
||||
// If this room has not ever been in `rooms` (across multiple requests), extract the
|
||||
// `PublicRoomsChunk` for this room.
|
||||
if !w.alreadySent(roomID) {
|
||||
// Collect rooms/events to send back (either locally or fetched via federation)
|
||||
var discoveredRooms []gomatrixserverlib.MSC2946Room
|
||||
var discoveredEvents []gomatrixserverlib.MSC2946StrippedEvent
|
||||
|
||||
// If we know about this room and the caller is authorised (joined/world_readable) then pull
|
||||
// events locally
|
||||
if w.roomExists(roomID) && w.authorised(roomID) {
|
||||
// Get all `m.space.child` and `m.space.parent` state events for the room. *In addition*, get
|
||||
// all `m.space.child` and `m.space.parent` state events which *point to* (via `state_key` or `content.room_id`)
|
||||
// this room. This requires servers to store reverse lookups.
|
||||
events, err := w.references(roomID)
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Error("failed to extract references for room")
|
||||
continue
|
||||
}
|
||||
discoveredEvents = events
|
||||
|
||||
pubRoom := w.publicRoomsChunk(roomID)
|
||||
roomType := ""
|
||||
create := w.stateEvent(roomID, "m.room.create", "")
|
||||
create := w.stateEvent(roomID, gomatrixserverlib.MRoomCreate, "")
|
||||
if create != nil {
|
||||
roomType = gjson.GetBytes(create.Content(), ConstCreateEventContentKey).Str
|
||||
// escape the `.`s so gjson doesn't think it's nested
|
||||
roomType = gjson.GetBytes(create.Content(), strings.ReplaceAll(ConstCreateEventContentKey, ".", `\.`)).Str
|
||||
}
|
||||
|
||||
// Add the total number of events to `PublicRoomsChunk` under `num_refs`. Add `PublicRoomsChunk` to `rooms`.
|
||||
res.Rooms = append(res.Rooms, Room{
|
||||
discoveredRooms = append(discoveredRooms, gomatrixserverlib.MSC2946Room{
|
||||
PublicRoom: *pubRoom,
|
||||
NumRefs: refs.len(),
|
||||
NumRefs: len(discoveredEvents),
|
||||
RoomType: roomType,
|
||||
})
|
||||
} else {
|
||||
// attempt to query this room over federation, as either we've never heard of it before
|
||||
// or we've left it and hence are not authorised (but info may be exposed regardless)
|
||||
fedRes, err := w.federatedRoomInfo(roomID)
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Errorf("failed to query federated spaces")
|
||||
continue
|
||||
}
|
||||
if fedRes != nil {
|
||||
discoveredRooms = fedRes.Rooms
|
||||
discoveredEvents = fedRes.Events
|
||||
}
|
||||
}
|
||||
|
||||
// If this room has not ever been in `rooms` (across multiple requests), send it now
|
||||
for _, room := range discoveredRooms {
|
||||
if !w.alreadySent(room.RoomID) && !w.roomIsExcluded(room.RoomID) {
|
||||
res.Rooms = append(res.Rooms, room)
|
||||
w.markSent(room.RoomID)
|
||||
}
|
||||
}
|
||||
|
||||
uniqueRooms := make(set)
|
||||
|
@ -214,36 +297,37 @@ func (w *walker) walk() *SpacesResponse {
|
|||
// If this is the root room from the original request, insert all these events into `events` if
|
||||
// they haven't been added before (across multiple requests).
|
||||
if w.rootRoomID == roomID {
|
||||
for _, ev := range refs.events() {
|
||||
if !w.alreadySent(ev.EventID()) {
|
||||
res.Events = append(res.Events, gomatrixserverlib.HeaderedToClientEvent(
|
||||
ev, gomatrixserverlib.FormatAll,
|
||||
))
|
||||
uniqueRooms[ev.RoomID()] = true
|
||||
uniqueRooms[SpaceTarget(ev)] = true
|
||||
w.markSent(ev.EventID())
|
||||
for _, ev := range discoveredEvents {
|
||||
if !w.alreadySent(eventKey(&ev)) {
|
||||
res.Events = append(res.Events, ev)
|
||||
uniqueRooms[ev.RoomID] = true
|
||||
uniqueRooms[spaceTargetStripped(&ev)] = true
|
||||
w.markSent(eventKey(&ev))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Else add them to `events` honouring the `limit` and `max_rooms_per_space` values. If either
|
||||
// are exceeded, stop adding events. If the event has already been added, do not add it again.
|
||||
numAdded := 0
|
||||
for _, ev := range refs.events() {
|
||||
for _, ev := range discoveredEvents {
|
||||
if w.req.Limit > 0 && len(res.Events) >= w.req.Limit {
|
||||
break
|
||||
}
|
||||
if w.req.MaxRoomsPerSpace > 0 && numAdded >= w.req.MaxRoomsPerSpace {
|
||||
break
|
||||
}
|
||||
if w.alreadySent(ev.EventID()) {
|
||||
if w.alreadySent(eventKey(&ev)) {
|
||||
continue
|
||||
}
|
||||
res.Events = append(res.Events, gomatrixserverlib.HeaderedToClientEvent(
|
||||
ev, gomatrixserverlib.FormatAll,
|
||||
))
|
||||
uniqueRooms[ev.RoomID()] = true
|
||||
uniqueRooms[SpaceTarget(ev)] = true
|
||||
w.markSent(ev.EventID())
|
||||
// Skip the room if it's part of exclude_rooms but ONLY IF the source matches, as we still
|
||||
// want to catch arrows which point to excluded rooms.
|
||||
if w.roomIsExcluded(ev.RoomID) {
|
||||
continue
|
||||
}
|
||||
res.Events = append(res.Events, ev)
|
||||
uniqueRooms[ev.RoomID] = true
|
||||
uniqueRooms[spaceTargetStripped(&ev)] = true
|
||||
w.markSent(eventKey(&ev))
|
||||
// we don't distinguish between child state events and parent state events for the purposes of
|
||||
// max_rooms_per_space, maybe we should?
|
||||
numAdded++
|
||||
|
@ -287,8 +371,120 @@ func (w *walker) publicRoomsChunk(roomID string) *gomatrixserverlib.PublicRoom {
|
|||
return &pubRooms[0]
|
||||
}
|
||||
|
||||
// federatedRoomInfo returns more of the spaces graph from another server. Returns nil if this was
|
||||
// unsuccessful.
|
||||
func (w *walker) federatedRoomInfo(roomID string) (*gomatrixserverlib.MSC2946SpacesResponse, error) {
|
||||
// only do federated requests for client requests
|
||||
if w.caller == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// extract events which point to this room ID and extract their vias
|
||||
events, err := w.db.References(w.ctx, roomID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get References events: %w", err)
|
||||
}
|
||||
vias := make(set)
|
||||
for _, ev := range events {
|
||||
if ev.StateKeyEquals(roomID) {
|
||||
// event points at this room, extract vias
|
||||
content := struct {
|
||||
Vias []string `json:"via"`
|
||||
}{}
|
||||
if err = json.Unmarshal(ev.Content(), &content); err != nil {
|
||||
continue // silently ignore corrupted state events
|
||||
}
|
||||
for _, v := range content.Vias {
|
||||
vias[v] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
util.GetLogger(w.ctx).Infof("Querying federatedRoomInfo via %+v", vias)
|
||||
ctx := context.Background()
|
||||
// query more of the spaces graph using these servers
|
||||
for serverName := range vias {
|
||||
if serverName == string(w.thisServer) {
|
||||
continue
|
||||
}
|
||||
res, err := w.fsAPI.MSC2946Spaces(ctx, gomatrixserverlib.ServerName(serverName), roomID, gomatrixserverlib.MSC2946SpacesRequest{
|
||||
Limit: w.req.Limit,
|
||||
MaxRoomsPerSpace: w.req.MaxRoomsPerSpace,
|
||||
})
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).Warnf("failed to call MSC2946Spaces on server %s", serverName)
|
||||
continue
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (w *walker) roomExists(roomID string) bool {
|
||||
var queryRes roomserver.QueryServerJoinedToRoomResponse
|
||||
err := w.rsAPI.QueryServerJoinedToRoom(w.ctx, &roomserver.QueryServerJoinedToRoomRequest{
|
||||
RoomID: roomID,
|
||||
ServerName: w.thisServer,
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryServerJoinedToRoom")
|
||||
return false
|
||||
}
|
||||
// if the room exists but we aren't in the room then we might have stale data so we want to fetch
|
||||
// it fresh via federation
|
||||
return queryRes.RoomExists && queryRes.IsInRoom
|
||||
}
|
||||
|
||||
// authorised returns true iff the user is joined this room or the room is world_readable
|
||||
func (w *walker) authorised(roomID string) bool {
|
||||
if w.caller != nil {
|
||||
return w.authorisedUser(roomID)
|
||||
}
|
||||
return w.authorisedServer(roomID)
|
||||
}
|
||||
|
||||
// authorisedServer returns true iff the server is joined this room or the room is world_readable
|
||||
func (w *walker) authorisedServer(roomID string) bool {
|
||||
// Check history visibility first
|
||||
hisVisTuple := gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomHistoryVisibility,
|
||||
StateKey: "",
|
||||
}
|
||||
var queryRoomRes roomserver.QueryCurrentStateResponse
|
||||
err := w.rsAPI.QueryCurrentState(w.ctx, &roomserver.QueryCurrentStateRequest{
|
||||
RoomID: roomID,
|
||||
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||
hisVisTuple,
|
||||
},
|
||||
}, &queryRoomRes)
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryCurrentState")
|
||||
return false
|
||||
}
|
||||
hisVisEv := queryRoomRes.StateEvents[hisVisTuple]
|
||||
if hisVisEv != nil {
|
||||
hisVis, _ := hisVisEv.HistoryVisibility()
|
||||
if hisVis == "world_readable" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// check if server is joined to the room
|
||||
var queryRes fs.QueryJoinedHostServerNamesInRoomResponse
|
||||
err = w.fsAPI.QueryJoinedHostServerNamesInRoom(w.ctx, &fs.QueryJoinedHostServerNamesInRoomRequest{
|
||||
RoomID: roomID,
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryJoinedHostServerNamesInRoom")
|
||||
return false
|
||||
}
|
||||
for _, srv := range queryRes.ServerNames {
|
||||
if srv == w.serverName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// authorisedUser returns true iff the user is joined this room or the room is world_readable
|
||||
func (w *walker) authorisedUser(roomID string) bool {
|
||||
hisVisTuple := gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomHistoryVisibility,
|
||||
StateKey: "",
|
||||
|
@ -326,49 +522,55 @@ func (w *walker) authorised(roomID string) bool {
|
|||
}
|
||||
|
||||
// references returns all references pointing to or from this room.
|
||||
func (w *walker) references(roomID string) (eventLookup, error) {
|
||||
func (w *walker) references(roomID string) ([]gomatrixserverlib.MSC2946StrippedEvent, error) {
|
||||
events, err := w.db.References(w.ctx, roomID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
el := make(eventLookup)
|
||||
el := make([]gomatrixserverlib.MSC2946StrippedEvent, 0, len(events))
|
||||
for _, ev := range events {
|
||||
// only return events that have a `via` key as per MSC1772
|
||||
// else we'll incorrectly walk redacted events (as the link
|
||||
// is in the state_key)
|
||||
if gjson.GetBytes(ev.Content(), "via").Exists() {
|
||||
el.set(ev)
|
||||
strip := stripped(ev.Event)
|
||||
if strip == nil {
|
||||
continue
|
||||
}
|
||||
el = append(el, *strip)
|
||||
}
|
||||
}
|
||||
return el, nil
|
||||
}
|
||||
|
||||
// state event lookup across multiple rooms keyed on event type
|
||||
// NOT THREAD SAFE
|
||||
type eventLookup map[string][]*gomatrixserverlib.HeaderedEvent
|
||||
|
||||
func (el eventLookup) set(ev *gomatrixserverlib.HeaderedEvent) {
|
||||
evs := el[ev.Type()]
|
||||
if evs == nil {
|
||||
evs = make([]*gomatrixserverlib.HeaderedEvent, 0)
|
||||
}
|
||||
evs = append(evs, ev)
|
||||
el[ev.Type()] = evs
|
||||
}
|
||||
|
||||
func (el eventLookup) len() int {
|
||||
sum := 0
|
||||
for _, evs := range el {
|
||||
sum += len(evs)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (el eventLookup) events() (events []*gomatrixserverlib.HeaderedEvent) {
|
||||
for _, evs := range el {
|
||||
events = append(events, evs...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type set map[string]bool
|
||||
|
||||
func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEvent {
|
||||
if ev.StateKey() == nil {
|
||||
return nil
|
||||
}
|
||||
return &gomatrixserverlib.MSC2946StrippedEvent{
|
||||
Type: ev.Type(),
|
||||
StateKey: *ev.StateKey(),
|
||||
Content: ev.Content(),
|
||||
Sender: ev.Sender(),
|
||||
RoomID: ev.RoomID(),
|
||||
}
|
||||
}
|
||||
|
||||
func eventKey(event *gomatrixserverlib.MSC2946StrippedEvent) string {
|
||||
return event.RoomID + "|" + event.Type + "|" + event.StateKey
|
||||
}
|
||||
|
||||
func spaceTargetStripped(event *gomatrixserverlib.MSC2946StrippedEvent) string {
|
||||
if event.StateKey == "" {
|
||||
return "" // no-op
|
||||
}
|
||||
switch event.Type {
|
||||
case ConstSpaceParentEventType:
|
||||
return event.StateKey
|
||||
case ConstSpaceChildEventType:
|
||||
return event.StateKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ var (
|
|||
client = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
roomVer = gomatrixserverlib.RoomVersionV6
|
||||
)
|
||||
|
||||
// Basic sanity check of MSC2946 logic. Tests a single room with a few state events
|
||||
|
@ -86,8 +87,7 @@ func TestMSC2946(t *testing.T) {
|
|||
Type: msc2946.ConstSpaceChildEventType,
|
||||
StateKey: &room1,
|
||||
Content: map[string]interface{}{
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
rootToR2 := mustCreateEvent(t, fledglingEvent{
|
||||
|
@ -96,8 +96,7 @@ func TestMSC2946(t *testing.T) {
|
|||
Type: msc2946.ConstSpaceChildEventType,
|
||||
StateKey: &room2,
|
||||
Content: map[string]interface{}{
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
rootToS1 := mustCreateEvent(t, fledglingEvent{
|
||||
|
@ -106,8 +105,7 @@ func TestMSC2946(t *testing.T) {
|
|||
Type: msc2946.ConstSpaceChildEventType,
|
||||
StateKey: &subSpaceS1,
|
||||
Content: map[string]interface{}{
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
s1ToR3 := mustCreateEvent(t, fledglingEvent{
|
||||
|
@ -116,8 +114,7 @@ func TestMSC2946(t *testing.T) {
|
|||
Type: msc2946.ConstSpaceChildEventType,
|
||||
StateKey: &room3,
|
||||
Content: map[string]interface{}{
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
s1ToR4 := mustCreateEvent(t, fledglingEvent{
|
||||
|
@ -126,8 +123,7 @@ func TestMSC2946(t *testing.T) {
|
|||
Type: msc2946.ConstSpaceChildEventType,
|
||||
StateKey: &room4,
|
||||
Content: map[string]interface{}{
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
s1ToS2 := mustCreateEvent(t, fledglingEvent{
|
||||
|
@ -136,8 +132,7 @@ func TestMSC2946(t *testing.T) {
|
|||
Type: msc2946.ConstSpaceChildEventType,
|
||||
StateKey: &subSpaceS2,
|
||||
Content: map[string]interface{}{
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
// This is a parent link only
|
||||
|
@ -145,11 +140,9 @@ func TestMSC2946(t *testing.T) {
|
|||
RoomID: room5,
|
||||
Sender: alice,
|
||||
Type: msc2946.ConstSpaceParentEventType,
|
||||
StateKey: &empty,
|
||||
StateKey: &subSpaceS2,
|
||||
Content: map[string]interface{}{
|
||||
"room_id": subSpaceS2,
|
||||
"via": []string{"localhost"},
|
||||
"present": true,
|
||||
"via": []string{"localhost"},
|
||||
},
|
||||
})
|
||||
// history visibility for R4
|
||||
|
@ -277,13 +270,13 @@ func TestMSC2946(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func newReq(t *testing.T, jsonBody map[string]interface{}) *msc2946.SpacesRequest {
|
||||
func newReq(t *testing.T, jsonBody map[string]interface{}) *gomatrixserverlib.MSC2946SpacesRequest {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(jsonBody)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %s", err)
|
||||
}
|
||||
var r msc2946.SpacesRequest
|
||||
var r gomatrixserverlib.MSC2946SpacesRequest
|
||||
if err := json.Unmarshal(b, &r); err != nil {
|
||||
t.Fatalf("Failed to unmarshal request: %s", err)
|
||||
}
|
||||
|
@ -307,10 +300,10 @@ func runServer(t *testing.T, router *mux.Router) func() {
|
|||
}
|
||||
}
|
||||
|
||||
func postSpaces(t *testing.T, expectCode int, accessToken, roomID string, req *msc2946.SpacesRequest) *msc2946.SpacesResponse {
|
||||
func postSpaces(t *testing.T, expectCode int, accessToken, roomID string, req *gomatrixserverlib.MSC2946SpacesRequest) *gomatrixserverlib.MSC2946SpacesResponse {
|
||||
t.Helper()
|
||||
var r msc2946.SpacesRequest
|
||||
r.Defaults()
|
||||
var r gomatrixserverlib.MSC2946SpacesRequest
|
||||
msc2946.Defaults(&r)
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal request: %s", err)
|
||||
|
@ -332,7 +325,7 @@ func postSpaces(t *testing.T, expectCode int, accessToken, roomID string, req *m
|
|||
t.Fatalf("wrong response code, got %d want %d - body: %s", res.StatusCode, expectCode, string(body))
|
||||
}
|
||||
if res.StatusCode == 200 {
|
||||
var result msc2946.SpacesResponse
|
||||
var result gomatrixserverlib.MSC2946SpacesResponse
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("response 200 OK but failed to read response body: %s", err)
|
||||
|
@ -408,6 +401,12 @@ type testRoomserverAPI struct {
|
|||
pubRoomState map[string]map[gomatrixserverlib.StateKeyTuple]string
|
||||
}
|
||||
|
||||
func (r *testRoomserverAPI) QueryServerJoinedToRoom(ctx context.Context, req *roomserver.QueryServerJoinedToRoomRequest, res *roomserver.QueryServerJoinedToRoomResponse) error {
|
||||
res.IsInRoom = true
|
||||
res.RoomExists = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testRoomserverAPI) QueryBulkStateContent(ctx context.Context, req *roomserver.QueryBulkStateContentRequest, res *roomserver.QueryBulkStateContentResponse) error {
|
||||
res.Rooms = make(map[string]map[gomatrixserverlib.StateKeyTuple]string)
|
||||
for _, roomID := range req.RoomIDs {
|
||||
|
@ -460,7 +459,7 @@ func injectEvents(t *testing.T, userAPI userapi.UserInternalAPI, rsAPI roomserve
|
|||
PublicFederationAPIMux: mux.NewRouter().PathPrefix(httputil.PublicFederationPathPrefix).Subrouter(),
|
||||
}
|
||||
|
||||
err := msc2946.Enable(base, rsAPI, userAPI)
|
||||
err := msc2946.Enable(base, rsAPI, userAPI, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to enable MSC2946: %s", err)
|
||||
}
|
||||
|
@ -480,7 +479,6 @@ type fledglingEvent struct {
|
|||
|
||||
func mustCreateEvent(t *testing.T, ev fledglingEvent) (result *gomatrixserverlib.HeaderedEvent) {
|
||||
t.Helper()
|
||||
roomVer := gomatrixserverlib.RoomVersionV6
|
||||
seed := make([]byte, ed25519.SeedSize) // zero seed
|
||||
key := ed25519.NewKeyFromSeed(seed)
|
||||
eb := gomatrixserverlib.EventBuilder{
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -175,7 +174,7 @@ func SpaceTarget(he *gomatrixserverlib.HeaderedEvent) string {
|
|||
}
|
||||
switch he.Type() {
|
||||
case ConstSpaceParentEventType:
|
||||
return gjson.GetBytes(he.Content(), "room_id").Str
|
||||
return *he.StateKey()
|
||||
case ConstSpaceChildEventType:
|
||||
return *he.StateKey()
|
||||
}
|
||||
|
|
|
@ -41,8 +41,11 @@ func EnableMSC(base *setup.BaseDendrite, monolith *setup.Monolith, msc string) e
|
|||
case "msc2836":
|
||||
return msc2836.Enable(base, monolith.RoomserverAPI, monolith.FederationSenderAPI, monolith.UserAPI, monolith.KeyRing)
|
||||
case "msc2946":
|
||||
return msc2946.Enable(base, monolith.RoomserverAPI, monolith.UserAPI)
|
||||
return msc2946.Enable(base, monolith.RoomserverAPI, monolith.UserAPI, monolith.FederationSenderAPI, monolith.KeyRing)
|
||||
case "msc2444": // enabled inside federationapi
|
||||
case "msc2753": // enabled inside clientapi
|
||||
default:
|
||||
return fmt.Errorf("EnableMSC: unknown msc '%s'", msc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
45
setup/process/process.go
Normal file
45
setup/process/process.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ProcessContext struct {
|
||||
wg *sync.WaitGroup // used to wait for components to shutdown
|
||||
ctx context.Context // cancelled when Stop is called
|
||||
shutdown context.CancelFunc // shut down Dendrite
|
||||
}
|
||||
|
||||
func NewProcessContext() *ProcessContext {
|
||||
ctx, shutdown := context.WithCancel(context.Background())
|
||||
return &ProcessContext{
|
||||
ctx: ctx,
|
||||
shutdown: shutdown,
|
||||
wg: &sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *ProcessContext) Context() context.Context {
|
||||
return context.WithValue(b.ctx, "scope", "process") // nolint:staticcheck
|
||||
}
|
||||
|
||||
func (b *ProcessContext) ComponentStarted() {
|
||||
b.wg.Add(1)
|
||||
}
|
||||
|
||||
func (b *ProcessContext) ComponentFinished() {
|
||||
b.wg.Done()
|
||||
}
|
||||
|
||||
func (b *ProcessContext) ShutdownDendrite() {
|
||||
b.shutdown()
|
||||
}
|
||||
|
||||
func (b *ProcessContext) WaitForShutdown() <-chan struct{} {
|
||||
return b.ctx.Done()
|
||||
}
|
||||
|
||||
func (b *ProcessContext) WaitForComponentsToFinish() {
|
||||
b.wg.Wait()
|
||||
}
|
|
@ -87,8 +87,9 @@ func TestMain(m *testing.M) {
|
|||
transport.RegisterProtocol("matrix", &MockRoundTripper{})
|
||||
|
||||
// Create the federation client.
|
||||
s.fedclient = gomatrixserverlib.NewFederationClientWithTransport(
|
||||
s.config.Matrix.ServerName, serverKeyID, testPriv, true, transport,
|
||||
s.fedclient = gomatrixserverlib.NewFederationClient(
|
||||
s.config.Matrix.ServerName, serverKeyID, testPriv,
|
||||
gomatrixserverlib.WithTransport(transport),
|
||||
)
|
||||
|
||||
// Finally, build the server key APIs.
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/syncapi/notifier"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -38,14 +39,15 @@ type OutputClientDataConsumer struct {
|
|||
|
||||
// NewOutputClientDataConsumer creates a new OutputClientData consumer. Call Start() to begin consuming from room servers.
|
||||
func NewOutputClientDataConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.SyncAPI,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
store storage.Database,
|
||||
notifier *notifier.Notifier,
|
||||
stream types.StreamProvider,
|
||||
) *OutputClientDataConsumer {
|
||||
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "syncapi/clientapi",
|
||||
Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputClientData)),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/syncapi/notifier"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -39,6 +40,7 @@ type OutputReceiptEventConsumer struct {
|
|||
// NewOutputReceiptEventConsumer creates a new OutputReceiptEventConsumer.
|
||||
// Call Start() to begin consuming from the EDU server.
|
||||
func NewOutputReceiptEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.SyncAPI,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
store storage.Database,
|
||||
|
@ -47,6 +49,7 @@ func NewOutputReceiptEventConsumer(
|
|||
) *OutputReceiptEventConsumer {
|
||||
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "syncapi/eduserver/receipt",
|
||||
Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputReceiptEvent),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/syncapi/notifier"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -42,6 +43,7 @@ type OutputSendToDeviceEventConsumer struct {
|
|||
// NewOutputSendToDeviceEventConsumer creates a new OutputSendToDeviceEventConsumer.
|
||||
// Call Start() to begin consuming from the EDU server.
|
||||
func NewOutputSendToDeviceEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.SyncAPI,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
store storage.Database,
|
||||
|
@ -50,6 +52,7 @@ func NewOutputSendToDeviceEventConsumer(
|
|||
) *OutputSendToDeviceEventConsumer {
|
||||
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "syncapi/eduserver/sendtodevice",
|
||||
Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputSendToDeviceEvent)),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/syncapi/notifier"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -39,6 +40,7 @@ type OutputTypingEventConsumer struct {
|
|||
// NewOutputTypingEventConsumer creates a new OutputTypingEventConsumer.
|
||||
// Call Start() to begin consuming from the EDU server.
|
||||
func NewOutputTypingEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.SyncAPI,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
store storage.Database,
|
||||
|
@ -48,6 +50,7 @@ func NewOutputTypingEventConsumer(
|
|||
) *OutputTypingEventConsumer {
|
||||
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "syncapi/eduserver/typing",
|
||||
Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputTypingEvent)),
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/keyserver/api"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/syncapi/notifier"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -46,6 +47,7 @@ type OutputKeyChangeEventConsumer struct {
|
|||
// NewOutputKeyChangeEventConsumer creates a new OutputKeyChangeEventConsumer.
|
||||
// Call Start() to begin consuming from the key server.
|
||||
func NewOutputKeyChangeEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
serverName gomatrixserverlib.ServerName,
|
||||
topic string,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
|
@ -57,6 +59,7 @@ func NewOutputKeyChangeEventConsumer(
|
|||
) *OutputKeyChangeEventConsumer {
|
||||
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "syncapi/keychange",
|
||||
Topic: topic,
|
||||
Consumer: kafkaConsumer,
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/syncapi/notifier"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -43,6 +44,7 @@ type OutputRoomEventConsumer struct {
|
|||
|
||||
// NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers.
|
||||
func NewOutputRoomEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.SyncAPI,
|
||||
kafkaConsumer sarama.Consumer,
|
||||
store storage.Database,
|
||||
|
@ -53,6 +55,7 @@ func NewOutputRoomEventConsumer(
|
|||
) *OutputRoomEventConsumer {
|
||||
|
||||
consumer := internal.ContinualConsumer{
|
||||
Process: process,
|
||||
ComponentName: "syncapi/roomserver",
|
||||
Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputRoomEvent)),
|
||||
Consumer: kafkaConsumer,
|
||||
|
@ -173,6 +176,7 @@ func (s *OutputRoomEventConsumer) onNewRoomEvent(
|
|||
if err != nil {
|
||||
// panic rather than continue with an inconsistent database
|
||||
log.WithFields(log.Fields{
|
||||
"event_id": ev.EventID(),
|
||||
"event": string(ev.JSON()),
|
||||
log.ErrorKey: err,
|
||||
"add": msg.AddsStateEventIDs,
|
||||
|
@ -215,6 +219,7 @@ func (s *OutputRoomEventConsumer) onOldRoomEvent(
|
|||
if err != nil {
|
||||
// panic rather than continue with an inconsistent database
|
||||
log.WithFields(log.Fields{
|
||||
"event_id": ev.EventID(),
|
||||
"event": string(ev.JSON()),
|
||||
log.ErrorKey: err,
|
||||
}).Panicf("roomserver output log: write old event failure")
|
||||
|
@ -271,6 +276,7 @@ func (s *OutputRoomEventConsumer) onNewInviteEvent(
|
|||
if err != nil {
|
||||
// panic rather than continue with an inconsistent database
|
||||
log.WithFields(log.Fields{
|
||||
"event_id": msg.Event.EventID(),
|
||||
"event": string(msg.Event.JSON()),
|
||||
"pdupos": pduPos,
|
||||
log.ErrorKey: err,
|
||||
|
|
|
@ -367,7 +367,6 @@ func newTestSyncRequest(userID, deviceID string, since types.StreamingToken) typ
|
|||
Timeout: 1 * time.Minute,
|
||||
Since: since,
|
||||
WantFullState: false,
|
||||
Limit: 20,
|
||||
Log: util.GetLogger(context.TODO()),
|
||||
Context: context.TODO(),
|
||||
}
|
||||
|
|
|
@ -235,12 +235,15 @@ func (r *messagesReq) retrieveEvents() (
|
|||
clientEvents []gomatrixserverlib.ClientEvent, start,
|
||||
end types.TopologyToken, err error,
|
||||
) {
|
||||
eventFilter := gomatrixserverlib.DefaultRoomEventFilter()
|
||||
eventFilter.Limit = r.limit
|
||||
|
||||
// Retrieve the events from the local database.
|
||||
var streamEvents []types.StreamEvent
|
||||
if r.fromStream != nil {
|
||||
toStream := r.to.StreamToken()
|
||||
streamEvents, err = r.db.GetEventsInStreamingRange(
|
||||
r.ctx, r.fromStream, &toStream, r.roomID, r.limit, r.backwardOrdering,
|
||||
r.ctx, r.fromStream, &toStream, r.roomID, &eventFilter, r.backwardOrdering,
|
||||
)
|
||||
} else {
|
||||
streamEvents, err = r.db.GetEventsInTopologicalRange(
|
||||
|
|
|
@ -40,7 +40,7 @@ type Database interface {
|
|||
GetStateDeltas(ctx context.Context, device *userapi.Device, r types.Range, userID string, stateFilter *gomatrixserverlib.StateFilter) ([]types.StateDelta, []string, error)
|
||||
RoomIDsWithMembership(ctx context.Context, userID string, membership string) ([]string, error)
|
||||
|
||||
RecentEvents(ctx context.Context, roomID string, r types.Range, limit int, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error)
|
||||
RecentEvents(ctx context.Context, roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error)
|
||||
|
||||
GetBackwardTopologyPos(ctx context.Context, events []types.StreamEvent) (types.TopologyToken, error)
|
||||
PositionInTopology(ctx context.Context, eventID string) (pos types.StreamPosition, spos types.StreamPosition, err error)
|
||||
|
@ -105,7 +105,7 @@ type Database interface {
|
|||
// Returns an error if there was a problem communicating with the database.
|
||||
DeletePeeks(ctx context.Context, RoomID, UserID string) (types.StreamPosition, error)
|
||||
// GetEventsInStreamingRange retrieves all of the events on a given ordering using the given extremities and limit.
|
||||
GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error)
|
||||
GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, eventFilter *gomatrixserverlib.RoomEventFilter, backwardOrdering bool) (events []types.StreamEvent, err error)
|
||||
// GetEventsInTopologicalRange retrieves all of the events on a given ordering using the given extremities and limit.
|
||||
GetEventsInTopologicalRange(ctx context.Context, from, to *types.TopologyToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error)
|
||||
// EventPositionInTopology returns the depth and stream position of the given event.
|
||||
|
|
|
@ -83,7 +83,7 @@ func (s *filterStatements) SelectFilter(
|
|||
}
|
||||
|
||||
// Unmarshal JSON into Filter struct
|
||||
var filter gomatrixserverlib.Filter
|
||||
filter := gomatrixserverlib.DefaultFilter()
|
||||
if err = json.Unmarshal(filterData, &filter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
111
syncapi/storage/postgres/memberships_table.go
Normal file
111
syncapi/storage/postgres/memberships_table.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage/tables"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// The memberships table is designed to track the last time that
|
||||
// the user was a given state. This allows us to find out the
|
||||
// most recent time that a user was invited to, joined or left
|
||||
// a room, either by choice or otherwise. This is important for
|
||||
// building history visibility.
|
||||
|
||||
const membershipsSchema = `
|
||||
CREATE TABLE IF NOT EXISTS syncapi_memberships (
|
||||
-- The 'room_id' key for the state event.
|
||||
room_id TEXT NOT NULL,
|
||||
-- The state event ID
|
||||
user_id TEXT NOT NULL,
|
||||
-- The status of the membership
|
||||
membership TEXT NOT NULL,
|
||||
-- The event ID that last changed the membership
|
||||
event_id TEXT NOT NULL,
|
||||
-- The stream position of the change
|
||||
stream_pos BIGINT NOT NULL,
|
||||
-- The topological position of the change in the room
|
||||
topological_pos BIGINT NOT NULL,
|
||||
-- Unique index
|
||||
CONSTRAINT syncapi_memberships_unique UNIQUE (room_id, user_id, membership)
|
||||
);
|
||||
`
|
||||
|
||||
const upsertMembershipSQL = "" +
|
||||
"INSERT INTO syncapi_memberships (room_id, user_id, membership, event_id, stream_pos, topological_pos)" +
|
||||
" VALUES ($1, $2, $3, $4, $5, $6)" +
|
||||
" ON CONFLICT ON CONSTRAINT syncapi_memberships_unique" +
|
||||
" DO UPDATE SET event_id = $4, stream_pos = $5, topological_pos = $6"
|
||||
|
||||
const selectMembershipSQL = "" +
|
||||
"SELECT event_id, stream_pos, topological_pos FROM syncapi_memberships" +
|
||||
" WHERE room_id = $1 AND user_id = $2 AND membership = ANY($3)" +
|
||||
" ORDER BY stream_pos DESC" +
|
||||
" LIMIT 1"
|
||||
|
||||
type membershipsStatements struct {
|
||||
upsertMembershipStmt *sql.Stmt
|
||||
selectMembershipStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewPostgresMembershipsTable(db *sql.DB) (tables.Memberships, error) {
|
||||
s := &membershipsStatements{}
|
||||
_, err := db.Exec(membershipsSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.upsertMembershipStmt, err = db.Prepare(upsertMembershipSQL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.selectMembershipStmt, err = db.Prepare(selectMembershipSQL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *membershipsStatements) UpsertMembership(
|
||||
ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent,
|
||||
streamPos, topologicalPos types.StreamPosition,
|
||||
) error {
|
||||
membership, err := event.Membership()
|
||||
if err != nil {
|
||||
return fmt.Errorf("event.Membership: %w", err)
|
||||
}
|
||||
_, err = sqlutil.TxStmt(txn, s.upsertMembershipStmt).ExecContext(
|
||||
ctx,
|
||||
event.RoomID(),
|
||||
*event.StateKey(),
|
||||
membership,
|
||||
event.EventID(),
|
||||
streamPos,
|
||||
topologicalPos,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *membershipsStatements) SelectMembership(
|
||||
ctx context.Context, txn *sql.Tx, roomID, userID, memberships []string,
|
||||
) (eventID string, streamPos, topologyPos types.StreamPosition, err error) {
|
||||
stmt := sqlutil.TxStmt(txn, s.selectMembershipStmt)
|
||||
err = stmt.QueryRowContext(ctx, roomID, userID, memberships).Scan(&eventID, &streamPos, &topologyPos)
|
||||
return
|
||||
}
|
|
@ -84,17 +84,29 @@ const selectEventsSQL = "" +
|
|||
const selectRecentEventsSQL = "" +
|
||||
"SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" +
|
||||
" WHERE room_id = $1 AND id > $2 AND id <= $3" +
|
||||
" ORDER BY id DESC LIMIT $4"
|
||||
" AND ( $4::text[] IS NULL OR sender = ANY($4) )" +
|
||||
" AND ( $5::text[] IS NULL OR NOT(sender = ANY($5)) )" +
|
||||
" AND ( $6::text[] IS NULL OR type LIKE ANY($6) )" +
|
||||
" AND ( $7::text[] IS NULL OR NOT(type LIKE ANY($7)) )" +
|
||||
" ORDER BY id DESC LIMIT $8"
|
||||
|
||||
const selectRecentEventsForSyncSQL = "" +
|
||||
"SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" +
|
||||
" WHERE room_id = $1 AND id > $2 AND id <= $3 AND exclude_from_sync = FALSE" +
|
||||
" ORDER BY id DESC LIMIT $4"
|
||||
" AND ( $4::text[] IS NULL OR sender = ANY($4) )" +
|
||||
" AND ( $5::text[] IS NULL OR NOT(sender = ANY($5)) )" +
|
||||
" AND ( $6::text[] IS NULL OR type LIKE ANY($6) )" +
|
||||
" AND ( $7::text[] IS NULL OR NOT(type LIKE ANY($7)) )" +
|
||||
" ORDER BY id DESC LIMIT $8"
|
||||
|
||||
const selectEarlyEventsSQL = "" +
|
||||
"SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" +
|
||||
" WHERE room_id = $1 AND id > $2 AND id <= $3" +
|
||||
" ORDER BY id ASC LIMIT $4"
|
||||
" AND ( $4::text[] IS NULL OR sender = ANY($4) )" +
|
||||
" AND ( $5::text[] IS NULL OR NOT(sender = ANY($5)) )" +
|
||||
" AND ( $6::text[] IS NULL OR type LIKE ANY($6) )" +
|
||||
" AND ( $7::text[] IS NULL OR NOT(type LIKE ANY($7)) )" +
|
||||
" ORDER BY id ASC LIMIT $8"
|
||||
|
||||
const selectMaxEventIDSQL = "" +
|
||||
"SELECT MAX(id) FROM syncapi_output_room_events"
|
||||
|
@ -322,7 +334,7 @@ func (s *outputRoomEventsStatements) InsertEvent(
|
|||
// from sync.
|
||||
func (s *outputRoomEventsStatements) SelectRecentEvents(
|
||||
ctx context.Context, txn *sql.Tx,
|
||||
roomID string, r types.Range, limit int,
|
||||
roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter,
|
||||
chronologicalOrder bool, onlySyncEvents bool,
|
||||
) ([]types.StreamEvent, bool, error) {
|
||||
var stmt *sql.Stmt
|
||||
|
@ -331,7 +343,14 @@ func (s *outputRoomEventsStatements) SelectRecentEvents(
|
|||
} else {
|
||||
stmt = sqlutil.TxStmt(txn, s.selectRecentEventsStmt)
|
||||
}
|
||||
rows, err := stmt.QueryContext(ctx, roomID, r.Low(), r.High(), limit+1)
|
||||
rows, err := stmt.QueryContext(
|
||||
ctx, roomID, r.Low(), r.High(),
|
||||
pq.StringArray(eventFilter.Senders),
|
||||
pq.StringArray(eventFilter.NotSenders),
|
||||
pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.Types)),
|
||||
pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.NotTypes)),
|
||||
eventFilter.Limit+1,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -350,7 +369,7 @@ func (s *outputRoomEventsStatements) SelectRecentEvents(
|
|||
}
|
||||
// we queried for 1 more than the limit, so if we returned one more mark limited=true
|
||||
limited := false
|
||||
if len(events) > limit {
|
||||
if len(events) > eventFilter.Limit {
|
||||
limited = true
|
||||
// re-slice the extra (oldest) event out: in chronological order this is the first entry, else the last.
|
||||
if chronologicalOrder {
|
||||
|
@ -367,10 +386,17 @@ func (s *outputRoomEventsStatements) SelectRecentEvents(
|
|||
// from a given position, up to a maximum of 'limit'.
|
||||
func (s *outputRoomEventsStatements) SelectEarlyEvents(
|
||||
ctx context.Context, txn *sql.Tx,
|
||||
roomID string, r types.Range, limit int,
|
||||
roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter,
|
||||
) ([]types.StreamEvent, error) {
|
||||
stmt := sqlutil.TxStmt(txn, s.selectEarlyEventsStmt)
|
||||
rows, err := stmt.QueryContext(ctx, roomID, r.Low(), r.High(), limit)
|
||||
rows, err := stmt.QueryContext(
|
||||
ctx, roomID, r.Low(), r.High(),
|
||||
pq.StringArray(eventFilter.Senders),
|
||||
pq.StringArray(eventFilter.NotSenders),
|
||||
pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.Types)),
|
||||
pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.NotTypes)),
|
||||
eventFilter.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ CREATE UNIQUE INDEX IF NOT EXISTS syncapi_event_topological_position_idx ON sync
|
|||
const insertEventInTopologySQL = "" +
|
||||
"INSERT INTO syncapi_output_room_events_topology (event_id, topological_position, room_id, stream_position)" +
|
||||
" VALUES ($1, $2, $3, $4)" +
|
||||
" ON CONFLICT (topological_position, stream_position, room_id) DO UPDATE SET event_id = $1"
|
||||
" ON CONFLICT (topological_position, stream_position, room_id) DO UPDATE SET event_id = $1" +
|
||||
" RETURNING topological_position"
|
||||
|
||||
const selectEventIDsInRangeASCSQL = "" +
|
||||
"SELECT event_id FROM syncapi_output_room_events_topology" +
|
||||
|
@ -115,10 +116,10 @@ func NewPostgresTopologyTable(db *sql.DB) (tables.Topology, error) {
|
|||
// on the event's depth.
|
||||
func (s *outputRoomEventsTopologyStatements) InsertEventInTopology(
|
||||
ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, pos types.StreamPosition,
|
||||
) (err error) {
|
||||
_, err = s.insertEventInTopologyStmt.ExecContext(
|
||||
) (topoPos types.StreamPosition, err error) {
|
||||
err = sqlutil.TxStmt(txn, s.insertEventInTopologyStmt).QueryRowContext(
|
||||
ctx, event.EventID(), event.Depth(), event.RoomID(), pos,
|
||||
)
|
||||
).Scan(&topoPos)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue