feat: sync multilayer td integration

This commit is contained in:
A 2026-07-15 13:32:06 +08:00
parent 20a310f6ca
commit 766c5db992
491 changed files with 26235 additions and 35340 deletions

View file

@ -11,12 +11,12 @@ import (
"testing"
"time"
"github.com/gotd/td/bin"
"github.com/gotd/td/crypto"
"github.com/gotd/td/mt"
"github.com/gotd/td/proto"
"github.com/gotd/td/tg"
"github.com/gotd/td/transport"
"github.com/iamxvbaba/td/bin"
"github.com/iamxvbaba/td/crypto"
"github.com/iamxvbaba/td/mt"
"github.com/iamxvbaba/td/proto"
"github.com/iamxvbaba/td/tg"
"github.com/iamxvbaba/td/transport"
)
type failAfterTransport struct {
@ -28,6 +28,44 @@ type failAfterTransport struct {
last []byte
}
func TestRPCResultReplayAttemptHooksArePhysicalConnectionLocal(t *testing.T) {
const reqMsgID = int64(771)
base := &encodedOutboundMessage{
body: make([]byte, 12),
typeID: proto.ResultTypeID,
reqMsgID: reqMsgID,
delivery: newRPCResultDelivery(reqMsgID),
}
var logical, firstAttempt, secondAttempt atomic.Int32
base.setDeliveryHook(func() { logical.Add(1) })
first, err := cloneRPCResultForRequest(base, reqMsgID, false)
if err != nil {
t.Fatal(err)
}
second, err := cloneRPCResultForRequest(base, reqMsgID, false)
if err != nil {
t.Fatal(err)
}
first.setAttemptDeliveryHook(func() { firstAttempt.Add(1) })
second.setAttemptDeliveryHook(func() { secondAttempt.Add(1) })
first.markDelivered()
deadline := time.Now().Add(time.Second)
for (logical.Load() != 1 || firstAttempt.Load() != 1) && time.Now().Before(deadline) {
time.Sleep(time.Millisecond)
}
if logical.Load() != 1 || firstAttempt.Load() != 1 || secondAttempt.Load() != 0 {
t.Fatalf("first delivery hooks = logical:%d first:%d second:%d", logical.Load(), firstAttempt.Load(), secondAttempt.Load())
}
second.markDelivered()
deadline = time.Now().Add(time.Second)
for secondAttempt.Load() != 1 && time.Now().Before(deadline) {
time.Sleep(time.Millisecond)
}
if logical.Load() != 1 || firstAttempt.Load() != 1 || secondAttempt.Load() != 1 {
t.Fatalf("second delivery hooks = logical:%d first:%d second:%d", logical.Load(), firstAttempt.Load(), secondAttempt.Load())
}
}
type blockingOutboundTransport struct {
started chan struct{}
release chan struct{}
@ -166,7 +204,7 @@ func TestEncodedControlFramesUseIndependentBudgetForQueuedAndPendingLifetime(t *
defer cancel()
// One content frame fills the ordinary body budget and remains pending.
if err := c.Send(ctx, proto.MessageFromServer, &tg.UpdatesTooLong{}); err != nil {
if err := c.SendEncoded(ctx, proto.MessageFromServer, exactTestUpdatesTooLong(t, c)); err != nil {
t.Fatalf("fill body budget: %v", err)
}
first, err := crypto.NewClientCipher(rand.Reader).DecryptFromBuffer(c.key, &bin.Buffer{Buf: tr.lastFrame()})
@ -297,7 +335,7 @@ func TestOutboundScratchAdmissionUsesWriteTimeoutWithoutClosingHealthyConnection
c.writeTimeout = 25 * time.Millisecond
start := time.Now()
err = c.Send(context.Background(), proto.MessageFromServer, &tg.UpdatesTooLong{})
err = c.SendEncoded(context.Background(), proto.MessageFromServer, exactTestUpdatesTooLong(t, c))
elapsed := time.Since(start)
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("scratch admission err = %v, want deadline exceeded", err)
@ -319,7 +357,7 @@ func TestOutboundScratchAdmissionUsesWriteTimeoutWithoutClosingHealthyConnection
pool.release(blocker)
c.writeTimeout = time.Second
if err := c.Send(context.Background(), proto.MessageFromServer, &tg.UpdatesTooLong{}); err != nil {
if err := c.SendEncoded(context.Background(), proto.MessageFromServer, exactTestUpdatesTooLong(t, c)); err != nil {
t.Fatalf("send after scratch capacity returned: %v", err)
}
if got := tr.sends.Load(); got != 1 {
@ -387,6 +425,7 @@ func newOutboundTestConn(t *testing.T, tr transport.Conn, budget *outboundTracke
sessionID: 456,
outboundTrackedBudget: budget,
}
legacyCanonicalTestConn(t, c)
c.startOutbound()
t.Cleanup(c.Close)
return c
@ -506,6 +545,7 @@ func TestOutboundActorSerializesConcurrentSends(t *testing.T) {
clientMsgID := proto.NewMessageIDGen(time.Now)
sendEncrypted(t, conn, cipher, auth, clientMsgID.New(proto.MessageFromClient), &mt.PingRequest{PingID: 1})
collectReplies(t, conn, cipher, auth.AuthKey, mt.MsgsAckTypeID)
freezeActiveTestSessionProfile(t, srv.Conns(), auth.AuthKey.ID, auth.SessionID, tg.LayerProfileCanonical)
srv.Conns().SetReceivesUpdates(auth.SessionID, true)
const sends = 64
@ -556,7 +596,7 @@ func TestOutboundWriteErrorTerminallyClosesWithoutActorDeadlock(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := c.Send(ctx, proto.MessageFromServer, &tg.UpdatesTooLong{}); err == nil {
if err := c.SendEncoded(ctx, proto.MessageFromServer, exactTestUpdatesTooLong(t, c)); err == nil {
t.Fatal("Send unexpectedly succeeded")
}
select {
@ -567,7 +607,7 @@ func TestOutboundWriteErrorTerminallyClosesWithoutActorDeadlock(t *testing.T) {
if got := tr.closes.Load(); got != 1 {
t.Fatalf("transport closes = %d, want 1", got)
}
if err := c.Send(ctx, proto.MessageFromServer, &tg.UpdatesTooLong{}); !errors.Is(err, ErrConnClosed) {
if err := c.SendEncoded(ctx, proto.MessageFromServer, exactTestUpdatesTooLong(t, c)); !errors.Is(err, ErrConnClosed) {
t.Fatalf("second Send err = %v, want ErrConnClosed", err)
}
if got := tr.sends.Load(); got != 1 {
@ -581,7 +621,7 @@ func TestOutboundResendWriteErrorTerminallyCloses(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := c.Send(ctx, proto.MessageFromServer, &tg.UpdatesTooLong{}); err != nil {
if err := c.SendEncoded(ctx, proto.MessageFromServer, exactTestUpdatesTooLong(t, c)); err != nil {
t.Fatalf("initial Send: %v", err)
}
data, err := crypto.NewClientCipher(rand.Reader).DecryptFromBuffer(c.key, &bin.Buffer{Buf: tr.lastFrame()})
@ -608,7 +648,7 @@ func TestOutboundTrackedBudgetSharedAcrossConnections(t *testing.T) {
tr2 := &failAfterTransport{}
c1 := newOutboundTestConn(t, tr1, budget)
c2 := newOutboundTestConn(t, tr2, budget)
body := &encodedOutboundMessage{body: make([]byte, 8), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c1, make([]byte, 8))
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
@ -687,7 +727,7 @@ func TestOutboundGlobalBudgetIncludesQueuedBodies(t *testing.T) {
budget := newOutboundTrackedBudget(24)
tr := newBlockingOutboundTransport()
c := newOutboundTestConn(t, tr, budget)
body := &encodedOutboundMessage{body: make([]byte, 8), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c, make([]byte, 8))
if err := c.SendBestEffortEncoded(context.Background(), proto.MessageFromServer, body, 0); err != nil {
t.Fatalf("enqueue writing body: %v", err)
@ -733,7 +773,7 @@ func TestOutboundOversizedBodyRejectedBeforeEncryption(t *testing.T) {
budget := newOutboundTrackedBudget(64 << 20)
tr := &failAfterTransport{}
c := newOutboundTestConn(t, tr, budget)
body := &encodedOutboundMessage{body: make([]byte, maxOutboundBodyBytes+1), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c, make([]byte, maxOutboundBodyBytes+1))
err := c.SendEncoded(context.Background(), proto.MessageFromServer, body)
if !errors.Is(err, ErrOutboundMessageTooLarge) {
t.Fatalf("oversized outbound err = %v, want ErrOutboundMessageTooLarge", err)
@ -749,7 +789,7 @@ func TestOutboundOversizedBodyRejectedBeforeEncryption(t *testing.T) {
func TestOutboundCloseRaceDrainsEveryProducerReservation(t *testing.T) {
budget := newOutboundTrackedBudget(1 << 20)
c := newOutboundTestConn(t, &failAfterTransport{}, budget)
body := &encodedOutboundMessage{body: make([]byte, 128), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c, make([]byte, 128))
start := make(chan struct{})
var wg sync.WaitGroup
for i := 0; i < 128; i++ {
@ -775,7 +815,7 @@ func TestOutboundTrackedBudgetAckAndCloseReturnExactly(t *testing.T) {
c := newOutboundTestConn(t, tr, budget)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
body := &encodedOutboundMessage{body: make([]byte, 12), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c, make([]byte, 12))
if err := c.SendEncoded(ctx, proto.MessageFromServer, body); err != nil {
t.Fatalf("send: %v", err)
}
@ -801,7 +841,7 @@ func TestOutboundTrackedBudgetAckAndCloseReturnExactly(t *testing.T) {
c := newOutboundTestConn(t, &failAfterTransport{}, budget)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
body := &encodedOutboundMessage{body: make([]byte, 12), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c, make([]byte, 12))
if err := c.SendEncoded(ctx, proto.MessageFromServer, body); err != nil {
t.Fatalf("send: %v", err)
}
@ -822,7 +862,7 @@ func TestOutboundTrackedBudgetWriteFailureReturnsReservation(t *testing.T) {
c := newOutboundTestConn(t, tr, budget)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
body := &encodedOutboundMessage{body: make([]byte, 12), typeID: tg.UpdatesTooLongTypeID}
body := exactTestUpdatesEncoded(t, c, make([]byte, 12))
if err := c.SendEncoded(ctx, proto.MessageFromServer, body); err == nil {
t.Fatal("send unexpectedly succeeded")
}
@ -995,6 +1035,7 @@ func TestOutboundResendAndAckState(t *testing.T) {
clientMsgID := proto.NewMessageIDGen(time.Now)
sendEncrypted(t, conn, cipher, auth, clientMsgID.New(proto.MessageFromClient), &mt.PingRequest{PingID: 1})
collectReplies(t, conn, cipher, auth.AuthKey, mt.MsgsAckTypeID)
freezeActiveTestSessionProfile(t, srv.Conns(), auth.AuthKey.ID, auth.SessionID, tg.LayerProfileCanonical)
srv.Conns().SetReceivesUpdates(auth.SessionID, true)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)