package memory import ( "context" "sort" "sync" "telesrv/internal/domain" ) // SecretChatStore 是 store.SecretChatStore 的进程内实现(rpc/app 单测 fixture 用)。 // 行为契约与 postgres 实现由 storetest 共享 contract test 钉死;凡动握手态迁移 // 语义两边必须同步。 type SecretChatStore struct { mu sync.Mutex chats map[int]domain.SecretChat } // NewSecretChatStore 创建内存实现。 func NewSecretChatStore() *SecretChatStore { return &SecretChatStore{chats: make(map[int]domain.SecretChat)} } func cloneSecretChat(c domain.SecretChat) domain.SecretChat { c.GA = append([]byte(nil), c.GA...) c.GB = append([]byte(nil), c.GB...) return c } func (s *SecretChatStore) CreateSecretChat(_ context.Context, chat domain.SecretChat) error { if chat.ID == 0 { return domain.ErrSecretChatNotFound } s.mu.Lock() defer s.mu.Unlock() if _, exists := s.chats[chat.ID]; exists { return domain.ErrSecretChatIDConflict } if chat.State == "" { chat.State = domain.SecretChatStateRequested } s.chats[chat.ID] = cloneSecretChat(chat) return nil } func (s *SecretChatStore) GetSecretChat(_ context.Context, chatID int) (domain.SecretChat, bool, error) { s.mu.Lock() defer s.mu.Unlock() c, ok := s.chats[chatID] if !ok { return domain.SecretChat{}, false, nil } return cloneSecretChat(c), true, nil } func (s *SecretChatStore) GetByAdminRandom(_ context.Context, adminAuthKeyID int64, randomID int32) (domain.SecretChat, bool, error) { s.mu.Lock() defer s.mu.Unlock() // 仅返回非终态匹配(与部分唯一索引 WHERE state <> 'discarded' 一致)。 for _, c := range s.chats { if c.AdminAuthKeyID == adminAuthKeyID && c.RandomID == randomID && !c.Terminal() { return cloneSecretChat(c), true, nil } } return domain.SecretChat{}, false, nil } func (s *SecretChatStore) AcceptSecretChat(_ context.Context, chatID int, participantAuthKeyID int64, gb []byte, keyFingerprint int64) (domain.SecretChat, error) { s.mu.Lock() defer s.mu.Unlock() c, ok := s.chats[chatID] if !ok { return domain.SecretChat{}, domain.ErrSecretChatNotFound } switch c.State { case domain.SecretChatStateNormal: return domain.SecretChat{}, domain.ErrSecretChatAlreadyAccepted case domain.SecretChatStateDiscarded: return domain.SecretChat{}, domain.ErrSecretChatAlreadyDeclined } // requested 且未绑定接受设备:CAS 成功。 if c.ParticipantAuthKeyID != 0 { return domain.SecretChat{}, domain.ErrSecretChatAlreadyAccepted } c.State = domain.SecretChatStateNormal c.ParticipantAuthKeyID = participantAuthKeyID c.GB = append([]byte(nil), gb...) c.KeyFingerprint = keyFingerprint s.chats[chatID] = c return cloneSecretChat(c), nil } func (s *SecretChatStore) DiscardSecretChat(_ context.Context, chatID int, historyDeleted bool) (domain.SecretChat, bool, error) { s.mu.Lock() defer s.mu.Unlock() c, ok := s.chats[chatID] if !ok { return domain.SecretChat{}, false, domain.ErrSecretChatNotFound } if c.State == domain.SecretChatStateDiscarded { return cloneSecretChat(c), true, nil } c.State = domain.SecretChatStateDiscarded c.HistoryDeleted = historyDeleted s.chats[chatID] = c return cloneSecretChat(c), false, nil } func (s *SecretChatStore) ListActiveSecretChatsByAuthKey(_ context.Context, authKeyID int64) ([]domain.SecretChat, error) { if authKeyID == 0 { return nil, nil } s.mu.Lock() defer s.mu.Unlock() var out []domain.SecretChat for _, c := range s.chats { if c.Terminal() { continue } if c.AdminAuthKeyID == authKeyID || c.ParticipantAuthKeyID == authKeyID { out = append(out, cloneSecretChat(c)) } } // map 遍历无序:按 chat_id 升序与 postgres ORDER BY 对齐,确定性供测试断言。 sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) return out, nil } func (s *SecretChatStore) MaxSecretChatID(_ context.Context) (int, error) { s.mu.Lock() defer s.mu.Unlock() max := 0 for id := range s.chats { if id > max { max = id } } return max, nil } // EncryptedQueueStore 是 store.EncryptedQueueStore 的进程内实现。 type EncryptedQueueStore struct { mu sync.Mutex byDevice map[int64][]domain.SecretChatMessage // receiverAuthKeyID → qts 升序消息 reserved map[int64]int confirmed map[int64]int dedup map[emqDedupKey]int // → qts stateEvents []domain.EncryptedStateEvent delivered map[int64]map[int64]bool // eventID → deviceAuthKeyID → true nextEventID int64 files map[int64]domain.EncryptedFileRef // file id → 快照 } type emqDedupKey struct { receiver int64 chat int random int64 } // NewEncryptedQueueStore 创建内存实现。 func NewEncryptedQueueStore() *EncryptedQueueStore { return &EncryptedQueueStore{ byDevice: make(map[int64][]domain.SecretChatMessage), reserved: make(map[int64]int), confirmed: make(map[int64]int), dedup: make(map[emqDedupKey]int), delivered: make(map[int64]map[int64]bool), } } func cloneSecretMessage(m domain.SecretChatMessage) domain.SecretChatMessage { m.Bytes = append([]byte(nil), m.Bytes...) if m.File != nil { f := *m.File m.File = &f } return m } func (s *EncryptedQueueStore) AppendEncryptedMessage(_ context.Context, msg domain.SecretChatMessage) (domain.SecretChatMessage, bool, error) { s.mu.Lock() defer s.mu.Unlock() key := emqDedupKey{msg.ReceiverAuthKeyID, msg.ChatID, msg.RandomID} if qts, ok := s.dedup[key]; ok { for _, m := range s.byDevice[msg.ReceiverAuthKeyID] { if m.Qts == qts { return cloneSecretMessage(m), true, nil } } } s.reserved[msg.ReceiverAuthKeyID]++ msg.Qts = s.reserved[msg.ReceiverAuthKeyID] stored := cloneSecretMessage(msg) s.byDevice[msg.ReceiverAuthKeyID] = append(s.byDevice[msg.ReceiverAuthKeyID], stored) s.dedup[key] = msg.Qts return cloneSecretMessage(stored), false, nil } func (s *EncryptedQueueStore) ListEncryptedMessagesSince(_ context.Context, receiverAuthKeyID int64, sinceQts, limit int) ([]domain.SecretChatMessage, error) { s.mu.Lock() defer s.mu.Unlock() if limit <= 0 { limit = 1000 } var out []domain.SecretChatMessage for _, m := range s.byDevice[receiverAuthKeyID] { if m.Qts > sinceQts { out = append(out, cloneSecretMessage(m)) if len(out) >= limit { break } } } return out, nil } func (s *EncryptedQueueStore) ReservedQts(_ context.Context, receiverAuthKeyID int64) (int, error) { s.mu.Lock() defer s.mu.Unlock() return s.reserved[receiverAuthKeyID], nil } func (s *EncryptedQueueStore) AckEncryptedMessages(_ context.Context, receiverAuthKeyID int64, maxQts int) error { s.mu.Lock() defer s.mu.Unlock() if maxQts > s.confirmed[receiverAuthKeyID] { s.confirmed[receiverAuthKeyID] = maxQts } return nil } func (s *EncryptedQueueStore) AppendStateEvent(_ context.Context, ev domain.EncryptedStateEvent) (int64, error) { s.mu.Lock() defer s.mu.Unlock() s.nextEventID++ ev.ID = s.nextEventID s.stateEvents = append(s.stateEvents, ev) return ev.ID, nil } func (s *EncryptedQueueStore) ListUndeliveredStateEvents(_ context.Context, targetUserID, deviceAuthKeyID int64, limit int) ([]domain.EncryptedStateEvent, error) { s.mu.Lock() defer s.mu.Unlock() if limit <= 0 { limit = 1000 } var out []domain.EncryptedStateEvent for _, ev := range s.stateEvents { if ev.TargetUserID != targetUserID { continue } if ev.TargetAuthKeyID != 0 && ev.TargetAuthKeyID != deviceAuthKeyID { continue } if s.delivered[ev.ID][deviceAuthKeyID] { continue } out = append(out, ev) if len(out) >= limit { break } } return out, nil } func (s *EncryptedQueueStore) MarkStateEventsDelivered(_ context.Context, deviceAuthKeyID int64, eventIDs []int64) error { s.mu.Lock() defer s.mu.Unlock() for _, id := range eventIDs { if s.delivered[id] == nil { s.delivered[id] = make(map[int64]bool) } s.delivered[id][deviceAuthKeyID] = true } return nil } func (s *EncryptedQueueStore) PutEncryptedFile(_ context.Context, _ int64, ref domain.EncryptedFileRef) error { s.mu.Lock() defer s.mu.Unlock() if s.files == nil { s.files = make(map[int64]domain.EncryptedFileRef) } s.files[ref.ID] = ref return nil } func (s *EncryptedQueueStore) GetEncryptedFile(_ context.Context, id, accessHash int64) (domain.EncryptedFileRef, bool, error) { s.mu.Lock() defer s.mu.Unlock() ref, ok := s.files[id] if !ok || ref.AccessHash != accessHash { return domain.EncryptedFileRef{}, false, nil } return ref, true, nil }