Skip to content
This repository was archived by the owner on Nov 25, 2024. It is now read-only.

Commit 39507ba

Browse files
authored
Peeking via MSC2753 (#1370)
Initial implementation of MSC2753, as tested by matrix-org/sytest#944. Doesn't yet handle unpeeks, peeked EDUs, or history viz changing during a peek - these will follow. #1370 has full details.
1 parent 35564dd commit 39507ba

File tree

29 files changed

+1209
-59
lines changed

29 files changed

+1209
-59
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ matrixdotorg/sytest-dendrite:latest tests/50federation/40devicelists.pl
8181
```
8282
See [sytest.md](docs/sytest.md) for the full description of these flags.
8383

84+
You can try running sytest outside of docker for faster runs, but the dependencies can be temperamental
85+
and we recommend using docker where possible.
86+
```
87+
cd sytest
88+
export PERL5LIB=$HOME/lib/perl5
89+
export PERL_MB_OPT=--install_base=$HOME
90+
export PERL_MM_OPT=INSTALL_BASE=$HOME
91+
./install-deps.pl
92+
93+
./run-tests.pl -I Dendrite::Monolith -d $PATH_TO_DENDRITE_BINARIES
94+
```
95+
8496
Sometimes Sytest is testing the wrong thing or is flakey, so it will need to be patched.
8597
Ask on `#dendrite-dev:matrix.org` if you think this is the case for you and we'll be happy to help.
8698

clientapi/routing/joinroom.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func JoinRoomByIDOrAlias(
5252
}
5353
}
5454

55-
// If content was provided in the request then incude that
55+
// If content was provided in the request then include that
5656
// in the request. It'll get used as a part of the membership
5757
// event content.
5858
_ = httputil.UnmarshalJSONRequest(req, &joinReq.Content)

clientapi/routing/peekroom.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2020 New Vector Ltd
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package routing
16+
17+
import (
18+
"net/http"
19+
20+
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
21+
"github.com/matrix-org/dendrite/userapi/api"
22+
"github.com/matrix-org/dendrite/userapi/storage/accounts"
23+
"github.com/matrix-org/gomatrixserverlib"
24+
"github.com/matrix-org/util"
25+
)
26+
27+
func PeekRoomByIDOrAlias(
28+
req *http.Request,
29+
device *api.Device,
30+
rsAPI roomserverAPI.RoomserverInternalAPI,
31+
accountDB accounts.Database,
32+
roomIDOrAlias string,
33+
) util.JSONResponse {
34+
// if this is a remote roomIDOrAlias, we have to ask the roomserver (or federation sender?) to
35+
// to call /peek and /state on the remote server.
36+
// TODO: in future we could skip this if we know we're already participating in the room,
37+
// but this is fiddly in case we stop participating in the room.
38+
39+
// then we create a local peek.
40+
peekReq := roomserverAPI.PerformPeekRequest{
41+
RoomIDOrAlias: roomIDOrAlias,
42+
UserID: device.UserID,
43+
DeviceID: device.ID,
44+
}
45+
peekRes := roomserverAPI.PerformPeekResponse{}
46+
47+
// Check to see if any ?server_name= query parameters were
48+
// given in the request.
49+
if serverNames, ok := req.URL.Query()["server_name"]; ok {
50+
for _, serverName := range serverNames {
51+
peekReq.ServerNames = append(
52+
peekReq.ServerNames,
53+
gomatrixserverlib.ServerName(serverName),
54+
)
55+
}
56+
}
57+
58+
// Ask the roomserver to perform the peek.
59+
rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes)
60+
if peekRes.Error != nil {
61+
return peekRes.Error.JSONResponse()
62+
}
63+
64+
// if this user is already joined to the room, we let them peek anyway
65+
// (given they might be about to part the room, and it makes things less fiddly)
66+
67+
// Peeking stops if none of the devices who started peeking have been
68+
// /syncing for a while, or if everyone who was peeking calls /leave
69+
// (or /unpeek with a server_name param? or DELETE /peek?)
70+
// on the peeked room.
71+
72+
return util.JSONResponse{
73+
Code: http.StatusOK,
74+
// TODO: Put the response struct somewhere internal.
75+
JSON: struct {
76+
RoomID string `json:"room_id"`
77+
}{peekRes.RoomID},
78+
}
79+
}

