Files
Warp/server/internal/storage/remotekeys_test.go
T
tajniak81andClaude Opus 5.5 74f7bc8805
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
keyserver: cache remote users' device and cross-signing keys
POST /keys/query for a remote user who shares a room with a local user is
now answered from a cache, fetched whole with GET /user/devices/{userId}
on a miss. Incoming m.device_list_update EDUs are applied when their
prev_id matches the cached stream_id; anything else marks the copy stale
and it is fetched again in the background, and a stale copy is never
served. m.signing_key_update replaces the cached cross-signing keys. A
user's copy is dropped once no local user shares a room with them, by a
keyserver listener on the roomserver's emit stage.

Migration 0009 adds keyserver_remote_users, keyserver_remote_device_keys
and keyserver_remote_cross_signing_keys for sqlite and postgres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:36:08 +02:00

186 lines
7.6 KiB
Go

package storage_test
import (
"slices"
"testing"
"github.com/dariuszjaniak/warp/server/internal/storage"
)
const carol = "@carol:remote.example"
func TestRemoteKeyCache(t *testing.T) {
for _, driver := range testDrivers {
t.Run(driver, func(t *testing.T) {
ctx := t.Context()
db := migratedKeysDB(t, driver)
keys := db.Keys()
if _, ok, err := keys.RemoteKeys(ctx, carol); ok || err != nil {
t.Fatalf("RemoteKeys before anything was cached = %v, %v", ok, err)
}
snapshot := storage.RemoteKeys{
UserID: carol, StreamID: 5,
Devices: []storage.RemoteDevice{
{DeviceID: "C2", JSON: []byte(`{"d":2}`)},
{DeviceID: "C1", DisplayName: "phone", JSON: []byte(`{"d":1}`)},
},
CrossSigning: map[string][]byte{
storage.CrossSigningMaster: []byte(`{"m":1}`),
storage.CrossSigningSelfSigning: []byte(`{"s":1}`),
},
}
if stored, err := keys.SetRemoteKeys(ctx, snapshot); !stored || err != nil {
t.Fatalf("SetRemoteKeys = %v, %v", stored, err)
}
got, ok, err := keys.RemoteKeys(ctx, carol)
if err != nil || !ok {
t.Fatalf("RemoteKeys = %v, %v", ok, err)
}
if got.StreamID != 5 || got.Stale || len(got.Devices) != 2 ||
got.Devices[0].DeviceID != "C1" || got.Devices[0].DisplayName != "phone" || string(got.Devices[1].JSON) != `{"d":2}` ||
string(got.CrossSigning[storage.CrossSigningMaster]) != `{"m":1}` || len(got.CrossSigning) != 2 {
t.Errorf("RemoteKeys = %+v", got)
}
// Caching moves nothing in the device list stream: that is the
// keyserver's call, per update.
if pos, _ := keys.DeviceListPosition(ctx, carol); pos != 0 {
t.Errorf("caching moved the device list to %d", pos)
}
apply := func(u storage.RemoteDeviceUpdate) storage.RemoteUpdateResult {
t.Helper()
u.UserID = carol
result, err := keys.UpdateRemoteDevice(ctx, u)
if err != nil {
t.Fatalf("UpdateRemoteDevice(%+v): %v", u, err)
}
return result
}
// Following on, a repeat, and a deletion.
if r := apply(storage.RemoteDeviceUpdate{DeviceID: "C3", JSON: []byte(`{"d":3}`), StreamID: 6, PrevIDs: []int64{5}}); r != storage.RemoteUpdateApplied {
t.Errorf("an update that follows on = %v", r)
}
if r := apply(storage.RemoteDeviceUpdate{DeviceID: "C3", JSON: []byte(`{"d":3}`), StreamID: 6, PrevIDs: []int64{5}}); r != storage.RemoteUpdateDuplicate {
t.Errorf("the same update again = %v", r)
}
if r := apply(storage.RemoteDeviceUpdate{DeviceID: "C2", Deleted: true, StreamID: 7, PrevIDs: []int64{4, 6}}); r != storage.RemoteUpdateApplied {
t.Errorf("a deletion naming the position held among others = %v", r)
}
got, _, _ = keys.RemoteKeys(ctx, carol)
var ids []string
for _, d := range got.Devices {
ids = append(ids, d.DeviceID)
}
if got.StreamID != 7 || !slices.Equal(ids, []string{"C1", "C3"}) {
t.Errorf("after the updates: position %d, devices %v", got.StreamID, ids)
}
// A gap marks the copy stale at the newest position heard of.
if r := apply(storage.RemoteDeviceUpdate{DeviceID: "C4", JSON: []byte(`{}`), StreamID: 12, PrevIDs: []int64{11}}); r != storage.RemoteUpdateStale {
t.Errorf("an update after a gap = %v", r)
}
got, _, _ = keys.RemoteKeys(ctx, carol)
if !got.Stale || got.StreamID != 12 || len(got.Devices) != 2 {
t.Errorf("after the gap: %+v", got)
}
if r := apply(storage.RemoteDeviceUpdate{DeviceID: "C4", JSON: []byte(`{}`), StreamID: 13, PrevIDs: []int64{12}}); r != storage.RemoteUpdateStale {
t.Errorf("an update to a stale copy = %v", r)
}
if stored, _ := keys.SetRemoteCrossSigningKeys(ctx, carol, map[string][]byte{storage.CrossSigningMaster: []byte(`{}`)}); stored {
t.Error("cross-signing keys were written into a stale copy")
}
// A snapshot older than the position held is not stored; one at
// or past it replaces the copy whole.
if stored, _ := keys.SetRemoteKeys(ctx, storage.RemoteKeys{UserID: carol, StreamID: 10}); stored {
t.Error("a snapshot older than the copy was stored")
}
if stored, _ := keys.SetRemoteKeys(ctx, storage.RemoteKeys{
UserID: carol, StreamID: 13, Devices: []storage.RemoteDevice{{DeviceID: "C4", JSON: []byte(`{"d":4}`)}},
}); !stored {
t.Error("a current snapshot was not stored")
}
got, _, _ = keys.RemoteKeys(ctx, carol)
if got.Stale || got.StreamID != 13 || len(got.Devices) != 1 || len(got.CrossSigning) != 0 {
t.Errorf("after the resync: %+v", got)
}
// Cross-signing keys are replaced as a set.
if stored, err := keys.SetRemoteCrossSigningKeys(ctx, carol, map[string][]byte{
storage.CrossSigningMaster: []byte(`{"m":2}`), storage.CrossSigningSelfSigning: nil,
}); !stored || err != nil {
t.Fatalf("SetRemoteCrossSigningKeys = %v, %v", stored, err)
}
got, _, _ = keys.RemoteKeys(ctx, carol)
if string(got.CrossSigning[storage.CrossSigningMaster]) != `{"m":2}` || len(got.CrossSigning) != 1 {
t.Errorf("cross-signing keys = %v", got.CrossSigning)
}
// An update about a user with nothing cached leaves a stale
// marker, and a snapshot from before the update loses to it.
const dave = "@dave:remote.example"
if r, _ := keys.UpdateRemoteDevice(ctx, storage.RemoteDeviceUpdate{UserID: dave, DeviceID: "V1", StreamID: 3}); r != storage.RemoteUpdateNotCached {
t.Errorf("an update about an uncached user = %v", r)
}
marker, ok, _ := keys.RemoteKeys(ctx, dave)
if !ok || !marker.Stale || marker.StreamID != 3 {
t.Errorf("the marker = %+v, %v", marker, ok)
}
if stored, _ := keys.SetRemoteKeys(ctx, storage.RemoteKeys{UserID: dave, StreamID: 2}); stored {
t.Error("a snapshot from before the update was stored")
}
if stored, _ := keys.SetRemoteCrossSigningKeys(ctx, "@nobody:remote.example", map[string][]byte{}); stored {
t.Error("cross-signing keys were cached for a user with no copy")
}
users, err := keys.RemoteKeyUsers(ctx)
if err != nil || !slices.Equal(users, []string{carol, dave}) {
t.Errorf("RemoteKeyUsers = %v, %v", users, err)
}
if err := keys.DeleteRemoteKeys(ctx, []string{carol, dave}); err != nil {
t.Fatalf("DeleteRemoteKeys: %v", err)
}
if _, ok, _ := keys.RemoteKeys(ctx, carol); ok {
t.Error("a dropped copy is still there")
}
if users, _ := keys.RemoteKeyUsers(ctx); len(users) != 0 {
t.Errorf("RemoteKeyUsers after dropping everybody = %v", users)
}
})
}
}
func TestRemoteKeyCacheFirstUpdate(t *testing.T) {
for _, driver := range testDrivers {
t.Run(driver, func(t *testing.T) {
ctx := t.Context()
keys := migratedKeysDB(t, driver).Keys()
// A user whose server had never moved their list is cached at
// position 0, and the first update, with no prev_id, follows on.
if _, err := keys.SetRemoteKeys(ctx, storage.RemoteKeys{UserID: carol}); err != nil {
t.Fatal(err)
}
r, err := keys.UpdateRemoteDevice(ctx, storage.RemoteDeviceUpdate{UserID: carol, DeviceID: "C1", JSON: []byte(`{}`), StreamID: 1})
if err != nil || r != storage.RemoteUpdateApplied {
t.Errorf("the first update = %v, %v", r, err)
}
// A later one with no prev_id does not.
r, _ = keys.UpdateRemoteDevice(ctx, storage.RemoteDeviceUpdate{UserID: carol, DeviceID: "C1", JSON: []byte(`{}`), StreamID: 2})
if r != storage.RemoteUpdateStale {
t.Errorf("a later update with no prev_id = %v", r)
}
// Nor does one that says a device changed without its keys.
_, _ = keys.SetRemoteKeys(ctx, storage.RemoteKeys{UserID: carol, StreamID: 2})
r, _ = keys.UpdateRemoteDevice(ctx, storage.RemoteDeviceUpdate{UserID: carol, DeviceID: "C1", StreamID: 3, PrevIDs: []int64{2}})
if r != storage.RemoteUpdateStale {
t.Errorf("an update with no keys = %v", r)
}
})
}
}