fix: sync non-PTS privacy updates
This commit is contained in:
parent
6cafa40c7b
commit
cc76cd3679
17 changed files with 196 additions and 421 deletions
|
|
@ -873,27 +873,7 @@ func (r *Router) onAccountSetPrivacy(ctx context.Context, req *tg.AccountSetPriv
|
|||
if r.deps.Privacy == nil {
|
||||
return &tg.AccountPrivacyRules{Rules: tgPrivacyRules(rules), Users: []tg.UserClass{}, Chats: []tg.ChatClass{}}, nil
|
||||
}
|
||||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||||
sessionID, _ := SessionIDFrom(ctx)
|
||||
var (
|
||||
saved domain.PrivacyRules
|
||||
event domain.UpdateEvent
|
||||
durableWrite bool
|
||||
)
|
||||
if durable, ok := r.deps.Privacy.(PrivacyDurableService); ok {
|
||||
saved, event, durableWrite, err = durable.SetRulesWithUpdate(
|
||||
ctx,
|
||||
userID,
|
||||
domainKey,
|
||||
rules,
|
||||
int(r.clock.Now().Unix()),
|
||||
rawAuthKeyIDForOrigin(ctx),
|
||||
sessionID,
|
||||
)
|
||||
}
|
||||
if err == nil && !durableWrite {
|
||||
saved, err = r.deps.Privacy.SetRules(ctx, userID, domainKey, rules)
|
||||
}
|
||||
saved, err := r.deps.Privacy.SetRules(ctx, userID, domainKey, rules)
|
||||
if err != nil {
|
||||
return nil, privacyErr(err)
|
||||
}
|
||||
|
|
@ -902,33 +882,20 @@ func (r *Router) onAccountSetPrivacy(ctx context.Context, req *tg.AccountSetPriv
|
|||
return nil, err
|
||||
}
|
||||
r.invalidateRPCProjectionForUser(userID)
|
||||
if durableWrite {
|
||||
if sessionID != 0 {
|
||||
r.bookkeepAuxPtsForCurrentSession(ctx, event)
|
||||
}
|
||||
r.pushUserUpdatesIfNoReliableDispatch(ctx, userID, tgUpdateForOutboxEvent(event))
|
||||
} else if updates, ok := r.deps.Updates.(PrivacyUpdatesService); ok {
|
||||
event, _, recordErr := updates.RecordPrivacy(
|
||||
ctx, authKeyID, userID, saved, rawAuthKeyIDForOrigin(ctx), sessionID,
|
||||
)
|
||||
if recordErr != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if sessionID != 0 {
|
||||
r.bookkeepAuxPtsForCurrentSession(ctx, event)
|
||||
}
|
||||
r.pushUserUpdatesIfNoReliableDispatch(ctx, userID, tgUpdateForOutboxEvent(event))
|
||||
} else {
|
||||
r.pushUserUpdates(ctx, userID, &tg.Updates{
|
||||
Updates: []tg.UpdateClass{&tg.UpdatePrivacy{
|
||||
Key: tgPrivacyKey(saved.Key),
|
||||
Rules: tgPrivacyRules(saved.Rules),
|
||||
}},
|
||||
Users: []tg.UserClass{},
|
||||
Chats: []tg.ChatClass{},
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
})
|
||||
}
|
||||
// updatePrivacy is an absolute, non-PTS account-state notification. The
|
||||
// originating session applies account.setPrivacy's response; other online
|
||||
// sessions receive this best-effort update, while offline sessions reload
|
||||
// the authoritative rules through account.getPrivacy.
|
||||
r.pushUserUpdates(ctx, userID, &tg.Updates{
|
||||
Updates: []tg.UpdateClass{&tg.UpdatePrivacy{
|
||||
Key: tgPrivacyKey(saved.Key),
|
||||
Rules: tgPrivacyRules(saved.Rules),
|
||||
}},
|
||||
Users: []tg.UserClass{},
|
||||
Chats: []tg.ChatClass{},
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
Seq: 0,
|
||||
})
|
||||
if domainKey == domain.PrivacyKeyStatusTimestamp {
|
||||
r.pushStatusPrivacyRefresh(ctx, userID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
func TestAccountPrivacyAllKeysRoundTripAndRecordDifferenceEvents(t *testing.T) {
|
||||
func TestAccountPrivacyAllKeysRoundTripWithoutAdvancingPts(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const userID int64 = 8101
|
||||
authKeyID := [8]byte{8, 1}
|
||||
|
|
@ -22,10 +22,11 @@ func TestAccountPrivacyAllKeysRoundTripAndRecordDifferenceEvents(t *testing.T) {
|
|||
privacy := appprivacy.NewService(memory.NewPrivacyStore(), memory.NewContactStore())
|
||||
events := memory.NewUpdateEventStore()
|
||||
updates := appupdates.NewService(memory.NewUpdateStateStore(), events)
|
||||
sessions := &captureSessions{}
|
||||
router := New(Config{}, Deps{
|
||||
Privacy: privacy,
|
||||
Updates: updates,
|
||||
Sessions: &captureSessions{},
|
||||
Sessions: sessions,
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
requestCtx := WithSessionID(WithAuthKeyID(WithUserID(ctx, userID), authKeyID), sessionID)
|
||||
|
||||
|
|
@ -83,40 +84,78 @@ func TestAccountPrivacyAllKeysRoundTripAndRecordDifferenceEvents(t *testing.T) {
|
|||
if _, ok := get.Rules[0].(*tg.PrivacyValueDisallowAll); !ok {
|
||||
t.Fatalf("getPrivacy rule=%T, want disallowAll", get.Rules[0])
|
||||
}
|
||||
pushed, ok := sessions.lastUserPush().(*tg.Updates)
|
||||
if !ok || len(pushed.Updates) != 1 {
|
||||
t.Fatalf("online push=%T/%+v, want one updatePrivacy", sessions.lastUserPush(), pushed)
|
||||
}
|
||||
privacyUpdate, ok := pushed.Updates[0].(*tg.UpdatePrivacy)
|
||||
if !ok {
|
||||
t.Fatalf("online push update=%T, want updatePrivacy(%q)", pushed.Updates[0], test.domain)
|
||||
}
|
||||
if !test.wire(privacyUpdate.Key) {
|
||||
t.Fatalf("online push key=%T, want %q", privacyUpdate.Key, test.domain)
|
||||
}
|
||||
})
|
||||
}
|
||||
if pushedUserIDs := sessions.pushedUserIDs(); len(pushedUserIDs) != len(keys) {
|
||||
t.Fatalf("online privacy pushes=%v, want exactly one per key", pushedUserIDs)
|
||||
} else {
|
||||
for i, pushedUserID := range pushedUserIDs {
|
||||
if pushedUserID != userID {
|
||||
t.Fatalf("online privacy push[%d] target=%d, want owner %d", i, pushedUserID, userID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if snapshot := sessions.snapshot(); snapshot.sessionID != sessionID || snapshot.userID != userID {
|
||||
t.Fatalf("online push exclusion/target=%+v, want current session %d excluded for user %d", snapshot, sessionID, userID)
|
||||
}
|
||||
|
||||
recorded, err := events.ListAfter(ctx, userID, 0, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("list privacy events: %v", err)
|
||||
t.Fatalf("list account update events: %v", err)
|
||||
}
|
||||
if len(recorded) != len(keys) {
|
||||
t.Fatalf("privacy events=%d, want %d", len(recorded), len(keys))
|
||||
if len(recorded) != 0 {
|
||||
t.Fatalf("account update events=%+v, want none for privacy changes", recorded)
|
||||
}
|
||||
for i, event := range recorded {
|
||||
if event.Type != domain.UpdateEventPrivacy ||
|
||||
event.Privacy.OwnerUserID != userID ||
|
||||
event.Privacy.Key != keys[i].domain ||
|
||||
event.PtsCount != 1 {
|
||||
t.Fatalf("event[%d]=%+v, want durable privacy snapshot for %q", i, event, keys[i].domain)
|
||||
}
|
||||
state, err := updates.CurrentState(ctx, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("current update state: %v", err)
|
||||
}
|
||||
if state.Pts != 0 {
|
||||
t.Fatalf("privacy changes advanced pts to %d, want 0", state.Pts)
|
||||
}
|
||||
|
||||
difference, err := updates.GetDifference(ctx, [8]byte{8, 2}, userID, domain.UpdateState{})
|
||||
if err != nil {
|
||||
t.Fatalf("getDifference: %v", err)
|
||||
}
|
||||
wireDifference, ok := tgUpdatesDifference(userID, difference).(*tg.UpdatesDifference)
|
||||
if !ok || len(wireDifference.OtherUpdates) != len(keys) {
|
||||
t.Fatalf("wire difference=%T updates=%d, want %d privacy updates", wireDifference, len(wireDifference.OtherUpdates), len(keys))
|
||||
if difference.State.Pts != 0 || len(difference.Events) != 0 {
|
||||
t.Fatalf("difference after privacy changes=%+v, want empty pts=0", difference)
|
||||
}
|
||||
for i, update := range wireDifference.OtherUpdates {
|
||||
privacyUpdate, ok := update.(*tg.UpdatePrivacy)
|
||||
if !ok {
|
||||
t.Fatalf("difference update[%d]=%T, want updatePrivacy", i, update)
|
||||
}
|
||||
if !keys[i].wire(privacyUpdate.Key) {
|
||||
t.Fatalf("difference update[%d] key=%T, want %q", i, privacyUpdate.Key, keys[i].domain)
|
||||
}
|
||||
|
||||
// A real message-box update immediately after privacy changes must still
|
||||
// receive pts=1. This catches both hidden privacy allocations and gaps left
|
||||
// behind by synthetic bookkeeping events.
|
||||
message := domain.Message{
|
||||
ID: 1,
|
||||
OwnerUserID: userID,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 8102},
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: 8102},
|
||||
Date: 1700000000,
|
||||
Body: "after privacy",
|
||||
}
|
||||
event, state, err := updates.RecordNewMessage(ctx, authKeyID, userID, message)
|
||||
if err != nil {
|
||||
t.Fatalf("record adjacent message update: %v", err)
|
||||
}
|
||||
if event.Pts != 1 || event.PtsCount != 1 || state.Pts != 1 {
|
||||
t.Fatalf("adjacent message event/state=%+v/%+v, want first pts=1", event, state)
|
||||
}
|
||||
difference, err = updates.GetDifference(ctx, [8]byte{8, 2}, userID, domain.UpdateState{})
|
||||
if err != nil {
|
||||
t.Fatalf("getDifference after message: %v", err)
|
||||
}
|
||||
if difference.State.Pts != 1 || len(difference.Events) != 1 || difference.Events[0].Type != domain.UpdateEventNewMessage {
|
||||
t.Fatalf("difference after adjacent message=%+v, want one contiguous new_message at pts=1", difference)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,14 +247,6 @@ func tgOtherUpdateFromEvent(event domain.UpdateEvent) tg.UpdateClass {
|
|||
return nil
|
||||
}
|
||||
return &tg.UpdateUser{UserID: event.Peer.ID}
|
||||
case domain.UpdateEventPrivacy:
|
||||
if event.Privacy.OwnerUserID == 0 || event.Privacy.Key == "" || len(event.Privacy.Rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &tg.UpdatePrivacy{
|
||||
Key: tgPrivacyKey(event.Privacy.Key),
|
||||
Rules: tgPrivacyRules(event.Privacy.Rules),
|
||||
}
|
||||
case domain.UpdateEventChannelState:
|
||||
if event.Peer.Type != domain.PeerTypeChannel || event.Peer.ID == 0 {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -487,26 +487,6 @@ type UserEmojiStatusUpdatesService interface {
|
|||
RecordUserEmojiStatus(ctx context.Context, stateAuthKeyID [8]byte, userID int64, status domain.UserEmojiStatus, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
|
||||
}
|
||||
|
||||
// PrivacyUpdatesService is the fallback durable extension for stores that do
|
||||
// not support the atomic privacy+event write boundary (mainly memory tests).
|
||||
type PrivacyUpdatesService interface {
|
||||
RecordPrivacy(ctx context.Context, stateAuthKeyID [8]byte, userID int64, rules domain.PrivacyRules, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
|
||||
}
|
||||
|
||||
// PrivacyDurableService is implemented by the production privacy service. Its
|
||||
// successful path commits rules+pts+event+dispatch in one transaction.
|
||||
type PrivacyDurableService interface {
|
||||
SetRulesWithUpdate(
|
||||
ctx context.Context,
|
||||
ownerUserID int64,
|
||||
key domain.PrivacyKey,
|
||||
rules []domain.PrivacyRule,
|
||||
date int,
|
||||
excludeAuthKeyID [8]byte,
|
||||
excludeSessionID int64,
|
||||
) (saved domain.PrivacyRules, event domain.UpdateEvent, durable bool, err error)
|
||||
}
|
||||
|
||||
// ContactsService 抽象通讯录查询。
|
||||
type ContactsService interface {
|
||||
GetContacts(ctx context.Context, userID int64, hash int64) (domain.ContactList, bool, error)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue