From b8832d6fcf00ae6d997cc032bb80135f1746a067 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 17 Nov 2023 12:59:03 +0000 Subject: [PATCH] Add FailedToDecrypt flag and impl it, along with MustGetEvent. Rust race fixes --- internal/api/client.go | 3 +- internal/api/js.go | 37 +++++++++++++- internal/api/rust.go | 95 +++++++++++++++++++++++++++++------ tests/membership_acls_test.go | 3 +- 4 files changed, 120 insertions(+), 18 deletions(-) diff --git a/internal/api/client.go b/internal/api/client.go index 62b127c..3a878e2 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -127,7 +127,8 @@ type Event struct { // FFI bindings don't expose state key Target string // FFI bindings don't expose type - Membership string + Membership string + FailedToDecrypt bool } type Waiter interface { diff --git a/internal/api/js.go b/internal/api/js.go index e45aa7c..dad65ca 100644 --- a/internal/api/js.go +++ b/internal/api/js.go @@ -20,6 +20,7 @@ import ( "github.com/chromedp/chromedp" "github.com/matrix-org/complement-crypto/internal/chrome" "github.com/matrix-org/complement/must" + "github.com/tidwall/gjson" ) const CONSOLE_LOG_CONTROL_STRING = "CC:" // for "complement-crypto" @@ -191,7 +192,41 @@ func (c *JSClient) UserID() string { } func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) Event { - return Event{} + // serialised output (if encrypted): + // { + // encrypted: { event } + // decrypted: { event } + // } + // else just returns { event } + evSerialised := chrome.MustExecuteInto[string](t, c.ctx, fmt.Sprintf(` + JSON.stringify(window.__client.getRoom("%s")?.getLiveTimeline()?.getEvents().filter((ev) => { + return ev.getId() === "%s"; + })[0].toJSON()); + `, roomID, eventID)) + if !gjson.Valid(evSerialised) { + fatalf(t, "MustGetEvent(%s, %s): invalid event, got %s", roomID, eventID, evSerialised) + } + result := gjson.Parse(evSerialised) + decryptedEvent := result.Get("decrypted") + if !decryptedEvent.Exists() { + decryptedEvent = result + } + encryptedEvent := result.Get("encrypted") + //fmt.Printf("DECRYPTED: %s\nENCRYPTED: %s\n\n", decryptedEvent.Raw, encryptedEvent.Raw) + ev := Event{ + ID: decryptedEvent.Get("event_id").Str, + Text: decryptedEvent.Get("content.body").Str, + Sender: decryptedEvent.Get("sender").Str, + } + if decryptedEvent.Get("type").Str == "m.room.member" { + ev.Membership = decryptedEvent.Get("content.membership").Str + ev.Target = decryptedEvent.Get("state_key").Str + } + if encryptedEvent.Exists() && decryptedEvent.Get("content.msgtype").Str == "m.bad.encrypted" { + ev.FailedToDecrypt = true + } + + return ev } // StartSyncing to begin syncing from sync v2 / sliding sync. diff --git a/internal/api/rust.go b/internal/api/rust.go index e09c2f5..e22d50b 100644 --- a/internal/api/rust.go +++ b/internal/api/rust.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "sync" "sync/atomic" "testing" "time" @@ -32,9 +33,11 @@ type RustRoomInfo struct { type RustClient struct { FFIClient *matrix_sdk_ffi.Client - rooms map[string]*RustRoomInfo listeners map[int32]func(roomID string) listenerID atomic.Int32 + allRooms *matrix_sdk_ffi.RoomList + rooms map[string]*RustRoomInfo + roomsMu *sync.RWMutex userID string } @@ -58,6 +61,7 @@ func NewRustClient(t *testing.T, opts ClientCreationOpts, ssURL string) (Client, FFIClient: client, rooms: make(map[string]*RustRoomInfo), listeners: make(map[int32]func(roomID string)), + roomsMu: &sync.RWMutex{}, } c.Logf(t, "NewRustClient[%s] created client", opts.UserID) return &LoggedClient{Client: c}, nil @@ -69,7 +73,16 @@ func (c *RustClient) Close(t *testing.T) { } func (c *RustClient) MustGetEvent(t *testing.T, roomID, eventID string) Event { - return Event{} + room := c.findRoom(t, roomID) + timelineItem, err := room.GetEventTimelineItemByEventId(eventID) + if err != nil { + fatalf(t, "MustGetEvent(%s, %s): %s", roomID, eventID, err) + } + ev := eventTimelineItemToEvent(timelineItem) + if ev == nil { + fatalf(t, "MustGetEvent(%s, %s): found timeline item but failed to convert it to an Event", roomID, eventID) + } + return *ev } // StartSyncing to begin syncing from sync v2 / sliding sync. @@ -86,6 +99,7 @@ func (c *RustClient) StartSyncing(t *testing.T) (stopSyncing func()) { }) must.NotError(t, "failed to call RoomList.LoadingState", err) go syncService.Start() + c.allRooms = roomList isSyncing := false @@ -116,7 +130,7 @@ func (c *RustClient) StartSyncing(t *testing.T) (stopSyncing func()) { // provide a bogus room ID. func (c *RustClient) IsRoomEncrypted(t *testing.T, roomID string) (bool, error) { t.Helper() - r := c.findRoom(roomID) + r := c.findRoom(t, roomID) if r == nil { rooms := c.FFIClient.Rooms() return false, fmt.Errorf("failed to find room %s, got %d rooms", roomID, len(rooms)) @@ -174,7 +188,7 @@ func (c *RustClient) SendMessage(t *testing.T, roomID, text string) (eventID str func (c *RustClient) MustBackpaginate(t *testing.T, roomID string, count int) { t.Helper() - r := c.findRoom(roomID) + r := c.findRoom(t, roomID) must.NotEqual(t, r, nil, "unknown room") must.NotError(t, "failed to backpaginate", r.PaginateBackwards(matrix_sdk_ffi.PaginationOptionsSingleRequest{ EventLimit: uint16(count), @@ -185,16 +199,50 @@ func (c *RustClient) UserID() string { return c.userID } -func (c *RustClient) findRoom(roomID string) *matrix_sdk_ffi.Room { +func (c *RustClient) findRoomInMap(roomID string) *matrix_sdk_ffi.Room { + c.roomsMu.RLock() + defer c.roomsMu.RUnlock() + // do we have a reference to it already? + roomInfo := c.rooms[roomID] + if roomInfo != nil { + return roomInfo.room + } + return nil +} + +// findRoom returns the room, waiting up to 5s for it to appear +func (c *RustClient) findRoom(t *testing.T, roomID string) *matrix_sdk_ffi.Room { + room := c.findRoomInMap(roomID) + if room != nil { + return room + } + // try to find it in all_rooms + if c.allRooms != nil { + roomListItem, err := c.allRooms.Room(roomID) + if err != nil { + c.Logf(t, "allRooms.Room(%s) err: %s", roomID, err) + } else if roomListItem != nil { + room := roomListItem.FullRoom() + c.roomsMu.Lock() + c.rooms[roomID] = &RustRoomInfo{ + room: room, + } + c.roomsMu.Unlock() + return room + } + } + // try to find it from cache? rooms := c.FFIClient.Rooms() for i, r := range rooms { rid := r.Id() // ensure we only store rooms once _, exists := c.rooms[rid] if !exists { + c.roomsMu.Lock() c.rooms[rid] = &RustRoomInfo{ room: rooms[i], } + c.roomsMu.Unlock() } if r.Id() == roomID { return c.rooms[rid].room @@ -210,7 +258,7 @@ func (c *RustClient) Logf(t *testing.T, format string, args ...interface{}) { } func (c *RustClient) ensureListening(t *testing.T, roomID string) *matrix_sdk_ffi.Room { - r := c.findRoom(roomID) + r := c.findRoom(t, roomID) must.NotEqual(t, r, nil, fmt.Sprintf("room %s does not exist", roomID)) info := c.rooms[roomID] @@ -218,10 +266,11 @@ func (c *RustClient) ensureListening(t *testing.T, roomID string) *matrix_sdk_ff return r } - t.Logf("[%s]AddTimelineListener[%s]", c.userID, roomID) + c.Logf(t, "[%s]AddTimelineListener[%s]", c.userID, roomID) // we need a timeline listener before we can send messages - r.AddTimelineListener(&timelineListener{fn: func(diff []*matrix_sdk_ffi.TimelineDiff) { + result := r.AddTimelineListener(&timelineListener{fn: func(diff []*matrix_sdk_ffi.TimelineDiff) { timeline := c.rooms[roomID].timeline + c.Logf(t, "[%s]AddTimelineListener[%s] TimelineDiff len=%d", c.userID, roomID, len(diff)) for _, d := range diff { switch d.Change() { case matrix_sdk_ffi.TimelineChangeInsert: @@ -275,6 +324,17 @@ func (c *RustClient) ensureListening(t *testing.T, roomID string) *matrix_sdk_ff l(roomID) } }}) + events := make([]*Event, len(result.Items)) + for i := range result.Items { + events[i] = timelineItemToEvent(result.Items[i]) + } + c.rooms[roomID].timeline = events + c.Logf(t, "[%s]AddTimelineListener[%s] result.Items len=%d", c.userID, roomID, len(result.Items)) + if len(events) > 0 { + for _, l := range c.listeners { + l(roomID) + } + } info.attachedListener = true return r } @@ -361,19 +421,22 @@ func timelineItemToEvent(item *matrix_sdk_ffi.TimelineItem) *Event { if ev == nil { // e.g day divider return nil } - evv := *ev - if evv == nil { + return eventTimelineItemToEvent(*ev) +} + +func eventTimelineItemToEvent(item *matrix_sdk_ffi.EventTimelineItem) *Event { + if item == nil { return nil } eventID := "" - if evv.EventId() != nil { - eventID = *evv.EventId() + if item.EventId() != nil { + eventID = *item.EventId() } complementEvent := Event{ ID: eventID, - Sender: evv.Sender(), + Sender: item.Sender(), } - switch k := evv.Content().Kind().(type) { + switch k := item.Content().Kind().(type) { case matrix_sdk_ffi.TimelineItemContentKindRoomMembership: complementEvent.Target = k.UserId change := *k.Change @@ -401,9 +464,11 @@ func timelineItemToEvent(item *matrix_sdk_ffi.TimelineItem) *Event { default: fmt.Printf("%s unhandled membership %d\n", k.UserId, change) } + case matrix_sdk_ffi.TimelineItemContentKindUnableToDecrypt: + complementEvent.FailedToDecrypt = true } - content := evv.Content() + content := item.Content() if content != nil { msg := content.AsMessage() if msg != nil { diff --git a/tests/membership_acls_test.go b/tests/membership_acls_test.go index 9777af1..f08dddc 100644 --- a/tests/membership_acls_test.go +++ b/tests/membership_acls_test.go @@ -240,7 +240,8 @@ func TestBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // bob hits scrollback and should see but not be able to decrypt the message bob.MustBackpaginate(t, roomID, 5) - ev := bob.MustGetEvent(t, roomID, evID) // TODO + ev := bob.MustGetEvent(t, roomID, evID) must.NotEqual(t, ev.Text, beforeJoinBody, "bob was able to decrypt a message from before he was joined") + must.Equal(t, ev.FailedToDecrypt, true, "message not marked as failed to decrypt") }) }