package rpc import ( "encoding/binary" "fmt" "testing" "github.com/iamxvbaba/td/bin" "github.com/iamxvbaba/td/clock" "github.com/iamxvbaba/td/tg" "go.uber.org/zap/zaptest" "github.com/iamxvbaba/td/tlprofile" compatandroid "telesrv/internal/compat/android" ) type androidPrivateLayerFixture struct { name string privateID uint32 semantic tlprofile.SemanticID method string currentJoinResult bool wire func(*testing.T) []byte } // TestAndroidPrivateLayerRPCsAdaptAcrossCanonicalBoundary is the production // admission gate for every audited DrKLO-private constructor. It proves both // halves of the bridge after a canonical schema upgrade: // // - the generated static client overlay emits a complete request in the current canonical // profile (Layer 228), never a stale Layer 227 intermediate; and // - Router.AdmitLayer feeds that intermediate through the generated // unknown-method view's AdaptCanonical path, yielding the exact Layer 227 // or Layer 228 wire call for the same semantic method. func TestAndroidPrivateLayerRPCsAdaptAcrossCanonicalBoundary(t *testing.T) { r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System) fixtures := androidPrivateLayerFixtures() if got, want := len(fixtures), 17; got != want { t.Fatalf("private fixture count = %d, want %d", got, want) } for _, fixture := range fixtures { fixture := fixture t.Run(fixture.name, func(t *testing.T) { raw := fixture.wire(t) if len(raw) < 4 || binary.LittleEndian.Uint32(raw[:4]) != fixture.privateID { t.Fatalf("fixture private id = %#x, want %#x", binary.LittleEndian.Uint32(raw[:4]), fixture.privateID) } private := bin.Buffer{Buf: append([]byte(nil), raw...)} canonical, handled, err := compatandroid.UpgradePrivateLayerRPC(tlprofile.ProfileCanonical, &private, tlprofile.Limits{}) if err != nil || !handled || canonical == nil { t.Fatalf("canonical upgrade = value:%v handled:%v err:%v", canonical != nil, handled, err) } if private.Len() != 0 { t.Fatalf("canonical upgrade left %d private bytes", private.Len()) } canonicalID, ok := tlprofile.WireID(tlprofile.ProfileCanonical, fixture.semantic) if !ok { t.Fatalf("canonical profile has no wire id for %s", fixture.method) } if got, peekErr := canonical.PeekID(); peekErr != nil || got != canonicalID { t.Fatalf("canonical upgrade id = %#x err=%v, want %#x", got, peekErr, canonicalID) } for _, profile := range []tlprofile.Profile{tlprofile.Profile227, tlprofile.Profile228} { profile := profile t.Run(fmt.Sprintf("layer_%d", profile), func(t *testing.T) { body := bin.Buffer{Buf: append([]byte(nil), raw...)} admitted, err := r.AdmitLayer(profile, &body, tlprofile.Limits{}) if err != nil { t.Fatalf("production exact admission: %v", err) } if body.Len() != 0 { t.Fatalf("production exact admission left %d bytes", body.Len()) } call := admitted.Call() if call.Profile() != profile || call.Method() != fixture.semantic { t.Fatalf("admitted call = profile:%d semantic:%#x, want profile:%d semantic:%#x", call.Profile(), call.Method(), profile, fixture.semantic) } category, method, ok := tlprofile.SemanticName(call.Method()) if !ok || category != "function" || method != fixture.method { t.Fatalf("admitted semantic name = (%q,%q,%v), want (function,%q,true)", category, method, ok, fixture.method) } wantWireID, ok := tlprofile.WireID(profile, fixture.semantic) if !ok || call.WireID() != wantWireID { t.Fatalf("admitted exact id = %#x, want %#x (ok=%v)", call.WireID(), wantWireID, ok) } if fixture.currentJoinResult { var result bin.Buffer if err := call.EncodeResult(&tg.MessagesChatInviteJoinResultOk{ Updates: &tg.UpdatesTooLong{}, }, &result); err != nil { t.Fatalf("encode current join result: %v", err) } if wireID, err := result.PeekID(); err != nil || wireID != 0x445663a7 { t.Fatalf("result wire = %#x err=%v, want chatInviteJoinResultOk#445663a7", wireID, err) } } }) } }) } } func androidPrivateLayerFixtures() []androidPrivateLayerFixture { return []androidPrivateLayerFixture{ { name: "messages.forwardMessages_alias", privateID: 0x41d41ade, semantic: tlprofile.SemanticMethodMessagesForwardMessages, method: "messages.forwardMessages", wire: func(*testing.T) []byte { return androidPrivateForwardMessagesWire() }, }, { name: "channels.inviteToChannel_alias", privateID: 0x199f3a6c, semantic: tlprofile.SemanticMethodChannelsInviteToChannel, method: "channels.inviteToChannel", wire: func(t *testing.T) []byte { return androidPrivateAliasWire(t, 0x199f3a6c, &tg.ChannelsInviteToChannelRequest{ Channel: &tg.InputChannel{ChannelID: 41, AccessHash: 42}, Users: []tg.InputUserClass{&tg.InputUser{UserID: 43, AccessHash: 44}}, }) }, }, { name: "messages.importChatInvite_alias", privateID: 0x6c50051c, semantic: tlprofile.SemanticMethodMessagesImportChatInvite, method: "messages.importChatInvite", currentJoinResult: true, wire: func(t *testing.T) []byte { return androidPrivateAliasWire(t, 0x6c50051c, &tg.MessagesImportChatInviteRequest{Hash: "private-invite"}) }, }, { name: "channels.joinChannel_alias", privateID: 0x24b524c5, semantic: tlprofile.SemanticMethodChannelsJoinChannel, method: "channels.joinChannel", currentJoinResult: true, wire: func(t *testing.T) []byte { return androidPrivateAliasWire(t, 0x24b524c5, &tg.ChannelsJoinChannelRequest{ Channel: &tg.InputChannel{ChannelID: 45, AccessHash: 46}, }) }, }, { name: "updates.getDifference_alias", privateID: 0x25939651, semantic: tlprofile.SemanticMethodUpdatesGetDifference, method: "updates.getDifference", wire: func(t *testing.T) []byte { return androidPrivateAliasWire(t, 0x25939651, &tg.UpdatesGetDifferenceRequest{Pts: 100, Date: 200, Qts: 3}) }, }, { name: "messages.createChat_alias", privateID: 0x0034a818, semantic: tlprofile.SemanticMethodMessagesCreateChat, method: "messages.createChat", wire: func(t *testing.T) []byte { return androidPrivateAliasWire(t, 0x0034a818, &tg.MessagesCreateChatRequest{ Title: "private group", }) }, }, { name: "messages.uploadMedia_transform", privateID: 0x519bc2b1, semantic: tlprofile.SemanticMethodMessagesUploadMedia, method: "messages.uploadMedia", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x519bc2b1, func(body *bin.Buffer) error { if err := (&tg.InputPeerSelf{}).Encode(body); err != nil { return err } return (&tg.InputMediaUploadedPhoto{File: &tg.InputFile{ID: 61, Parts: 1, Name: "private.jpg"}}).Encode(body) }) }, }, { name: "auth.signUp_transform", privateID: 0x80eee427, semantic: tlprofile.SemanticMethodAuthSignUp, method: "auth.signUp", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x80eee427, func(body *bin.Buffer) error { body.PutString("+15550000228") body.PutString("private-hash") body.PutString("Private") body.PutString("Layer") return nil }) }, }, { name: "messages.getMessages_transform", privateID: 0x4222fa74, semantic: tlprofile.SemanticMethodMessagesGetMessages, method: "messages.getMessages", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x4222fa74, func(body *bin.Buffer) error { body.PutVectorHeader(2) body.PutInt(71) body.PutInt(72) return nil }) }, }, { name: "channels.getMessages_transform", privateID: 0x93d7b347, semantic: tlprofile.SemanticMethodChannelsGetMessages, method: "channels.getMessages", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x93d7b347, func(body *bin.Buffer) error { if err := (&tg.InputChannel{ChannelID: 81, AccessHash: 82}).Encode(body); err != nil { return err } body.PutVectorHeader(2) body.PutInt(83) body.PutInt(84) return nil }) }, }, { name: "bots.exportBotToken_transform", privateID: 0x0063b089, semantic: tlprofile.SemanticMethodBotsExportBotToken, method: "bots.exportBotToken", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x0063b089, func(body *bin.Buffer) error { body.PutLong(91) body.PutID(tg.BoolTrueTypeID) return nil }) }, }, { name: "account.registerDevice_transform", privateID: 0x637ea878, semantic: tlprofile.SemanticMethodAccountRegisterDevice, method: "account.registerDevice", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x637ea878, func(body *bin.Buffer) error { body.PutInt(2) body.PutString("private-device-token") return nil }) }, }, { name: "contacts.search_transform", privateID: 0x11f812d8, semantic: tlprofile.SemanticMethodContactsSearch, method: "contacts.search", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x11f812d8, func(body *bin.Buffer) error { body.PutString("private-query") body.PutInt(20) return nil }) }, }, { name: "langpack.getLangPack_transform", privateID: 0x9ab5c58e, semantic: tlprofile.SemanticMethodLangpackGetLangPack, method: "langpack.getLangPack", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x9ab5c58e, func(body *bin.Buffer) error { body.PutString("en") return nil }) }, }, { name: "langpack.getStrings_transform", privateID: 0x2e1ee318, semantic: tlprofile.SemanticMethodLangpackGetStrings, method: "langpack.getStrings", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x2e1ee318, func(body *bin.Buffer) error { body.PutString("en") body.PutVectorHeader(2) body.PutString("private.one") body.PutString("private.two") return nil }) }, }, { name: "langpack.getLanguages_transform", privateID: 0x800fd57d, semantic: tlprofile.SemanticMethodLangpackGetLanguages, method: "langpack.getLanguages", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x800fd57d, func(*bin.Buffer) error { return nil }) }, }, { name: "messages.editChatCreator_transform", privateID: 0x8f38cd1f, semantic: tlprofile.SemanticMethodMessagesEditChatCreator, method: "messages.editChatCreator", wire: func(t *testing.T) []byte { return androidPrivateRawWire(t, 0x8f38cd1f, func(body *bin.Buffer) error { if err := (&tg.InputChannel{ChannelID: 101, AccessHash: 102}).Encode(body); err != nil { return err } if err := (&tg.InputUser{UserID: 103, AccessHash: 104}).Encode(body); err != nil { return err } return (&tg.InputCheckPasswordEmpty{}).Encode(body) }) }, }, } } func androidPrivateAliasWire(t *testing.T, privateID uint32, request bin.Encoder) []byte { t.Helper() var body bin.Buffer if err := request.Encode(&body); err != nil { t.Fatalf("encode private alias body: %v", err) } if body.Len() < 4 { t.Fatal("encoded private alias body has no constructor id") } binary.LittleEndian.PutUint32(body.Buf[:4], privateID) return body.Copy() } func androidPrivateRawWire(t *testing.T, privateID uint32, encodeBody func(*bin.Buffer) error) []byte { t.Helper() var body bin.Buffer body.PutID(privateID) if err := encodeBody(&body); err != nil { t.Fatalf("encode private raw body: %v", err) } return body.Copy() }