fix: sync call ringing device scope

This commit is contained in:
A 2026-07-21 17:25:53 +08:00
parent 27ec9dce33
commit bf72c246b6
3 changed files with 103 additions and 12 deletions

View file

@ -25,6 +25,7 @@ import (
// phonePushRecord 记录一次定向推送(目标用户、被排除的 session、载荷
type phonePushRecord struct {
userID int64
rawAuthKeyID [8]byte
targetSession int64
excludeSession int64
msg bin.Encoder
@ -32,8 +33,9 @@ type phonePushRecord struct {
// phoneCaptureSessions 是带完整推送日志的 SessionBinder fakecaptureSessions 只留最后一条)。
type phoneCaptureSessions struct {
mu sync.Mutex
log []phonePushRecord
mu sync.Mutex
log []phonePushRecord
pushErr error
}
func (s *phoneCaptureSessions) BindAuthKeyForSession([8]byte, int64, [8]byte) {}
@ -47,11 +49,11 @@ func (s *phoneCaptureSessions) UserIDResolvedForAuthKey([8]byte, int64) (int64,
func (s *phoneCaptureSessions) UnbindAuthKey([8]byte) int { return 0 }
func (s *phoneCaptureSessions) SetReceivesUpdatesForAuthKey([8]byte, int64, bool) {}
func (s *phoneCaptureSessions) PushToSessionForAuthKey(_ context.Context, _ [8]byte, sessionID int64, _ proto.MessageType, msg tg.UpdatesClass) error {
func (s *phoneCaptureSessions) PushToSessionForAuthKey(_ context.Context, rawAuthKeyID [8]byte, sessionID int64, _ proto.MessageType, msg tg.UpdatesClass) error {
s.mu.Lock()
defer s.mu.Unlock()
s.log = append(s.log, phonePushRecord{targetSession: sessionID, msg: msg})
return nil
s.log = append(s.log, phonePushRecord{rawAuthKeyID: rawAuthKeyID, targetSession: sessionID, msg: msg})
return s.pushErr
}
func (s *phoneCaptureSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, _ [8]byte, excludeSessionID int64, _ proto.MessageType, msg tg.UpdatesClass) (int, error) {
@ -73,6 +75,12 @@ func (s *phoneCaptureSessions) reset() {
s.log = nil
}
func (s *phoneCaptureSessions) setPushError(err error) {
s.mu.Lock()
defer s.mu.Unlock()
s.pushErr = err
}
// stubPrivacy 只为 CanSee 服务;其余接口方法不在通话链路使用。
type stubPrivacy struct {
deny map[domain.PrivacyKey]bool
@ -104,8 +112,15 @@ type phoneFixture struct {
}
const (
phoneCallerSession = int64(101)
phoneCalleeSession = int64(202)
phoneCallerSession = int64(101)
phoneCalleeSession = int64(202)
phoneOtherCalleeSession = int64(303)
)
var (
phoneCallerRawAuthKey = [8]byte{0x11, 0x01}
phoneCalleeRawAuthKey = [8]byte{0x22, 0x02}
phoneOtherCalleeRawAuthKey = [8]byte{0x33, 0x03}
)
func newPhoneFixture(t *testing.T, privacy PrivacyService) *phoneFixture {
@ -133,11 +148,16 @@ func newPhoneFixture(t *testing.T, privacy PrivacyService) *phoneFixture {
}
func (f *phoneFixture) callerCtx() context.Context {
return WithSessionID(WithUserID(f.ctx, f.caller.ID), phoneCallerSession)
return WithSessionID(WithRawAuthKeyID(WithUserID(f.ctx, f.caller.ID), phoneCallerRawAuthKey), phoneCallerSession)
}
func (f *phoneFixture) calleeCtx() context.Context {
return WithSessionID(WithUserID(f.ctx, f.callee.ID), phoneCalleeSession)
return WithSessionID(WithRawAuthKeyID(WithUserID(f.ctx, f.callee.ID), phoneCalleeRawAuthKey), phoneCalleeSession)
}
func (f *phoneFixture) otherCalleeCtx() context.Context {
return WithSessionID(WithRawAuthKeyID(WithUserID(f.ctx, f.callee.ID), phoneOtherCalleeRawAuthKey), phoneOtherCalleeSession)
}
func phoneTestProtocol() tg.PhoneCallProtocol {
@ -226,8 +246,8 @@ func TestPhoneCallRPCHappyPath(t *testing.T) {
t.Fatalf("receivedCall = %v err=%v", ok, err)
}
pushes = f.sessions.records()
if len(pushes) != 1 || pushes[0].userID != f.caller.ID {
t.Fatalf("receivedCall pushes = %+v, want one to caller", pushes)
if len(pushes) != 1 || pushes[0].rawAuthKeyID != phoneCallerRawAuthKey || pushes[0].targetSession != phoneCallerSession {
t.Fatalf("receivedCall pushes = %+v, want caller device %x/%d", pushes, phoneCallerRawAuthKey, phoneCallerSession)
}
ringing, ok := phoneCallPayload(t, pushes[0]).(*tg.PhoneCallWaiting)
if !ok || ringing.ReceiveDate == 0 {
@ -236,6 +256,15 @@ func TestPhoneCallRPCHappyPath(t *testing.T) {
}
f.sessions.reset()
// 其它被叫设备晚到的 receivedCall 幂等成功,但不得再次推 ringing。
if ok, err := f.router.onPhoneReceivedCall(f.otherCalleeCtx(), tg.InputPhoneCall{ID: callID, AccessHash: accessHash}); err != nil || !ok {
t.Fatalf("duplicate receivedCall = %v err=%v", ok, err)
}
if pushes := f.sessions.records(); len(pushes) != 0 {
t.Fatalf("duplicate receivedCall pushes = %+v, want none", pushes)
}
f.sessions.reset()
// --- acceptCall被叫赢家设备 ---
acceptRes, err := f.router.onPhoneAcceptCall(f.calleeCtx(), &tg.PhoneAcceptCallRequest{
Peer: tg.InputPhoneCall{ID: callID, AccessHash: accessHash},
@ -388,6 +417,35 @@ func TestPhoneCallRPCHappyPath(t *testing.T) {
}
}
func TestPhoneReceivedCallDevicePushFailureDoesNotBroadcast(t *testing.T) {
f := newPhoneFixture(t, stubPrivacy{})
_, gaHash, _ := phoneTestKeys()
res, err := f.router.onPhoneRequestCall(f.callerCtx(), &tg.PhoneRequestCallRequest{
UserID: &tg.InputUser{UserID: f.callee.ID, AccessHash: f.callee.AccessHash},
RandomID: 43,
GAHash: gaHash,
Protocol: phoneTestProtocol(),
})
if err != nil {
t.Fatalf("requestCall: %v", err)
}
waiting := res.PhoneCall.(*tg.PhoneCallWaiting)
f.sessions.reset()
f.sessions.setPushError(errors.New("caller session gone"))
if ok, err := f.router.onPhoneReceivedCall(f.calleeCtx(), tg.InputPhoneCall{ID: waiting.ID, AccessHash: waiting.AccessHash}); err != nil || !ok {
t.Fatalf("receivedCall = %v err=%v", ok, err)
}
pushes := f.sessions.records()
if len(pushes) != 1 || pushes[0].rawAuthKeyID != phoneCallerRawAuthKey || pushes[0].targetSession != phoneCallerSession {
t.Fatalf("receivedCall pushes = %+v, want one failed attempt to caller device", pushes)
}
if pushes[0].userID != 0 {
t.Fatalf("receivedCall failure fell back to user broadcast: %+v", pushes)
}
}
func TestPhoneCallRPCValidation(t *testing.T) {
f := newPhoneFixture(t, stubPrivacy{})
_, gaHash, gb := phoneTestKeys()