clientapi/routing/routing.go

+11
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ func Setup(
103103
)
104104
}),
105105
).Methods(http.MethodPost, http.MethodOptions)
106+
r0mux.Handle("/peek/{roomIDOrAlias}",
107+
httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
108+
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
109+
if err != nil {
110+
return util.ErrorResponse(err)
111+
}
112+
return PeekRoomByIDOrAlias(
113+
req, device, rsAPI, accountDB, vars["roomIDOrAlias"],
114+
)
115+
}),
116+
).Methods(http.MethodPost, http.MethodOptions)
106117
r0mux.Handle("/joined_rooms",
107118
httputil.MakeAuthAPI("joined_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
108119
return GetJoinedRooms(req, device, rsAPI)

docs/peeking.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Peeking
2+
3+
Peeking is implemented as per [MSC2753](https://github.com/matrix-org/matrix-doc/pull/2753).
4+
5+
Implementationwise, this means:
6+
* Users call `/peek` and `/unpeek` on the clientapi from a given device.
7+
* The clientapi delegates these via HTTP to the roomserver, which coordinates peeking in general for a given room
8+
* The roomserver writes an NewPeek event into the kafka log headed to the syncserver
9+
* 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.
10+
11+
Questions (given this is [my](https://github.com/ara4n) first time hacking on Dendrite):
12+
* 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`?
13+
14+
In future, peeking over federation will be added as per [MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444).
15+
* The `roomserver` will kick the `federationsender` much as it does for a federated `/join` in order to trigger a federated `/peek`
16+
* The `federationsender` tracks the existence of the remote peek in question
17+
* The `federationsender` regularly renews the remote peek as long as there are still peeking devices syncing for it.
18+
* TBD: how do we tell if there are no devices currently syncing for a given peeked room? The syncserver needs to tell the roomserver
19+
somehow who then needs to warn the federationsender.

federationapi/routing/send_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ func (t *testRoomserverAPI) PerformJoin(
112112
) {
113113
}
114114

115+
func (t *testRoomserverAPI) PerformPeek(
116+
ctx context.Context,
117+
req *api.PerformPeekRequest,
118+
res *api.PerformPeekResponse,
119+
) {
120+
}
121+
115122
func (t *testRoomserverAPI) PerformPublish(
116123
ctx context.Context,
117124
req *api.PerformPublishRequest,

federationsender/storage/shared/storage.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (d *Database) StoreJSON(
138138
var err error
139139
_ = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
140140
nid, err = d.FederationSenderQueueJSON.InsertQueueJSON(ctx, txn, js)
141-
return nil
141+
return err
142142
})
143143
if err != nil {
144144
return nil, fmt.Errorf("d.insertQueueJSON: %w", err)

internal/sqlutil/trace.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (in *traceInterceptor) RowsNext(c context.Context, rows driver.Rows, dest [
7474

7575
b := strings.Builder{}
7676
for i, val := range dest {
77-
b.WriteString(fmt.Sprintf("%v", val))
77+
b.WriteString(fmt.Sprintf("%q", val))
7878
if i+1 <= len(dest)-1 {
7979
b.WriteString(" | ")
8080
}

keyserver/storage/shared/storage.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (d *Database) ExistingOneTimeKeys(ctx context.Context, userID, deviceID str
4141
func (d *Database) StoreOneTimeKeys(ctx context.Context, keys api.OneTimeKeys) (counts *api.OneTimeKeysCount, err error) {
4242
_ = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
4343
counts, err = d.OneTimeKeysTable.InsertOneTimeKeys(ctx, txn, keys)
44-
return nil
44+
return err
4545
})
4646
return
4747
}

roomserver/api/api.go

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ type RoomserverInternalAPI interface {
3636
res *PerformLeaveResponse,
3737
) error
3838

39+
PerformPeek(
40+
ctx context.Context,
41+
req *PerformPeekRequest,
42+
res *PerformPeekResponse,
43+
)
44+
3945
PerformPublish(
4046
ctx context.Context,
4147
req *PerformPublishRequest,

roomserver/api/api_trace.go

+9
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ func (t *RoomserverInternalAPITrace) PerformInvite(
3838
return t.Impl.PerformInvite(ctx, req, res)
3939
}
4040

41+
func (t *RoomserverInternalAPITrace) PerformPeek(
42+
ctx context.Context,
43+
req *PerformPeekRequest,
44+
res *PerformPeekResponse,
45+
) {
46+
t.Impl.PerformPeek(ctx, req, res)
47+
util.GetLogger(ctx).Infof("PerformPeek req=%+v res=%+v", js(req), js(res))
48+
}
49+
4150
func (t *RoomserverInternalAPITrace) PerformJoin(
4251
ctx context.Context,
4352
req *PerformJoinRequest,

roomserver/api/output.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const (
4646
// - Redact the event and set the corresponding `unsigned` fields to indicate it as redacted.
4747
// - Replace the event in the database.
4848
OutputTypeRedactedEvent OutputType = "redacted_event"
49+
50+
// OutputTypeNewPeek indicates that the kafka event is an OutputNewPeek
51+
OutputTypeNewPeek OutputType = "new_peek"
4952
)
5053

5154
// An OutputEvent is an entry in the roomserver output kafka log.
@@ -59,8 +62,10 @@ type OutputEvent struct {
5962
NewInviteEvent *OutputNewInviteEvent `json:"new_invite_event,omitempty"`
6063
// The content of event with type OutputTypeRetireInviteEvent
6164
RetireInviteEvent *OutputRetireInviteEvent `json:"retire_invite_event,omitempty"`
62-
// The content of event with type OutputTypeRedactedEvent
65+
// The content of event with type OutputTypeRedactedEvent
6366
RedactedEvent *OutputRedactedEvent `json:"redacted_event,omitempty"`
67+
// The content of event with type OutputTypeNewPeek
68+
NewPeek *OutputNewPeek `json:"new_peek,omitempty"`
6469
}
6570

6671
// An OutputNewRoomEvent is written when the roomserver receives a new event.
@@ -195,3 +200,11 @@ type OutputRedactedEvent struct {
195200
// The value of `unsigned.redacted_because` - the redaction event itself
196201
RedactedBecause gomatrixserverlib.HeaderedEvent
197202
}
203+
204+
// An OutputNewPeek is written whenever a user starts peeking into a room
205+
// using a given device.
206+
type OutputNewPeek struct {
207+
RoomID string
208+
UserID string
209+
DeviceID string
210+
}

roomserver/api/perform.go

+14
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,20 @@ type PerformInviteResponse struct {
108108
Error *PerformError
109109
}
110110

111+
type PerformPeekRequest struct {
112+
RoomIDOrAlias string `json:"room_id_or_alias"`
113+
UserID string `json:"user_id"`
114+
DeviceID string `json:"device_id"`
115+
ServerNames []gomatrixserverlib.ServerName `json:"server_names"`
116+
}
117+
118+
type PerformPeekResponse struct {
119+
// The room ID, populated on success.
120+
RoomID string `json:"room_id"`
121+
// If non-nil, the join request failed. Contains more information why it failed.
122+
Error *PerformError
123+
}
124+
111125
// PerformBackfillRequest is a request to PerformBackfill.
112126
type PerformBackfillRequest struct {
113127
// The room to backfill

roomserver/internal/api.go

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type RoomserverInternalAPI struct {
2222
*query.Queryer
2323
*perform.Inviter
2424
*perform.Joiner
25+
*perform.Peeker
2526
*perform.Leaver
2627
*perform.Publisher
2728
*perform.Backfiller
@@ -83,6 +84,13 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen
8384
FSAPI: r.fsAPI,
8485
Inputer: r.Inputer,
8586
}
87+
r.Peeker = &perform.Peeker{
88+
ServerName: r.Cfg.Matrix.ServerName,
89+
Cfg: r.Cfg,
90+
DB: r.DB,
91+
FSAPI: r.fsAPI,
92+
Inputer: r.Inputer,
93+
}
8694
r.Leaver = &perform.Leaver{
8795
Cfg: r.Cfg,
8896
DB: r.DB,

0 commit comments

Comments
 (0)