perf: sync protocol and core hardening updates
This commit is contained in:
parent
152fed3b87
commit
4390ebf5a9
283 changed files with 29231 additions and 2295 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue