perf: sync protocol and core hardening updates

This commit is contained in:
A 2026-07-11 19:48:26 +08:00
parent 152fed3b87
commit 4390ebf5a9
283 changed files with 29231 additions and 2295 deletions

View file

@ -1242,6 +1242,25 @@ func (s *Service) SendMessage(ctx context.Context, userID int64, req domain.Send
if req.UserID != userID {
return domain.SendChannelMessageResult{}, domain.ErrChannelInvalid
}
if req.RandomID != 0 && !req.IdempotencyPreflighted {
fingerprint, err := store.ChannelSendFingerprint(req)
if err != nil {
return domain.SendChannelMessageResult{}, err
}
req.IdempotencyFingerprint = fingerprint
if replayStore, ok := s.channels.(store.ChannelSendReplayStore); ok {
replay, found, err := replayStore.LookupChannelSendReplay(ctx, domain.ChannelSendReplayRequest{
ChannelID: req.ChannelID,
SenderUserID: req.UserID,
RandomID: req.RandomID,
IdempotencyFingerprint: fingerprint,
})
if err != nil || found {
return replay, err
}
req.IdempotencyPreflighted = true
}
}
if err := s.ensureCanSend(ctx, req.UserID); err != nil {
return domain.SendChannelMessageResult{}, err
}
@ -1253,6 +1272,25 @@ func (s *Service) SendMessage(ctx context.Context, userID int64, req domain.Send
return s.channels.SendChannelMessage(ctx, req)
}
// LookupChannelSendReplay reads a regular-channel or monoforum receipt without current
// membership/send-gate checks. The authenticated caller remains bound to SenderUserID.
func (s *Service) LookupChannelSendReplay(ctx context.Context, userID int64, req domain.ChannelSendReplayRequest) (domain.SendChannelMessageResult, bool, error) {
if s == nil || s.channels == nil || userID == 0 {
return domain.SendChannelMessageResult{}, false, nil
}
if req.SenderUserID == 0 {
req.SenderUserID = userID
}
if req.SenderUserID != userID || req.ChannelID == 0 || req.RandomID == 0 {
return domain.SendChannelMessageResult{}, false, domain.ErrChannelInvalid
}
replayStore, ok := s.channels.(store.ChannelSendReplayStore)
if !ok {
return domain.SendChannelMessageResult{}, false, nil
}
return replayStore.LookupChannelSendReplay(ctx, req)
}
func (s *Service) ensureCanSend(ctx context.Context, userID int64) error {
if s == nil || s.sendGate == nil || userID == 0 {
return nil
@ -1754,6 +1792,26 @@ func (s *Service) SendMonoforumMessage(ctx context.Context, req domain.SendMonof
if s == nil || s.channels == nil || req.MonoforumID == 0 || req.SenderUserID == 0 || req.SavedPeer.ID == 0 {
return domain.SendChannelMessageResult{}, domain.ErrChannelInvalid
}
if req.RandomID != 0 && !req.IdempotencyPreflighted {
fingerprint, err := store.MonoforumSendFingerprint(req)
if err != nil {
return domain.SendChannelMessageResult{}, err
}
req.IdempotencyFingerprint = fingerprint
if replayStore, ok := s.channels.(store.ChannelSendReplayStore); ok {
replay, found, err := replayStore.LookupChannelSendReplay(ctx, domain.ChannelSendReplayRequest{
ChannelID: req.MonoforumID,
SenderUserID: req.SenderUserID,
SavedPeer: req.SavedPeer,
RandomID: req.RandomID,
IdempotencyFingerprint: fingerprint,
})
if err != nil || found {
return replay, err
}
req.IdempotencyPreflighted = true
}
}
if err := s.ensureCanSend(ctx, req.SenderUserID); err != nil {
return domain.SendChannelMessageResult{}, err
}
@ -2021,6 +2079,26 @@ func (s *Service) DirtyActiveChannelsForUser(ctx context.Context, userID int64,
return s.channels.ListDirtyActiveChannelsForUser(ctx, userID, sinceDate, afterChannelID, limit)
}
// MaxChannelPts returns the durable channel watermark used by the fan-out saturation recovery
// sweep. It intentionally performs no viewer access check: target visibility is derived from the
// process-local joined-membership index, while getChannelDifference performs authoritative access
// validation when a client consumes the nudge.
func (s *Service) MaxChannelPts(ctx context.Context, channelID int64) (int, error) {
if s == nil || s.channels == nil || channelID == 0 {
return 0, domain.ErrChannelInvalid
}
return s.channels.MaxChannelPts(ctx, channelID)
}
// MaxChannelPtsBatch reloads a bounded recovery page in one store call. Missing ids are omitted:
// they represent channels deleted after the process-local online-membership snapshot was taken.
func (s *Service) MaxChannelPtsBatch(ctx context.Context, channelIDs []int64) (map[int64]int, error) {
if s == nil || s.channels == nil {
return nil, domain.ErrChannelInvalid
}
return s.channels.MaxChannelPtsBatch(ctx, channelIDs)
}
// ActiveMemberIDs returns a bounded list for transient online fanout such as typing.
func (s *Service) ActiveMemberIDs(ctx context.Context, userID, channelID int64, limit int) ([]int64, error) {
if s == nil || s.channels == nil || userID == 0 || channelID == 0 {

View file

@ -30,6 +30,46 @@ func TestServiceSendMessageHonorsSendPermissionGate(t *testing.T) {
}
}
func TestServiceChannelReplayPrecedesCurrentSendPermissionGate(t *testing.T) {
ctx := context.Background()
channels := memory.NewChannelStore()
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
CreatorUserID: 1001,
Title: "replay gate",
Megagroup: true,
Date: 1_700_000_000,
})
if err != nil {
t.Fatalf("CreateChannel: %v", err)
}
req := domain.SendChannelMessageRequest{
ChannelID: created.Channel.ID,
RandomID: 92,
Message: "committed before restriction",
Date: 1_700_000_001,
}
allowed := NewService(channels)
first, err := allowed.SendMessage(ctx, 1001, req)
if err != nil {
t.Fatalf("first SendMessage: %v", err)
}
denied := NewService(channels, WithSendPermissionChecker(channelDenySendChecker{}))
req.Date++
replay, err := denied.SendMessage(ctx, 1001, req)
if err != nil {
t.Fatalf("replay through denied gate: %v", err)
}
if !replay.Duplicate || replay.Message.ID != first.Message.ID {
t.Fatalf("replay = %+v, want committed duplicate %d", replay, first.Message.ID)
}
req.Message = "different intent"
if _, err := denied.SendMessage(ctx, 1001, req); !errors.Is(err, domain.ErrMessageRandomIDDuplicate) {
t.Fatalf("conflicting replay err=%v, want ErrMessageRandomIDDuplicate before send gate", err)
}
}
func TestServiceSendMonoforumMessageHonorsSendPermissionGate(t *testing.T) {
ctx := context.Background()
svc := NewService(memory.NewChannelStore(), WithSendPermissionChecker(channelDenySendChecker{}))
@ -44,6 +84,52 @@ func TestServiceSendMonoforumMessageHonorsSendPermissionGate(t *testing.T) {
}
}
func TestServiceMonoforumReplayPrecedesCurrentSendPermissionGate(t *testing.T) {
ctx := context.Background()
channels := memory.NewChannelStore()
parent, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
CreatorUserID: 1001,
Title: "direct messages",
Broadcast: true,
Date: 1_700_000_010,
})
if err != nil {
t.Fatalf("CreateChannel: %v", err)
}
enabled, err := channels.SetPaidMessagesPrice(ctx, 1001, parent.Channel.ID, 0, true)
if err != nil {
t.Fatalf("SetPaidMessagesPrice: %v", err)
}
req := domain.SendMonoforumMessageRequest{
MonoforumID: enabled.Channel.LinkedMonoforumID,
SenderUserID: 1002,
SavedPeer: domain.Peer{Type: domain.PeerTypeUser, ID: 1002},
RandomID: 93,
Message: "committed direct message",
Date: 1_700_000_011,
}
allowed := NewService(channels)
first, err := allowed.SendMonoforumMessage(ctx, req)
if err != nil {
t.Fatalf("first SendMonoforumMessage: %v", err)
}
denied := NewService(channels, WithSendPermissionChecker(channelDenySendChecker{}))
req.Date++
replay, err := denied.SendMonoforumMessage(ctx, req)
if err != nil {
t.Fatalf("monoforum replay through denied gate: %v", err)
}
if !replay.Duplicate || replay.Message.ID != first.Message.ID {
t.Fatalf("monoforum replay = %+v, want committed duplicate %d", replay, first.Message.ID)
}
req.Message = "different intent"
if _, err := denied.SendMonoforumMessage(ctx, req); !errors.Is(err, domain.ErrMessageRandomIDDuplicate) {
t.Fatalf("conflicting monoforum replay err=%v, want ErrMessageRandomIDDuplicate before send gate", err)
}
}
type channelDenySendChecker struct{}
func (channelDenySendChecker) CanSendMessages(context.Context, int64) error {
@ -813,7 +899,8 @@ func TestCreateChatCreatesMegagroupWithChannelPts(t *testing.T) {
duplicate, err := service.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
ChannelID: created.Channel.ID,
RandomID: 99,
Message: "hello again",
Message: "hello",
ViaBotID: 1003,
Date: 12,
})
if err != nil {
@ -2032,12 +2119,12 @@ func TestChannelEditDeleteAndLocalClearUseChannelPts(t *testing.T) {
if edited.Event.Type != domain.ChannelUpdateEditMessage || edited.Event.Pts != 4 || edited.Event.PtsCount != 1 {
t.Fatalf("edit event = %+v, want channel edit pts=4 count=1", edited.Event)
}
duplicate, err := service.SendMessage(ctx, 1002, domain.SendChannelMessageRequest{ChannelID: created.Channel.ID, RandomID: 2, Message: "two retry", Date: 13})
duplicate, err := service.SendMessage(ctx, 1002, domain.SendChannelMessageRequest{ChannelID: created.Channel.ID, RandomID: 2, Message: "two", Date: 13})
if err != nil {
t.Fatalf("duplicate SendMessage after edit: %v", err)
}
if !duplicate.Duplicate || duplicate.Event.Type != domain.ChannelUpdateNewMessage || duplicate.Message.Body != "two" || duplicate.Event.Message.Body != "two" {
t.Fatalf("duplicate after edit = %+v, want original new-message snapshot", duplicate)
if !duplicate.Duplicate || duplicate.Event.Type != domain.ChannelUpdateNewMessage || duplicate.Message.Body != "two edited" || duplicate.Event.Message.Body != "two edited" {
t.Fatalf("duplicate after edit = %+v, want current message in new-message replay", duplicate)
}
deleted, err := service.DeleteMessages(ctx, 1001, domain.DeleteChannelMessagesRequest{