Kritikan terhadap protokol dan pendekatan organisasi Telegram. Bahagian 1, teknikal: pengalaman menulis pelanggan dari awal - TL, MT

Baru-baru ini, siaran tentang betapa baiknya Telegram, betapa cemerlang dan berpengalamannya saudara Durov dalam membina sistem rangkaian, dsb. telah mula muncul lebih kerap di HabrΓ©. Pada masa yang sama, sangat sedikit orang yang benar-benar melibatkan diri dalam peranti teknikal - paling banyak, mereka menggunakan API Bot yang agak mudah (dan agak berbeza daripada MTProto) berdasarkan JSON, dan biasanya hanya menerima atas iman segala pujian dan PR yang berkisar pada utusan. Hampir setahun setengah yang lalu, rakan sekerja saya di NGO Eshelon Vasily (malangnya, akaunnya di HabrΓ© telah dipadamkan bersama draf) mula menulis pelanggan Telegramnya sendiri dari awal di Perl, dan kemudian pengarang baris ini menyertainya. Mengapa Perl, ada yang akan segera bertanya? Kerana projek sebegini sudah wujud dalam bahasa lain. Sebenarnya, ini bukan maksudnya, mungkin ada bahasa lain di mana tidak ada perpustakaan siap sedia, dan oleh itu penulis mesti pergi sepanjang jalan dari awal. Selain itu, kriptografi adalah soal kepercayaan, tetapi sahkan. Dengan produk yang bertujuan untuk keselamatan, anda tidak boleh hanya bergantung pada perpustakaan siap pakai daripada pengilang dan mempercayainya secara membuta tuli (namun, ini adalah topik untuk bahagian kedua). Pada masa ini, perpustakaan berfungsi dengan baik pada tahap "purata" (membolehkan anda membuat sebarang permintaan API).

Walau bagaimanapun, tidak akan terdapat banyak kriptografi atau matematik dalam siri siaran ini. Tetapi akan ada banyak butiran teknikal dan tongkat seni bina yang lain (juga berguna untuk mereka yang tidak akan menulis dari awal, tetapi akan menggunakan perpustakaan dalam mana-mana bahasa). Jadi, matlamat utama adalah untuk cuba melaksanakan pelanggan dari awal mengikut dokumentasi rasmi. Iaitu, mari kita anggap bahawa kod sumber pelanggan rasmi ditutup (sekali lagi, di bahagian kedua kita akan membincangkan dengan lebih terperinci topik fakta bahawa ini benar berlaku jadi), tetapi, seperti pada zaman dahulu, sebagai contoh, terdapat standard seperti RFC - adakah mungkin untuk menulis pelanggan mengikut spesifikasi sahaja, "tanpa melihat" pada kod sumber, sama ada rasmi (Telegram Desktop, mudah alih), atau Telethon tidak rasmi?

:Π›Π°Π²Π»Π΅Π½ΠΈΠ΅:

Dokumentasi... ada kan? Adakah benar?..

Serpihan nota untuk artikel ini mula dikumpul pada musim panas lalu. Selama ini di laman web rasmi https://core.telegram.org Dokumentasi adalah pada Lapisan 23, i.e. terperangkap di suatu tempat pada tahun 2014 (ingat, tidak ada saluran ketika itu?). Sudah tentu, secara teori, ini sepatutnya membolehkan kami melaksanakan klien dengan fungsi pada masa itu pada tahun 2014. Tetapi walaupun dalam keadaan ini, dokumentasi itu, pertama, tidak lengkap, dan kedua, di tempat yang bercanggah dengan dirinya sendiri. Lebih sebulan yang lalu, pada September 2019, ia berlaku secara kebetulan Telah didapati bahawa terdapat kemas kini besar dokumentasi di tapak, untuk Lapisan 105 yang baru-baru ini, dengan nota bahawa kini semuanya perlu dibaca semula. Memang banyak artikel telah disemak, tetapi banyak yang kekal tidak berubah. Oleh itu, apabila membaca kritikan di bawah tentang dokumentasi, anda harus ingat bahawa beberapa perkara ini tidak lagi relevan, tetapi ada yang masih agak. Lagipun, 5 tahun dalam dunia moden bukan sahaja masa yang lama, tetapi ΠΎΡ‡Π΅Π½ΡŒ banyak. Sejak masa itu (terutamanya jika anda tidak mengambil kira tapak geosembang yang dibuang dan dihidupkan semula sejak itu), bilangan kaedah API dalam skim telah meningkat daripada seratus kepada lebih daripada dua ratus lima puluh!

Di mana untuk bermula sebagai pengarang muda?

Tidak kira sama ada anda menulis dari awal atau menggunakan, contohnya, perpustakaan siap sedia seperti Telethon untuk Python atau Madeline untuk PHP, dalam apa jua keadaan, anda perlu terlebih dahulu daftarkan permohonan anda - dapatkan parameter api_id ΠΈ api_hash (mereka yang telah bekerja dengan API VKontakte segera memahami) yang mana pelayan akan mengenal pasti aplikasi itu. ini akan perlu lakukannya atas sebab undang-undang, tetapi kita akan bercakap lebih lanjut tentang sebab pengarang perpustakaan tidak boleh menerbitkannya di bahagian kedua. Anda mungkin berpuas hati dengan nilai ujian, walaupun ia sangat terhad - hakikatnya kini anda boleh mendaftar hanya satu app, jadi jangan tergesa-gesa ke dalamnya.

Sekarang, dari sudut pandangan teknikal, kita sepatutnya berminat dengan fakta bahawa selepas pendaftaran kita harus menerima pemberitahuan daripada Telegram tentang kemas kini kepada dokumentasi, protokol, dll. Iaitu, seseorang boleh mengandaikan bahawa tapak dengan dok hanya ditinggalkan dan terus bekerja secara khusus dengan mereka yang mula membuat pelanggan, kerana ia lebih mudah. Tetapi tidak, tiada apa-apa seperti itu diperhatikan, tiada maklumat datang.

Dan jika anda menulis dari awal, maka menggunakan parameter yang diperoleh sebenarnya masih jauh. Walaupun https://core.telegram.org/ dan bercakap tentang mereka dalam Bermula pertama sekali, sebenarnya, anda perlu melaksanakannya terlebih dahulu Protokol MTProto - tetapi jika anda percaya susun atur mengikut model OSI pada penghujung halaman untuk penerangan umum protokol, maka ia benar-benar sia-sia.

Sebenarnya, sebelum dan selepas MTProto, pada beberapa peringkat sekaligus (seperti yang dikatakan oleh perangkaian asing yang bekerja dalam kernel OS, pelanggaran lapisan), topik yang besar, menyakitkan dan mengerikan akan menghalang...

Siri binari: TL (Bahasa Jenis) dan skema, dan lapisannya, dan banyak lagi perkataan menakutkan

Topik ini, sebenarnya, adalah kunci kepada masalah Telegram. Dan akan ada banyak perkataan yang menakutkan jika anda cuba mendalaminya.

Jadi, inilah diagramnya. Jika perkataan ini terlintas di fikiran anda, katakan, Skema JSON, Anda fikir dengan betul. Matlamatnya adalah sama: beberapa bahasa untuk menerangkan kemungkinan set data yang dihantar. Di sinilah persamaan berakhir. Jika dari halaman Protokol MTProto, atau daripada pokok sumber pelanggan rasmi, kami akan cuba membuka beberapa skema, kami akan melihat sesuatu seperti:

int ? = Int;
long ? = Long;
double ? = Double;
string ? = String;

vector#1cb5c415 {t:Type} # [ t ] = Vector t;

rpc_error#2144ca19 error_code:int error_message:string = RpcError;

rpc_answer_unknown#5e2ad36e = RpcDropAnswer;
rpc_answer_dropped_running#cd78e586 = RpcDropAnswer;
rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;

msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;

---functions---

set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer;

ping#7abe77ec ping_id:long = Pong;
ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;

invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;

account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User;
account.sendChangePhoneCode#8e57deb flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode;

Seseorang yang melihat ini buat kali pertama secara intuitif akan dapat mengenali hanya sebahagian daripada apa yang ditulis - nah, ini nampaknya struktur (walaupun di mana nama, di sebelah kiri atau di sebelah kanan?), terdapat medan di dalamnya, selepas itu jenis mengikuti selepas kolon... mungkin. Di sini dalam kurungan sudut mungkin terdapat templat seperti dalam C++ (sebenarnya, sebenarnya tidak). Dan apakah maksud semua simbol lain, tanda soal, tanda seru, peratusan, tanda cincang (dan jelas sekali ia bermaksud perkara yang berbeza di tempat yang berbeza), kadangkala hadir dan kadangkala tidak, nombor heksadesimal - dan yang paling penting, bagaimana untuk mendapatkan daripada ini biasa (yang tidak akan ditolak oleh pelayan) aliran bait? Anda perlu membaca dokumentasi (ya, terdapat pautan ke skema dalam versi JSON berdekatan - tetapi itu tidak menjadikannya lebih jelas).

Buka halaman Pensirian Data Perduaan dan menyelami dunia ajaib cendawan dan matematik diskret, sesuatu yang serupa dengan matan pada tahun ke-4. Abjad, jenis, nilai, penggabung, penggabung berfungsi, bentuk biasa, jenis komposit, jenis polimorfik... dan itu semua hanya halaman pertama! Seterusnya menanti anda Bahasa TL, yang, walaupun ia sudah mengandungi contoh permintaan dan tindak balas yang remeh, tidak memberikan jawapan sama sekali kepada kes yang lebih tipikal, yang bermaksud bahawa anda perlu mengharungi penceritaan semula matematik yang diterjemahkan daripada bahasa Rusia ke bahasa Inggeris pada lapan lagi tertanam muka surat!

Pembaca yang biasa dengan bahasa berfungsi dan inferens jenis automatik akan, sudah tentu, melihat bahasa penerangan dalam bahasa ini, walaupun dari contoh, sebagai lebih biasa, dan boleh mengatakan bahawa ini sebenarnya tidak buruk pada dasarnya. Bantahan terhadap perkara ini ialah:

  • ya, tujuannya kedengaran bagus, tetapi sayangnya, dia tidak tercapai
  • Pendidikan di universiti Rusia berbeza-beza walaupun di kalangan kepakaran IT - tidak semua orang telah mengambil kursus yang sepadan
  • Akhirnya, seperti yang akan kita lihat, dalam amalan ia adalah tidak diperlukan, kerana hanya subset terhad walaupun TL yang diterangkan digunakan

Seperti yang dikatakan LeoNerd di saluran #perl dalam rangkaian FreeNode IRC, yang cuba melaksanakan gerbang dari Telegram ke Matrix (terjemahan petikan tidak tepat dari ingatan):

Rasanya seperti seseorang diperkenalkan kepada teori menaip buat kali pertama, teruja, dan mula cuba bermain-main dengannya, tidak begitu peduli sama ada ia diperlukan dalam amalan.

Lihat sendiri, jika keperluan untuk jenis kosong (int, panjang, dll.) sebagai sesuatu yang asas tidak menimbulkan persoalan - akhirnya ia mesti dilaksanakan secara manual - sebagai contoh, mari kita cuba untuk mendapatkannya vektor. Iaitu, sebenarnya, sebaris, jika anda memanggil perkara yang terhasil dengan nama yang betul.

Tetapi sebelum

Penerangan ringkas subset sintaks TL untuk mereka yang tidak membaca dokumentasi rasmi

constructor = Type;
myVec ids:Vector<long> = Type;

fixed#abcdef34 id:int = Type2;

fixedVec set:Vector<Type2> = FixedVec;

constructorOne#crc32 field1:int = PolymorType;
constructorTwo#2crc32 field_a:long field_b:Type3 field_c:int = PolymorType;
constructorThree#deadcrc bit_flags_of_what_really_present:# optional_field4:bit_flags_of_what_really_present.1?Type = PolymorType;

an_id#12abcd34 id:int = Type3;
a_null#6789cdef = Type3;

Definisi sentiasa bermula pereka, selepas itu secara pilihan (dalam amalan - sentiasa) melalui simbol # sepatutnya CRC32 daripada rentetan perihalan ternormal jenis ini. Seterusnya ialah penerangan tentang medan; jika ia wujud, jenisnya mungkin kosong. Ini semua berakhir dengan tanda yang sama, nama jenis yang pembina ini - iaitu, sebenarnya, subjenis - milik. Lelaki di sebelah kanan tanda sama ialah polimorfik - iaitu, beberapa jenis tertentu boleh sepadan dengannya.

Jika definisi berlaku selepas baris ---functions---, maka sintaks akan tetap sama, tetapi maknanya akan berbeza: pembina akan menjadi nama fungsi RPC, medan akan menjadi parameter (iaitu, ia akan tetap sama struktur yang diberikan, seperti yang diterangkan di bawah , ini hanya akan menjadi makna yang diberikan), dan "jenis polimorfik " - jenis hasil yang dikembalikan. Benar, ia masih kekal polimorfik - hanya ditakrifkan dalam bahagian ---types---, tetapi pembina ini "tidak akan dipertimbangkan". Melebihkan jenis fungsi yang dipanggil dengan hujah mereka, i.e. Atas sebab tertentu, beberapa fungsi dengan nama yang sama tetapi tandatangan berbeza, seperti dalam C++, tidak disediakan dalam TL.

Mengapa "pembina" dan "polimorfik" jika ia bukan OOP? Sebenarnya, lebih mudah bagi seseorang untuk memikirkan perkara ini dalam istilah OOP - jenis polimorfik sebagai kelas abstrak, dan pembina ialah kelas keturunan langsungnya, dan final dalam terminologi beberapa bahasa. Malah, sudah tentu, di sini sahaja persamaan dengan kaedah pembina terbeban sebenar dalam bahasa pengaturcaraan OO. Oleh kerana di sini hanyalah struktur data, tiada kaedah (walaupun huraian fungsi dan kaedah lebih lanjut agak mampu menimbulkan kekeliruan di kepala bahawa ia wujud, tetapi itu adalah perkara yang berbeza) - anda boleh menganggap pembina sebagai nilai daripada yang sedang dibina taip semasa membaca aliran bait.

Bagaimana ini berlaku? Deserializer, yang sentiasa membaca 4 bait, melihat nilainya 0xcrc32 - dan memahami apa yang akan berlaku seterusnya field1 dengan jenis int, iaitu membaca betul-betul 4 bait, pada ini medan atas dengan jenis PolymorType membaca. Nampak 0x2crc32 dan memahami bahawa terdapat dua bidang lagi, pertama long, yang bermaksud kita membaca 8 bait. Dan sekali lagi jenis yang kompleks, yang dinyahsiri dengan cara yang sama. Sebagai contoh, Type3 boleh diisytiharkan dalam litar sebaik sahaja dua pembina, masing-masing, maka mereka mesti bertemu sama ada 0x12abcd34, selepas itu anda perlu membaca 4 lagi bait intAtau 0x6789cdef, selepas itu tidak akan ada apa-apa. Apa-apa lagi - anda perlu membuang pengecualian. Bagaimanapun, selepas ini kita kembali membaca 4 bait int bidang field_c Π² constructorTwo dan dengan itu kami selesai membaca kami PolymorType.

Akhirnya, jika anda tertangkap 0xdeadcrc untuk constructorThree, maka semuanya menjadi lebih rumit. Bidang pertama kami ialah bit_flags_of_what_really_present dengan jenis # - sebenarnya, ini hanyalah alias untuk jenis nat, bermaksud "nombor asli". Sebenarnya, int yang tidak ditandatangani ialah satu-satunya kes apabila nombor yang tidak ditandatangani berlaku dalam litar sebenar. Jadi, seterusnya adalah pembinaan dengan tanda soal, yang bermaksud bahawa medan ini - ia akan hadir pada wayar hanya jika bit yang sepadan ditetapkan dalam medan yang dirujuk (kira-kira seperti pengendali ternary). Jadi, mari kita anggap bahawa bit ini telah ditetapkan, yang bermaksud bahawa kita perlu membaca medan seperti itu Type, yang dalam contoh kami mempunyai 2 pembina. Satu kosong (hanya terdiri daripada pengecam), satu lagi mempunyai medan ids dengan jenis ids:Vector<long>.

Anda mungkin berfikir bahawa kedua-dua templat dan generik adalah dalam pro atau Java. Tetapi tidak. Hampir. ini tunggal kes menggunakan kurungan sudut dalam litar sebenar, dan ia digunakan HANYA untuk Vektor. Dalam strim bait, ini ialah 4 bait CRC32 untuk jenis Vektor itu sendiri, sentiasa sama, kemudian 4 bait - bilangan elemen tatasusunan, dan kemudian elemen ini sendiri.

Tambah pada fakta ini bahawa serialisasi sentiasa berlaku dalam perkataan 4 bait, semua jenis adalah gandaan daripadanya - jenis terbina dalam juga diterangkan bytes ΠΈ string dengan bersiri manual panjang dan penjajaran ini sebanyak 4 - baik, nampaknya kedengaran normal dan juga agak berkesan? Walaupun TL didakwa sebagai siri binari yang berkesan, benarkah dengan mereka, dengan pengembangan hampir semua perkara, walaupun nilai Boolean dan rentetan aksara tunggal hingga 4 bait, adakah JSON masih akan menjadi lebih tebal? Lihat, medan yang tidak perlu pun boleh dilangkau dengan bendera bit, semuanya agak baik, malah boleh diperluaskan untuk masa hadapan, jadi mengapa tidak menambah medan pilihan baharu pada pembina nanti?..

