fix for calls
This commit is contained in:
parent
406a8805d8
commit
46afb97396
10 changed files with 151 additions and 101 deletions
52
internal/mtprotoedge/phone_stop_ringing_exclude_test.go
Normal file
52
internal/mtprotoedge/phone_stop_ringing_exclude_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package mtprotoedge
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestShouldExcludeDeviceMatchesAllSessionsOfDevice guards the phone-call
|
||||
// "stop ringing" fix: the accepting device (identified by its perm/business
|
||||
// auth key) must be excluded across ALL its connections, not just the one
|
||||
// session that carried the accept — otherwise the stop-ringing phoneCallDiscarded
|
||||
// leaks onto the device's other connections and kills the call it just accepted
|
||||
// (the "B answers → instantly Failed to connect" asymmetry).
|
||||
func TestShouldExcludeDeviceMatchesAllSessionsOfDevice(t *testing.T) {
|
||||
device := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
other := [8]byte{9, 9, 9, 9, 9, 9, 9, 9}
|
||||
|
||||
// Two connections of the SAME device but different raw keys / sessions —
|
||||
// exactly the OwpenGram multi-connection (dc 1..5 → one server) shape.
|
||||
connA := &Conn{authKeyID: [8]byte{0xA}, sessionID: 111}
|
||||
connA.SetBusinessAuthKeyID(device)
|
||||
connB := &Conn{authKeyID: [8]byte{0xB}, sessionID: 222}
|
||||
connB.SetBusinessAuthKeyID(device)
|
||||
// A connection of a DIFFERENT device (the real "other device" that should
|
||||
// still receive the stop-ringing).
|
||||
connOther := &Conn{authKeyID: [8]byte{0xC}, sessionID: 333}
|
||||
connOther.SetBusinessAuthKeyID(other)
|
||||
|
||||
if !shouldExcludeDevice(connA, device) {
|
||||
t.Fatal("accepting device's connection A must be excluded")
|
||||
}
|
||||
if !shouldExcludeDevice(connB, device) {
|
||||
t.Fatal("accepting device's connection B (other session) must ALSO be excluded")
|
||||
}
|
||||
if shouldExcludeDevice(connOther, device) {
|
||||
t.Fatal("a different device must NOT be excluded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldExcludeDeviceZeroKeyExcludesNothing(t *testing.T) {
|
||||
c := &Conn{authKeyID: [8]byte{0xA}, sessionID: 111}
|
||||
c.SetBusinessAuthKeyID([8]byte{1, 2, 3})
|
||||
if shouldExcludeDevice(c, [8]byte{}) {
|
||||
t.Fatal("zero business auth key must exclude nothing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldExcludeDeviceUnresolvedBusinessKeyNotExcluded(t *testing.T) {
|
||||
// A connection whose business auth key isn't resolved yet must not be
|
||||
// matched (can't prove it's the accepting device).
|
||||
c := &Conn{authKeyID: [8]byte{0xA}, sessionID: 111}
|
||||
if shouldExcludeDevice(c, [8]byte{1, 2, 3, 4, 5, 6, 7, 8}) {
|
||||
t.Fatal("connection with unresolved business auth key must not be excluded")
|
||||
}
|
||||
}
|
||||
|
|
@ -201,7 +201,7 @@ func (m *SessionManager) PushToUserExceptSession(ctx context.Context, userID, ex
|
|||
}
|
||||
|
||||
func (m *SessionManager) PushToUserExceptSessionBestEffort(ctx context.Context, userID, excludeSessionID int64, t proto.MessageType, msg tg.UpdatesClass, timeout time.Duration) (int, error) {
|
||||
return m.pushToUserBestEffort(ctx, userID, nil, excludeSessionID, t, msg, timeout)
|
||||
return m.pushToUserBestEffort(ctx, userID, nil, excludeSessionID, [8]byte{}, t, msg, timeout)
|
||||
}
|
||||
|
||||
func (m *SessionManager) Online() int {
|
||||
|
|
|
|||
|
|
@ -1476,7 +1476,7 @@ func (m *SessionManager) pushToBusinessAuthKey(ctx context.Context, userID int64
|
|||
|
||||
func (m *SessionManager) pushToUser(ctx context.Context, userID int64, excludeAuthKeyID *[8]byte, excludeSessionID int64, t proto.MessageType, msg tg.UpdatesClass) (int, error) {
|
||||
getUpdates := onceLayerUpdatesFanout(ctx, msg)
|
||||
return m.pushToUserWithSender(ctx, userID, excludeAuthKeyID, excludeSessionID, t, getUpdates, true, func(c *Conn) error {
|
||||
return m.pushToUserWithSender(ctx, userID, excludeAuthKeyID, excludeSessionID, [8]byte{}, t, getUpdates, true, func(c *Conn) error {
|
||||
if c.outbound == nil || c.outboundControl == nil {
|
||||
return ErrConnClosed
|
||||
}
|
||||
|
|
@ -1499,7 +1499,7 @@ func (m *SessionManager) pushToUser(ctx context.Context, userID int64, excludeAu
|
|||
// 「durable 兜底」丢弃。走 best-effort 发送,不阻塞调用方。
|
||||
func (m *SessionManager) PushToUserTransientExceptAuthKeySession(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg tg.UpdatesClass, timeout time.Duration) (int, error) {
|
||||
getUpdates := onceLayerUpdatesFanout(ctx, msg)
|
||||
return m.pushToUserWithSender(ctx, userID, &excludeAuthKeyID, excludeSessionID, t, getUpdates, false, func(c *Conn) error {
|
||||
return m.pushToUserWithSender(ctx, userID, &excludeAuthKeyID, excludeSessionID, [8]byte{}, t, getUpdates, false, func(c *Conn) error {
|
||||
if c.outbound == nil || c.outboundControl == nil {
|
||||
return ErrConnClosed
|
||||
}
|
||||
|
|
@ -1516,10 +1516,38 @@ func (m *SessionManager) PushToUserTransientExceptAuthKeySession(ctx context.Con
|
|||
}
|
||||
|
||||
func (m *SessionManager) PushToUserExceptAuthKeySessionBestEffort(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg tg.UpdatesClass, timeout time.Duration) (int, error) {
|
||||
return m.pushToUserBestEffort(ctx, userID, &excludeAuthKeyID, excludeSessionID, t, msg, timeout)
|
||||
return m.pushToUserBestEffort(ctx, userID, &excludeAuthKeyID, excludeSessionID, [8]byte{}, t, msg, timeout)
|
||||
}
|
||||
|
||||
func (m *SessionManager) pushToUserBestEffort(ctx context.Context, userID int64, excludeAuthKeyID *[8]byte, excludeSessionID int64, t proto.MessageType, msg tg.UpdatesClass, timeout time.Duration) (int, error) {
|
||||
// PushToUserExceptBusinessAuthKey fans msg out to every ready connection of
|
||||
// userID EXCEPT those belonging to the device identified by
|
||||
// excludeBusinessAuthKeyID (perm/business auth key) — i.e. it excludes the
|
||||
// whole accepting DEVICE, all of its connections/sessions, not just the one
|
||||
// session that carried the request. Used for phone-call "stop ringing": see
|
||||
// shouldExcludeDevice. Falls back to durable (non-best-effort) fan-out when no
|
||||
// outbound push timeout is configured.
|
||||
func (m *SessionManager) PushToUserExceptBusinessAuthKey(ctx context.Context, userID int64, excludeBusinessAuthKeyID [8]byte, t proto.MessageType, msg tg.UpdatesClass, timeout time.Duration) (int, error) {
|
||||
if timeout > 0 {
|
||||
return m.pushToUserBestEffort(ctx, userID, nil, 0, excludeBusinessAuthKeyID, t, msg, timeout)
|
||||
}
|
||||
getUpdates := onceLayerUpdatesFanout(ctx, msg)
|
||||
return m.pushToUserWithSender(ctx, userID, nil, 0, excludeBusinessAuthKeyID, t, getUpdates, true, func(c *Conn) error {
|
||||
if c.outbound == nil || c.outboundControl == nil {
|
||||
return ErrConnClosed
|
||||
}
|
||||
updates, err := getUpdates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := updates.prepareForConn(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SendEncoded(ctx, t, encoded)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *SessionManager) pushToUserBestEffort(ctx context.Context, userID int64, excludeAuthKeyID *[8]byte, excludeSessionID int64, excludeBusinessAuthKeyID [8]byte, t proto.MessageType, msg tg.UpdatesClass, timeout time.Duration) (int, error) {
|
||||
if ctx != nil && ctx.Err() != nil {
|
||||
return 0, ctx.Err()
|
||||
}
|
||||
|
|
@ -1545,7 +1573,7 @@ func (m *SessionManager) pushToUserBestEffort(ctx context.Context, userID int64,
|
|||
defer cancel()
|
||||
}
|
||||
getUpdates := onceLayerUpdatesFanout(sendCtx, msg)
|
||||
return m.pushToUserWithSender(ctx, userID, excludeAuthKeyID, excludeSessionID, t, getUpdates, true, func(c *Conn) error {
|
||||
return m.pushToUserWithSender(ctx, userID, excludeAuthKeyID, excludeSessionID, excludeBusinessAuthKeyID, t, getUpdates, true, func(c *Conn) error {
|
||||
if c.outbound == nil || c.outboundControl == nil {
|
||||
return ErrConnClosed
|
||||
}
|
||||
|
|
@ -1592,7 +1620,7 @@ func onceLayerUpdatesFanout(ctx context.Context, msg tg.UpdatesClass) func() (*l
|
|||
}
|
||||
}
|
||||
|
||||
func (m *SessionManager) pushToUserWithSender(ctx context.Context, userID int64, excludeAuthKeyID *[8]byte, excludeSessionID int64, t proto.MessageType, getUpdates func() (*layerUpdatesFanout, error), queueWhenNotReady bool, send func(*Conn) error) (int, error) {
|
||||
func (m *SessionManager) pushToUserWithSender(ctx context.Context, userID int64, excludeAuthKeyID *[8]byte, excludeSessionID int64, excludeBusinessAuthKeyID [8]byte, t proto.MessageType, getUpdates func() (*layerUpdatesFanout, error), queueWhenNotReady bool, send func(*Conn) error) (int, error) {
|
||||
// push fan-out 是连接层最热路径之一:debug 日志的字段构造(含 auth_key hex 格式化)
|
||||
// 在关闭 debug 时也会求值,先查级别一次、按需记日志。
|
||||
debug := m.log.Core().Enabled(zapcore.DebugLevel)
|
||||
|
|
@ -1608,7 +1636,7 @@ func (m *SessionManager) pushToUserWithSender(ctx context.Context, userID int64,
|
|||
skipped := 0
|
||||
needQueue := false
|
||||
for _, c := range m.byUser[userID] {
|
||||
if shouldExcludeSession(c, excludeAuthKeyID, excludeSessionID) {
|
||||
if shouldExcludeSession(c, excludeAuthKeyID, excludeSessionID) || shouldExcludeDevice(c, excludeBusinessAuthKeyID) {
|
||||
excluded++
|
||||
continue
|
||||
}
|
||||
|
|
@ -2478,6 +2506,22 @@ func shouldExcludeSession(c *Conn, excludeAuthKeyID *[8]byte, excludeSessionID i
|
|||
return c.authKeyID == *excludeAuthKeyID
|
||||
}
|
||||
|
||||
// shouldExcludeDevice reports whether c belongs to the device identified by
|
||||
// excludeBusinessAuthKeyID (the perm/business auth key). Unlike the per-session
|
||||
// exclusion above, this matches EVERY connection of that device regardless of
|
||||
// session_id or raw temp-key. Required for signals like phone-call "stop
|
||||
// ringing": a device that aliases dc 1..5 onto one server (the OwpenGram
|
||||
// client) holds several connections, so excluding only the one session that
|
||||
// carried the accept would let the stop/discard leak onto the device's other
|
||||
// connections and kill the call it just accepted.
|
||||
func shouldExcludeDevice(c *Conn, excludeBusinessAuthKeyID [8]byte) bool {
|
||||
if excludeBusinessAuthKeyID == ([8]byte{}) {
|
||||
return false
|
||||
}
|
||||
id, resolved := c.BusinessAuthKeyID()
|
||||
return resolved && id == excludeBusinessAuthKeyID
|
||||
}
|
||||
|
||||
func sessionKeyLog(id [8]byte) string {
|
||||
return fmt.Sprintf("%x", id)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue