Ctrl K
ComunidadGuías…

internal/mtprotoedge/conn_mgmt_fixes_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package mtprotoedge

import (
	"context"
	"testing"
	"time"

	"go.uber.org/zap/zaptest"

	"github.com/iamxvbaba/td/proto"
	"github.com/iamxvbaba/td/tg"
	"github.com/iamxvbaba/td/tlprofile"
)

// TestPushSkipsConnReboundToOtherUser locks in the fix for the cross-account
// delivery window: pushToUserWithSender rechecks c.userID outside the lock
// before sending. It simulates the window where a connection is concurrently
// rebound between "collect conns" and "send" (the atomic userID changed but the
// byUser index is not yet updated), verifying that an update belonging to userA
// is not delivered to a connection that has changed hands to userB.
func TestPushSkipsConnReboundToOtherUser(t *testing.T) {
	sm := NewSessionManager(zaptest.NewLogger(t))
	const userA, userB = int64(100), int64(200)
	mk := func(sid int64, authKey byte) *Conn {
		c := &Conn{
			sessionID:       sid,
			authKeyID:       [8]byte{authKey},
			outbound:        make(chan *outboundOp, 4),
			outboundControl: make(chan *outboundOp, 4),
			outboundStop:    make(chan struct{}),
		}
		c.userID.Store(userA)
		c.userIDResolved.Store(true)
		c.receivesUpdates.Store(true)
		if err := c.FreezeLayerProfile(tlprofile.ProfileCanonical); err != nil {
			t.Fatal(err)
		}
		sm.Register(c)
		return c
	}
	stale := mk(1, 1) // registered as userA then rebound to userB
	live := mk(2, 2)  // always userA

	// Change the atomic userID directly without touching the byUser index:
	// reproduce the rebound window between lock release and send.
	stale.userID.Store(userB)

	// Use the best-effort push: it shares pushToUserWithSender (including the
	// userID recheck) with the normal push, but only enqueues without waiting for
	// op.done, so we can observe "delivered vs skipped" without starting an
	// outbound actor (ops go into the buffered c.outbound).
	if _, err := sm.PushToUserExceptSessionBestEffort(context.Background(), userA, 0, proto.MessageFromServer, &tg.UpdatesTooLong{}, time.Second); err != nil {
		t.Fatalf("push: %v", err)
	}
	if n := len(stale.outbound); n != 0 {
		t.Fatalf("rebound conn received %d ops, want 0 (must skip cross-account delivery)", n)
	}
	if n := len(live.outbound); n != 1 {
		t.Fatalf("live conn received %d ops, want 1", n)
	}
}

// TestRegisterEvictsAtSessionCap locks in the per-auth_key session count cap:
// registering a new session beyond maxSessionsPerAuthKey evicts an existing
// session, preventing an adversarial client from blowing up the index with a
// flood of session_ids.
func TestRegisterEvictsAtSessionCap(t *testing.T) {
	sm := NewSessionManager(zaptest.NewLogger(t))
	authKey := [8]byte{7}
	for i := 1; i <= maxSessionsPerAuthKey+5; i++ {
		// Naked Conn (no outbound/rpc channels): on eviction Close() is a safe no-op.
		sm.Register(&Conn{sessionID: int64(i), authKeyID: authKey})
	}
	sm.mu.RLock()
	perKey := len(sm.byAuthKey[authKey])
	total := len(sm.bySession)
	sm.mu.RUnlock()
	if perKey != maxSessionsPerAuthKey {
		t.Fatalf("sessions for auth key = %d, want cap %d", perKey, maxSessionsPerAuthKey)
	}
	if total != maxSessionsPerAuthKey {
		t.Fatalf("total online = %d, want %d", total, maxSessionsPerAuthKey)
	}
}