Tetapi tidak, jika anda tidak membaca penerangan ringkas saya, tetapi dokumentasi penuh, dan fikirkan tentang pelaksanaannya. Pertama, CRC32 pembina dikira mengikut baris ternormal perihalan teks skema (alih keluar ruang kosong tambahan, dsb.) - jadi jika medan baharu ditambah, baris penerangan jenis akan berubah, dan oleh itu CRC32 dan , akibatnya, bersiri. Dan apakah yang akan dilakukan oleh pelanggan lama jika dia menerima medan dengan bendera baharu yang ditetapkan, dan dia tidak tahu apa yang perlu dilakukan dengan mereka seterusnya?..

Kedua, mari kita ingat CRC32, yang digunakan di sini pada asasnya sebagai fungsi hash untuk secara unik menentukan jenis yang sedang (dinyah)bersiri. Di sini kita berhadapan dengan masalah perlanggaran - dan tidak, kebarangkalian bukan satu dalam 232, tetapi lebih besar. Siapa yang masih ingat bahawa CRC32 direka untuk mengesan (dan membetulkan) ralat dalam saluran komunikasi, dan dengan itu menambah baik sifat ini sehingga merugikan orang lain? Sebagai contoh, ia tidak mengambil berat tentang menyusun semula bait: jika anda mengira CRC32 daripada dua baris, dalam detik anda menukar 4 bait pertama dengan 4 bait seterusnya - ia akan menjadi sama. Apabila input kami adalah rentetan teks daripada abjad Latin (dan sedikit tanda baca), dan nama ini tidak terlalu rawak, kemungkinan penyusunan semula sedemikian meningkat dengan ketara.

By the way, siapa yang menyemak apa yang ada di sana? benar-benar CRC32? Salah satu kod sumber awal (walaupun sebelum Waltman) mempunyai fungsi cincang yang mendarabkan setiap aksara dengan nombor 239, sangat disukai oleh orang ini, ha ha!

Akhirnya, okey, kami menyedari bahawa pembina dengan jenis medan Vector<int> ΠΈ Vector<PolymorType> akan mempunyai CRC32 yang berbeza. Bagaimana dengan prestasi dalam talian? Dan dari sudut teori, adakah ini menjadi sebahagian daripada jenis? Katakan kita lulus tatasusunan sepuluh ribu nombor, baik dengan Vector<int> semuanya jelas, panjang dan 40000 bait lagi. Bagaimana jika ini Vector<Type2>, yang terdiri daripada satu medan sahaja int dan ia bersendirian dalam jenis - adakah kita perlu mengulangi 10000xabcdef0 34 kali dan kemudian 4 bait int, atau bahasa itu dapat MEMERDEKAkannya untuk kita daripada pembina fixedVec dan bukannya 80000 bait, pindahkan lagi hanya 40000?

Ini sama sekali bukan soalan teori terbiar - bayangkan anda menerima senarai pengguna kumpulan, yang masing-masing mempunyai id, nama pertama, nama keluarga - perbezaan dalam jumlah data yang dipindahkan melalui sambungan mudah alih boleh menjadi ketara. Ia adalah tepat keberkesanan penyiaran Telegram yang diiklankan kepada kami.

Oleh itu ...

Vektor, yang tidak pernah dikeluarkan

Jika anda cuba melayari halaman perihalan penggabung dan sebagainya, anda akan melihat bahawa vektor (dan juga matriks) secara rasmi cuba untuk dikeluarkan melalui tupel beberapa helaian. Tetapi pada akhirnya mereka lupa, langkah terakhir dilangkau, dan definisi vektor diberikan secara ringkas, yang belum terikat pada jenis. Apa masalahnya? Dalam bahasa pengaturcaraan, terutamanya yang berfungsi, ia agak tipikal untuk menerangkan struktur secara rekursif - pengkompil dengan penilaian malasnya akan memahami dan melakukan segala-galanya sendiri. Dalam bahasa siri data apa yang diperlukan ialah KECEKAPAN: cukup sekadar menerangkan senarai, iaitu struktur dua elemen - yang pertama adalah elemen data, yang kedua adalah struktur yang sama itu sendiri atau ruang kosong untuk ekor (pek (cons) dalam Lisp). Tetapi ini jelas memerlukan masing-masing elemen membelanjakan 4 bait tambahan (CRC32 dalam kes dalam TL) untuk menerangkan jenisnya. Tatasusunan juga boleh diterangkan dengan mudah saiz tetap, tetapi dalam kes tatasusunan panjang yang tidak diketahui terlebih dahulu, kami putuskan.

Oleh itu, memandangkan TL tidak membenarkan mengeluarkan vektor, ia perlu ditambah di sebelah. Akhirnya dokumentasi berkata:

