From 14e9b7ecd6fc5f3ada855fe6885ce00a74bf6eb0 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 21 Jun 2021 18:51:23 +0100 Subject: [PATCH 1/8] add HandleInviteRequests ... useful if you want to receive federation invite requests --- internal/federation/handle.go | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/federation/handle.go b/internal/federation/handle.go index 77b505e2..82977535 100644 --- a/internal/federation/handle.go +++ b/internal/federation/handle.go @@ -128,6 +128,52 @@ func HandleMakeSendJoinRequests() func(*Server) { } } +// HandleInviteRequests is an option which makes the server process invite requests. +// +// inviteCallback is a callback function that if non-nil will be called and passed the incoming invite event +func HandleInviteRequests(inviteCallback func(*gomatrixserverlib.Event)) func(*Server) { + return func(s *Server) { + // https://matrix.org/docs/spec/server_server/r0.1.4#put-matrix-federation-v2-invite-roomid-eventid + s.mux.Handle("/_matrix/federation/v2/invite/{roomID}/{eventID}", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + fedReq, errResp := gomatrixserverlib.VerifyHTTPRequest( + req, time.Now(), gomatrixserverlib.ServerName(s.ServerName), s.keyRing, + ) + if fedReq == nil { + w.WriteHeader(errResp.Code) + b, _ := json.Marshal(errResp.JSON) + w.Write(b) + return + } + + var inviteRequest gomatrixserverlib.InviteV2Request + if err := json.Unmarshal(fedReq.Content(), &inviteRequest); err != nil { + log.Printf( + "complement: Unable to unmarshal incoming /invite request: %s", + err.Error(), + ) + + errResp := util.MessageResponse(400, err.Error()) + w.WriteHeader(errResp.Code) + b, _ := json.Marshal(errResp.JSON) + w.Write(b) + return + } + + if inviteCallback != nil { + inviteCallback(inviteRequest.Event()) + } + + // Send the response + res := map[string]interface{}{ + "event": inviteRequest.Event(), + } + w.WriteHeader(200) + b, _ := json.Marshal(res) + w.Write(b) + })).Methods("PUT") + } +} + // HandleDirectoryLookups will automatically return room IDs for any aliases present on this server. func HandleDirectoryLookups() func(*Server) { return func(s *Server) { From 9a04b8ac6bc116dc73ceadaaac616cc7e2b22880 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 21 Jun 2021 18:59:51 +0100 Subject: [PATCH 2/8] Add server.MustJoinRoom --- internal/federation/server.go | 48 ++++++++++++++++++++++++++---- internal/federation/server_room.go | 10 +++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/internal/federation/server.go b/internal/federation/server.go index 080a42f6..0368c02c 100644 --- a/internal/federation/server.go +++ b/internal/federation/server.go @@ -133,12 +133,8 @@ func (s *Server) MakeAliasMapping(aliasLocalpart, roomID string) string { // The `events` will be added to this room. Returns the created room. func (s *Server) MustMakeRoom(t *testing.T, roomVer gomatrixserverlib.RoomVersion, events []b.Event) *ServerRoom { roomID := fmt.Sprintf("!%d:%s", len(s.rooms), s.ServerName) - room := &ServerRoom{ - RoomID: roomID, - Version: roomVer, - State: make(map[string]*gomatrixserverlib.Event), - ForwardExtremities: make([]string, 0), - } + room := newRoom(roomVer, roomID) + // sign all these events for _, ev := range events { signedEvent := s.MustCreateEvent(t, room, ev) @@ -196,6 +192,37 @@ func (s *Server) MustCreateEvent(t *testing.T, room *ServerRoom, ev b.Event) *go return signedEvent } +// MustJoinRoom will make the server send a make_join and a send_join to join a room +// It returns the resultant room. +func (s *Server) MustJoinRoom(t *testing.T, deployment *docker.Deployment, remoteServer gomatrixserverlib.ServerName, roomID string, userID string) *ServerRoom { + t.Helper() + fedClient := s.FederationClient(deployment) + makeJoinResp, err := fedClient.MakeJoin(context.Background(), remoteServer, roomID, userID, SupportedRoomVersions()) + if err != nil { + t.Fatalf("MustJoinRoom: make_join failed: %v", err) + } + roomVer := makeJoinResp.RoomVersion + joinEvent, err := makeJoinResp.JoinEvent.Build(time.Now(), gomatrixserverlib.ServerName(s.ServerName), s.KeyID, s.Priv, roomVer) + if err != nil { + t.Fatalf("MustJoinRoom: failed to sign event: %v", err) + } + sendJoinResp, err := fedClient.SendJoin(context.Background(), gomatrixserverlib.ServerName(remoteServer), joinEvent, roomVer) + if err != nil { + t.Fatalf("MustJoinRoom: send_join failed: %v", err) + } + + room := newRoom(roomVer, roomID) + for _, ev := range sendJoinResp.StateEvents { + room.replaceCurrentState(ev) + } + room.AddEvent(joinEvent) + s.rooms[roomID] = room + + t.Logf("Server.MustJoinRoom joined room ID %s", roomID) + + return room +} + // Mux returns this server's router so you can attach additional paths func (s *Server) Mux() *mux.Router { return s.mux @@ -481,3 +508,12 @@ func (f *basicKeyFetcher) FetchKeys( func (f *basicKeyFetcher) FetcherName() string { return "basicKeyFetcher" } + +// SupportedRoomVersions is a convenience method which returns a list of the room versions supported by gomatrixserverlib. +func SupportedRoomVersions() []gomatrixserverlib.RoomVersion { + supportedRoomVersions := make([]gomatrixserverlib.RoomVersion, 0, 10) + for v := range gomatrixserverlib.SupportedRoomVersions() { + supportedRoomVersions = append(supportedRoomVersions, v) + } + return supportedRoomVersions +} diff --git a/internal/federation/server_room.go b/internal/federation/server_room.go index ac60d6bb..4c584281 100644 --- a/internal/federation/server_room.go +++ b/internal/federation/server_room.go @@ -19,6 +19,16 @@ type ServerRoom struct { Depth int64 } +// newRoom creates an empty room structure with no events +func newRoom(roomVer gomatrixserverlib.RoomVersion, roomId string) *ServerRoom { + return &ServerRoom{ + RoomID: roomId, + Version: roomVer, + State: make(map[string]*gomatrixserverlib.Event), + ForwardExtremities: make([]string, 0), + } +} + // AddEvent adds a new event to the timeline, updating current state if it is a state event. // Updates depth and forward extremities. func (r *ServerRoom) AddEvent(ev *gomatrixserverlib.Event) { From 4db4fbd61c538761adbcbd723c4d85b4e8226279 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 21 Jun 2021 19:09:50 +0100 Subject: [PATCH 3/8] Test that knocks are sent over federation --- go.mod | 2 +- go.sum | 4 ++-- tests/msc2403_test.go | 49 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 246c5e36..aa73570f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 // indirect github.com/gorilla/mux v1.8.0 - github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37 + github.com/matrix-org/gomatrixserverlib v0.0.0-20210621174423-185789a71f3a github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 55d5dd93..8bc253c9 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37 h1:si2CZZpwOLWZfDXfgHPkaTlaAkdJvpJzr1zVqyKXd0I= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210621174423-185789a71f3a h1:ttpygr7uLtACsxDoTol1BTVqmMOyib0ZBovMOD6OzkU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210621174423-185789a71f3a/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= diff --git a/tests/msc2403_test.go b/tests/msc2403_test.go index 1fd5dd4e..23751cdf 100644 --- a/tests/msc2403_test.go +++ b/tests/msc2403_test.go @@ -10,12 +10,17 @@ import ( "fmt" "net/url" "testing" + "time" + + "github.com/matrix-org/gomatrixserverlib" + + "github.com/tidwall/gjson" "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/client" + "github.com/matrix-org/complement/internal/federation" "github.com/matrix-org/complement/internal/match" "github.com/matrix-org/complement/internal/must" - "github.com/tidwall/gjson" ) // A reason to include in the request body when testing knock reason parameters @@ -40,6 +45,20 @@ func TestKnocking(t *testing.T) { charlieUserID := "@charlie:hs2" charlie := deployment.Client(t, "hs2", charlieUserID) + // Create a server to observe + inviteWaiter := NewWaiter() + srv := federation.NewServer(t, deployment, + federation.HandleKeyRequests(), + federation.HandleInviteRequests(func(ev *gomatrixserverlib.Event) { + inviteWaiter.Finish() + }), + federation.HandleTransactionRequests(nil, nil), + ) + cancel := srv.Listen() + defer cancel() + srv.UnexpectedRequestsAreErrors = false + david := srv.UserID("david") + // Create a room for alice and bob to test knocking with roomIDOne := alice.CreateRoom(t, struct { Preset string `json:"preset"` @@ -48,9 +67,12 @@ func TestKnocking(t *testing.T) { "private_chat", // Set to private in order to get an invite-only room "7", // Room version required for knocking. }) + alice.InviteRoom(t, roomIDOne, david) + inviteWaiter.Wait(t, 5*time.Second) + serverRoomOne := srv.MustJoinRoom(t, deployment, "hs1", roomIDOne, david) // Test knocking between two users on the same homeserver - knockingBetweenTwoUsersTest(t, roomIDOne, alice, bob, false) + knockingBetweenTwoUsersTest(t, roomIDOne, alice, bob, serverRoomOne, false) // Create a room for alice and charlie to test knocking with roomIDTwo := alice.CreateRoom(t, struct { @@ -60,12 +82,16 @@ func TestKnocking(t *testing.T) { "private_chat", // Set to private in order to get an invite-only room "7", // Room version required for knocking. }) + inviteWaiter = NewWaiter() + alice.InviteRoom(t, roomIDTwo, david) + inviteWaiter.Wait(t, 5*time.Second) + serverRoomTwo := srv.MustJoinRoom(t, deployment, "hs1", roomIDTwo, david) // Test knocking between two users, each on a separate homeserver - knockingBetweenTwoUsersTest(t, roomIDTwo, alice, charlie, true) + knockingBetweenTwoUsersTest(t, roomIDTwo, alice, charlie, serverRoomTwo, true) } -func knockingBetweenTwoUsersTest(t *testing.T, roomID string, inRoomUser, knockingUser *client.CSAPI, federation bool) { +func knockingBetweenTwoUsersTest(t *testing.T, roomID string, inRoomUser, knockingUser *client.CSAPI, serverRoom *federation.ServerRoom, testFederation bool) { t.Run("Knocking on a room with a join rule other than 'knock' should fail", func(t *testing.T) { knockOnRoomWithStatus(t, knockingUser, roomID, "Can I knock anyways?", []string{"hs1"}, 403) }) @@ -109,6 +135,19 @@ func knockingBetweenTwoUsersTest(t *testing.T, roomID string, inRoomUser, knocki }) t.Run("Users in the room see a user's membership update when they knock", func(t *testing.T) { + // check the membership seen over the federation + knockerState := serverRoom.CurrentState("m.room.member", knockingUser.UserID) + if knockerState == nil { + t.Errorf("Did not get membership state for knocking user") + } else { + m, err := knockerState.Membership() + if err != nil { + t.Errorf("Unable to unpack membership state for knocking user: %v", err) + } else if m != "knock" { + t.Errorf("membership for knocking user: got %#v, want \"knock\"", m) + } + } + inRoomUser.SyncUntilTimelineHas(t, roomID, func(ev gjson.Result) bool { if ev.Get("type").Str != "m.room.member" || ev.Get("sender").Str != knockingUser.UserID { return false @@ -119,7 +158,7 @@ func knockingBetweenTwoUsersTest(t *testing.T, roomID string, inRoomUser, knocki }) }) - if !federation { + if !testFederation { // Rescinding a knock over federation is currently not specced t.Run("A user that has knocked on a local room can rescind their knock and then knock again", func(t *testing.T) { // We need to carry out an incremental sync after knocking in order to get leave information From 11b06da497f2da1f5234cbe15d8a1c050b9648e2 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 21 Jun 2021 19:28:27 +0100 Subject: [PATCH 4/8] Test that invite rejections are sent out correctly over federation --- tests/federation_room_invite_test.go | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/federation_room_invite_test.go diff --git a/tests/federation_room_invite_test.go b/tests/federation_room_invite_test.go new file mode 100644 index 00000000..88684b54 --- /dev/null +++ b/tests/federation_room_invite_test.go @@ -0,0 +1,78 @@ +package tests + +import ( + "fmt" + "testing" + "time" + + "github.com/matrix-org/gomatrixserverlib" + + "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/federation" +) + +// This test ensures that invite rejections are correctly sent out over federation. +// +// We start with two users in a room - alice@hs1, and 'delia' on the Complement test server. +// alice sends an invite to charlie@hs2, which he rejects. +// We check that delia sees the rejection. +// +func TestFederationRejectInvite(t *testing.T) { + deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) + defer deployment.Destroy(t) + alice := deployment.Client(t, "hs1", "@alice:hs1") + charlie := deployment.Client(t, "hs2", "@charlie:hs2") + + waiter := NewWaiter() + srv := federation.NewServer(t, deployment, + federation.HandleKeyRequests(), + federation.HandleTransactionRequests(func(ev *gomatrixserverlib.Event) { + defer waiter.Finish() + sk := "" + if ev.StateKey() != nil { + sk = *ev.StateKey() + } + t.Logf("Received PDU %s/%s", ev.Type(), sk) + }, nil), + ) + srv.UnexpectedRequestsAreErrors = false + cancel := srv.Listen() + defer cancel() + delia := srv.UserID("delia") + + // Alice creates the room, and delia joins + roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + room := srv.MustJoinRoom(t, deployment, "hs1", roomID, delia) + + // Alice invites Charlie; Delia should see the invite + alice.InviteRoom(t, roomID, charlie.UserID) + waiter.Wait(t, 5*time.Second) + if err := checkMembershipForUser(room, charlie.UserID, "invite"); err != nil { + t.Errorf("Membership state for charlie after invite: %v", err) + } + + // Charlie rejects the invite; Delia should see the rejection. + waiter = NewWaiter() + charlie.LeaveRoom(t, roomID) + waiter.Wait(t, 5*time.Second) + if err := checkMembershipForUser(room, charlie.UserID, "leave"); err != nil { + t.Errorf("Membership state for charlie after reject: %v", err) + } +} + +func checkMembershipForUser(room *federation.ServerRoom, userID, wantMembership string) (err error) { + state := room.CurrentState("m.room.member", userID) + if state == nil { + err = fmt.Errorf("no membership state for %s", userID) + return + } + m, err := state.Membership() + if err != nil { + return + } + if m != wantMembership { + err = fmt.Errorf("incorrect membership state: got %s, want %s", m, wantMembership) + return + } + return +} From 1649c27f57d8b12b2dcf0cd703f37856cf503136 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 24 Jun 2021 15:49:40 +0100 Subject: [PATCH 5/8] update gomatrixserverlib links --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index aa73570f..b50041cc 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 // indirect github.com/gorilla/mux v1.8.0 - github.com/matrix-org/gomatrixserverlib v0.0.0-20210621174423-185789a71f3a + github.com/matrix-org/gomatrixserverlib v0.0.0-20210624115417-42ac4e797a58 github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 8bc253c9..ed999bf5 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,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/gomatrixserverlib v0.0.0-20210621174423-185789a71f3a h1:ttpygr7uLtACsxDoTol1BTVqmMOyib0ZBovMOD6OzkU= github.com/matrix-org/gomatrixserverlib v0.0.0-20210621174423-185789a71f3a/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210624115417-42ac4e797a58 h1:PVn5mCHmdONm0k5d0/H+fdtI+/C156+J7vylnnvQWPY= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210624115417-42ac4e797a58/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= From 54a1292f574b17b1f2865476eb03b93091d8695e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 24 Jun 2021 15:59:41 +0100 Subject: [PATCH 6/8] Introspect incoming transactions --- tests/federation_room_invite_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/federation_room_invite_test.go b/tests/federation_room_invite_test.go index 88684b54..bc53e568 100644 --- a/tests/federation_room_invite_test.go +++ b/tests/federation_room_invite_test.go @@ -23,16 +23,20 @@ func TestFederationRejectInvite(t *testing.T) { alice := deployment.Client(t, "hs1", "@alice:hs1") charlie := deployment.Client(t, "hs2", "@charlie:hs2") - waiter := NewWaiter() + // we'll awaken this Waiter when we receive a membership event for Charlie + var waiter *Waiter + srv := federation.NewServer(t, deployment, federation.HandleKeyRequests(), federation.HandleTransactionRequests(func(ev *gomatrixserverlib.Event) { - defer waiter.Finish() sk := "" if ev.StateKey() != nil { sk = *ev.StateKey() } t.Logf("Received PDU %s/%s", ev.Type(), sk) + if waiter != nil && ev.Type() == "m.room.member" && ev.StateKey() != nil && *ev.StateKey() == charlie.UserID { + waiter.Finish() + } }, nil), ) srv.UnexpectedRequestsAreErrors = false @@ -45,6 +49,7 @@ func TestFederationRejectInvite(t *testing.T) { room := srv.MustJoinRoom(t, deployment, "hs1", roomID, delia) // Alice invites Charlie; Delia should see the invite + waiter = NewWaiter() alice.InviteRoom(t, roomID, charlie.UserID) waiter.Wait(t, 5*time.Second) if err := checkMembershipForUser(room, charlie.UserID, "invite"); err != nil { From 43586407c92c9557cd6c0825e54fb98cde83e741 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 9 Jul 2021 12:14:28 +0100 Subject: [PATCH 7/8] Sign invite event before returning it --- internal/federation/handle.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/federation/handle.go b/internal/federation/handle.go index 82977535..c564697b 100644 --- a/internal/federation/handle.go +++ b/internal/federation/handle.go @@ -163,9 +163,12 @@ func HandleInviteRequests(inviteCallback func(*gomatrixserverlib.Event)) func(*S inviteCallback(inviteRequest.Event()) } + // Sign the event before we send it back + signedEvent := inviteRequest.Event().Sign(s.ServerName, s.KeyID, s.Priv) + // Send the response res := map[string]interface{}{ - "event": inviteRequest.Event(), + "event": signedEvent, } w.WriteHeader(200) b, _ := json.Marshal(res) From 270213577c92fc390b5d5dd8a1cbee405a092adb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 9 Jul 2021 12:16:52 +0100 Subject: [PATCH 8/8] Use `StateKeyEquals` --- tests/federation_room_invite_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/federation_room_invite_test.go b/tests/federation_room_invite_test.go index bc53e568..599e3124 100644 --- a/tests/federation_room_invite_test.go +++ b/tests/federation_room_invite_test.go @@ -34,7 +34,7 @@ func TestFederationRejectInvite(t *testing.T) { sk = *ev.StateKey() } t.Logf("Received PDU %s/%s", ev.Type(), sk) - if waiter != nil && ev.Type() == "m.room.member" && ev.StateKey() != nil && *ev.StateKey() == charlie.UserID { + if waiter != nil && ev.Type() == "m.room.member" && ev.StateKeyEquals(charlie.UserID) { waiter.Finish() } }, nil),