1009 lines
38 KiB
Go
1009 lines
38 KiB
Go
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/iamxvbaba/td/bin"
|
|
"github.com/iamxvbaba/td/clock"
|
|
"github.com/iamxvbaba/td/tg"
|
|
"github.com/iamxvbaba/td/tgerr"
|
|
"go.uber.org/zap/zaptest"
|
|
|
|
appfiles "telesrv/internal/app/files"
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/postresponse"
|
|
)
|
|
|
|
func TestLayerAdmissionAndroidPrivateOverlayUsesExactProfile(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
|
|
// DrKLO's private messages.forwardMessages constructor has the canonical
|
|
// body layout for this empty flags/vectors fixture, but is not an official
|
|
// Layer 227 method id.
|
|
var body bin.Buffer
|
|
body.PutID(0x41d41ade)
|
|
body.PutInt(0)
|
|
body.PutID(0x7f3b18ea) // inputPeerEmpty
|
|
body.PutVectorHeader(0)
|
|
body.PutVectorHeader(0)
|
|
body.PutID(0x7f3b18ea) // inputPeerEmpty
|
|
|
|
admitted, err := r.AdmitLayer(tg.LayerProfile227, &body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body.Len() != 0 {
|
|
t.Fatalf("private request left %d bytes", body.Len())
|
|
}
|
|
call := admitted.Call()
|
|
if call.Profile() != tg.LayerProfile227 || call.Method() != tg.LayerSemanticMethodMessagesForwardMessages {
|
|
t.Fatalf("private admission = profile:%d method:%#x", call.Profile(), call.Method())
|
|
}
|
|
if want, ok := tg.LayerWireID(tg.LayerProfile227, call.Method()); !ok || call.WireID() != want {
|
|
t.Fatalf("private admission wire id = %#x, want %#x (ok=%v)", call.WireID(), want, ok)
|
|
}
|
|
}
|
|
|
|
func TestLayerAdmissionOfficialProfileOwnsOverlappingAndroidID(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
request := &tg.ContactsSearchRequest{Q: "exact", Limit: 20}
|
|
body := encodeExactLayerRPC(t, tg.LayerProfile225, request)
|
|
admitted, err := r.AdmitLayer(tg.LayerProfile225, &body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body.Len() != 0 || admitted.Call().Method() != tg.LayerSemanticMethodContactsSearch {
|
|
t.Fatalf("official overlap = remaining:%d method:%#x", body.Len(), admitted.Call().Method())
|
|
}
|
|
}
|
|
|
|
func TestLayerAdmissionAndroidOverlayFailureDoesNotConsumeInput(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
bounded := bin.Buffer{Buf: androidPrivateForwardMessagesWire()}
|
|
boundedOriginal := bounded.Copy()
|
|
if _, err := r.AdmitLayer(tg.LayerProfile227, &bounded, tg.LayerDecodeLimits{MaxWireBytes: len(boundedOriginal) - 1}); err == nil {
|
|
t.Fatal("oversized Android-private request was admitted")
|
|
}
|
|
if !bytes.Equal(bounded.Raw(), boundedOriginal) {
|
|
t.Fatal("wire-limited Android-private request consumed or mutated caller input")
|
|
}
|
|
|
|
malformed := bin.Buffer{}
|
|
malformed.PutID(0x41d41ade)
|
|
original := malformed.Copy()
|
|
if _, err := r.AdmitLayer(tg.LayerProfile227, &malformed, tg.LayerDecodeLimits{}); err == nil {
|
|
t.Fatal("malformed Android-private request was admitted")
|
|
}
|
|
if string(malformed.Raw()) != string(original) {
|
|
t.Fatal("failed Android-private admission consumed or mutated caller input")
|
|
}
|
|
|
|
unknown := bin.Buffer{}
|
|
unknown.PutID(0xdeadbeef)
|
|
original = unknown.Copy()
|
|
if _, err := r.AdmitLayer(tg.LayerProfile227, &unknown, tg.LayerDecodeLimits{}); !errors.Is(err, tg.ErrLayerUnknownRPCMethod) {
|
|
t.Fatalf("official unknown error = %v", err)
|
|
}
|
|
if string(unknown.Raw()) != string(original) {
|
|
t.Fatal("unknown official request consumed or mutated caller input")
|
|
}
|
|
}
|
|
|
|
func androidPrivateForwardMessagesWire() []byte {
|
|
var body bin.Buffer
|
|
body.PutID(0x41d41ade)
|
|
body.PutInt(0)
|
|
body.PutID(0x7f3b18ea) // inputPeerEmpty
|
|
body.PutVectorHeader(0)
|
|
body.PutVectorHeader(0)
|
|
body.PutID(0x7f3b18ea) // inputPeerEmpty
|
|
return body.Copy()
|
|
}
|
|
|
|
func replaceTerminalRPC(t *testing.T, encoded *bin.Buffer, terminal []byte) {
|
|
t.Helper()
|
|
var placeholder bin.Buffer
|
|
if err := (&tg.HelpGetConfigRequest{}).Encode(&placeholder); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.HasSuffix(encoded.Raw(), placeholder.Raw()) {
|
|
t.Fatalf("wrapper does not end in placeholder RPC: %x", encoded.Raw())
|
|
}
|
|
encoded.Buf = append(encoded.Buf[:encoded.Len()-placeholder.Len()], terminal...)
|
|
}
|
|
|
|
func TestLayerAdmissionAndroidPrivateInnermostAcrossWrappers(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
private := androidPrivateForwardMessagesWire()
|
|
|
|
var withoutUpdates bin.Buffer
|
|
withoutUpdates.PutID(tg.InvokeWithoutUpdatesRequestTypeID)
|
|
withoutUpdates.Put(private)
|
|
|
|
var afterMsg bin.Buffer
|
|
afterMsg.PutID(tg.InvokeAfterMsgRequestTypeID)
|
|
afterMsg.PutLong(11)
|
|
afterMsg.Put(private)
|
|
|
|
var afterMsgs bin.Buffer
|
|
afterMsgs.PutID(tg.InvokeAfterMsgsRequestTypeID)
|
|
afterMsgs.PutVectorHeader(2)
|
|
afterMsgs.PutLong(11)
|
|
afterMsgs.PutLong(12)
|
|
afterMsgs.Put(private)
|
|
|
|
init := &tg.InitConnectionRequest{
|
|
APIID: 6,
|
|
DeviceModel: "Android",
|
|
SystemVersion: "test",
|
|
AppVersion: "private-layer",
|
|
SystemLangCode: "en",
|
|
LangPack: "android",
|
|
LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
}
|
|
var initWire bin.Buffer
|
|
if err := init.Encode(&initWire); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
replaceTerminalRPC(t, &initWire, private)
|
|
var unprofiledInit bin.Buffer
|
|
unprofiledInit.PutID(tg.InvokeWithLayerRequestTypeID)
|
|
unprofiledInit.PutInt(int(tg.LayerProfile227))
|
|
unprofiledInit.Put(initWire.Raw())
|
|
|
|
var unprofiledBare bin.Buffer
|
|
unprofiledBare.PutID(tg.InvokeWithLayerRequestTypeID)
|
|
unprofiledBare.PutInt(int(tg.LayerProfile227))
|
|
unprofiledBare.Put(private)
|
|
|
|
tests := []struct {
|
|
name string
|
|
body []byte
|
|
unprofiled bool
|
|
wrappers int
|
|
}{
|
|
{"naked", private, false, 0},
|
|
{"invokeWithoutUpdates", withoutUpdates.Copy(), false, 1},
|
|
{"invokeAfterMsg", afterMsg.Copy(), false, 1},
|
|
{"invokeAfterMsgs", afterMsgs.Copy(), false, 1},
|
|
{"invokeWithLayer", unprofiledBare.Copy(), true, 1},
|
|
{"invokeWithLayer_initConnection", unprofiledInit.Copy(), true, 2},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
body := bin.Buffer{Buf: append([]byte(nil), tc.body...)}
|
|
var (
|
|
admitted tg.LayerRequest
|
|
err error
|
|
)
|
|
if tc.unprofiled {
|
|
admitted, err = r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
} else {
|
|
admitted, err = r.AdmitLayer(tg.LayerProfile227, &body, tg.LayerDecodeLimits{})
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body.Len() != 0 || admitted.WrapperCount() != tc.wrappers || admitted.Call().Method() != tg.LayerSemanticMethodMessagesForwardMessages {
|
|
t.Fatalf("admission = remaining:%d wrappers:%d method:%#x", body.Len(), admitted.WrapperCount(), admitted.Call().Method())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLayerAdmissionAndroidPrivateFieldPolicyBeforeTypedMaterialization(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
request := &tg.MessagesCreateChatRequest{
|
|
Users: repeatLayerPreflightValue[tg.InputUserClass](201, &tg.InputUserEmpty{}),
|
|
Title: "private-field-policy",
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// DrKLO's private createChat constructor is body-identical to canonical.
|
|
binary.LittleEndian.PutUint32(body.Buf[:4], 0x0034a818)
|
|
original := body.Copy()
|
|
if _, err := r.AdmitLayer(tg.LayerProfile227, &body, tg.LayerDecodeLimits{MaxVectorElements: 8 << 10}); !tgerr.Is(err, "LIMIT_INVALID") {
|
|
t.Fatalf("private createChat admission err = %v, want LIMIT_INVALID", err)
|
|
}
|
|
if !bytes.Equal(body.Raw(), original) {
|
|
t.Fatal("field-rejected private createChat consumed or mutated input")
|
|
}
|
|
}
|
|
|
|
func TestLayerDispatchExactProfilesShareOneHandler(t *testing.T) {
|
|
for _, profile := range []tg.LayerProfile{tg.LayerProfile225, tg.LayerProfile227} {
|
|
t.Run(fmt.Sprintf("layer_%d", profile), func(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
request := &tg.InvokeWithLayerRequest{
|
|
Layer: int(profile),
|
|
Query: &tg.InitConnectionRequest{
|
|
APIID: 123,
|
|
DeviceModel: "Desktop",
|
|
SystemVersion: "Windows",
|
|
AppVersion: "test",
|
|
SystemLangCode: "en",
|
|
LangPack: "tdesktop",
|
|
LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body.Len() != 0 || admitted.Call().Profile() != profile {
|
|
t.Fatalf("admission = profile:%d remaining:%d", admitted.Call().Profile(), body.Len())
|
|
}
|
|
result, method, err := r.DispatchAdmitted(context.Background(), [8]byte{1}, 10, 0, 0, admitted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if method != "help.getConfig" || result.Prepared().Call().Identity() != admitted.Call().Identity() {
|
|
t.Fatalf("result = method:%q call:%#v", method, result.Prepared().Call())
|
|
}
|
|
var exact bin.Buffer
|
|
if err := result.Encode(&exact); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decoded, err := tg.DecodeLayer(profile, tg.LayerConstructorConfigType(), &exact)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if exact.Len() != 0 || decoded.ThisDC != 2 {
|
|
t.Fatalf("decoded config = dc:%d remaining:%d", decoded.ThisDC, exact.Len())
|
|
}
|
|
frozen, err := result.Freeze()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prepared, err := admitted.Call().PrepareFrozenResult(frozen)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var replay bin.Buffer
|
|
if err := prepared.Encode(admitted.Call(), &replay); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(replay.Raw()) != string(tgBufferBytes(t, result)) {
|
|
t.Fatalf("prepared replay differs from direct result")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLayerDispatchUnprofiledInvariantDoesNotPublishRepresentativeLayer(t *testing.T) {
|
|
rawAuthKeyID := [8]byte{1}
|
|
const sessionID = int64(10)
|
|
auth := &captureAuthService{
|
|
authKeyClientInfos: map[[8]byte]domain.AuthKeyClientInfo{
|
|
rawAuthKeyID: {Layer: 225, DeviceModel: "stale-device"},
|
|
},
|
|
}
|
|
sessions := &layerCaptureSessions{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth, Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
request := &tg.AuthBindTempAuthKeyRequest{
|
|
PermAuthKeyID: 1,
|
|
Nonce: 2,
|
|
ExpiresAt: 3,
|
|
EncryptedMessage: []byte("bind"),
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := admitted.ProfileEvidence(); ok {
|
|
t.Fatal("unprofiled invariant request exposed its canonical codec representative as client evidence")
|
|
}
|
|
if !admitted.Call().WireInvariant() {
|
|
t.Fatal("auth.bindTempAuthKey did not carry generated wire-invariant proof")
|
|
}
|
|
result, method, err := r.DispatchAdmitted(WithLayer(context.Background(), 226), rawAuthKeyID, sessionID, 0, 0, admitted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if method != "auth.bindTempAuthKey" || result == nil || !result.WireInvariant() {
|
|
t.Fatalf("invariant dispatch = method:%q result:%T invariant:%v", method, result, result != nil && result.WireInvariant())
|
|
}
|
|
if auth.bindTempCalls != 1 || auth.bindTempLayer != 0 {
|
|
t.Fatalf("unprofiled bind handler context = calls:%d layer:%d, want one call with unknown layer", auth.bindTempCalls, auth.bindTempLayer)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("stale auth metadata reached exact layer binder: %+v", calls)
|
|
}
|
|
if layer, ok := r.NegotiatedSessionLayer(rawAuthKeyID, sessionID); ok || layer != 0 {
|
|
t.Fatalf("unprofiled invariant published session layer = (%d,%v)", layer, ok)
|
|
}
|
|
|
|
var profiledBody bin.Buffer
|
|
if err := (&tg.InvokeWithLayerRequest{Layer: 227, Query: request}).Encode(&profiledBody); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
profiled, err := r.AdmitUnprofiled(&profiledBody, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if profile, ok := profiled.ProfileEvidence(); !ok || profile != tg.LayerProfile227 {
|
|
t.Fatalf("profiled bind evidence = (%d,%v), want layer 227", profile, ok)
|
|
}
|
|
freezeAndPublishLayer(t, r, rawAuthKeyID, sessionID, 100, 1, 227)
|
|
if _, _, err := r.DispatchAdmitted(context.Background(), rawAuthKeyID, sessionID, 100, 1, profiled); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if auth.bindTempCalls != 2 || auth.bindTempLayer != 227 {
|
|
t.Fatalf("profiled bind handler context = calls:%d layer:%d, want layer 227", auth.bindTempCalls, auth.bindTempLayer)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("Router duplicated edge exact-profile binder calls = %+v", calls)
|
|
}
|
|
if layer, ok := r.NegotiatedSessionLayer(rawAuthKeyID, sessionID); !ok || layer != 227 {
|
|
t.Fatalf("authoritative wrapper session layer = (%d,%v), want (227,true)", layer, ok)
|
|
}
|
|
}
|
|
|
|
func TestLayerDispatchInheritedDefaultIsEffectiveWithoutBecomingExplicitEvidence(t *testing.T) {
|
|
rawAuthKeyID := [8]byte{0x73}
|
|
const sessionID = int64(73)
|
|
auth := &captureAuthService{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
request := &tg.AuthBindTempAuthKeyRequest{PermAuthKeyID: businessAuthKeyInt64(rawAuthKeyID)}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitDefaultLayer(tg.LayerProfile225, &body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if profile, ok := admitted.EffectiveProfile(); !ok || profile != tg.LayerProfile225 {
|
|
t.Fatalf("effective profile = (%d,%v), want (225,true)", profile, ok)
|
|
}
|
|
if profile, ok := admitted.ProfileEvidence(); ok || profile != tg.LayerProfile(0) {
|
|
t.Fatalf("explicit evidence = (%d,%v), want (0,false)", profile, ok)
|
|
}
|
|
result, _, err := r.DispatchAdmitted(WithLayer(context.Background(), 227), rawAuthKeyID, sessionID, 0, 0, admitted)
|
|
if err != nil || result == nil {
|
|
t.Fatalf("dispatch inherited default = (%T,%v)", result, err)
|
|
}
|
|
if auth.bindTempLayer != 225 {
|
|
t.Fatalf("handler layer = %d, want immutable effective 225", auth.bindTempLayer)
|
|
}
|
|
if layer, ok := r.NegotiatedSessionLayer(rawAuthKeyID, sessionID); ok || layer != 0 {
|
|
t.Fatalf("inherited default became explicit registry state = (%d,%v)", layer, ok)
|
|
}
|
|
}
|
|
|
|
func TestLayerDispatchProfiledBareIgnoresStaleMetadataLayer(t *testing.T) {
|
|
rawAuthKeyID := [8]byte{2, 2, 0}
|
|
const sessionID = int64(225)
|
|
auth := &captureAuthService{
|
|
authKeyClientInfos: map[[8]byte]domain.AuthKeyClientInfo{
|
|
rawAuthKeyID: {Layer: 227, DeviceModel: "newer-device-metadata"},
|
|
},
|
|
}
|
|
sessions := &layerCaptureSessions{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth, Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
request := &tg.AuthBindTempAuthKeyRequest{
|
|
PermAuthKeyID: 1,
|
|
Nonce: 2,
|
|
ExpiresAt: 3,
|
|
EncryptedMessage: []byte("bind"),
|
|
}
|
|
body := encodeExactLayerRPC(t, tg.LayerProfile225, request)
|
|
admitted, err := r.AdmitLayer(tg.LayerProfile225, &body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := r.DispatchAdmitted(WithLayer(context.Background(), 226), rawAuthKeyID, sessionID, 0, 0, admitted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if auth.bindTempCalls != 1 || auth.bindTempLayer != 225 {
|
|
t.Fatalf("profiled bare handler context = calls:%d layer:%d, want one call at 225", auth.bindTempCalls, auth.bindTempLayer)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("profiled bare request rebound stale metadata before exact evidence: %+v", calls)
|
|
}
|
|
}
|
|
|
|
func TestLayerDispatchUnprofiledInvariantWithholdsUpdatesReadinessUntilEvidence(t *testing.T) {
|
|
const (
|
|
userID = int64(1000000227)
|
|
sessionID = int64(220227)
|
|
)
|
|
rawAuthKeyID := [8]byte{0x22, 0x02, 0x27}
|
|
auth := &captureAuthService{
|
|
userID: userID,
|
|
authKeyClientInfos: map[[8]byte]domain.AuthKeyClientInfo{
|
|
rawAuthKeyID: {Layer: 225, DeviceModel: "stale-device"},
|
|
},
|
|
}
|
|
sessions := &layerCaptureSessions{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth, Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
// updates.getState stages readiness inside its handler in addition to the
|
|
// common bare-RPC path, so this locks both no-evidence gates.
|
|
request := &tg.UpdatesGetStateRequest{}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, evidence := admitted.ProfileEvidence(); evidence || !admitted.Call().WireInvariant() {
|
|
t.Fatalf("unprofiled getState evidence/invariant = %v/%v", evidence, admitted.Call().WireInvariant())
|
|
}
|
|
|
|
dispatchCtx := postresponse.WithCallbacks(context.Background())
|
|
result, method, err := r.DispatchAdmitted(dispatchCtx, rawAuthKeyID, sessionID, 10, 1, admitted)
|
|
if err != nil || result == nil || method != "updates.getState" {
|
|
t.Fatalf("unprofiled getState dispatch = method:%q result:%T err:%v", method, result, err)
|
|
}
|
|
postresponse.Run(dispatchCtx)
|
|
if got := sessions.snapshot(); got.receives || got.receivesCalls != 0 {
|
|
t.Fatalf("unprofiled invariant activated updates after delivery: %+v", got)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("unprofiled invariant bound stale layer: %+v", calls)
|
|
}
|
|
|
|
restore, err := r.PrepareAdmittedReplay(context.Background(), rawAuthKeyID, sessionID, 10, 1, admitted)
|
|
if err != nil || restore == nil {
|
|
t.Fatalf("prepare unprofiled invariant replay = callback:%v err:%v", restore != nil, err)
|
|
}
|
|
if err := restore(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := sessions.snapshot(); got.receives || got.receivesCalls != 0 {
|
|
t.Fatalf("unprofiled invariant replay activated updates: %+v", got)
|
|
}
|
|
|
|
var wrapped bin.Buffer
|
|
if err := (&tg.InvokeWithLayerRequest{Layer: 227, Query: request}).Encode(&wrapped); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
profiled, err := r.AdmitUnprofiled(&wrapped, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
freezeAndPublishLayer(t, r, rawAuthKeyID, sessionID, 20, 2, 227)
|
|
profiledCtx := postresponse.WithCallbacks(context.Background())
|
|
if _, _, err := r.DispatchAdmitted(profiledCtx, rawAuthKeyID, sessionID, 20, 2, profiled); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
postresponse.Run(profiledCtx)
|
|
if got := sessions.snapshot(); !got.receives || got.receivesCalls != 1 {
|
|
t.Fatalf("authoritative profile did not activate updates: %+v", got)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("Router duplicated edge authoritative-profile bind: %+v", calls)
|
|
}
|
|
}
|
|
|
|
func TestLayerAdmissionPreflightRunsBeforeLargeVectorDecode(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
users := make([]tg.InputUserClass, 101)
|
|
for index := range users {
|
|
users[index] = &tg.InputUserEmpty{}
|
|
}
|
|
request := &tg.InvokeWithLayerRequest{
|
|
Layer: int(tg.LayerProfile225),
|
|
Query: &tg.UsersGetUsersRequest{ID: users},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{}); !tgerr.Is(err, "INPUT_REQUEST_TOO_LONG") {
|
|
t.Fatalf("admission err = %v, want INPUT_REQUEST_TOO_LONG", err)
|
|
}
|
|
}
|
|
|
|
func TestLayerAdmissionFieldPoliciesCoverEveryRoutableProfile(t *testing.T) {
|
|
type vectorCase struct {
|
|
name string
|
|
method tg.LayerSemanticID
|
|
max int
|
|
errorCode string
|
|
request func(int) bin.Object
|
|
}
|
|
cases := []vectorCase{
|
|
{"users.getUsers", tg.LayerSemanticMethodUsersGetUsers, 100, "INPUT_REQUEST_TOO_LONG", func(n int) bin.Object {
|
|
return &tg.UsersGetUsersRequest{ID: repeatLayerPreflightValue[tg.InputUserClass](n, &tg.InputUserEmpty{})}
|
|
}},
|
|
{"users.getRequirementsToContact", tg.LayerSemanticMethodUsersGetRequirementsToContact, maxRequirementsToContactUsers, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.UsersGetRequirementsToContactRequest{ID: repeatLayerPreflightValue[tg.InputUserClass](n, &tg.InputUserEmpty{})}
|
|
}},
|
|
{"contacts.importContacts", tg.LayerSemanticMethodContactsImportContacts, maxContactImportBatch, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.ContactsImportContactsRequest{Contacts: make([]tg.InputPhoneContact, n)}
|
|
}},
|
|
{"contacts.deleteContacts", tg.LayerSemanticMethodContactsDeleteContacts, maxContactDeleteBatch, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.ContactsDeleteContactsRequest{ID: repeatLayerPreflightValue[tg.InputUserClass](n, &tg.InputUserEmpty{})}
|
|
}},
|
|
{"contacts.editCloseFriends", tg.LayerSemanticMethodContactsEditCloseFriends, maxCloseFriendsCount, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.ContactsEditCloseFriendsRequest{ID: make([]int64, n)}
|
|
}},
|
|
{"contacts.setBlocked", tg.LayerSemanticMethodContactsSetBlocked, maxContactSetBlocked, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.ContactsSetBlockedRequest{ID: repeatLayerPreflightValue[tg.InputPeerClass](n, &tg.InputPeerEmpty{})}
|
|
}},
|
|
{"messages.getMessages", tg.LayerSemanticMethodMessagesGetMessages, maxGetMessagesIDs, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesGetMessagesRequest{ID: repeatLayerPreflightValue[tg.InputMessageClass](n, &tg.InputMessageID{})}
|
|
}},
|
|
{"messages.getChats", tg.LayerSemanticMethodMessagesGetChats, maxGetMessagesIDs, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesGetChatsRequest{ID: make([]int64, n)}
|
|
}},
|
|
{"messages.getPeerDialogs", tg.LayerSemanticMethodMessagesGetPeerDialogs, maxDialogInputPeers, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesGetPeerDialogsRequest{Peers: repeatLayerPreflightValue[tg.InputDialogPeerClass](n, &tg.InputDialogPeer{Peer: &tg.InputPeerEmpty{}})}
|
|
}},
|
|
{"messages.readMessageContents", tg.LayerSemanticMethodMessagesReadMessageContents, maxGetMessagesIDs, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesReadMessageContentsRequest{ID: make([]int, n)}
|
|
}},
|
|
{"messages.getCustomEmojiDocuments", tg.LayerSemanticMethodMessagesGetCustomEmojiDocuments, maxEmojiDocuments, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesGetCustomEmojiDocumentsRequest{DocumentID: make([]int64, n)}
|
|
}},
|
|
{"messages.deleteMessages", tg.LayerSemanticMethodMessagesDeleteMessages, domain.MaxDeleteMessageIDs, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesDeleteMessagesRequest{ID: make([]int, n)}
|
|
}},
|
|
{"messages.createChat", tg.LayerSemanticMethodMessagesCreateChat, 200, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.MessagesCreateChatRequest{Users: repeatLayerPreflightValue[tg.InputUserClass](n, &tg.InputUserEmpty{}), Title: "layer-policy"}
|
|
}},
|
|
{"channels.getChannels", tg.LayerSemanticMethodChannelsGetChannels, maxGetMessagesIDs, "LIMIT_INVALID", func(n int) bin.Object {
|
|
return &tg.ChannelsGetChannelsRequest{ID: repeatLayerPreflightValue[tg.InputChannelClass](n, &tg.InputChannelEmpty{})}
|
|
}},
|
|
}
|
|
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
limits := tg.LayerDecodeLimits{MaxVectorElements: 8 << 10}
|
|
for profile := tg.LayerProfile225; profile <= tg.LayerProfile227; profile++ {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
if _, available := tg.LayerWireID(profile, tc.method); !available {
|
|
continue
|
|
}
|
|
t.Run(fmt.Sprintf("layer_%d/%s/at_cap", profile, tc.name), func(t *testing.T) {
|
|
body := encodeExactLayerRPC(t, profile, tc.request(tc.max))
|
|
if _, err := r.AdmitLayer(profile, &body, limits); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body.Len() != 0 {
|
|
t.Fatalf("admitted request left %d bytes", body.Len())
|
|
}
|
|
})
|
|
t.Run(fmt.Sprintf("layer_%d/%s/over_cap", profile, tc.name), func(t *testing.T) {
|
|
body := encodeExactLayerRPC(t, profile, tc.request(tc.max+1))
|
|
original := body.Copy()
|
|
if _, err := r.AdmitLayer(profile, &body, limits); !tgerr.Is(err, tc.errorCode) {
|
|
t.Fatalf("admission err = %v, want %s", err, tc.errorCode)
|
|
}
|
|
if string(body.Raw()) != string(original) {
|
|
t.Fatal("rejected request consumed or mutated caller input")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLayerAdmissionUploadFieldsCoverEveryProfile(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
for profile := tg.LayerProfile225; profile <= tg.LayerProfile227; profile++ {
|
|
t.Run(fmt.Sprintf("layer_%d/saveFilePart", profile), func(t *testing.T) {
|
|
atCap := encodeExactLayerRPC(t, profile, &tg.UploadSaveFilePartRequest{Bytes: make([]byte, appfiles.MaxUploadPartBytes)})
|
|
if _, err := r.AdmitLayer(profile, &atCap, tg.LayerDecodeLimits{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
over := encodeExactLayerRPC(t, profile, &tg.UploadSaveFilePartRequest{Bytes: make([]byte, appfiles.MaxUploadPartBytes+1)})
|
|
original := over.Copy()
|
|
if _, err := r.AdmitLayer(profile, &over, tg.LayerDecodeLimits{}); !tgerr.Is(err, "FILE_PART_TOO_BIG") {
|
|
t.Fatalf("oversized part err = %v", err)
|
|
}
|
|
if string(over.Raw()) != string(original) {
|
|
t.Fatal("oversized part consumed input")
|
|
}
|
|
})
|
|
t.Run(fmt.Sprintf("layer_%d/saveBigFilePart", profile), func(t *testing.T) {
|
|
atCap := encodeExactLayerRPC(t, profile, &tg.UploadSaveBigFilePartRequest{FileTotalParts: appfiles.MaxUploadParts, Bytes: make([]byte, appfiles.MaxUploadPartBytes)})
|
|
if _, err := r.AdmitLayer(profile, &atCap, tg.LayerDecodeLimits{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, totalParts := range []int{0, -1, appfiles.MaxUploadParts + 1} {
|
|
body := encodeExactLayerRPC(t, profile, &tg.UploadSaveBigFilePartRequest{FileTotalParts: totalParts})
|
|
original := body.Copy()
|
|
if _, err := r.AdmitLayer(profile, &body, tg.LayerDecodeLimits{}); !tgerr.Is(err, "FILE_PART_INVALID") {
|
|
t.Fatalf("total parts %d err = %v", totalParts, err)
|
|
}
|
|
if string(body.Raw()) != string(original) {
|
|
t.Fatalf("invalid total parts %d consumed input", totalParts)
|
|
}
|
|
}
|
|
over := encodeExactLayerRPC(t, profile, &tg.UploadSaveBigFilePartRequest{FileTotalParts: 1, Bytes: make([]byte, appfiles.MaxUploadPartBytes+1)})
|
|
if _, err := r.AdmitLayer(profile, &over, tg.LayerDecodeLimits{}); !tgerr.Is(err, "FILE_PART_TOO_BIG") {
|
|
t.Fatalf("oversized big part err = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func repeatLayerPreflightValue[T any](n int, value T) []T {
|
|
result := make([]T, n)
|
|
for index := range result {
|
|
result[index] = value
|
|
}
|
|
return result
|
|
}
|
|
|
|
func encodeExactLayerRPC(t *testing.T, profile tg.LayerProfile, request bin.Object) bin.Buffer {
|
|
t.Helper()
|
|
outbound, err := tg.PrepareLayerOutboundCall(profile, request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var body bin.Buffer
|
|
if err := outbound.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return body
|
|
}
|
|
|
|
func TestLayerDispatchRejectsUnsupportedWrapperBeforeHandler(t *testing.T) {
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
request := &tg.InvokeWithLayerRequest{
|
|
Layer: int(tg.LayerProfile227),
|
|
Query: &tg.InvokeWithTakeoutRequest{
|
|
TakeoutID: 1,
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, method, err := r.DispatchAdmitted(context.Background(), [8]byte{1}, 10, 0, 0, admitted); method != "help.getConfig" || !tgerr.Is(err, "NOT_IMPLEMENTED") {
|
|
t.Fatalf("dispatch = method:%q err:%v", method, err)
|
|
}
|
|
}
|
|
|
|
func TestPrepareAdmittedReplayRestoresReadinessOnlyAfterDelivery(t *testing.T) {
|
|
const (
|
|
userID = int64(1000000901)
|
|
sessionID = int64(901)
|
|
)
|
|
authKeyID := [8]byte{9, 0, 1}
|
|
sessions := &captureSessions{userID: userID, userResolved: true}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
request := &tg.InvokeWithLayerRequest{
|
|
Layer: int(tg.LayerProfile225),
|
|
Query: &tg.InitConnectionRequest{
|
|
APIID: 123, DeviceModel: "Desktop", SystemVersion: "Windows", AppVersion: "test",
|
|
SystemLangCode: "en", LangPack: "tdesktop", LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
freezeAndPublishLayer(t, r, authKeyID, sessionID, 9010, 1, 225)
|
|
after, err := r.PrepareAdmittedReplay(context.Background(), authKeyID, sessionID, 9010, 1, admitted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if after == nil {
|
|
t.Fatal("successful naked replay has no delivery callback")
|
|
}
|
|
if got := sessions.snapshot(); got.receives || got.receivesCalls != 0 {
|
|
t.Fatalf("replay marked ready before physical delivery: %+v", got)
|
|
}
|
|
if layer, ok := r.NegotiatedSessionLayer(authKeyID, sessionID); !ok || layer != 225 {
|
|
t.Fatalf("admission-time protocol evidence = (%d,%v), want (225,true)", layer, ok)
|
|
}
|
|
if err := after(); err != nil {
|
|
t.Fatalf("restore delivered replay: %v", err)
|
|
}
|
|
if got := sessions.snapshot(); !got.receives || got.receivesCalls != 1 {
|
|
t.Fatalf("delivered replay readiness = %+v", got)
|
|
}
|
|
if layer, ok := r.NegotiatedSessionLayer(authKeyID, sessionID); !ok || layer != 225 {
|
|
t.Fatalf("delivered replay changed protocol evidence = (%d,%v), want (225,true)", layer, ok)
|
|
}
|
|
if err := after(); err != nil {
|
|
t.Fatalf("idempotent replay restore: %v", err)
|
|
}
|
|
if got := sessions.snapshot(); got.receivesCalls != 1 {
|
|
t.Fatalf("replay delivery callback was not idempotent: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestRequestBoundOnlyLayerRPCDoesNotPublishMetadataOrReadiness(t *testing.T) {
|
|
for _, mode := range []string{"dispatch", "replay"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
authKeyID := [8]byte{9, 0, 11, byte(len(mode))}
|
|
const sessionID = int64(911)
|
|
sessions := &captureSessions{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
request := &tg.InvokeWithLayerRequest{
|
|
Layer: int(tg.LayerProfile225),
|
|
Query: &tg.InitConnectionRequest{
|
|
APIID: 123, DeviceModel: "Stale Desktop", SystemVersion: "Windows", AppVersion: "stale",
|
|
SystemLangCode: "en", LangPack: "tdesktop", LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx := r.WithLayerRPCProfileEvidenceFresh(context.Background(), false)
|
|
switch mode {
|
|
case "dispatch":
|
|
if result, method, err := r.DispatchAdmitted(ctx, authKeyID, sessionID, 9011, 1, admitted); err != nil || result == nil || method != "help.getConfig" {
|
|
t.Fatalf("request-bound dispatch = (%T,%q,%v)", result, method, err)
|
|
}
|
|
case "replay":
|
|
after, err := r.PrepareAdmittedReplay(ctx, authKeyID, sessionID, 9011, 1, admitted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := after(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if got := sessions.snapshot(); got.receives || got.receivesCalls != 0 {
|
|
t.Fatalf("request-bound %s published readiness: %+v", mode, got)
|
|
}
|
|
r.clientInfoMu.RLock()
|
|
sessionInfo := r.clientInfo[clientInfoSessionKey{rawAuthKeyID: authKeyID, sessionID: sessionID}]
|
|
authInfo := r.authInfo[authKeyID]
|
|
r.clientInfoMu.RUnlock()
|
|
if sessionInfo.hasClientInfo || authInfo.hasClientInfo {
|
|
t.Fatalf("request-bound %s published init metadata: session=%+v auth=%+v", mode, sessionInfo, authInfo)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrepareAdmittedReplayDoesNotRollBackNewerExplicitLayerOrClientInfo(t *testing.T) {
|
|
authKeyID := [8]byte{9, 0, 2}
|
|
const sessionID = int64(902)
|
|
auth := &captureAuthService{authKeyClientInfos: make(map[[8]byte]domain.AuthKeyClientInfo)}
|
|
sessions := &layerCaptureSessions{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth, Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
oldRequest := &tg.InvokeWithLayerRequest{
|
|
Layer: int(tg.LayerProfile225),
|
|
Query: &tg.InitConnectionRequest{
|
|
APIID: 123, DeviceModel: "Old Desktop", SystemVersion: "Windows 10", AppVersion: "old",
|
|
SystemLangCode: "en", LangPack: "tdesktop", LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
},
|
|
}
|
|
var body bin.Buffer
|
|
if err := oldRequest.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
freezeAndPublishLayer(t, r, authKeyID, sessionID, 10, 1, 225)
|
|
after, err := r.PrepareAdmittedReplay(context.Background(), authKeyID, sessionID, 10, 1, admitted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
freezeAndPublishLayer(t, r, authKeyID, sessionID, 20, 2, 227)
|
|
newerCtx := WithAuthKeyID(WithSessionID(WithRawAuthKeyID(context.Background(), authKeyID), sessionID), authKeyID)
|
|
r.rememberClientInfoAt(newerCtx, ClientInfo{
|
|
APIID: 123, DeviceModel: "New Desktop", SystemVersion: "Windows 11", AppVersion: "new", Type: ClientTypeTDesktop,
|
|
}, 2)
|
|
if err := after(); err != nil {
|
|
t.Fatalf("restore old replay after correction: %v", err)
|
|
}
|
|
|
|
if layer, ok := r.NegotiatedSessionLayer(authKeyID, sessionID); !ok || layer != 227 {
|
|
t.Fatalf("exact session rolled back = (%d,%v), want (227,true)", layer, ok)
|
|
}
|
|
if got := auth.authKeyClientInfos[authKeyID].Layer; got != 227 {
|
|
t.Fatalf("durable default rolled back = %d, want 227", got)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("Router replay touched edge-owned exact binder: %+v", calls)
|
|
}
|
|
info, ok, _ := r.clientSessionInfo(newerCtx)
|
|
if !ok || !info.hasClientInfo || info.clientInfo.DeviceModel != "New Desktop" || info.clientInfo.AppVersion != "new" {
|
|
t.Fatalf("newer client info was overwritten by replay: %+v ok=%v", info, ok)
|
|
}
|
|
}
|
|
|
|
func TestDelayedExplicitDispatchCannotRollBackAdmissionTimeLayerOrInitMetadata(t *testing.T) {
|
|
authKeyID := [8]byte{9, 0, 3}
|
|
const sessionID = int64(903)
|
|
auth := &captureAuthService{authKeyClientInfos: make(map[[8]byte]domain.AuthKeyClientInfo)}
|
|
sessions := &layerCaptureSessions{}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth, Sessions: sessions},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
admit := func(layer int, device, version string) tg.LayerRequest {
|
|
t.Helper()
|
|
request := &tg.InvokeWithLayerRequest{
|
|
Layer: layer,
|
|
Query: &tg.InitConnectionRequest{
|
|
APIID: 123, DeviceModel: device, SystemVersion: "Windows", AppVersion: version,
|
|
SystemLangCode: "en", LangPack: "tdesktop", LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitUnprofiled(&body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return admitted
|
|
}
|
|
old := admit(225, "Old Desktop", "old")
|
|
newer := admit(227, "New Desktop", "new")
|
|
freezeAndPublishLayer(t, r, authKeyID, sessionID, 10, 1, 225)
|
|
freezeAndPublishLayer(t, r, authKeyID, sessionID, 20, 2, 227)
|
|
|
|
if _, _, err := r.DispatchAdmitted(context.Background(), authKeyID, sessionID, 20, 2, newer); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := r.DispatchAdmitted(context.Background(), authKeyID, sessionID, 10, 1, old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if layer, msgID, ok := r.NegotiatedSessionLayerEvidence(authKeyID, sessionID); !ok || layer != 227 || msgID != 20 {
|
|
t.Fatalf("exact evidence after delayed dispatch = (%d,%d,%v), want (227,20,true)", layer, msgID, ok)
|
|
}
|
|
if got := auth.authKeyClientInfos[authKeyID]; got.Layer != 227 || got.DeviceModel != "New Desktop" || got.AppVersion != "new" {
|
|
t.Fatalf("durable protocol metadata rolled back: %+v", got)
|
|
}
|
|
if calls := sessions.layerCallsSnapshot(); len(calls) != 0 {
|
|
t.Fatalf("handler dispatch duplicated/rolled back edge profile binding: %+v", calls)
|
|
}
|
|
}
|
|
|
|
func TestSameLayerNakedInitConnectionUsesMessageIDWatermark(t *testing.T) {
|
|
authKeyID := [8]byte{9, 0, 4}
|
|
const sessionID = int64(904)
|
|
auth := &captureAuthService{authKeyClientInfos: map[[8]byte]domain.AuthKeyClientInfo{
|
|
authKeyID: {Layer: 227},
|
|
}}
|
|
r := New(
|
|
Config{DC: 2, IP: "127.0.0.1", Port: 2398},
|
|
Deps{Auth: auth},
|
|
zaptest.NewLogger(t),
|
|
clock.System,
|
|
)
|
|
admit := func(device, version string) tg.LayerRequest {
|
|
t.Helper()
|
|
request := &tg.InitConnectionRequest{
|
|
APIID: 123, DeviceModel: device, SystemVersion: "Windows", AppVersion: version,
|
|
SystemLangCode: "en", LangPack: "tdesktop", LangCode: "en",
|
|
Query: &tg.HelpGetConfigRequest{},
|
|
}
|
|
var body bin.Buffer
|
|
if err := request.Encode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admitted, err := r.AdmitDefaultLayer(tg.LayerProfile227, &body, tg.LayerDecodeLimits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return admitted
|
|
}
|
|
newer := admit("New Desktop", "new")
|
|
old := admit("Old Desktop", "old")
|
|
if _, _, err := r.DispatchAdmitted(context.Background(), authKeyID, sessionID, 20, 2, newer); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := r.DispatchAdmitted(context.Background(), authKeyID, sessionID, 10, 1, old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := auth.authKeyClientInfos[authKeyID]; got.DeviceModel != "New Desktop" || got.AppVersion != "new" || got.Layer != 227 {
|
|
t.Fatalf("same-layer naked init metadata rolled back: %+v", got)
|
|
}
|
|
ctx := WithAuthKeyID(WithSessionID(WithRawAuthKeyID(context.Background(), authKeyID), sessionID), authKeyID)
|
|
info, ok, _ := r.clientSessionInfo(ctx)
|
|
if !ok || info.wrapperMsgID != 20 || !info.hasClientInfo || info.clientInfo.DeviceModel != "New Desktop" {
|
|
t.Fatalf("session wrapper watermark/client info = %+v ok=%v", info, ok)
|
|
}
|
|
}
|
|
|
|
func TestCrossSessionInitMetadataUsesAdmissionSequence(t *testing.T) {
|
|
rawOld := [8]byte{9, 0, 5, 1}
|
|
rawNew := [8]byte{9, 0, 5, 2}
|
|
permAuthKeyID := [8]byte{9, 0, 5, 3}
|
|
auth := &captureAuthService{
|
|
resolvedAuthKeyID: permAuthKeyID,
|
|
hasResolved: true,
|
|
authKeyClientInfos: make(map[[8]byte]domain.AuthKeyClientInfo),
|
|
}
|
|
r := New(Config{DC: 2}, Deps{Auth: auth}, zaptest.NewLogger(t), clock.System)
|
|
newCtx := WithAuthKeyID(WithSessionID(WithRawAuthKeyID(context.Background(), rawNew), 2), permAuthKeyID)
|
|
oldCtx := WithAuthKeyID(WithSessionID(WithRawAuthKeyID(context.Background(), rawOld), 1), permAuthKeyID)
|
|
r.rememberClientInfoAt(newCtx, ClientInfo{APIID: 123, DeviceModel: "New Desktop", AppVersion: "new", Type: ClientTypeTDesktop}, 2)
|
|
r.rememberClientInfoAt(oldCtx, ClientInfo{APIID: 123, DeviceModel: "Old Desktop", AppVersion: "old", Type: ClientTypeTDesktop}, 1)
|
|
|
|
if got := auth.authKeyClientInfos[permAuthKeyID]; got.DeviceModel != "New Desktop" || got.AppVersion != "new" {
|
|
t.Fatalf("cross-session durable metadata rolled back: %+v", got)
|
|
}
|
|
oldInfo, ok, _ := r.clientSessionInfo(oldCtx)
|
|
if !ok || !oldInfo.hasClientInfo || oldInfo.clientInfo.DeviceModel != "Old Desktop" {
|
|
t.Fatalf("old session lost its request-local metadata: %+v ok=%v", oldInfo, ok)
|
|
}
|
|
}
|
|
|
|
func tgBufferBytes(t *testing.T, encoder interface{ Encode(*bin.Buffer) error }) []byte {
|
|
t.Helper()
|
|
var encoded bin.Buffer
|
|
if err := encoder.Encode(&encoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return encoded.Copy()
|
|
}
|