Serialisasi sentiasa menggunakan "vektor" pembina yang sama (const 0x1cb5c415 = crc32("vektor t:Jenis # [ t ] = Vektor t") yang tidak bergantung pada nilai khusus pembolehubah jenis t.

Nilai parameter pilihan t tidak terlibat dalam bersiri kerana ia diperoleh daripada jenis hasil (sentiasa diketahui sebelum penyahsiran).

Perhatikan betul-betul: vector {t:Type} # [ t ] = Vector t - tetapi tiada tempat Takrifan ini sendiri tidak mengatakan bahawa nombor pertama mestilah sama dengan panjang vektor! Dan ia tidak datang dari mana-mana. Ini adalah pemberian yang perlu diingat dan dilaksanakan dengan tangan anda. Di tempat lain, dokumentasi malah dengan jujur ​​menyebut bahawa jenis itu tidak nyata:

Pseudotaip polimorfik Vektor t ialah "jenis" yang nilainya ialah jujukan nilai mana-mana jenis t, sama ada berkotak atau kosong.

... tetapi tidak memberi tumpuan kepadanya. Apabila anda, bosan mengharungi regangan matematik (mungkin juga anda tahu dari kursus universiti), memutuskan untuk berputus asa dan benar-benar melihat bagaimana untuk bekerja dengannya dalam amalan, tanggapan yang tertinggal di kepala anda ialah ini Serius Matematik pada teras, ia jelas dicipta oleh Cool People (dua ahli matematik - pemenang ACM), dan bukan sesiapa sahaja. Matlamat - untuk menunjuk-nunjuk - telah dicapai.

By the way, tentang nombor. Biar kami ingatkan anda itu # ia adalah sinonim nat, nombor asli:

Terdapat jenis ungkapan (jenis-expr) dan ungkapan angka (nat-expr). Walau bagaimanapun, mereka ditakrifkan dengan cara yang sama.

type-expr ::= expr
nat-expr ::= expr

tetapi dalam tatabahasa mereka diterangkan dengan cara yang sama, i.e. Perbezaan ini mesti diingat semula dan dilaksanakan dengan tangan.

Ya, jenis templat (vector<int>, vector<User>) mempunyai pengecam biasa (#1cb5c415), iaitu jika anda tahu bahawa panggilan itu diumumkan sebagai

users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;

maka anda tidak lagi menunggu hanya vektor, tetapi vektor pengguna. Lebih tepat, sepatutnya tunggu - dalam kod sebenar, setiap elemen, jika bukan jenis kosong, akan mempunyai pembina, dan dengan cara yang baik dalam pelaksanaan, ia perlu untuk menyemak - tetapi kami dihantar tepat dalam setiap elemen vektor ini jenis itu? Bagaimana jika ia adalah sejenis PHP, di mana tatasusunan boleh mengandungi jenis yang berbeza dalam elemen yang berbeza?

Pada ketika ini anda mula berfikir - adakah TL seperti itu perlu? Mungkin untuk troli itu mungkin menggunakan penyeri bersiri manusia, protobuf yang sama yang telah wujud ketika itu? Itu teorinya, mari kita lihat amalan.

Pelaksanaan TL sedia ada dalam kod

TL dilahirkan di kedalaman VKontakte walaupun sebelum peristiwa terkenal dengan penjualan bahagian Durov dan (pasti), walaupun sebelum pembangunan Telegram bermula. Dan dalam sumber terbuka kod sumber pelaksanaan pertama anda boleh menemui banyak tongkat lucu. Dan bahasa itu sendiri telah dilaksanakan di sana dengan lebih lengkap daripada sekarang di Telegram. Sebagai contoh, cincang tidak digunakan sama sekali dalam skema (bermaksud pseudotaip terbina dalam (seperti vektor) dengan tingkah laku menyimpang). Ataupun

Templates are not used now. Instead, the same universal constructors (for example, vector {t:Type} [t] = Vector t) are used w

tetapi marilah kita mempertimbangkan, demi kesempurnaan, untuk mengesan, boleh dikatakan, evolusi Giant of Thought.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

Atau yang cantik ini:

    static const char *reserved_words_polymorhic[] = {

      "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", NULL

      };

Serpihan ini adalah mengenai templat seperti:

intHash {alpha:Type} vector<coupleInt<alpha>> = IntHash<alpha>;

Ini ialah takrifan jenis templat peta hash sebagai vektor pasangan int - Jenis. Dalam C++ ia akan kelihatan seperti ini:

    template <T> class IntHash {
      vector<pair<int,T>> _map;
    }

jadi, alpha - kata kunci! Tetapi hanya dalam C++ anda boleh menulis T, tetapi anda harus menulis alpha, beta... Tetapi tidak lebih daripada 8 parameter, di situlah fantasi berakhir. Nampaknya suatu ketika dahulu di St. Petersburg beberapa dialog seperti ini berlaku:

-- Надо ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Π² TL ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹
-- Π‘Π»... Ну ΠΏΡƒΡΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π·ΠΎΠ²ΡƒΡ‚ Π°Π»ΡŒΡ„Π°, Π±Π΅Ρ‚Π°,... КакиС Ρ‚Π°ΠΌ Π΅Ρ‰Ρ‘ Π±ΡƒΠΊΠ²Ρ‹ Π΅ΡΡ‚ΡŒ... О, тэта!
-- Π“Ρ€Π°ΠΌΠΌΠ°Ρ‚ΠΈΠΊΠ°? Ну ΠΏΠΎΡ‚ΠΎΠΌ напишСм

-- Π‘ΠΌΠΎΡ‚Ρ€ΠΈΡ‚Π΅, ΠΊΠ°ΠΊΠΎΠΉ я синтаксис ΠΏΡ€ΠΈΠ΄ΡƒΠΌΠ°Π» для шаблонов ΠΈ Π²Π΅ΠΊΡ‚ΠΎΡ€Π°!
-- Π’Ρ‹ долбанулся, ΠΊΠ°ΠΊ ΠΌΡ‹ это ΠΏΠ°Ρ€ΡΠΈΡ‚ΡŒ Π±ΡƒΠ΄Π΅ΠΌ?
-- Π”Π° Π½Π΅ ссытС, ΠΎΠ½ Ρ‚Π°ΠΌ ΠΎΠ΄ΠΈΠ½ Π² схСмС, Π·Π°Ρ…Π°Ρ€ΠΊΠΎΠ΄ΠΈΡ‚ΡŒ -- ΠΈ ΠΎΠΊ

Tetapi ini adalah mengenai pelaksanaan pertama TL yang diterbitkan "secara umum". Mari kita teruskan untuk mempertimbangkan pelaksanaan dalam pelanggan Telegram itu sendiri.

Kata kepada Vasily:

Vasily, [09.10.18 17:07] Paling penting, keldai itu panas kerana mereka mencipta sekumpulan abstraksi, dan kemudian memalu bolt pada mereka, dan menutup penjana kod dengan tongkat
Hasilnya, pertama dari dok pilot.jpg
Kemudian dari kod dzhekichan.webp

Sudah tentu, daripada orang yang biasa dengan algoritma dan matematik, kita boleh menjangkakan bahawa mereka telah membaca Aho, Ullmann, dan biasa dengan alatan yang telah menjadi standard de facto dalam industri selama beberapa dekad untuk menulis penyusun DSL mereka, bukan?..

Pengarang telegram-cli ialah Vitaly Valtman, seperti yang boleh difahami daripada berlakunya format TLO di luar sempadannya (cli), ahli pasukan - kini perpustakaan untuk penghuraian TL telah diperuntukkan berasingan, apa kesan dia Penghurai TL? ..

16.12 04:18 Vasily: Saya rasa seseorang tidak menguasai lex+yacc
16.12 04:18 Vasily: Saya tidak boleh menjelaskan sebaliknya
16.12 04:18 Vasily: baik, atau mereka dibayar untuk bilangan baris dalam VK
16.12 04:19 Vasily: 3k+ baris dsb.<censored> bukannya penghurai

Mungkin pengecualian? Mari kita lihat bagaimana adakah Ini ialah pelanggan RASMI - Telegram Desktop:

    nametype = re.match(r'([a-zA-Z.0-9_]+)(#[0-9a-f]+)?([^=]*)=s*([a-zA-Z.<>0-9_]+);', line);
    if (not nametype):
      if (not re.match(r'vector#1cb5c415 {t:Type} # [ t ] = Vector t;', line)):
         print('Bad line found: ' + line);

1100+ baris dalam Python, beberapa ungkapan biasa + kes khas seperti vektor, yang, sudah tentu, diisytiharkan dalam skema seperti yang sepatutnya mengikut sintaks TL, tetapi mereka bergantung pada sintaks ini untuk menghuraikannya... Timbul persoalan, kenapa semua itu satu keajaiban?ΠΈIa lebih berlapis jika tiada siapa yang akan menghuraikannya mengikut dokumentasi pula?!

By the way... Ingat kita bercakap tentang pemeriksaan CRC32? Jadi, dalam penjana kod Desktop Telegram terdapat senarai pengecualian untuk jenis tersebut di mana CRC32 dikira tidak sepadan dengan yang ditunjukkan dalam rajah!

Vasily, [18.12/22 49:XNUMX] dan di sini saya akan memikirkan sama ada TL sedemikian diperlukan
jika saya mahu mengacaukan pelaksanaan alternatif, saya akan mula memasukkan pemisah baris, separuh daripada penghurai akan pecah pada definisi berbilang baris
tdesktop, walau bagaimanapun, juga

Ingat perkara tentang satu pelapik, kami akan kembali kepadanya sedikit kemudian.

Okay, telegram-cli tidak rasmi, Telegram Desktop rasmi, tetapi bagaimana dengan yang lain? Siapa tahu?.. Dalam kod pelanggan Android tiada penghurai skema sama sekali (yang menimbulkan persoalan tentang sumber terbuka, tetapi ini untuk bahagian kedua), tetapi terdapat beberapa keping kod lucu yang lain, tetapi lebih banyak lagi mengenainya dalam subseksyen di bawah.

Apakah soalan lain yang ditimbulkan oleh siri dalam amalan? Sebagai contoh, mereka melakukan banyak perkara, sudah tentu, dengan medan bit dan medan bersyarat:

Vasily: flags.0? true
bermakna medan itu hadir dan sama dengan benar jika bendera ditetapkan

Vasily: flags.1? int
bermakna bahawa medan itu ada dan perlu dinyahsiri

Vasily: Ass, jangan risau tentang apa yang anda lakukan!
Vasily: Terdapat sebutan di suatu tempat dalam dokumen bahawa true ialah jenis sifar panjang, tetapi mustahil untuk memasang apa-apa daripada dokumen mereka
Vasily: Dalam pelaksanaan sumber terbuka ini juga tidak berlaku, tetapi terdapat sekumpulan tongkat dan sokongan

Bagaimana pula dengan Telethon? Melihat ke hadapan kepada topik MTProto, contoh - dalam dokumentasi terdapat kepingan sedemikian, tetapi tandanya % ia hanya digambarkan sebagai "sepadan dengan jenis kosong yang diberikan", i.e. dalam contoh di bawah terdapat sama ada ralat atau sesuatu yang tidak didokumenkan:

Vasily, [22.06.18 18:38] Di satu tempat:

msg_container#73f1f8dc messages:vector message = MessageContainer;

Dalam berbeza:

msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;

Dan ini adalah dua perbezaan besar, dalam kehidupan sebenar beberapa jenis vektor telanjang datang

Saya tidak melihat definisi vektor kosong dan tidak menemuinya

Analisis ditulis dengan tangan dalam telethon

Dalam rajahnya definisi diulas msg_container

Sekali lagi, persoalannya kekal kira-kira %. Ia tidak diterangkan.

Vadim Goncharov, [22.06.18 19:22] dan dalam tdesktop?

Vasily, [22.06.18 19:23] Tetapi penghurai TL mereka pada enjin biasa kemungkinan besar tidak akan memakan ini sama ada

// parsed manually

TL adalah abstraksi yang indah, tiada siapa yang melaksanakannya sepenuhnya

Dan % tiada dalam versi skema mereka

Tetapi di sini dokumentasi itu bercanggah dengan dirinya sendiri, jadi idk

Ia ditemui dalam tatabahasa, mereka mungkin terlupa untuk menerangkan semantik

Anda melihat dokumen itu di TL, anda tidak boleh memikirkannya tanpa setengah liter

"Nah, katakan," pembaca lain akan berkata, "anda mengkritik sesuatu, jadi tunjukkan saya bagaimana ia harus dilakukan."

Vasily menjawab: "Bagi penghurai, saya suka perkara seperti itu

    args: /* empty */ { $$ = NULL; }
        | args arg { $$ = g_list_append( $1, $2 ); }
        ;

    arg: LC_ID ':' type-term { $$ = tl_arg_new( $1, $3 ); }
            | LC_ID ':' condition '?' type-term { $$ = tl_arg_new_cond( $1, $5, $3 ); free($3); }
            | UC_ID ':' type-term { $$ = tl_arg_new( $1, $3 ); }
            | type-term { $$ = tl_arg_new( "", $1 ); }
            | '[' LC_ID ']' { $$ = tl_arg_new_mult( "", tl_type_new( $2, TYPE_MOD_NONE ) ); }
            ;

entah bagaimana lebih suka daripada

struct tree *parse_args4 (void) {
  PARSE_INIT (type_args4);
  struct parse so = save_parse ();
  PARSE_TRY (parse_optional_arg_def);
  if (S) {
    tree_add_child (T, S);
  } else {
    load_parse (so);
  }
  if (LEX_CHAR ('!')) {
    PARSE_ADD (type_exclam);
    EXPECT ("!");
  }
  PARSE_TRY_PES (parse_type_term);
  PARSE_OK;
}

atau

        # Regex to match the whole line
        match = re.match(r'''
            ^                  # We want to match from the beginning to the end
            ([w.]+)           # The .tl object can contain alpha_name or namespace.alpha_name
            (?:
                #             # After the name, comes the ID of the object
                ([0-9a-f]+)    # The constructor ID is in hexadecimal form
            )?                 # If no constructor ID was given, CRC32 the 'tl' to determine it

            (?:s              # After that, we want to match its arguments (name:type)
                {?             # For handling the start of the '{X:Type}' case
                w+            # The argument name will always be an alpha-only name
                :              # Then comes the separator between name:type
                [wd<>#.?!]+  # The type is slightly more complex, since it's alphanumeric and it can
                               # also have Vector<type>, flags:# and flags.0?default, plus :!X as type
                }?             # For handling the end of the '{X:Type}' case
            )*                 # Match 0 or more arguments
            s                 # Leave a space between the arguments and the equal
            =
            s                 # Leave another space between the equal and the result
            ([wd<>#.?]+)     # The result can again be as complex as any argument type
            ;$                 # Finally, the line should always end with ;
            ''', tl, re.IGNORECASE | re.VERBOSE)

ini adalah keseluruhan lexer:

    ---functions---         return FUNCTIONS;
    ---types---             return TYPES;
    [a-z][a-zA-Z0-9_]*      yylval.string = strdup(yytext); return LC_ID;
    [A-Z][a-zA-Z0-9_]*      yylval.string = strdup(yytext); return UC_ID;
    [0-9]+                  yylval.number = atoi(yytext); return NUM;
    #[0-9a-fA-F]{1,8}       yylval.number = strtol(yytext+1, NULL, 16); return ID_HASH;

    n                      /* skip new line */
    [ t]+                  /* skip spaces */
    //.*$                 /* skip comments */
    /*.**/              /* skip comments */
    .                       return (int)yytext[0];

mereka. lebih mudah ialah meletakkannya secara sederhana.”

Secara umum, hasilnya, penghurai dan penjana kod untuk subset TL yang sebenarnya digunakan sesuai dengan kira-kira 100 baris tatabahasa dan ~300 baris penjana (mengira semua printkod yang dihasilkan), termasuk jenis roti maklumat untuk introspeksi dalam setiap kelas. Setiap jenis polimorfik bertukar menjadi kelas asas abstrak kosong, dan pembina mewarisi daripadanya dan mempunyai kaedah untuk bersiri dan penyahserikatan.

Kekurangan jenis dalam bahasa jenis

Menaip kuat adalah perkara yang baik, bukan? Tidak, ini bukan holivar (walaupun saya lebih suka bahasa dinamik), tetapi postulat dalam rangka kerja TL. Berdasarkan itu, bahasa itu harus menyediakan semua jenis pemeriksaan untuk kita. Baiklah, okey, mungkin bukan dia sendiri, tetapi pelaksanaannya, tetapi dia sekurang-kurangnya harus menerangkannya. Dan apakah jenis peluang yang kita mahukan?

Pertama sekali, kekangan. Di sini kita lihat dalam dokumentasi untuk memuat naik fail:

Kandungan perduaan fail kemudiannya dibahagikan kepada beberapa bahagian. Semua bahagian mesti mempunyai saiz yang sama ( bahagian_saiz ) dan syarat-syarat berikut mesti dipenuhi:

  • part_size % 1024 = 0 (boleh dibahagi dengan 1KB)
  • 524288 % part_size = 0 (512KB mesti boleh dibahagikan sama rata dengan saiz_bahagian)

Bahagian terakhir tidak perlu memenuhi syarat ini, dengan syarat saiznya kurang daripada saiz_bahagian.

Setiap bahagian hendaklah mempunyai nombor urutan, bahagian_fail, dengan nilai antara 0 hingga 2,999.

Selepas fail telah dibahagikan, anda perlu memilih kaedah untuk menyimpannya pada pelayan. guna upload.saveBigFilePart sekiranya saiz penuh fail melebihi 10 MB dan upload.saveFilePart untuk fail yang lebih kecil.
[...] salah satu daripada ralat input data berikut mungkin dikembalikan:

  • FILE_PARTS_INVALID β€” Bilangan bahagian tidak sah. Nilainya bukan antara 1..3000

Adakah mana-mana ini dalam rajah? Adakah ini boleh diungkapkan menggunakan TL? Tidak. Tetapi maaf, Turbo Pascal datuk pun dapat menerangkan jenis yang dinyatakan julat. Dan dia tahu satu perkara lagi, kini lebih dikenali sebagai enum - jenis yang terdiri daripada penghitungan bilangan nilai tetap (kecil). Dalam bahasa seperti C - angka, ambil perhatian bahawa setakat ini kita hanya bercakap tentang jenis nombor. Tetapi terdapat juga tatasusunan, rentetan ... sebagai contoh, adalah baik untuk menerangkan bahawa rentetan ini hanya boleh mengandungi nombor telefon, bukan?

Tiada satu pun daripada ini dalam TL. Tetapi ada, sebagai contoh, dalam Skema JSON. Dan jika orang lain mungkin berhujah tentang kebolehbahagi 512 KB, bahawa ini masih perlu disemak dalam kod, kemudian pastikan bahawa pelanggan hanya tidak boleh menghantar nombor di luar julat 1..3000 (dan ralat yang sepadan tidak mungkin timbul) ia mungkin mungkin, bukan?..

Dengan cara ini, tentang ralat dan nilai pulangan. Malah mereka yang pernah bekerja dengan TL mengaburkan mata mereka - kami tidak menyedarinya dengan segera setiap satu fungsi dalam TL sebenarnya boleh mengembalikan bukan sahaja jenis pulangan yang diterangkan, tetapi juga ralat. Tetapi ini tidak boleh disimpulkan dalam apa cara sekalipun menggunakan TL itu sendiri. Sudah tentu, ia sudah jelas dan tidak ada keperluan untuk apa-apa dalam amalan (walaupun sebenarnya, RPC boleh dilakukan dengan cara yang berbeza, kita akan kembali kepada perkara ini kemudian) - tetapi bagaimana pula dengan Kesucian konsep Matematik Jenis Abstrak dari dunia syurga?.. Saya angkat tarik tunda - jadi padanlah.

Dan akhirnya, bagaimana pula dengan kebolehbacaan? Nah, di sana, secara umum, saya mahu Penerangan pastikan ia betul-betul dalam skema (dalam skema JSON, sekali lagi, ia adalah), tetapi jika anda sudah tegang dengannya, maka bagaimana pula dengan bahagian praktikal - sekurang-kurangnya memandang remeh perbezaan semasa kemas kini? Lihat sendiri di contoh sebenar:

-channelFull#76af5481 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int = ChatFull;
+channelFull#1c87a71a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int = ChatFull;

atau

-message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message;
+message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message;

Ia bergantung kepada semua orang, tetapi GitHub, sebagai contoh, enggan menyerlahkan perubahan dalam barisan yang begitu panjang. Permainan "cari 10 perbezaan", dan apa yang otak segera lihat ialah permulaan dan pengakhiran dalam kedua-dua contoh adalah sama, anda perlu membaca dengan membosankan di suatu tempat di tengah... Pada pendapat saya, ini bukan hanya dalam teori, tetapi secara visual semata-mata kotor dan selekeh.

Dengan cara ini, tentang kesucian teori. Mengapa kita memerlukan medan bit? Nampak tak mereka bau buruk dari sudut teori jenis? Penjelasan boleh dilihat dalam versi awal rajah. Pada mulanya, ya, begitulah keadaannya, untuk setiap bersin jenis baharu dicipta. Asas-asas ini masih wujud dalam bentuk ini, contohnya:

storage.fileUnknown#aa963b05 = storage.FileType;
storage.filePartial#40bc6f52 = storage.FileType;
storage.fileJpeg#7efe0e = storage.FileType;
storage.fileGif#cae1aadf = storage.FileType;
storage.filePng#a4f63c0 = storage.FileType;
storage.filePdf#ae1e508d = storage.FileType;
storage.fileMp3#528a0677 = storage.FileType;
storage.fileMov#4b09ebbc = storage.FileType;
storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;

Tetapi sekarang bayangkan, jika anda mempunyai 5 medan pilihan dalam struktur anda, maka anda memerlukan 32 jenis untuk semua pilihan yang mungkin. Letupan kombinatorial. Oleh itu, ketulenan kristal teori TL sekali lagi berkecai terhadap keldai besi tuang realiti bersiri yang keras.

Di samping itu, di beberapa tempat lelaki ini sendiri melanggar tipologi mereka sendiri. Sebagai contoh, dalam MTProto (bab seterusnya) tindak balas boleh dimampatkan oleh Gzip, semuanya baik-baik saja - kecuali lapisan dan litar dilanggar. Sekali lagi, bukan RpcResult sendiri yang dituai, tetapi kandungannya. Nah, kenapa buat ini?.. Saya terpaksa memotong tongkat supaya pemampatan berfungsi di mana-mana sahaja.

Atau contoh lain, kami pernah menemui ralat - ia telah dihantar InputPeerUser bukannya InputUser. Atau sebaliknya. Tetapi ia berjaya! Iaitu, pelayan tidak mengambil berat tentang jenisnya. Bagaimana ini boleh terjadi? Jawapannya boleh diberikan kepada kami melalui serpihan kod dari telegram-cli:

  if (tgl_get_peer_type (E->id) != TGL_PEER_CHANNEL || (C && (C->flags & TGLCHF_MEGAGROUP))) {
    out_int (CODE_messages_get_history);
    out_peer_id (TLS, E->id);
  } else {    
    out_int (CODE_channels_get_important_history);

    out_int (CODE_input_channel);
    out_int (tgl_get_peer_id (E->id));
    out_long (E->id.access_hash);
  }
  out_int (E->max_id);
  out_int (E->offset);
  out_int (E->limit);
  out_int (0);
  out_int (0);

Dalam erti kata lain, di sinilah serialisasi dilakukan SECARA MANUAL, bukan kod yang dijana! Mungkin pelayan dilaksanakan dengan cara yang sama?.. Pada dasarnya, ini akan berfungsi jika dilakukan sekali, tetapi bagaimana ia boleh disokong kemudian semasa kemas kini? Adakah ini sebabnya skim itu dicipta? Dan di sini kita beralih kepada soalan seterusnya.

Versi. Lapisan

Mengapa versi skema dipanggil lapisan hanya boleh dibuat spekulasi berdasarkan sejarah skema yang diterbitkan. Nampaknya, pada mulanya penulis berpendapat bahawa perkara asas boleh dilakukan menggunakan skema yang tidak berubah, dan hanya jika perlu, untuk permintaan tertentu, menunjukkan bahawa ia sedang dilakukan menggunakan versi yang berbeza. Pada dasarnya, walaupun idea yang baik - dan yang baru akan, seolah-olah, "bercampur", berlapis di atas yang lama. Tetapi mari kita lihat bagaimana ia dilakukan. Benar, saya tidak dapat melihatnya dari awal - ia lucu, tetapi gambar rajah lapisan asas tidak wujud. Lapisan bermula dengan 2. Dokumentasi memberitahu kami tentang ciri TL khas:

Jika pelanggan menyokong Lapisan 2, maka pembina berikut mesti digunakan:

invokeWithLayer2#289dd1f6 {X:Type} query:!X = X;

Dalam amalan, ini bermakna sebelum setiap panggilan API, int dengan nilai 0x289dd1f6 mesti ditambah sebelum nombor kaedah.

Bunyi biasa. Tetapi apa yang berlaku seterusnya? Kemudian muncul

invokeWithLayer3#b7475268 query:!X = X;

Jadi apa yang seterusnya? Seperti yang anda duga,

invokeWithLayer4#dea0d430 query:!X = X;

kelakar? Tidak, masih terlalu awal untuk ketawa, fikirkan tentang hakikat itu setiap permintaan daripada lapisan lain perlu dibalut dengan jenis istimewa - jika semuanya berbeza untuk anda, bagaimana lagi anda boleh membezakannya? Dan menambah hanya 4 bait di hadapan adalah kaedah yang cukup cekap. Jadi,

invokeWithLayer5#417a57ae query:!X = X;

Tetapi jelas bahawa selepas beberapa ketika ini akan menjadi sejenis bacchanalia. Dan penyelesaiannya datang:

Kemas kini: Bermula dengan Lapisan 9, kaedah pembantu invokeWithLayerN hanya boleh digunakan bersama-sama dengan initConnection

Hooray! Selepas 9 versi, kami akhirnya mencapai apa yang dilakukan dalam protokol Internet pada tahun 80-an - bersetuju dengan versi sekali pada permulaan sambungan!

Jadi apa seterusnya?..

invokeWithLayer10#39620c41 query:!X = X;
...
invokeWithLayer18#1c900537 query:!X = X;

Tetapi sekarang anda masih boleh ketawa. Hanya selepas 9 lapisan lagi, pembina universal dengan nombor versi akhirnya ditambah, yang perlu dipanggil sekali sahaja pada permulaan sambungan, dan makna lapisan itu seolah-olah telah hilang, kini ia hanya versi bersyarat, seperti di tempat lain. Masalah selesai.

Betul ke?..

Vasily, [16.07.18 14:01] Malah pada hari Jumaat saya fikir:
Teleserver menghantar acara tanpa permintaan. Permintaan mesti dibungkus dalam InvokeWithLayer. Pelayan tidak membungkus kemas kini; tiada struktur untuk membungkus respons dan kemas kini.

Itu. pelanggan tidak boleh menentukan lapisan di mana dia mahu kemas kini

Vadim Goncharov, [16.07.18 14:02] bukankah InvokeWithLayer merupakan tongkat pada dasarnya?

Vasily, [16.07.18 14:02] Ini adalah satu-satunya cara

Vadim Goncharov, [16.07.18 14:02] yang pada asasnya bermaksud bersetuju pada lapisan pada permulaan sesi

Dengan cara ini, ia berikutan penurunan taraf pelanggan tidak disediakan

Kemas kini, i.e. menaip Updates dalam skema, inilah yang dihantar pelayan kepada klien bukan sebagai tindak balas kepada permintaan API, tetapi secara bebas apabila peristiwa berlaku. Ini adalah topik yang kompleks yang akan dibincangkan dalam siaran lain, tetapi buat masa ini adalah penting untuk mengetahui bahawa pelayan menyimpan Kemas Kini walaupun pelanggan berada di luar talian.

Oleh itu, jika anda enggan membungkus masing-masing pakej untuk menunjukkan versinya, ini secara logiknya membawa kepada masalah yang mungkin berikut:

  • pelayan menghantar kemas kini kepada klien walaupun sebelum klien memaklumkan versi yang ia sokong
  • apakah yang perlu saya lakukan selepas menaik taraf pelanggan?
  • yang jaminanbahawa pendapat pelayan tentang nombor lapisan tidak akan berubah semasa proses?

Adakah anda fikir ini adalah spekulasi teori semata-mata, dan dalam praktiknya ini tidak boleh berlaku, kerana pelayan ditulis dengan betul (sekurang-kurangnya, ia diuji dengan baik)? Ha! Tidak kira bagaimana keadaannya!

Inilah yang kami temui pada bulan Ogos. Pada 14 Ogos, terdapat mesej bahawa sesuatu sedang dikemas kini pada pelayan Telegram... dan kemudian dalam log:

2019-08-15 09:28:35.880640 MSK warn  main: ANON:87: unknown object type: 0x80d182d1 at TL/Object.pm line 213.
2019-08-15 09:28:35.751899 MSK warn  main: ANON:87: unknown object type: 0xb5223b0f at TL/Object.pm line 213.

dan kemudian beberapa megabait jejak tindanan (baik, pada masa yang sama pembalakan telah ditetapkan). Lagipun, jika sesuatu tidak dikenali dalam TL anda, ia adalah binari dengan tandatangan, lebih jauh ke bawah SEMUA pergi, penyahkodan akan menjadi mustahil. Apa yang perlu anda lakukan dalam keadaan sedemikian?

Nah, perkara pertama yang terlintas di fikiran sesiapa sahaja ialah memutuskan sambungan dan cuba lagi. Tidak membantu. Kami google CRC32 - ini ternyata objek daripada skema 73, walaupun kami bekerja pada 82. Kami melihat dengan teliti pada log - terdapat pengecam daripada dua skema berbeza!

Mungkin masalahnya adalah semata-mata pada pelanggan tidak rasmi kami? Tidak, kami melancarkan Telegram Desktop 1.2.17 (versi yang dibekalkan dalam beberapa pengedaran Linux), ia menulis pada log Pengecualian: MTP Jenis id tidak dijangka #b5223b0f dibaca dalam MTPMessageMedia…

Kritikan terhadap protokol dan pendekatan organisasi Telegram. Bahagian 1, teknikal: pengalaman menulis pelanggan dari awal - TL, MT

Google menunjukkan bahawa masalah yang sama telah berlaku kepada salah satu pelanggan tidak rasmi, tetapi kemudian nombor versi dan, oleh itu, andaian adalah berbeza...

Jadi apa yang patut kita buat? Vasily dan saya berpisah: dia cuba mengemas kini litar kepada 91, saya memutuskan untuk menunggu beberapa hari dan mencuba 73. Kedua-dua kaedah itu berkesan, tetapi kerana ia adalah empirikal, tidak ada pemahaman tentang berapa banyak versi ke atas atau ke bawah yang anda perlukan untuk melompat, atau berapa lama anda perlu menunggu .

Kemudian saya dapat menghasilkan semula keadaan: kami melancarkan pelanggan, mematikannya, menyusun semula litar ke lapisan lain, mulakan semula, menangkap masalah sekali lagi, kembali ke yang sebelumnya - oops, tiada jumlah pensuisan litar dan pelanggan dimulakan semula untuk beberapa minit akan membantu. Anda akan menerima gabungan struktur data daripada lapisan yang berbeza.

Penjelasan? Seperti yang anda boleh meneka dari pelbagai gejala tidak langsung, pelayan terdiri daripada banyak proses jenis yang berbeza pada mesin yang berbeza. Kemungkinan besar, pelayan yang bertanggungjawab untuk "menimbalan" memasukkan ke dalam baris gilir apa yang diberikan oleh atasannya, dan mereka memberikannya dalam skema yang telah ditetapkan pada masa penjanaan. Dan sehingga barisan ini "busuk", tiada apa yang boleh dilakukan mengenainya.

Mungkin... tetapi ini adalah tongkat yang dahsyat?!.. Tidak, sebelum memikirkan idea gila, mari kita lihat kod pelanggan rasmi. Dalam versi Android kami tidak menjumpai sebarang penghurai TL, tetapi kami menemui fail yang besar (GitHub enggan menyentuhnya) dengan (nyah) siri. Berikut ialah coretan kod:

public static class TL_message_layer68 extends TL_message {
    public static int constructor = 0xc09be45f;
//...
//Π΅Ρ‰Π΅ ΠΏΠ°Ρ‡ΠΊΠ° ΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ…
//...
    public static class TL_message_layer47 extends TL_message {
        public static int constructor = 0xc992e15c;
        public static Message TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) {
            Message result = null;
            switch (constructor) {
                case 0x1d86f70e:
                    result = new TL_messageService_old2();
                    break;
                case 0xa7ab1991:
                    result = new TL_message_old3();
                    break;
                case 0xc3060325:
                    result = new TL_message_old4();
                    break;
                case 0x555555fa:
                    result = new TL_message_secret();
                    break;
                case 0x555555f9:
                    result = new TL_message_secret_layer72();
                    break;
                case 0x90dddc11:
                    result = new TL_message_layer72();
                    break;
                case 0xc09be45f:
                    result = new TL_message_layer68();
                    break;
                case 0xc992e15c:
                    result = new TL_message_layer47();
                    break;
                case 0x5ba66c13:
                    result = new TL_message_old7();
                    break;
                case 0xc06b9607:
                    result = new TL_messageService_layer48();
                    break;
                case 0x83e5de54:
                    result = new TL_messageEmpty();
                    break;
                case 0x2bebfa86:
                    result = new TL_message_old6();
                    break;
                case 0x44f9b43d:
                    result = new TL_message_layer104();
                    break;
                case 0x1c9b1027:
                    result = new TL_message_layer104_2();
                    break;
                case 0xa367e716:
                    result = new TL_messageForwarded_old2(); //custom
                    break;
                case 0x5f46804:
                    result = new TL_messageForwarded_old(); //custom
                    break;
                case 0x567699b3:
                    result = new TL_message_old2(); //custom
                    break;
                case 0x9f8d60bb:
                    result = new TL_messageService_old(); //custom
                    break;
                case 0x22eb6aba:
                    result = new TL_message_old(); //custom
                    break;
                case 0x555555F8:
                    result = new TL_message_secret_old(); //custom
                    break;
                case 0x9789dac4:
                    result = new TL_message_layer104_3();
                    break;

atau

    boolean fixCaption = !TextUtils.isEmpty(message) &&
    (media instanceof TLRPC.TL_messageMediaPhoto_old ||
     media instanceof TLRPC.TL_messageMediaPhoto_layer68 ||
     media instanceof TLRPC.TL_messageMediaPhoto_layer74 ||
     media instanceof TLRPC.TL_messageMediaDocument_old ||
     media instanceof TLRPC.TL_messageMediaDocument_layer68 ||
     media instanceof TLRPC.TL_messageMediaDocument_layer74)
    && message.startsWith("-1");

Hmm... nampak liar. Tetapi, mungkin, ini adalah kod yang dijana, kemudian okay?.. Tetapi ia pasti menyokong semua versi! Benar, tidak jelas mengapa semuanya bercampur-campur, sembang rahsia, dan segala macam _old7 entah bagaimana tidak kelihatan seperti penjanaan mesin... Namun, kebanyakannya saya terpesona

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Kawan-kawan, tidakkah anda boleh memutuskan apa yang ada di dalam satu lapisan?! Baiklah, okey, katakan "dua" telah dikeluarkan dengan ralat, baik, ia berlaku, tetapi TIGA?.. Segera, garu yang sama lagi? Apakah jenis lucah ini, maaf?..

Dalam kod sumber Telegram Desktop, dengan cara itu, perkara yang sama berlaku - jika ya, beberapa komit berturut-turut kepada skema tidak mengubah nombor lapisannya, tetapi membetulkan sesuatu. Dalam keadaan di mana tiada sumber data rasmi untuk skim tersebut, dari manakah ia boleh diperolehi, kecuali kod sumber pelanggan rasmi? Dan jika anda mengambilnya dari sana, anda tidak boleh memastikan bahawa skema itu betul-betul betul sehingga anda menguji semua kaedah.

Bagaimana ini boleh diuji? Saya harap peminat ujian unit, fungsian dan lain-lain akan berkongsi dalam ulasan.

Okay, mari kita lihat sekeping kod yang lain:

public static class TL_folders_deleteFolder extends TLObject {
    public static int constructor = 0x1c295881;

    public int folder_id;

    public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) {
        return Updates.TLdeserialize(stream, constructor, exception);
    }

    public void serializeToStream(AbstractSerializedData stream) {
        stream.writeInt32(constructor);
        stream.writeInt32(folder_id);
    }
}

//manually created

//RichText start
public static abstract class RichText extends TLObject {
    public String url;
    public long webpage_id;
    public String email;
    public ArrayList<RichText> texts = new ArrayList<>();
    public RichText parentRichText;

    public static RichText TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) {
        RichText result = null;
        switch (constructor) {
            case 0x1ccb966a:
                result = new TL_textPhone();
                break;
            case 0xc7fb5e01:
                result = new TL_textSuperscript();
                break;

Ulasan ini "dicipta secara manual" menunjukkan bahawa hanya sebahagian daripada fail ini ditulis secara manual (bolehkah anda bayangkan keseluruhan mimpi ngeri penyelenggaraan?), dan selebihnya dijana mesin. Walau bagaimanapun, kemudian timbul persoalan lain - bahawa sumbernya tersedia tidak sepenuhnya (ala gumpalan GPL dalam kernel Linux), tetapi ini sudah menjadi topik untuk bahagian kedua.

Tapi cukuplah. Mari kita beralih kepada protokol di mana semua siri ini dijalankan.

MT Proto

Jadi, mari kita buka Deskripsi umum ΠΈ penerangan terperinci tentang protokol dan perkara pertama yang kita tersandung ialah istilah. Dan dengan kelimpahan segala-galanya. Secara umum, ini nampaknya merupakan ciri proprietari Telegram - memanggil sesuatu secara berbeza di tempat yang berbeza, atau perkara yang berbeza dengan satu perkataan, atau sebaliknya (contohnya, dalam API peringkat tinggi, jika anda melihat pek pelekat, ia tidak apa yang anda fikirkan).

Sebagai contoh, "mesej" dan "sesi" bermaksud sesuatu yang berbeza di sini daripada antara muka pelanggan Telegram yang biasa. Nah, semuanya jelas dengan mesej, ia boleh ditafsirkan dalam istilah OOP, atau hanya dipanggil perkataan "paket" - ini adalah tahap pengangkutan yang rendah, tidak ada mesej yang sama seperti dalam antara muka, terdapat banyak mesej perkhidmatan . Tetapi sesi... tetapi perkara pertama dahulu.

lapisan pengangkutan

Perkara pertama ialah pengangkutan. Mereka akan memberitahu kami tentang 5 pilihan:

  • TCP
  • Soket web
  • Soket web melalui HTTPS
  • HTTP
  • HTTPS

Vasily, [15.06.18 15:04] Terdapat juga pengangkutan UDP, tetapi ia tidak didokumenkan

Dan TCP dalam tiga varian

Yang pertama adalah serupa dengan UDP melalui TCP, setiap paket termasuk nombor urutan dan crc
Mengapa membaca dokumen pada troli sangat menyakitkan?

Nah, itulah sekarang TCP sudah ada dalam 4 varian:

  • Ringkas
  • Perantaraan
  • Perantaraan empuk
  • Penuh

Baik, ok, Perantaraan empuk untuk MTProxy, ini kemudiannya ditambah kerana acara yang terkenal. Tetapi mengapa dua lagi versi (jumlah tiga) sedangkan anda boleh bertahan dengan satu? Keempat-empat pada dasarnya berbeza hanya dalam cara menetapkan panjang dan muatan MTProto utama, yang akan dibincangkan lebih lanjut:

  • dalam Ringkas ialah 1 atau 4 bait, tetapi bukan 0xef, kemudian badan
  • dalam Pertengahan ini ialah 4 bait panjang dan medan, dan kali pertama pelanggan mesti menghantar 0xeeeeeeee untuk menunjukkan bahawa ia adalah Pertengahan
  • sepenuhnya yang paling ketagihan, dari sudut pandangan rangkaian: panjang, nombor urutan, dan BUKAN YANG terutamanya MTProto, badan, CRC32. Ya, semua ini adalah di atas TCP. Yang memberikan kami pengangkutan yang boleh dipercayai dalam bentuk aliran bait berjujukan; tiada jujukan diperlukan, terutamanya checksum. Okay, sekarang seseorang akan membantah saya bahawa TCP mempunyai jumlah semak 16-bit, jadi rasuah data berlaku. Hebat, tetapi kami sebenarnya mempunyai protokol kriptografi dengan cincangan lebih panjang daripada 16 bait, semua ralat ini - dan lebih banyak lagi - akan ditangkap oleh ketidakpadanan SHA pada tahap yang lebih tinggi. Tiada gunanya dalam CRC32 selain daripada ini.

Mari kita bandingkan Ringkas, di mana satu bait panjang mungkin, dengan Pertengahan, yang mewajarkan "Sekiranya penjajaran data 4-bait diperlukan," yang agak mengarut. Apakah, adalah dipercayai bahawa pengaturcara Telegram sangat tidak cekap sehingga mereka tidak boleh membaca data dari soket ke dalam penimbal sejajar? Anda masih perlu melakukan ini, kerana membaca boleh mengembalikan anda sebarang bilangan bait (dan terdapat juga pelayan proksi, sebagai contoh...). Atau sebaliknya, mengapa menyekat Abridged jika kita masih mempunyai padding yang besar di atas 16 bait - simpan 3 bait kadang-kadang ?

Seseorang mendapat tanggapan bahawa Nikolai Durov sangat suka mencipta semula roda, termasuk protokol rangkaian, tanpa sebarang keperluan praktikal yang sebenar.

Pilihan pengangkutan lain, termasuk. Web dan MTProxy, kami tidak akan pertimbangkan sekarang, mungkin dalam post lain, jika ada permintaan. Mengenai MTProxy yang sama ini, mari kita ingat sekarang bahawa sejurus selepas dikeluarkan pada 2018, penyedia dengan cepat belajar untuk menyekatnya, bertujuan untuk menyekat pintasanmenurut saiz bungkusan! Dan juga hakikat bahawa pelayan MTProxy yang ditulis (sekali lagi oleh Waltman) dalam C terlalu terikat dengan spesifik Linux, walaupun ini tidak diperlukan sama sekali (Phil Kulin akan mengesahkan), dan pelayan yang serupa sama ada dalam Go atau Node.js akan muat dalam kurang daripada seratus baris.

Tetapi kami akan membuat kesimpulan tentang celik teknikal orang-orang ini di akhir bahagian, selepas mempertimbangkan isu-isu lain. Buat masa ini, mari beralih ke lapisan OSI 5, sesi - di mana mereka meletakkan sesi MTProto.

Kunci, mesej, sesi, Diffie-Hellman

Mereka meletakkannya di sana tidak sepenuhnya dengan betul... Sesi bukanlah sesi yang sama yang kelihatan dalam antara muka di bawah Sesi aktif. Tetapi mengikut tertib.

Kritikan terhadap protokol dan pendekatan organisasi Telegram. Bahagian 1, teknikal: pengalaman menulis pelanggan dari awal - TL, MT

Jadi kami menerima rentetan bait panjang yang diketahui daripada lapisan pengangkutan. Ini sama ada mesej yang disulitkan atau teks biasa - jika kita masih di peringkat perjanjian utama dan sebenarnya melakukannya. Manakah antara sekumpulan konsep yang dipanggil "kunci" yang sedang kita bicarakan? Mari kita jelaskan isu ini untuk pasukan Telegram itu sendiri (Saya memohon maaf kerana menterjemah dokumentasi saya sendiri dari bahasa Inggeris dengan otak yang letih pada pukul 4 pagi, lebih mudah untuk meninggalkan beberapa frasa sebagaimana adanya):

Terdapat dua entiti yang dipanggil Sesi - satu dalam UI pelanggan rasmi di bawah "sesi semasa", di mana setiap sesi sepadan dengan keseluruhan peranti / OS.
Yang kedua ialah Sesi MTProto, yang mempunyai nombor jujukan mesej (dalam erti kata peringkat rendah) di dalamnya, dan yang mungkin bertahan antara sambungan TCP yang berbeza. Beberapa sesi MTProto boleh dipasang pada masa yang sama, sebagai contoh, untuk mempercepatkan muat turun fail.

Antara dua ni sesi ada konsep kebenaran. Dalam kes yang merosot, kita boleh mengatakan bahawa sesi UI adalah sama seperti kebenaran, tetapi sayangnya, semuanya rumit. Mari lihat:

  • Pengguna pada peranti baharu mula-mula menjana kunci_auth dan menghadkannya ke akaun, contohnya melalui SMS - itulah sebabnya kebenaran
  • Ia berlaku di dalam yang pertama Sesi MTProto, yang mempunyai session_id dalam diri sendiri.
  • Pada langkah ini, gabungan kebenaran ΠΈ session_id boleh dipanggil contohnya - perkataan ini muncul dalam dokumentasi dan kod sesetengah pelanggan
  • Kemudian, pelanggan boleh membuka beberapa Sesi MTProto di bawah yang sama kunci_auth - ke DC yang sama.
  • Kemudian, suatu hari pelanggan perlu meminta fail daripada DC yang lain - dan untuk DC ini yang baru akan dihasilkan kunci_auth !
  • Untuk memaklumkan sistem bahawa bukan pengguna baru yang mendaftar, tetapi sama kebenaran (sesi UI), pelanggan menggunakan panggilan API auth.exportAuthorization di rumah DC auth.importAuthorization dalam DC baharu.
  • Semuanya sama, beberapa mungkin terbuka Sesi MTProto (masing-masing dengan sendiri session_id) ke DC baharu ini, di bawah beliau kunci_auth.
  • Akhir sekali, pelanggan mungkin mahukan Kerahsiaan Ke Hadapan Sempurna. Setiap kunci_auth adalah kekal kunci - setiap DC - dan pelanggan boleh memanggil auth.bindTempAuthKey untuk kegunaan sementara kunci_auth - dan sekali lagi, hanya satu temp_auth_key setiap DC, biasa kepada semua Sesi MTProto ke DC ini.

Perhatikan bahawa garam (dan garam masa depan) juga satu pada kunci_auth mereka. dikongsi antara semua orang Sesi MTProto ke DC yang sama.

Apakah maksud "antara sambungan TCP yang berbeza"? Jadi ini bermakna sesuatu seperti kuki kebenaran di tapak web - ia berterusan (bertahan) banyak sambungan TCP ke pelayan tertentu, tetapi suatu hari ia menjadi rosak. Hanya tidak seperti HTTP, dalam MTProto mesej dalam sesi dinomborkan secara berurutan dan disahkan; jika mereka memasuki terowong, sambungan itu terputus - selepas membuat sambungan baharu, pelayan akan menghantar segala-galanya dalam sesi ini yang tidak dihantar pada sesi sebelumnya. sambungan TCP.

Walau bagaimanapun, maklumat di atas diringkaskan selepas beberapa bulan penyiasatan. Sementara itu, adakah kami melaksanakan pelanggan kami dari awal? - mari kita kembali ke permulaan.

Jadi mari kita menjana auth_key pada Versi Diffie-Hellman dari Telegram. Mari cuba fahami dokumentasi...

Vasily, [19.06.18 20:05] data_with_hash := SHA1(data) + data + (sebarang bait rawak); supaya panjangnya sama dengan 255 bait;
encrypted_data := RSA(data_with_hash, server_public_key); nombor panjang 255-bait (endian besar) dinaikkan kepada kuasa yang diperlukan ke atas modulus yang diperlukan, dan hasilnya disimpan sebagai nombor 256-bait.

Mereka mempunyai DH dadah

Nampak tak DH orang sihat
Tiada dua kunci awam dalam dx

Nah, pada akhirnya ini telah diselesaikan, tetapi sisa kekal - bukti kerja dilakukan oleh pelanggan bahawa dia dapat memfaktorkan nombor itu. Jenis perlindungan terhadap serangan DoS. Dan kunci RSA hanya digunakan sekali dalam satu arah, pada asasnya untuk penyulitan new_nonce. Tetapi sementara operasi yang kelihatan mudah ini akan berjaya, apakah yang perlu anda hadapi?

Vasily, [20.06.18/00/26 XNUMX:XNUMX] Saya belum sampai ke permintaan permohonan lagi

Saya menghantar permintaan ini kepada DH

Dan, dalam dok pengangkutan ia mengatakan bahawa ia boleh bertindak balas dengan 4 bait kod ralat. Itu sahaja

Nah, dia memberitahu saya -404, jadi apa?

Jadi saya memberitahunya: "Tangkap omong kosong anda yang disulitkan dengan kunci pelayan dengan cap jari seperti ini, saya mahu DH," dan ia membalas dengan 404 bodoh

Apakah pendapat anda tentang respons pelayan ini? Apa nak buat? Tidak ada yang bertanya (tetapi lebih lanjut mengenai itu di bahagian kedua).

Di sini semua minat dilakukan di dok

Saya tidak ada apa-apa lagi yang perlu dilakukan, saya hanya bermimpi untuk menukar nombor ke belakang dan ke belakang

Dua nombor 32 bit. Saya membungkus mereka seperti orang lain

Tetapi tidak, kedua-dua ini perlu ditambah pada baris terlebih dahulu sebagai BE

Vadim Goncharov, [20.06.18 15:49] dan kerana ini 404?

Vasily, [20.06.18 15:49] YA!

Vadim Goncharov, [20.06.18 15:50] jadi saya tidak faham apa yang dia boleh "tidak jumpa"

Vasily, [20.06.18 15:50] kira-kira

Saya tidak dapat mencari penguraian sedemikian kepada faktor utama%)

Kami tidak menguruskan pelaporan ralat

Vasily, [20.06.18 20:18] Oh, ada juga MD5. Sudah tiga cincang berbeza

Cap jari kunci dikira seperti berikut:

digest = md5(key + iv)
fingerprint = substr(digest, 0, 4) XOR substr(digest, 4, 4)

SHA1 dan sha2

Jadi mari kita letakkannya auth_key kami menerima saiz 2048 bit menggunakan Diffie-Hellman. Apa yang akan datang? Seterusnya kami mendapati bahawa 1024 bit yang lebih rendah daripada kunci ini tidak digunakan dalam apa jua cara... tetapi mari kita fikirkan tentang perkara ini buat masa ini. Pada langkah ini, kami mempunyai rahsia yang dikongsi dengan pelayan. Analog sesi TLS telah ditubuhkan, yang merupakan prosedur yang sangat mahal. Tetapi pelayan masih tidak tahu apa-apa tentang siapa kami! Belum lagi sebenarnya. kebenaran. Itu. jika anda berfikir dari segi "kata laluan log masuk", seperti yang pernah anda lakukan dalam ICQ, atau sekurang-kurangnya "kunci masuk", seperti dalam SSH (contohnya, pada beberapa gitlab/github). Kami menerima yang tidak dikenali. Bagaimana jika pelayan memberitahu kami "nombor telefon ini diservis oleh DC lain"? Atau pun "nombor telefon anda diharamkan"? Yang terbaik yang boleh kita lakukan ialah menyimpan kunci itu dengan harapan ia akan berguna dan tidak akan busuk ketika itu.

Dengan cara ini, kami "menerima" dengan tempahan. Sebagai contoh, adakah kita mempercayai pelayan? Bagaimana jika ia palsu? Pemeriksaan kriptografi akan diperlukan:

Vasily, [21.06.18 17:53] Mereka menawarkan pelanggan mudah alih untuk menyemak nombor 2kbit untuk keutamaan%)

Tetapi ia tidak jelas sama sekali, nafeijoa

Vasily, [21.06.18 18:02] Dokumen itu tidak menyatakan apa yang perlu dilakukan jika ternyata tidak mudah

Tidak dikatakan. Mari lihat apa yang pelanggan Android rasmi lakukan dalam kes ini? A itulah yang (dan ya, keseluruhan fail itu menarik) - seperti yang mereka katakan, saya akan tinggalkan ini di sini:

278     static const char *goodPrime = "c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b";
279   if (!strcasecmp(prime, goodPrime)) {

Tidak, sudah tentu ia masih ada sebilangan Terdapat ujian untuk keutamaan nombor, tetapi secara peribadi saya tidak lagi mempunyai pengetahuan matematik yang mencukupi.

Okey, kami mendapat kunci utama. Untuk log masuk, i.e. menghantar permintaan, anda perlu melakukan penyulitan lanjut, menggunakan AES.

Kekunci mesej ditakrifkan sebagai 128 bit tengah SHA256 badan mesej (termasuk sesi, ID mesej, dsb.), termasuk bait padding, diawali dengan 32 bait yang diambil daripada kunci kebenaran.

Vasily, [22.06.18 14:08] Purata, jalang, bit

Sudah auth_key. Semua. Di luar mereka... ia tidak jelas daripada dokumen itu. Jangan ragu untuk mengkaji kod sumber terbuka.

Ambil perhatian bahawa MTProto 2.0 memerlukan daripada 12 hingga 1024 bait pelapik, masih tertakluk kepada syarat bahawa panjang mesej yang terhasil boleh dibahagikan dengan 16 bait.

Jadi berapa banyak padding yang perlu anda tambah?

Dan ya, terdapat juga 404 sekiranya berlaku ralat

Jika sesiapa mengkaji rajah dan teks dokumentasi dengan teliti, mereka mendapati tiada MAC di sana. Dan AES itu digunakan dalam mod IGE tertentu yang tidak digunakan di tempat lain. Mereka, sudah tentu, menulis tentang perkara ini dalam Soalan Lazim mereka... Di sini, seperti, kunci mesej itu sendiri juga merupakan cincang SHA bagi data yang dinyahsulit, digunakan untuk menyemak integriti - dan sekiranya berlaku ketidakpadanan, dokumentasi atas sebab tertentu mengesyorkan secara senyap mengabaikan mereka (tetapi bagaimana dengan keselamatan, bagaimana jika mereka melanggar kita?).

Saya bukan seorang kriptografi, mungkin tidak ada yang salah dengan mod ini dalam kes ini dari sudut pandangan teori. Tetapi saya boleh menamakan dengan jelas masalah praktikal, menggunakan Desktop Telegram sebagai contoh. Ia menyulitkan cache setempat (semua D877F783D5D3EF8C ini) dengan cara yang sama seperti mesej dalam MTProto (hanya dalam kes ini versi 1.0), i.e. pertama kekunci mesej, kemudian data itu sendiri (dan di suatu tempat mengetepikan kunci utama auth_key 256 bait, tanpanya msg_key tidak berguna). Jadi, masalah menjadi ketara pada fail besar. Iaitu, anda perlu menyimpan dua salinan data - disulitkan dan dinyahsulit. Dan jika terdapat megabait, atau video penstriman, contohnya?.. Skim klasik dengan MAC selepas teks sifir membolehkan anda membacanya strim, serta-merta menghantarnya. Tetapi dengan MTProto anda perlu melakukannya pada mulanya menyulitkan atau menyahsulit keseluruhan mesej, hanya kemudian memindahkannya ke rangkaian atau ke cakera. Oleh itu, dalam versi terkini Telegram Desktop dalam cache dalam user_data Format lain juga digunakan - dengan AES dalam mod CTR.

Vasily, [21.06.18 01:27] Oh, saya dapati apa itu IGE: IGE ialah percubaan pertama pada "mod penyulitan pengesahan," yang asalnya untuk Kerberos. Ia adalah percubaan yang gagal (ia tidak memberikan perlindungan integriti), dan terpaksa dialih keluar. Itulah permulaan pencarian selama 20 tahun untuk mod penyulitan pengesahan yang berfungsi, yang baru-baru ini memuncak dalam mod seperti OCB dan GCM.

Dan sekarang hujah dari sisi troli:

Pasukan di belakang Telegram, diketuai oleh Nikolai Durov, terdiri daripada enam juara ACM, separuh daripada mereka Ph.D dalam matematik. Mereka mengambil masa kira-kira dua tahun untuk melancarkan versi semasa MTProto.

Itu kelakar. Dua tahun di peringkat bawah

Atau anda boleh ambil tls

Baiklah, katakan kami telah melakukan penyulitan dan nuansa lain. Adakah akhirnya mungkin untuk menghantar permintaan bersiri dalam TL dan menyahsiri respons? Jadi apa dan bagaimana anda perlu menghantar? Di sini, katakan, kaedahnya initConnection, mungkin inikah?

Vasily, [25.06.18 18:46] Memulakan sambungan dan menyimpan maklumat pada peranti dan aplikasi pengguna.

Ia menerima app_id, device_model, system_version, app_version dan lang_code.

Dan beberapa pertanyaan

Dokumentasi seperti biasa. Jangan ragu untuk mengkaji sumber terbuka

Jika semuanya lebih jelas dengan invokeWithLayer, maka apakah yang salah di sini? Ternyata, katakan kami - pelanggan sudah mempunyai sesuatu untuk ditanyakan kepada pelayan - terdapat permintaan yang ingin kami hantar:

Vasily, [25.06.18 19:13] Berdasarkan kod, panggilan pertama dibungkus dalam omong kosong ini, dan omong kosong itu sendiri dibalut dengan invokewithlayer

Mengapa initConnection tidak boleh menjadi panggilan yang berasingan, tetapi mestilah pembungkus? Ya, ternyata, ia mesti dilakukan setiap kali pada permulaan setiap sesi, dan bukan sekali, seperti dengan kunci utama. Tetapi! Ia tidak boleh dipanggil oleh pengguna yang tidak dibenarkan! Sekarang kita telah mencapai tahap di mana ia boleh digunakan Yang ini halaman dokumentasi - dan ia memberitahu kami bahawa...

Hanya sebahagian kecil daripada kaedah API tersedia untuk pengguna yang tidak dibenarkan:

  • auth.sendCode
  • auth.resendCode
  • account.getPassword
  • auth.checkPassword
  • auth.checkPhone
  • auth.signUp
  • auth.signIn
  • auth.importAuthorization
  • help.getConfig
  • help.getNearestDc
  • help.getAppUpdate
  • help.getCdnConfig
  • langpack.getLangPack
  • langpack.getStrings
  • langpack.getDifference
  • langpack.getLanguages
  • langpack.getLanguage

Yang pertama daripada mereka, auth.sendCode, dan terdapat permintaan pertama yang dihargai di mana kami menghantar api_id dan api_hash, dan selepas itu kami menerima SMS dengan kod. Dan jika kami berada di DC yang salah (nombor telefon di negara ini dilayan oleh orang lain, sebagai contoh), maka kami akan menerima ralat dengan nombor DC yang dikehendaki. Untuk mengetahui alamat IP dengan nombor DC yang perlu anda sambungkan, bantu kami help.getConfig. Pada satu masa hanya terdapat 5 penyertaan, tetapi selepas peristiwa terkenal 2018, jumlahnya telah meningkat dengan ketara.

Sekarang mari kita ingat bahawa kita sampai ke peringkat ini pada pelayan tanpa nama. Bukankah terlalu mahal untuk hanya mendapatkan alamat IP? Mengapa tidak melakukan ini, dan operasi lain, di bahagian MTProto yang tidak disulitkan? Saya mendengar bantahan: "bagaimana kita boleh memastikan bahawa bukan RKN yang akan membalas dengan alamat palsu?" Untuk ini kita ingat bahawa, secara amnya, pelanggan rasmi Kunci RSA dibenamkan, iaitu boleh awak saja tanda maklumat ini. Sebenarnya, ini sudah dilakukan untuk maklumat tentang memintas menyekat yang diterima pelanggan melalui saluran lain (secara logiknya, ini tidak boleh dilakukan dalam MTProto sendiri; anda juga perlu tahu di mana untuk menyambung).

OKEY. Pada peringkat kebenaran pelanggan ini, kami belum diberi kuasa dan belum mendaftarkan permohonan kami. Kami hanya mahu melihat apa yang pelayan bertindak balas terhadap kaedah yang tersedia kepada pengguna yang tidak dibenarkan. Dan di sini…

Vasily, [10.07.18 14:45] https://core.telegram.org/method/help.getConfig

config#7dae33e0 [...] = Config;
help.getConfig#c4f9186b = Config;

https://core.telegram.org/api/datacenter

config#232d5905 [...] = Config;
help.getConfig#c4f9186b = Config;

Dalam skema, pertama datang kedua

Dalam skema tdesktop nilai ketiga ialah

Ya, sejak itu, sudah tentu dokumentasi telah dikemas kini. Walaupun ia tidak lama lagi mungkin menjadi tidak relevan lagi. Bagaimanakah pemaju baru tahu? Mungkin jika anda mendaftar permohonan anda, mereka akan memberitahu anda? Vasily melakukan ini, tetapi malangnya, mereka tidak menghantar apa-apa kepadanya (sekali lagi, kita akan membincangkan perkara ini di bahagian kedua).

...Anda perasan bahawa kami telah pun berpindah ke API, i.e. ke peringkat seterusnya, dan terlepas sesuatu dalam topik MTProto? Tidak mengejutkan:

Vasily, [28.06.18 02:04] Mm, mereka sedang menyelongkar beberapa algoritma pada e2e

Mtproto mentakrifkan algoritma dan kunci penyulitan untuk kedua-dua domain, serta sedikit struktur pembalut

Tetapi mereka sentiasa mencampurkan tahap tindanan yang berbeza, jadi tidak selalu jelas di mana mtproto berakhir dan tahap seterusnya bermula

Bagaimana mereka bercampur? Nah, berikut adalah kunci sementara yang sama untuk PFS, sebagai contoh (dengan cara itu, Telegram Desktop tidak boleh melakukannya). Ia dilaksanakan oleh permintaan API auth.bindTempAuthKey, iaitu dari peringkat atasan. Tetapi pada masa yang sama ia mengganggu penyulitan di peringkat bawah - selepas itu, sebagai contoh, anda perlu melakukannya sekali lagi initConnection dan lain-lain, ini tidak hanya permintaan biasa. Apa yang istimewa ialah anda hanya boleh memiliki SATU kunci sementara bagi setiap DC, walaupun medan itu auth_key_id dalam setiap mesej membolehkan anda menukar kunci sekurang-kurangnya setiap mesej, dan pelayan mempunyai hak untuk "melupakan" kunci sementara pada bila-bila masa - dokumentasi tidak mengatakan apa yang perlu dilakukan dalam kes ini... baik, mengapa boleh 'Adakah anda mempunyai beberapa kunci, seperti satu set garam masa depan, dan ?..

Terdapat beberapa perkara lain yang perlu diberi perhatian tentang tema MTProto.

Mesej mesej, msg_id, msg_seqno, pengesahan, ping ke arah yang salah dan keanehan lain

Mengapa anda perlu tahu tentang mereka? Kerana ia "bocor" ke tahap yang lebih tinggi, dan anda perlu menyedarinya apabila bekerja dengan API. Katakan kita tidak berminat dengan msg_key; peringkat bawah telah menyahsulit segala-galanya untuk kita. Tetapi di dalam data yang disulitkan kami mempunyai medan berikut (juga panjang data, jadi kami tahu di mana padding itu, tetapi itu tidak penting):

  • garam - int64
  • session_id - int64
  • message_id β€” int64
  • seq_no - int32

Biar kami mengingatkan anda bahawa hanya ada satu garam untuk keseluruhan DC. Kenapa tahu tentang dia? Bukan hanya kerana ada permintaan get_future_salts, yang memberitahu anda selang mana yang akan sah, tetapi juga kerana jika garam anda "busuk", maka mesej (permintaan) akan hilang begitu saja. Pelayan akan, sudah tentu, melaporkan garam baru dengan mengeluarkan new_session_created - tetapi dengan yang lama anda perlu menghantar semula entah bagaimana, sebagai contoh. Dan isu ini menjejaskan seni bina aplikasi.

Pelayan dibenarkan untuk menggugurkan sesi sama sekali dan bertindak balas dengan cara ini atas banyak sebab. Sebenarnya, apakah sesi MTProto dari pihak pelanggan? Ini adalah dua nombor session_id ΠΈ seq_no mesej dalam sesi ini. Nah, dan sambungan TCP asas, sudah tentu. Katakan pelanggan kami masih tidak tahu melakukan banyak perkara, dia memutuskan sambungan dan menyambung semula. Jika ini berlaku dengan cepat - sesi lama diteruskan dalam sambungan TCP baharu, tingkatkan seq_no selanjutnya. Jika ia mengambil masa yang lama, pelayan boleh memadamkannya, kerana di sebelahnya ia juga beratur, seperti yang kami ketahui.

Apa yang sepatutnya seq_no? Oh, itu soalan rumit. Cuba fahami dengan jujur ​​apa yang dimaksudkan:

Mesej berkaitan kandungan

Mesej yang memerlukan pengakuan yang jelas. Ini termasuk semua pengguna dan banyak mesej perkhidmatan, hampir semuanya kecuali bekas dan pengakuan.

Nombor Urutan Mesej (msg_seqno)

Nombor 32-bit bersamaan dua kali ganda bilangan mesej "berkaitan kandungan" (yang memerlukan pengakuan, dan khususnya yang bukan bekas) yang dicipta oleh pengirim sebelum mesej ini dan kemudiannya ditambah dengan satu jika mesej semasa ialah mesej berkaitan kandungan. Bekas sentiasa dihasilkan selepas keseluruhan kandungannya; oleh itu, nombor jujukannya lebih besar daripada atau sama dengan nombor jujukan mesej yang terkandung di dalamnya.

Apakah jenis sarkas ini dengan kenaikan sebanyak 1, dan kemudian satu lagi sebanyak 2?.. Saya mengesyaki bahawa pada mulanya ia bermaksud "bit paling tidak ketara untuk ACK, selebihnya ialah nombor", tetapi hasilnya tidak sama - khususnya, ia keluar, boleh dihantar beberapa pengesahan yang mempunyai perkara yang sama seq_no! Bagaimana? Nah, sebagai contoh, pelayan menghantar sesuatu kepada kami, menghantarnya, dan kami sendiri berdiam diri, hanya membalas dengan mesej perkhidmatan yang mengesahkan penerimaan mesejnya. Dalam kes ini, pengesahan keluar kami akan mempunyai nombor keluar yang sama. Jika anda sudah biasa dengan TCP dan berpendapat bahawa ini kelihatan seperti liar, tetapi nampaknya tidak begitu liar, kerana dalam TCP seq_no tidak berubah, tetapi pengesahan pergi ke seq_no di sisi lain, saya akan bersegera untuk mengganggu anda. Pengesahan disediakan dalam MTProto TIDAK pada seq_no, seperti dalam TCP, tetapi oleh msg_id !

Apakah ini msg_id, yang paling penting dalam bidang ini? Pengecam mesej unik, seperti namanya. Ia ditakrifkan sebagai nombor 64-bit, bit terendah sekali lagi mempunyai sihir "pelayan-bukan-pelayan", dan selebihnya ialah cap waktu Unix, termasuk bahagian pecahan, beralih 32 bit ke kiri. Itu. cap waktu per se (dan mesej dengan masa yang terlalu banyak berbeza akan ditolak oleh pelayan). Dari sini ternyata secara umum ini adalah pengecam yang global untuk pelanggan. Memandangkan itu - mari kita ingat session_id - kami dijamin: Dalam apa jua keadaan, mesej yang dimaksudkan untuk satu sesi tidak boleh dihantar ke sesi yang berbeza. Iaitu, ternyata sudah ada 3 tahap - sesi, nombor sesi, id mesej. Mengapa terlalu rumit, misteri ini sangat hebat.

Oleh itu, msg_id diperlukan untuk...

RPC: permintaan, respons, ralat. Pengesahan.

Seperti yang anda mungkin perasan, tiada jenis atau fungsi "buat permintaan RPC" khas di mana-mana dalam rajah, walaupun terdapat jawapan. Lagipun, kami mempunyai mesej berkaitan kandungan! Itu dia, mana-mana mesej itu boleh menjadi permintaan! Atau tidak menjadi. Lagipun, masing-masing ada msg_id. Tetapi ada jawapan:

rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult;

Di sinilah ia ditunjukkan kepada mesej yang mana ini adalah respons. Oleh itu, pada peringkat teratas API, anda perlu mengingati jumlah permintaan anda - Saya rasa tidak perlu menjelaskan bahawa kerja itu tidak segerak, dan mungkin terdapat beberapa permintaan yang sedang dijalankan pada masa yang sama, jawapan yang boleh dikembalikan dalam sebarang susunan? Pada dasarnya, daripada ini dan mesej ralat seperti tiada pekerja, seni bina di sebalik ini boleh dikesan: pelayan yang mengekalkan sambungan TCP dengan anda ialah pengimbang bahagian hadapan, ia memajukan permintaan ke bahagian belakang dan mengumpulnya kembali melalui message_id. Nampaknya semuanya di sini jelas, logik dan baik.

Ya?.. Dan jika anda memikirkannya? Lagipun, respons RPC itu sendiri juga mempunyai medan msg_id! Adakah kita perlu menjerit pada pelayan "anda tidak menjawab jawapan saya!"? Dan ya, apa yang ada tentang pengesahan? Mengenai halaman mesej tentang mesej memberitahu kita apa itu

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

dan ia mesti dilakukan oleh setiap pihak. Tetapi tidak selalu! Jika anda menerima RpcResult, ia sendiri berfungsi sebagai pengesahan. Iaitu, pelayan boleh membalas permintaan anda dengan MsgsAck - seperti, "Saya menerimanya." RpcResult boleh bertindak balas dengan segera. Boleh jadi kedua-duanya.

Dan ya, anda masih perlu menjawab jawapannya! Pengesahan. Jika tidak, pelayan akan menganggapnya tidak boleh dihantar dan menghantarnya kembali kepada anda sekali lagi. Walaupun selepas disambung semula. Tetapi di sini, sudah tentu, isu tamat masa timbul. Mari lihat mereka sedikit kemudian.

Sementara itu, mari kita lihat kemungkinan ralat pelaksanaan pertanyaan.

rpc_error#2144ca19 error_code:int error_message:string = RpcError;

Oh, seseorang akan berseru, ini adalah format yang lebih berperikemanusiaan - ada garis! Ambil masa anda. Di sini senarai ralat, tetapi sudah tentu tidak lengkap. Daripadanya kita belajar bahawa kod itu adalah sesuatu seperti Ralat HTTP (tentu saja, semantik respons tidak dihormati, di beberapa tempat ia diedarkan secara rawak di antara kod), dan barisnya kelihatan seperti CAPITAL_LETTERS_AND_NUMBERS. Contohnya, PHONE_NUMBER_OCCUPIED atau FILE_PART_Π₯_MISSING. Maksudnya, anda masih memerlukan talian ini menghuraikan. Contohnya FLOOD_WAIT_3600 bermakna anda perlu menunggu sejam, dan PHONE_MIGRATE_5, bahawa nombor telefon dengan awalan ini mesti didaftarkan dalam DC ke-5. Kami mempunyai bahasa jenis, bukan? Kami tidak memerlukan hujah daripada rentetan, yang biasa akan dilakukan, okay.

Sekali lagi, ini bukan pada halaman mesej perkhidmatan, tetapi, seperti biasa dengan projek ini, maklumat boleh didapati pada halaman dokumentasi yang lain. Atau melemparkan syak wasangka. Pertama, lihat, pelanggaran menaip/lapisan - RpcError boleh bersarang RpcResult. Kenapa tidak di luar? Apa yang kita tidak ambil kira?.. Sehubungan itu, di manakah jaminan bahawa RpcError TIDAK boleh tertanam dalam RpcResult, tetapi secara langsung atau bersarang dalam jenis lain?.. Dan jika ia tidak boleh, mengapa ia tidak berada di peringkat teratas, i.e. ia hilang req_msg_id ? ..

Tetapi mari kita teruskan tentang mesej perkhidmatan. Pelanggan mungkin berfikir bahawa pelayan sedang berfikir untuk masa yang lama dan membuat permintaan yang menarik ini:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Terdapat tiga kemungkinan jawapan untuk soalan ini, sekali lagi bersilang dengan mekanisme pengesahan; cuba memahami apa yang sepatutnya (dan senarai umum jenis yang tidak memerlukan pengesahan) diserahkan kepada pembaca sebagai kerja rumah (nota: maklumat dalam kod sumber Desktop Telegram tidak lengkap).

Ketagihan dadah: status mesej

Secara umumnya, banyak tempat di TL, MTProto dan Telegram secara umum meninggalkan perasaan degil, tetapi kerana kesopanan, kebijaksanaan dan lain-lain kemahiran insaniah Kami dengan sopan mendiamkan diri mengenainya, dan menapis kata-kata lucah dalam dialog. Walau bagaimanapun, tempat iniОkebanyakan halaman adalah tentang mesej tentang mesej Ia mengejutkan walaupun bagi saya, yang telah lama bekerja dengan protokol rangkaian dan telah melihat basikal dengan pelbagai tahap kebengkokan.

Ia bermula dengan tidak berbahaya, dengan pengesahan. Seterusnya mereka memberitahu kami tentang

bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;

Nah, semua orang yang mula bekerja dengan MTProto perlu berurusan dengan mereka; dalam kitaran "diperbetulkan - disusun semula - dilancarkan", mendapat ralat nombor atau garam yang telah berjaya menjadi buruk semasa pengeditan adalah perkara biasa. Walau bagaimanapun, terdapat dua perkara di sini:

  1. Ini bermakna mesej asal telah hilang. Kami perlu membuat beberapa baris gilir, kami akan melihatnya kemudian.
  2. Apakah nombor ralat pelik ini? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... mana nombor lain, Tommy?

Dokumentasi menyatakan:

Niatnya ialah nilai kod_ralat dikumpulkan (kod_error >> 4): contohnya, kod 0x40 β€” 0x4f sepadan dengan ralat dalam penguraian bekas.

tetapi, pertama, peralihan ke arah lain, dan kedua, tidak mengapa, di manakah kod lain? Dalam kepala penulis?.. Namun, ini adalah perkara remeh.

Ketagihan bermula dalam mesej tentang status mesej dan salinan mesej:

  • Permintaan untuk Maklumat Status Mesej
    Jika mana-mana pihak tidak menerima maklumat tentang status mesej keluarnya untuk sementara waktu, ia mungkin memintanya secara eksplisit daripada pihak yang satu lagi:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Mesej Maklumat berkenaan Status Mesej
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Di sini, info ialah rentetan yang mengandungi tepat satu bait status mesej untuk setiap mesej daripada senarai msg_ids masuk:

    • 1 = tiada apa yang diketahui tentang mesej tersebut (msg_id terlalu rendah, pihak lain mungkin terlupa)
    • 2 = mesej tidak diterima (msg_id termasuk dalam julat pengecam yang disimpan; namun, pihak yang satu lagi pastinya tidak menerima mesej seperti itu)
    • 3 = mesej tidak diterima (msg_id terlalu tinggi; namun pihak yang satu lagi pasti belum menerimanya lagi)
    • 4 = mesej diterima (perhatikan bahawa respons ini juga pada masa yang sama pengakuan resit)
    • +8 = mesej sudah diakui
    • +16 = mesej tidak memerlukan pengakuan
    • +32 = Pertanyaan RPC yang terkandung dalam mesej sedang diproses atau pemprosesan sudah selesai
    • +64 = respons berkaitan kandungan kepada mesej yang telah dihasilkan
    • +128 = pihak lain mengetahui hakikat bahawa mesej sudah diterima
      Respons ini tidak memerlukan pengakuan. Ia adalah pengakuan terhadap msgs_state_req yang berkaitan, dalam dan dengan sendirinya.
      Ambil perhatian bahawa jika tiba-tiba ternyata pihak yang satu lagi tidak mempunyai mesej yang kelihatan seperti telah dihantar kepadanya, mesej itu hanya boleh dihantar semula. Walaupun pihak lain harus menerima dua salinan mesej pada masa yang sama, pendua akan diabaikan. (Jika terlalu banyak masa telah berlalu, dan msg_id asal tidak lagi sah, mesej itu hendaklah dibungkus dalam msg_copy).
  • Komunikasi Sukarela Status Mesej
    Mana-mana pihak boleh secara sukarela memberitahu pihak yang satu lagi tentang status mesej yang dihantar oleh pihak yang satu lagi.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Komunikasi Sukarela Lanjutan Status Satu Mesej
    ...
    msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
    msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
  • Permintaan Eksplisit untuk Menghantar Semula Mesej
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    Pihak jauh segera bertindak balas dengan menghantar semula mesej yang diminta […]
  • Permintaan Eksplisit untuk Menghantar Semula Jawapan
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    Pihak terpencil segera bertindak balas dengan menghantar semula jawapan kepada mesej yang diminta […]
  • Salinan Mesej
    Dalam sesetengah situasi, mesej lama dengan msg_id yang tidak lagi sah perlu dihantar semula. Kemudian, ia dibungkus dalam bekas salinan:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Sebaik sahaja diterima, mesej diproses seolah-olah pembalut tidak ada. Walau bagaimanapun, jika diketahui dengan pasti bahawa mesej orig_message.msg_id telah diterima, maka mesej baharu itu tidak diproses (sementara pada masa yang sama, ia dan orig_message.msg_id diterima). Nilai orig_message.msg_id mestilah lebih rendah daripada msg_id bekas.

Biar pun senyap apa msgs_state_info sekali lagi telinga TL yang belum selesai menonjol (kami memerlukan vektor bait, dan dalam dua bit bawah terdapat enum, dan dalam dua bit yang lebih tinggi terdapat bendera). Intinya berbeza. Adakah sesiapa faham mengapa semua ini dalam amalan? dalam pelanggan sebenar perlu?.. Dengan kesukaran, tetapi seseorang boleh membayangkan beberapa faedah jika seseorang terlibat dalam penyahpepijatan, dan dalam mod interaktif - tanya pelayan apa dan bagaimana. Tetapi di sini permintaan diterangkan pergi balik.

Oleh itu, setiap pihak bukan sahaja mesti menyulitkan dan menghantar mesej, tetapi juga menyimpan data tentang diri mereka sendiri, tentang respons kepada mereka, untuk jangka masa yang tidak diketahui. Dokumentasi tidak menerangkan sama ada pemasaan atau kebolehgunaan praktikal ciri ini. dengan cara tidak. Apa yang paling menakjubkan ialah ia sebenarnya digunakan dalam kod pelanggan rasmi! Rupa-rupanya mereka diberitahu sesuatu yang tidak termasuk dalam dokumentasi awam. Fahami dari kod kenapa, tidak lagi semudah dalam kes TL - ia bukan bahagian (relatif) terpencil secara logik, tetapi sekeping yang terikat pada seni bina aplikasi, i.e. akan memerlukan lebih banyak masa untuk memahami kod aplikasi.

Ping dan pemasaan. Beratur.

Daripada segala-galanya, jika kita mengingati tekaan tentang seni bina pelayan (pengedaran permintaan merentasi bahagian belakang), perkara yang agak menyedihkan berlaku - walaupun semua jaminan penghantaran dalam TCP (sama ada data dihantar, atau anda akan dimaklumkan tentang jurang, tetapi data akan dihantar sebelum masalah berlaku), bahawa pengesahan dalam MTProto sendiri - tiada jaminan. Pelayan boleh kehilangan atau membuang mesej anda dengan mudah, dan tiada apa yang boleh dilakukan mengenainya, hanya gunakan pelbagai jenis tongkat.

Dan pertama sekali - baris gilir mesej. Nah, dengan satu perkara, semuanya jelas sejak awal - mesej yang tidak disahkan mesti disimpan dan dibenci. Dan selepas pukul berapa? Dan pelawak itu mengenalinya. Mungkin mesej perkhidmatan ketagih itu entah bagaimana menyelesaikan masalah ini dengan tongkat, katakan, dalam Desktop Telegram terdapat kira-kira 4 baris gilir yang sepadan dengan mereka (mungkin lebih banyak, seperti yang telah disebutkan, untuk ini anda perlu menyelidiki kod dan seni binanya dengan lebih serius; pada masa yang sama masa, kita Kami tahu bahawa ia tidak boleh diambil sebagai sampel; beberapa jenis daripada skim MTProto tidak digunakan di dalamnya).

Kenapa ini terjadi? Mungkin, pengaturcara pelayan tidak dapat memastikan kebolehpercayaan dalam kelompok, atau penimbal pada pengimbang hadapan, dan memindahkan masalah ini kepada klien. Kerana putus asa, Vasily cuba melaksanakan pilihan alternatif, dengan hanya dua baris gilir, menggunakan algoritma dari TCP - mengukur RTT ke pelayan dan melaraskan saiz "tetingkap" (dalam mesej) bergantung pada bilangan permintaan yang tidak disahkan. Iaitu, heuristik kasar untuk menilai beban pelayan ialah berapa banyak permintaan kami yang boleh dikunyah pada masa yang sama dan tidak hilang.

Nah, itu, anda faham, bukan? Jika anda perlu melaksanakan TCP sekali lagi di atas protokol yang berjalan di atas TCP, ini menunjukkan protokol yang direka bentuk dengan sangat buruk.

Oh ya, mengapa anda memerlukan lebih daripada satu baris gilir, dan apakah maksudnya bagi seseorang yang bekerja dengan API peringkat tinggi? Lihat, anda membuat permintaan, menyusunnya secara bersiri, tetapi selalunya anda tidak boleh menghantarnya dengan segera. kenapa? Kerana jawapannya akan menjadi msg_id, yang bersifat sementaraΠ°Saya seorang label, tugasan yang paling baik ditangguhkan sehingga selewat mungkin - sekiranya pelayan menolaknya kerana masa yang tidak sepadan antara kita dan dia (sudah tentu, kita boleh membuat tongkat yang mengalihkan masa kita dari sekarang ke pelayan dengan menambahkan delta yang dikira daripada respons pelayan - pelanggan rasmi melakukan ini, tetapi ia adalah kasar dan tidak tepat kerana penimbalan). Oleh itu, apabila anda membuat permintaan dengan panggilan fungsi setempat daripada pustaka, mesej itu melalui peringkat berikut:

  1. Ia terletak dalam satu baris gilir dan menunggu penyulitan.
  2. Dilantik msg_id dan mesej pergi ke baris gilir lain - kemungkinan pemajuan; hantar ke soket.
  3. a) Pelayan membalas MsgsAck - mesej telah dihantar, kami memadamkannya daripada "baris gilir lain".
    b) Atau sebaliknya, dia tidak menyukai sesuatu, dia menjawab badmsg - hantar semula dari "baris lain"
    c) Tiada apa-apa yang diketahui, mesej itu perlu dihantar semula dari baris gilir lain - tetapi tidak diketahui dengan tepat bila.
  4. Pelayan akhirnya bertindak balas RpcResult - respons sebenar (atau ralat) - bukan sahaja dihantar, tetapi juga diproses.

Mungkin, penggunaan bekas boleh menyelesaikan sebahagian masalah. Ini adalah apabila sekumpulan mesej dimasukkan ke dalam satu, dan pelayan membalas dengan pengesahan kepada kesemuanya sekali gus, dalam satu msg_id. Tetapi dia juga akan menolak pek ini, jika ada masalah, secara keseluruhannya.

Dan pada ketika ini pertimbangan bukan teknikal mula dimainkan. Dari pengalaman, kita telah melihat banyak tongkat, dan sebagai tambahan, kita kini akan melihat lebih banyak contoh nasihat dan seni bina yang tidak baik - dalam keadaan sedemikian, adakah patut dipercayai dan membuat keputusan sedemikian? Persoalannya adalah retorik (tentu saja tidak).

Apa yang kita bincangkan? Jika mengenai topik "mesej dadah tentang mesej" anda masih boleh membuat spekulasi dengan bantahan seperti "anda bodoh, anda tidak memahami rancangan cemerlang kami!" (jadi tulis dokumentasi dahulu, seperti yang sepatutnya orang biasa, dengan rasional dan contoh pertukaran paket, kemudian kita akan bercakap), kemudian masa/masa tamat adalah soalan praktikal dan khusus semata-mata, semuanya di sini telah diketahui sejak sekian lama. Apakah yang diberitahu oleh dokumentasi kepada kami tentang tamat masa?

Pelayan biasanya mengakui penerimaan mesej daripada klien (biasanya, pertanyaan RPC) menggunakan respons RPC. Jika respons sudah lama datang, pelayan mungkin menghantar pengakuan resit dahulu, dan agak kemudian, respons RPC itu sendiri.

Pelanggan biasanya mengakui penerimaan mesej daripada pelayan (biasanya, respons RPC) dengan menambahkan pengakuan kepada pertanyaan RPC seterusnya jika ia tidak dihantar terlalu lewat (jika ia dijana, katakan, 60-120 saat selepas resit daripada mesej daripada pelayan). Walau bagaimanapun, jika untuk jangka masa yang panjang tiada sebab untuk menghantar mesej kepada pelayan atau jika terdapat sejumlah besar mesej yang tidak diketahui daripada pelayan (katakan, lebih 16 tahun), pelanggan menghantar pengakuan yang berdiri sendiri.

... Saya terjemah: kita sendiri tidak tahu berapa banyak dan bagaimana kita memerlukannya, jadi mari kita anggap bahawa biarkan ia menjadi seperti ini.

Dan tentang ping:

Mesej Ping (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Respons biasanya dikembalikan kepada sambungan yang sama:

pong#347773c5 msg_id:long ping_id:long = Pong;

Mesej ini tidak memerlukan pengakuan. Pong dihantar hanya sebagai tindak balas kepada ping manakala ping boleh dimulakan oleh kedua-dua belah pihak.

Penutupan Sambungan Tertunda + PING

ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;

Berfungsi seperti ping. Di samping itu, selepas ini diterima, pelayan memulakan pemasa yang akan menutup sambungan semasa disconnect_delay beberapa saat kemudian melainkan ia menerima mesej baharu jenis yang sama yang secara automatik menetapkan semula semua pemasa sebelumnya. Jika pelanggan menghantar ping ini sekali setiap 60 saat, contohnya, ia mungkin menetapkan disconnect_delay sama dengan 75 saat.

Adakah awak gila?! Dalam 60 saat, kereta api akan memasuki stesen, menurunkan dan mengambil penumpang, dan sekali lagi terputus hubungan di dalam terowong. Dalam 120 saat, semasa anda mendengarnya, ia akan tiba di satu lagi dan kemungkinan besar sambungan akan terputus. Nah, jelas dari mana datangnya kaki - "Saya mendengar deringan, tetapi tidak tahu di mana", terdapat algoritma Nagl dan pilihan TCP_NODELAY, bertujuan untuk kerja interaktif. Tetapi, maafkan saya, berpegang pada nilai lalainya - 200 Millidetik Jika anda benar-benar ingin menggambarkan sesuatu yang serupa dan menjimatkan beberapa paket yang mungkin, kemudian tangguhkannya selama 5 saat, atau apa sahaja "Pengguna sedang menaip..." tamat masa mesej sekarang. Tetapi tidak lagi.

Dan akhirnya, ping. Iaitu, menyemak keaktifan sambungan TCP. Memang kelakar, tetapi kira-kira 10 tahun yang lalu saya menulis teks kritikal tentang utusan asrama fakulti kami - pengarang di sana juga melakukan ping ke pelayan daripada pelanggan, dan bukan sebaliknya. Tetapi pelajar tahun 3 adalah satu perkara, dan pejabat antarabangsa adalah satu lagi, bukan?..

Pertama, sedikit program pendidikan. Sambungan TCP, jika tiada pertukaran paket, boleh hidup selama berminggu-minggu. Ini adalah baik dan buruk, bergantung pada tujuannya. Adalah baik jika anda mempunyai sambungan SSH yang terbuka ke pelayan, anda bangun dari komputer, but semula penghala, kembali ke tempat anda - sesi melalui pelayan ini tidak koyak (anda tidak menaip apa-apa, tidak ada paket) , ianya mudah. Adalah buruk jika terdapat beribu-ribu pelanggan pada pelayan, masing-masing menggunakan sumber (hello, Postgres!), dan hos pelanggan mungkin telah but semula sejak lama - tetapi kami tidak akan mengetahuinya.

Sistem sembang/IM jatuh ke dalam kes kedua atas satu sebab tambahan - status dalam talian. Jika pengguna "jatuh", anda perlu memaklumkan rakannya tentang perkara ini. Jika tidak, anda akan mengalami kesilapan yang dibuat oleh pencipta Jabber (dan diperbetulkan selama 20 tahun) - pengguna telah memutuskan sambungan, tetapi mereka terus menulis mesej kepadanya, mempercayai bahawa dia berada dalam talian (yang juga hilang sepenuhnya dalam ini. beberapa minit sebelum putus sambungan ditemui). Tidak, pilihan TCP_KEEPALIVE, yang ramai orang yang tidak memahami cara pemasa TCP berfungsi secara rawak (dengan menetapkan nilai liar seperti berpuluh-puluh saat), tidak akan membantu di sini - anda perlu memastikan bahawa bukan sahaja kernel OS mesin pengguna masih hidup, tetapi juga berfungsi seperti biasa, dapat bertindak balas, dan aplikasi itu sendiri (adakah anda fikir ia tidak boleh membekukan? Telegram Desktop pada Ubuntu 18.04 membeku untuk saya lebih daripada sekali).

Itulah sebabnya anda perlu ping pelayan pelanggan, dan bukan sebaliknya - jika pelanggan melakukan ini, jika sambungan terputus, ping tidak akan dihantar, matlamat tidak akan tercapai.

Apa yang kita lihat di Telegram? Ia betul-betul bertentangan! Nah, begitulah. Secara rasmi, sudah tentu, kedua-dua pihak boleh ping antara satu sama lain. Dalam amalan, pelanggan menggunakan tongkat ping_delay_disconnect, yang menetapkan pemasa pada pelayan. Baiklah, maaf, bukan terpulang kepada pelanggan untuk menentukan berapa lama dia mahu tinggal di sana tanpa ping. Pelayan, berdasarkan bebannya, lebih mengetahui. Tetapi, sudah tentu, jika anda tidak keberatan dengan sumber, maka anda akan menjadi Pinocchio jahat anda sendiri, dan tongkat akan melakukan...

Bagaimana ia sepatutnya direka?

Saya percaya fakta di atas jelas menunjukkan bahawa pasukan Telegram/VKontakte tidak begitu cekap dalam bidang pengangkutan (dan lebih rendah) tahap rangkaian komputer dan kelayakan rendah mereka dalam perkara yang berkaitan.

Mengapa ia ternyata begitu rumit, dan bagaimanakah arkitek Telegram boleh cuba membantah? Hakikat bahawa mereka cuba membuat sesi yang bertahan sambungan TCP putus, iaitu, apa yang tidak dihantar sekarang, kami akan menyampaikan kemudian. Mereka mungkin juga cuba membuat pengangkutan UDP, tetapi mereka menghadapi kesukaran dan meninggalkannya (itulah sebabnya dokumentasi itu kosong - tiada apa yang boleh dibanggakan). Tetapi disebabkan oleh kekurangan pemahaman tentang cara rangkaian secara umum dan TCP khususnya berfungsi, di mana anda boleh bergantung padanya, dan di mana anda perlu melakukannya sendiri (dan bagaimana), dan percubaan untuk menggabungkan ini dengan kriptografi "dua burung dengan satu batu”, inilah hasilnya.

Bagaimanakah ia perlu? Berdasarkan fakta bahawa msg_id adalah cap masa yang diperlukan dari sudut pandangan kriptografi untuk mengelakkan serangan ulang tayang, adalah satu kesilapan untuk melampirkan fungsi pengecam unik padanya. Oleh itu, tanpa mengubah secara asas seni bina semasa (apabila aliran Kemas Kini dijana, itu topik API peringkat tinggi untuk bahagian lain dalam siri siaran ini), seseorang perlu:

  1. Pelayan yang memegang sambungan TCP kepada pelanggan bertanggungjawab - jika ia telah membaca dari soket, sila akui, proses atau kembalikan ralat, tiada kerugian. Kemudian pengesahan bukan vektor id, tetapi hanya "seq_no yang terakhir diterima" - hanya nombor, seperti dalam TCP (dua nombor - seq anda dan yang disahkan). Kami sentiasa berada dalam sesi itu, bukan?
  2. Cap masa untuk menghalang serangan ulangan menjadi medan yang berasingan, a la nonce. Ia diperiksa, tetapi tidak menjejaskan apa-apa lagi. Cukup dan uint32 - jika garam kita berubah sekurang-kurangnya setiap setengah hari, kita boleh memperuntukkan 16 bit kepada bit tertib rendah bahagian integer masa semasa, selebihnya - kepada bahagian pecahan saat (seperti sekarang).
  3. Dialih keluar msg_id sama sekali - dari sudut pandangan membezakan permintaan pada bahagian belakang, terdapat, pertama, id pelanggan, dan kedua, id sesi, menggabungkannya. Sehubungan itu, hanya satu perkara yang mencukupi sebagai pengecam permintaan seq_no.

Ini juga bukan pilihan yang paling berjaya; rawak lengkap boleh berfungsi sebagai pengecam - ini sudah dilakukan dalam API peringkat tinggi apabila menghantar mesej, dengan cara itu. Adalah lebih baik untuk membuat semula sepenuhnya seni bina daripada relatif kepada mutlak, tetapi ini adalah topik untuk bahagian lain, bukan siaran ini.

API?

Ta-daam! Oleh itu, setelah bergelut melalui laluan yang penuh dengan kesakitan dan tongkat, kami akhirnya dapat menghantar sebarang permintaan kepada pelayan dan menerima sebarang jawapan kepada mereka, serta menerima kemas kini daripada pelayan (bukan sebagai tindak balas kepada permintaan, tetapi ia sendiri menghantar kami, seperti PUSH, jika ada yang lebih jelas seperti itu).

Perhatian, kini akan ada satu-satunya contoh dalam Perl dalam artikel! (bagi mereka yang tidak biasa dengan sintaks, hujah pertama bless ialah struktur data objek, yang kedua ialah kelasnya):

2019.10.24 12:00:51 $1 = {
'cb' => 'TeleUpd::__ANON__',
'out' => bless( {
'filter' => bless( {}, 'Telegram::ChannelMessagesFilterEmpty' ),
'channel' => bless( {
'access_hash' => '-6698103710539760874',
'channel_id' => '1380524958'
}, 'Telegram::InputPeerChannel' ),
'pts' => '158503',
'flags' => 0,
'limit' => 0
}, 'Telegram::Updates::GetChannelDifference' ),
'req_id' => '6751291954012037292'
};
2019.10.24 12:00:51 $1 = {
'in' => bless( {
'req_msg_id' => '6751291954012037292',
'result' => bless( {
'pts' => 158508,
'flags' => 3,
'final' => 1,
'new_messages' => [],
'users' => [],
'chats' => [
bless( {
'title' => 'Π₯ΡƒΠ»ΠΈΠ½ΠΎΠΌΠΈΠΊΠ°',
'username' => 'hoolinomics',
'flags' => 8288,
'id' => 1380524958,
'access_hash' => '-6698103710539760874',
'broadcast' => 1,
'version' => 0,
'photo' => bless( {
'photo_small' => bless( {
'volume_id' => 246933270,
'file_reference' => '
'secret' => '1854156056801727328',
'local_id' => 228648,
'dc_id' => 2
}, 'Telegram::FileLocation' ),
'photo_big' => bless( {
'dc_id' => 2,
'local_id' => 228650,
'file_reference' => '
'secret' => '1275570353387113110',
'volume_id' => 246933270
}, 'Telegram::FileLocation' )
}, 'Telegram::ChatPhoto' ),
'date' => 1531221081
}, 'Telegram::Channel' )
],
'timeout' => 300,
'other_updates' => [
bless( {
'pts_count' => 0,
'message' => bless( {
'post' => 1,
'id' => 852,
'flags' => 50368,
'views' => 8013,
'entities' => [
bless( {
'length' => 20,
'offset' => 0
}, 'Telegram::MessageEntityBold' ),
bless( {
'length' => 18,
'offset' => 480,
'url' => 'https://alexeymarkov.livejournal.com/[url_Π²Ρ‹Ρ€Π΅Π·Π°Π½].html'
}, 'Telegram::MessageEntityTextUrl' )
],
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'text' => '???? 165',
'data' => 'send_reaction_0'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'data' => 'send_reaction_1',
'text' => '???? 9'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'message' => 'А Π²ΠΎΡ‚ ΠΈ новая ΠΊΠ½ΠΈΠ³Π°! 
// [тСкст сообщСния Π²Ρ‹Ρ€Π΅Π·Π°Π½ Ρ‡Ρ‚ΠΎΠ± Π½Π΅ Π½Π°Ρ€ΡƒΡˆΠ°Ρ‚ΡŒ ΠΏΡ€Π°Π²ΠΈΠ» Π₯Π°Π±Ρ€Π° ΠΎ Ρ€Π΅ΠΊΠ»Π°ΠΌΠ΅]
Π½Π°ΠΏΠ΅Ρ‡Π°Ρ‚Π°ΡŽ.',
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'date' => 1571724559,
'edit_date' => 1571907562
}, 'Telegram::Message' ),
'pts' => 158508
}, 'Telegram::UpdateEditChannelMessage' ),
bless( {
'pts' => 158508,
'message' => bless( {
'edit_date' => 1571907589,
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'date' => 1571807301,
'message' => 'ΠŸΠΎΡ‡Π΅ΠΌΡƒ Π’Ρ‹ считаСтС Facebook ΠΏΠ»ΠΎΡ…ΠΎΠΉ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠ΅ΠΉ? ΠœΠΎΠΆΠ΅Ρ‚Π΅ ΠΏΡ€ΠΎΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ? По-ΠΌΠΎΠ΅ΠΌΡƒ, это ΡˆΠΈΠΊΠ°Ρ€Π½Π°Ρ компания. Π‘Π΅Π· Π΄ΠΎΠ»Π³ΠΎΠ², с Ρ…ΠΎΡ€ΠΎΡˆΠ΅ΠΉ ΠΏΡ€ΠΈΠ±Ρ‹Π»ΡŒΡŽ, Π° Ссли Ρ€Π΅ΡˆΠ°Ρ‚ Π΄ΠΈΠ²Ρ‹ ΠΏΠ»Π°Ρ‚ΠΈΡ‚ΡŒ, Ρ‚ΠΎ ΠΈ Π΅Ρ‰Π΅ ΠΌΠΎΠ³ΡƒΡ‚ Π½Π΅Ρ…ΠΈΠ»ΠΎ ΠΏΠΎΠ΄ΠΎΡ€ΠΎΠΆΠ°Ρ‚ΡŒ.
Для мСня ΠΎΡ‚Π²Π΅Ρ‚ ΡΠΎΠ²Π΅Ρ€ΡˆΠ΅Π½Π½ΠΎ ΠΎΡ‡Π΅Π²ΠΈΠ΄Π΅Π½: ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ Facebook Π΄Π΅Π»Π°Π΅Ρ‚ уТасный ΠΏΠΎ качСству ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚. Π”Π°, Ρƒ Π½Π΅Π³ΠΎ монопольноС ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈ Π΄Π°, ΠΈΠΌ ΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΎΠ³Ρ€ΠΎΠΌΠ½ΠΎΠ΅ количСство людСй. Но ΠΌΠΈΡ€ Π½Π΅ стоит Π½Π° мСстС. Когда-Ρ‚ΠΎ Π²Π»Π°Π΄Π΅Π»ΡŒΡ†Π°ΠΌ Нокии Π±Ρ‹Π»ΠΎ смСшно ΠΎΡ‚ ΠΏΠ΅Ρ€Π²ΠΎΠ³ΠΎ Айфона. Они Π΄ΡƒΠΌΠ°Π»ΠΈ, Ρ‡Ρ‚ΠΎ Π»ΡƒΡ‡ΡˆΠ΅ Нокии Π½ΠΈΡ‡Π΅Π³ΠΎ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΈ ΠΎΠ½Π° навсСгда останСтся самым ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΌ, красивым ΠΈ Ρ‚Π²Ρ‘Ρ€Π΄Ρ‹ΠΌ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½ΠΎΠΌ - ΠΈ доля Ρ€Ρ‹Π½ΠΊΠ° это краснорСчиво дСмонстрировала. Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΈΠΌ Π½Π΅ смСшно.
ΠšΠΎΠ½Π΅Ρ‡Π½ΠΎ, Ρ€Π΅ΠΏΡ‚ΠΈΠ»ΠΎΠΈΠ΄Ρ‹ ΡΠΎΠΏΡ€ΠΎΡ‚ΠΈΠ²Π»ΡΡŽΡ‚ΡΡ Π½Π°ΠΏΠΎΡ€Ρƒ ΠΌΠΎΠ»ΠΎΠ΄Ρ‹Ρ… Π³Π΅Π½ΠΈΠ΅Π²: Ρ‚Π°ΠΊ Π¦ΡƒΠΊΠ΅Ρ€Π±Π΅Ρ€Π³ΠΎΠΌ Π±Ρ‹Π» ΠΏΠΎΠΆΡ€Π°Π½ Whatsapp, ΠΏΠΎΡ‚ΠΎΠΌ Instagram. Но всё ΠΈΠΌ Π½Π΅ ΠΏΠΎΠΆΡ€Π°Ρ‚ΡŒ, Паша Π”ΡƒΡ€ΠΎΠ² Π½Π΅ продаётся!
Π’Π°ΠΊ Π±ΡƒΠ΄Π΅Ρ‚ ΠΈ с ЀСйсбуком. НСльзя всё врСмя Π΄Π΅Π»Π°Ρ‚ΡŒ Π³ΠΎΠ²Π½ΠΎ. ΠšΡ‚ΠΎ-Ρ‚ΠΎ ΠΊΠΎΠ³Π΄Π°-Ρ‚ΠΎ сдСлаСт Ρ…ΠΎΡ€ΠΎΡˆΠΈΠΉ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚, ΠΊΡƒΠ΄Π° всё ΠΈ ΡƒΠΉΠ΄ΡƒΡ‚.
#соцсСти #facebook #Π°ΠΊΡ†ΠΈΠΈ #Ρ€Π΅ΠΏΡ‚ΠΈΠ»ΠΎΠΈΠ΄Ρ‹',
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'data' => 'send_reaction_0',
'text' => '???? 452'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'text' => '???? 21',
'data' => 'send_reaction_1'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'entities' => [
bless( {
'length' => 199,
'offset' => 0
}, 'Telegram::MessageEntityBold' ),
bless( {
'length' => 8,
'offset' => 919
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'offset' => 928,
'length' => 9
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'length' => 6,
'offset' => 938
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'length' => 11,
'offset' => 945
}, 'Telegram::MessageEntityHashtag' )
],
'views' => 6964,
'flags' => 50368,
'id' => 854,
'post' => 1
}, 'Telegram::Message' ),
'pts_count' => 0
}, 'Telegram::UpdateEditChannelMessage' ),
bless( {
'message' => bless( {
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'data' => 'send_reaction_0',
'text' => '???? 213'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'data' => 'send_reaction_1',
'text' => '???? 8'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'views' => 2940,
'entities' => [
bless( {
'length' => 609,
'offset' => 348
}, 'Telegram::MessageEntityItalic' )
],
'flags' => 50368,
'post' => 1,
'id' => 857,
'edit_date' => 1571907636,
'date' => 1571902479,
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'message' => 'ΠŸΠΎΡΡ‚ ΠΏΡ€ΠΎ 1Π‘ Π²Ρ‹Π·Π²Π°Π» Π±ΡƒΡ€Π½ΡƒΡŽ ΠΏΠΎΠ»Π΅ΠΌΠΈΠΊΡƒ. Π§Π΅Π»ΠΎΠ²Π΅ΠΊ 10 (Π²ΠΈΠ΄ΠΈΠΌΠΎ, 1с-программистов) Π΅Π΄ΠΈΠ½ΠΎΠ΄ΡƒΡˆΠ½ΠΎ написали:
// [тСкст сообщСния Π²Ρ‹Ρ€Π΅Π·Π°Π½ Ρ‡Ρ‚ΠΎΠ± Π½Π΅ Π½Π°Ρ€ΡƒΡˆΠ°Ρ‚ΡŒ ΠΏΡ€Π°Π²ΠΈΠ» Π₯Π°Π±Ρ€Π° ΠΎ Ρ€Π΅ΠΊΠ»Π°ΠΌΠ΅]
Π― Π±Ρ‹ Π΄ΠΎΠ±Π°Π²ΠΈΠ», Ρ‡Ρ‚ΠΎ блСстящая Ρƒ 1Π‘ дистрибуция, Π° ΠΌΠ°Ρ€ΠΊΠ΅Ρ‚ΠΈΠ½Π³... Π½Ρƒ, Ρ‚Π°ΠΊΠΎΠ΅.'
}, 'Telegram::Message' ),
'pts_count' => 0,
'pts' => 158508
}, 'Telegram::UpdateEditChannelMessage' ),
bless( {
'pts' => 158508,
'pts_count' => 0,
'message' => bless( {
'message' => 'ЗдравствуйтС, расскаТитС, поТалуйста, Ρ‡Π΅ΠΌ Π²Ρ€Π΅Π΄ΠΈΡ‚ экономикС 1Π‘?
// [тСкст сообщСния Π²Ρ‹Ρ€Π΅Π·Π°Π½ Ρ‡Ρ‚ΠΎΠ± Π½Π΅ Π½Π°Ρ€ΡƒΡˆΠ°Ρ‚ΡŒ ΠΏΡ€Π°Π²ΠΈΠ» Π₯Π°Π±Ρ€Π° ΠΎ Ρ€Π΅ΠΊΠ»Π°ΠΌΠ΅]
#софт #it #экономика',
'edit_date' => 1571907650,
'date' => 1571893707,
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'flags' => 50368,
'post' => 1,
'id' => 856,
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'data' => 'send_reaction_0',
'text' => '???? 360'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'data' => 'send_reaction_1',
'text' => '???? 32'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'views' => 4416,
'entities' => [
bless( {
'offset' => 0,
'length' => 64
}, 'Telegram::MessageEntityBold' ),
bless( {
'offset' => 1551,
'length' => 5
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'length' => 3,
'offset' => 1557
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'offset' => 1561,
'length' => 10
}, 'Telegram::MessageEntityHashtag' )
]
}, 'Telegram::Message' )
}, 'Telegram::UpdateEditChannelMessage' )
]
}, 'Telegram::Updates::ChannelDifference' )
}, 'MTProto::RpcResult' )
};
2019.10.24 12:00:51 $1 = {
'in' => bless( {
'update' => bless( {
'user_id' => 2507460,
'status' => bless( {
'was_online' => 1571907651
}, 'Telegram::UserStatusOffline' )
}, 'Telegram::UpdateUserStatus' ),
'date' => 1571907650
}, 'Telegram::UpdateShort' )
};
2019.10.24 12:05:46 $1 = {
'in' => bless( {
'chats' => [],
'date' => 1571907946,
'seq' => 0,
'updates' => [
bless( {
'max_id' => 141719,
'channel_id' => 1295963795
}, 'Telegram::UpdateReadChannelInbox' )
],
'users' => []
}, 'Telegram::Updates' )
};
2019.10.24 13:01:23 $1 = {
'in' => bless( {
'server_salt' => '4914425622822907323',
'unique_id' => '5297282355827493819',
'first_msg_id' => '6751307555044380692'
}, 'MTProto::NewSessionCreated' )
};
2019.10.24 13:24:21 $1 = {
'in' => bless( {
'chats' => [
bless( {
'username' => 'freebsd_ru',
'version' => 0,
'flags' => 5440,
'title' => 'freebsd_ru',
'min' => 1,
'photo' => bless( {
'photo_small' => bless( {
'local_id' => 328733,
'volume_id' => 235140688,
'dc_id' => 2,
'file_reference' => '
'secret' => '4426006807282303416'
}, 'Telegram::FileLocation' ),
'photo_big' => bless( {
'dc_id' => 2,
'file_reference' => '
'volume_id' => 235140688,
'local_id' => 328735,
'secret' => '71251192991540083'
}, 'Telegram::FileLocation' )
}, 'Telegram::ChatPhoto' ),
'date' => 1461248502,
'id' => 1038300508,
'democracy' => 1,
'megagroup' => 1
}, 'Telegram::Channel' )
],
'users' => [
bless( {
'last_name' => 'Panov',
'flags' => 1048646,
'min' => 1,
'id' => 82234609,
'status' => bless( {}, 'Telegram::UserStatusRecently' ),
'first_name' => 'Dima'
}, 'Telegram::User' )
],
'seq' => 0,
'date' => 1571912647,
'updates' => [
bless( {
'pts' => 137596,
'message' => bless( {
'flags' => 256,
'message' => 'Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π΄ΠΆΠ΅ΠΉΠ» с ΠΈΠΌΠ΅Π½Π΅ΠΌ ΠΏΠΎΠΊΠΎΡ€ΠΎΡ‡Π΅ ??',
'to_id' => bless( {
'channel_id' => 1038300508
}, 'Telegram::PeerChannel' ),
'id' => 119634,
'date' => 1571912647,
'from_id' => 82234609
}, 'Telegram::Message' ),
'pts_count' => 1
}, 'Telegram::UpdateNewChannelMessage' )
]
}, 'Telegram::Updates' )
};

Ya, bukan spoiler dengan sengaja - jika anda belum membacanya lagi, teruskan dan lakukannya!

Oh, wai~~... macam mana rupanya? Sesuatu yang sangat biasa... mungkin ini adalah struktur data API Web biasa dalam JSON, kecuali kelas juga dilampirkan pada objek?..

Jadi begini rupanya... Apa itu semua, kawan-kawan?.. Begitu banyak usaha - dan kami berhenti untuk berehat di tempat pengaturcara Web baru bermula?..Bukankah hanya JSON melalui HTTPS menjadi lebih mudah?! Apa yang kita dapat sebagai pertukaran? Adakah usaha itu berbaloi?

Mari kita nilai apa yang TL+MTProto berikan kepada kami dan apakah alternatif yang mungkin. Nah, HTTP, yang memfokuskan pada model tindak balas permintaan, adalah tidak sesuai, tetapi sekurang-kurangnya sesuatu di atas TLS?

Siri padat. Melihat struktur data ini, serupa dengan JSON, saya masih ingat bahawa terdapat versi binarinya. Mari kita tandai MsgPack sebagai tidak cukup diperluaskan, tetapi terdapat, sebagai contoh, CBOR - dengan cara ini, standard yang diterangkan dalam RFC 7049. Ia terkenal kerana fakta bahawa ia mentakrifkan tanda nama, sebagai mekanisme pengembangan, dan antara sudah diseragamkan tersedia:

  • 25 + 256 - menggantikan baris berulang dengan rujukan kepada nombor baris, kaedah mampatan yang murah
  • 26 - objek Perl bersiri dengan nama kelas dan hujah pembina
  • 27 - objek bebas bahasa bersiri dengan nama jenis dan hujah pembina

Nah, saya cuba mensirikan data yang sama dalam TL dan dalam CBOR dengan rentetan dan pembungkusan objek didayakan. Hasilnya mula berbeza-beza memihak kepada CBOR dari satu megabait:

cborlen=1039673 tl_len=1095092

Oleh itu, kesimpulan: Terdapat format yang jauh lebih mudah yang tidak tertakluk kepada masalah kegagalan penyegerakan atau pengecam yang tidak diketahui, dengan kecekapan yang setanding.

Penubuhan sambungan pantas. Ini bermakna sifar RTT selepas penyambungan semula (apabila kunci telah dijana sekali) - terpakai daripada mesej MTProto yang pertama, tetapi dengan beberapa tempahan - tekan garam yang sama, sesi tidak busuk, dsb. Apakah yang ditawarkan oleh TLS kepada kami? Petikan mengenai topik:

Apabila menggunakan PFS dalam TLS, tiket sesi TLS (RFC 5077) untuk menyambung semula sesi yang disulitkan tanpa merundingkan semula kunci dan tanpa menyimpan maklumat utama pada pelayan. Apabila membuka sambungan pertama dan mencipta kunci, pelayan menyulitkan keadaan sambungan dan menghantarnya kepada pelanggan (dalam bentuk tiket sesi). Sehubungan itu, apabila sambungan disambung semula, pelanggan menghantar tiket sesi, termasuk kunci sesi, kembali ke pelayan. Tiket itu sendiri disulitkan dengan kunci sementara (kunci tiket sesi), yang disimpan pada pelayan dan mesti diedarkan di antara semua pelayan hadapan yang memproses SSL dalam penyelesaian berkelompok.[10]. Oleh itu, pengenalan tiket sesi mungkin melanggar PFS jika kunci pelayan sementara dikompromi, contohnya, apabila ia disimpan untuk masa yang lama (OpenSSL, nginx, Apache menyimpannya secara lalai untuk keseluruhan tempoh program; tapak popular digunakan kunci selama beberapa jam, sehingga hari).

Di sini RTT bukan sifar, anda perlu menukar sekurang-kurangnya ClientHello dan ServerHello, selepas itu pelanggan boleh menghantar data bersama-sama dengan Selesai. Tetapi di sini kita harus ingat bahawa kita tidak mempunyai Web, dengan sekumpulan sambungan yang baru dibuka, tetapi messenger, sambungannya selalunya satu dan lebih atau kurang lama, permintaan yang agak singkat ke halaman Web - semuanya berganda. secara dalaman. Iaitu, agak boleh diterima jika kami tidak menemui bahagian kereta api bawah tanah yang sangat teruk.

Terlupa sesuatu yang lain? Tulis dalam komen.

Akan diteruskan!

Dalam bahagian kedua siri siaran ini, kami akan mempertimbangkan bukan teknikal, tetapi isu organisasi - pendekatan, ideologi, antara muka, sikap terhadap pengguna, dll. Walau bagaimanapun, berdasarkan maklumat teknikal yang dibentangkan di sini.

Bahagian ketiga akan terus menganalisis komponen teknikal / pengalaman pembangunan. Anda akan belajar, khususnya:

  • kesinambungan pandemonium dengan kepelbagaian jenis TL
  • perkara yang tidak diketahui tentang saluran dan supergroup
  • mengapa dialog lebih teruk daripada senarai
  • tentang pengalamatan mesej mutlak vs relatif
  • apakah perbezaan antara foto dan imej
  • bagaimana emoji mengganggu teks condong

dan tongkat lain! Nantikan!

Sumber: www.habr.com

Tambah komen