Telegram protokoli va tashkiliy yondashuvlarini tanqid qilish. 1-qism, texnik: mijozni noldan yozish tajribasi - TL, MT

So‘nggi paytlarda Habré’da Telegram qanchalik yaxshi ekanligi, aka-uka Durovlar tarmoq tizimlarini yaratishda naqadar zo‘r va tajribali ekani va hokazolar haqidagi postlar tez-tez chiqa boshladi. Shu bilan birga, juda kam odam haqiqatan ham texnik qurilmaga sho'ng'idi - ko'pi bilan ular juda oddiy (va MTProto'dan juda farq qiladigan) JSON-ga asoslangan Bot API-dan foydalanadilar va odatda shunchaki qabul qilishadi. imon ustida messenjer atrofida aylanadigan barcha maqtovlar va PR. Deyarli bir yarim yil oldin, mening NPO Echelon Vasiliydagi hamkasbim (afsuski, uning Habrédagi akkaunti qoralama bilan birga o'chirib tashlangan) Perl'da noldan o'zining Telegram mijozini yozishni boshladi va keyinchalik bu satrlar muallifi qo'shildi. Nega Perl, ba'zilar darhol so'rashadi? Chunki boshqa tillarda bunday loyihalar allaqachon mavjud.Aslida gap bunda emas, qayerda boshqa til bo'lishi mumkin tugallangan kutubxona, va shunga ko'ra muallif barcha yo'lni bosib o'tishi kerak noldan. Bundan tashqari, kriptografiya shunday narsa - ishoning, lekin tasdiqlang. Xavfsizlikka yo'naltirilgan mahsulot bilan siz sotuvchining tayyor kutubxonasiga tayanib, unga ko'r-ko'rona ishonishingiz mumkin emas (ammo bu ikkinchi qismda batafsilroq mavzu). Ayni paytda kutubxona "o'rta" darajada yaxshi ishlaydi (sizga har qanday API so'rovlarini amalga oshirish imkonini beradi).

Biroq, ushbu postlar seriyasida kriptografiya va matematika ko'p bo'lmaydi. Ammo boshqa ko'plab texnik tafsilotlar va me'moriy tayoqchalar bo'ladi (bu noldan yozmaydigan, lekin kutubxonadan istalgan tilda foydalanadiganlar uchun ham foydali bo'ladi). Shunday qilib, asosiy maqsad mijozni noldan amalga oshirishga harakat qilish edi rasmiy hujjatlarga muvofiq. Ya'ni, rasmiy mijozlarning manba kodi yopilgan deylik (yana ikkinchi qismda biz bu haqiqatan ham nima ekanligini batafsilroq ochib beramiz. bu sodir bo'ladi shunday), lekin, eski kunlarda bo'lgani kabi, masalan, RFC kabi standart mavjud - mijozni faqat spetsifikatsiyaga muvofiq, manba kodiga "ko'zdan kechirmasdan" yozish mumkinmi, hatto rasmiy (Telegram Desktop, mobil) ), hatto norasmiy Telemarafonmi?

Oglavlenie:

Hujjatlar ... u erdami? Bu rostmi?..

Ushbu maqola uchun eslatmalarning parchalari o'tgan yozda to'plana boshladi. Bu vaqt davomida rasmiy saytda https://core.telegram.org hujjatlar 23-qatlamga ko'ra edi, ya'ni. 2014 yilda biror joyga yopishib qolgan (esingizdami, o'sha paytda kanallar ham yo'q edi?). Albatta, nazariy jihatdan, bu 2014 yilda o'sha paytda funksionallikka ega bo'lgan mijozni amalga oshirishga imkon berishi kerak edi. Ammo bu holatda ham hujjatlar, birinchidan, to'liq bo'lmagan, ikkinchidan, joylarda u o'ziga zid edi. Bir oydan sal ko'proq vaqt oldin, 2019 yil sentyabr oyida shunday edi tasodifan Saytda butunlay yangi Layer 105 uchun katta hajmdagi hujjatlar yangilanganligi aniqlandi va endi hamma narsani qayta o'qish kerakligi haqida eslatma mavjud. Darhaqiqat, ko'plab maqolalar qayta ko'rib chiqilgan, ammo ko'plari o'zgarishsiz qolgan. Shuning uchun, hujjatlar haqida quyidagi tanqidni o'qiyotganda, bu narsalarning ba'zilari endi ahamiyatli emasligini yodda tutishingiz kerak, ammo ba'zilari hali ham to'liq. Axir, zamonaviy dunyoda 5 yil nafaqat ko'p, balki juda ko'p juda ko'p. O'shandan beri (ayniqsa, o'sha paytdan beri tashlab yuborilgan va qayta tiklangan geochatlarni hisobga olmasangiz), sxemadagi API usullari soni yuzdan ikki yuz ellikdan oshdi!

Yosh yozuvchi sifatida ijodingizni qayerdan boshlaysiz?

Siz noldan yozasizmi yoki masalan, kabi tayyor kutubxonalardan foydalanasizmi, muhim emas Python uchun telethon yoki PHP uchun Madeline, har qanday holatda, birinchi navbatda sizga kerak bo'ladi arizangizni ro'yxatdan o'tkazing - parametrlarni oling api_id и api_hash (VKontakte API bilan ishlaganlar darhol tushunadilar) qaysi orqali server dasturni aniqlaydi. Bu kerak huquqiy sabablarga ko'ra, lekin nima uchun kutubxona mualliflari uni ikkinchi qismda nashr eta olmasligi haqida ko'proq gaplashamiz. Ehtimol, test qiymatlari sizni qoniqtiradi, garchi ular juda cheklangan bo'lsa ham - haqiqat shundaki, endi siz o'z raqamingizda ro'yxatdan o'tishingiz mumkin. faqat bitta dastur, shuning uchun shoshilmang.

Endi, texnik nuqtai nazardan, biz ro'yxatdan o'tganimizdan so'ng biz Telegram'dan hujjatlar, protokol va boshqalarga yangilanishlar haqida bildirishnoma olishimiz kerakligi bilan qiziqishimiz kerak edi. Ya'ni, dockli sayt shunchaki "ball" qilingan va mijozlar qilishni boshlaganlar bilan ishlashni davom ettirgan deb taxmin qilish mumkin, chunki. osonroq. Lekin yo'q, shunga o'xshash hech narsa kuzatilmadi, hech qanday ma'lumot kelmadi.

Va agar siz noldan yozsangiz, unda qabul qilingan parametrlardan foydalanish aslida hali ham uzoqdir. Garchi https://core.telegram.org/ va ular haqida birinchi navbatda Ishga kirishishda gapiradi, aslida siz birinchi navbatda amalga oshirishingiz kerak MTProto protokoli - lekin ishonsangiz OSI modeliga muvofiq tartib protokolning umumiy tavsifi sahifasining oxirida, keyin butunlay behuda.

Darhaqiqat, MTProto-dan oldin ham, undan keyin ham bir vaqtning o'zida bir nechta darajalarda (OS yadrosida ishlaydigan xorijiy tarmoqchilar aytganidek, qatlam buzilishi) katta, og'riqli va dahshatli mavzu to'sqinlik qiladi ...

Ikkilik ketma-ketlashtirish: TL (Tip tili) va uning sxemasi, qatlamlari va boshqa ko'plab qo'rqinchli so'zlar

Bu mavzu, aslida, Telegram muammolarining kalitidir. Va agar siz uni o'rganishga harakat qilsangiz, juda ko'p dahshatli so'zlar bo'ladi.

Shunday qilib, sxema. Agar bu so'zni eslasangiz, ayting: JSON sxemasiTo'g'ri o'yladingiz. Maqsad bir xil: uzatiladigan ma'lumotlarning mumkin bo'lgan to'plamini tavsiflash uchun ba'zi tillar. Bu, aslida, o'xshashlik tugaydi. Agar sahifadan MTProto protokoli, yoki rasmiy mijozning manba daraxtidan biz qandaydir sxemani ochishga harakat qilamiz, biz shunga o'xshash narsani ko'ramiz:

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;

Buni birinchi marta ko'rgan odam intuitiv ravishda yozilganlarning faqat bir qismini taniydi - bular aftidan tuzilmalar (garchi ism qaerda, chapda yoki o'ngda?), Ularda maydonlar mavjud, shundan so'ng. turi yo'g'on ichak orqali o'tadi ... ehtimol. Bu erda, burchakli qavslarda, ehtimol, C ++ dagi kabi shablonlar mavjud (aslida, unchalik emas). Va boshqa barcha belgilar nimani anglatadi, savol belgilari, undov nuqtalari, foizlar, panjaralar (va aniqki, ular turli joylarda turli xil narsalarni anglatadi), bir joyda mavjud, lekin bir joyda emas, o'n oltilik raqamlar - va eng muhimi, bundan qanday qilib olish mumkin muntazam ravishda (server tomonidan rad etilmaydi) bayt oqimi? Hujjatlarni o'qib chiqishingiz kerak (Ha, yaqin atrofdagi JSON versiyasida sxemaga havolalar mavjud - lekin bu uni aniqroq qilmaydi).

Sahifani ochish Ikkilik ma'lumotlarni ketma-ketlashtirish va qo'ziqorinlar va diskret matematikaning sehrli dunyosiga sho'ng'ing, 4-kursdagi matanga o'xshash narsa. Alifbo, tur, qiymat, kombinator, funksional kombinator, normal shakl, kompozit tip, polimorf tip... va bu faqat birinchi sahifa! Keyingisi sizni kutmoqda TL tili, u allaqachon arzimas so'rov va javob misolini o'z ichiga olgan bo'lsa-da, odatiy holatlarga umuman javob bermaydi, ya'ni siz rus tilidan ingliz tiliga tarjima qilingan matematikani yana sakkizta o'rindiqda qayta o'qib chiqishingiz kerak bo'ladi. sahifalar!

Funktsional tillar va avtomatik turdagi xulosalar bilan tanish bo'lgan o'quvchilar, albatta, ushbu tilda tavsiflarni, hatto misoldan ham ko'proq tanish bo'lgan va bu umuman printsipial jihatdan yomon emasligini aytishlari mumkin. Bunga e'tirozlar:

  • ha, цель yaxshi eshitiladi, lekin afsuski erishilmagan
  • Rossiya universitetlarida ta'lim hatto IT mutaxassisliklari orasida ham farq qiladi - hamma ham tegishli kursni o'qimaydi
  • Nihoyat, biz ko'rib turganimizdek, amalda shunday talab qilinmaydi, chunki tasvirlangan TL ning faqat cheklangan kichik to'plami ishlatilgan

Aytgancha LeoNerd kanalda #perl FreeNode IRC tarmog'ida Telegram-dan Matritsaga eshikni amalga oshirishga harakat qilmoqda (iqtibosning tarjimasi xotiradan noto'g'ri):

Bu tip nazariyasi bilan birinchi marta tanishgan, hayajonlangan va u bilan o'ynashga harakat qilgan, amalda zarur bo'lganiga ahamiyat bermagan odamga o'xshaydi.

Yalang'och turlarga (int, long va h.k.) ehtiyoj oddiy narsa sifatida savollar tug'dirmaydimi yoki yo'qligini o'zingiz ko'ring - oxirida ular qo'lda amalga oshirilishi kerak - masalan, ulardan kelib chiqishga harakat qilaylik. vektor. Ya'ni, aslida qator, agar siz hosil bo'lgan narsalarni o'z nomlari bilan chaqirsangiz.

Lekin oldin

TL sintaksisining quyi toʻplamining qisqacha tavsifi… rasmiy hujjatlarni oʻqing

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;

Har doim ta'rifni boshlaydi конструктор, shundan keyin ixtiyoriy ravishda (amalda, har doim) belgi orqali # bo'lishi kerak CRC32 berilgan turdagi normalangan tavsif qatoridan. Keyin maydonlarning tavsifi keladi, agar ular bo'lsa - turi bo'sh bo'lishi mumkin. Hammasi teng belgisi bilan tugaydi, berilgan konstruktor qaysi tipga tegishli bo'lsa, ya'ni aslida kichik tip. Tenglik belgisining o'ng tomonidagi tur polimorfik - ya'ni bir nechta o'ziga xos turlarga mos kelishi mumkin.

Agar ta'rif chiziqdan keyin sodir bo'lsa ---functions---, u holda sintaksis bir xil bo'lib qoladi, ammo ma'no boshqacha bo'ladi: konstruktor RPC funktsiyasining nomiga aylanadi, maydonlar parametrlarga aylanadi (yaxshi, ya'ni quyida tavsiflanganidek, xuddi shu berilgan tuzilma bo'lib qoladi, bu faqat berilgan ma'no bo'ladi) va "polimorfik tip" - qaytarilgan natijaning turi. To'g'ri, u hali ham polimorfik bo'lib qoladi - faqat bo'limda aniqlangan ---types---, va bu konstruktor hisobga olinmaydi. Chaqirilgan funksiyalarning ortiqcha yuklarini ularning argumentlari bo'yicha yozing, ya'ni. ba'zi sabablarga ko'ra, C++ da bo'lgani kabi bir xil nomga ega, ammo boshqa imzoga ega bo'lgan bir nechta funktsiyalar TLda taqdim etilmaydi.

Agar OOP bo'lmasa, nima uchun "konstruktor" va "polimorfik"? Darhaqiqat, kimdir bu haqda OOP nuqtai nazaridan o'ylash osonroq bo'ladi - mavhum sinf sifatida polimorf tip va konstruktorlar uning to'g'ridan-to'g'ri avlodlari sinflari, bundan tashqari final bir qator tillar terminologiyasida. Aslida, albatta, bu erda o'xshashlik OO dasturlash tillarida haqiqiy haddan tashqari yuklangan konstruktor usullari bilan. Bu erda faqat ma'lumotlar tuzilmalari mavjud bo'lganligi sababli, hech qanday usullar yo'q (garchi quyidagi funktsiyalar va usullarning tavsifi ular nima ekanligi haqida boshda chalkashliklarni keltirib chiqarishga qodir, ammo bu boshqa narsa haqida) - siz konstruktor haqida o'ylashingiz mumkin. qaysi qiymatdan qurilmoqda baytlar oqimini o'qiyotganda yozing.

Bu qanday sodir bo'ladi? Har doim 4 baytni o'qiydigan deserializer qiymatni ko'radi 0xcrc32 - va keyin nima bo'lishini tushunadi field1 turi bilan int, ya'ni. turi bilan qoplangan ushbu maydonda aniq 4 baytni o'qiydi PolymorType o'qing. Ko'radi 0x2crc32 va yana ikkita maydon borligini tushunadi, birinchi long, shuning uchun biz 8 baytni o'qiymiz. Va keyin yana bir xil tarzda seriyasizlashtirilgan murakkab tur. Masalan, Type3 sxemada ikkita konstruktor mos ravishda uchrashishi bilanoq e'lon qilinishi mumkin 0x12abcd34, shundan so'ng siz yana 4 baytni o'qishingiz kerak int, yoki 0x6789cdef, undan keyin hech narsa bo'lmaydi. Boshqa har qanday narsa - siz istisno qilishingiz kerak. Har holda, bundan keyin biz 4 baytni o'qishga qaytamiz int cheklovlar field_c в constructorTwo va shu bilan biz o'qishni tugatamiz PolymorType.

Nihoyat, agar qo'lga olinsa 0xdeadcrc uchun constructorThree, keyin ishlar yanada murakkablashadi. Bizning birinchi maydonimiz bit_flags_of_what_really_present turi bilan # - aslida bu tur uchun taxallus nat"tabiiy son" degan ma'noni anglatadi. Ya'ni, aslida, unsigned int, aytmoqchi, haqiqiy sxemalarda imzosiz raqamlar topilgan yagona holat. Demak, keyingisi savol belgisi bilan konstruksiya, ya'ni bu maydon - u simda faqat havola qilingan maydonda mos keladigan bit o'rnatilgan bo'lsa (taxminan uchlik operator kabi) mavjud bo'ladi. Shunday qilib, bu bit yoqilgan deb taxmin qiling, keyin siz kabi maydonni o'qishingiz kerak Type, bizning misolimizda 2 ta konstruktor mavjud. Biri bo'sh (faqat identifikatordan iborat), ikkinchisida maydon mavjud ids turi bilan ids:Vector<long>.

Shablonlar ham, generiklar ham yaxshi yoki Java deb o'ylashingiz mumkin. Lekin yoq. Deyarli. Bu yakka haqiqiy zanjirlarda burchakli qavslar holati va u FAQAT Vektor uchun ishlatiladi. Bayt oqimida bu Vektor turining o'zi uchun 4 CRC32 bayt bo'ladi, har doim bir xil, keyin 4 bayt - massiv elementlarining soni, keyin esa bu elementlarning o'zi.

Bunga serializatsiya har doim 4 baytlik so'zlarda sodir bo'lishini qo'shing, barcha turlar unga ko'paytiriladi - o'rnatilgan turlar ham tavsiflanadi. bytes и string uzunlikni qo'lda ketma-ketlashtirish va bu hizalamani 4 ga tenglashtirish bilan - bu normal va hatto nisbatan samarali bo'lib tuyuladimi? TL samarali ikkilik seriyali deb da'vo qilingan bo'lsa-da, lekin ular bilan jahannam, har qanday narsaning, hatto mantiqiy qiymatlarning va 4 baytgacha bo'lgan bir belgili satrlarning kengayishi bilan JSON hali ham qalinroq bo'ladimi? Qarang, hatto keraksiz maydonlarni ham bit bayroqlari bilan o'tkazib yuborish mumkin, hammasi yaxshi va hatto kelajak uchun kengaytirilishi mumkin, keyinroq konstruktorga yangi ixtiyoriy maydonlarni qo'shdingizmi?..

Yo'q, agar siz mening qisqacha tavsifimni emas, balki to'liq hujjatlarni o'qib chiqsangiz va amalga oshirish haqida o'ylaysiz. Birinchidan, konstruktorning CRC32 normallashtirilgan sxema matn tavsifi satri bilan hisoblanadi (qo‘shimcha bo‘shliqni olib tashlang va h.k.) - shuning uchun yangi maydon qo‘shilsa, tip tavsifi qatori o‘zgaradi va shuning uchun uning CRC32 va shunga mos ravishda ketma-ketlashtirish. Va agar eski mijoz yangi bayroqlar o'rnatilgan maydonni olsa, nima qiladi, lekin u keyin ular bilan nima qilishni bilmas edi? ..

Ikkinchidan, eslaylik CRC32, bu yerda asosan sifatida ishlatiladi hash funktsiyalari Qaysi turdagi (de) seriyali qilinayotganini yagona aniqlash uchun. Bu erda biz to'qnashuv muammosiga duch kelamiz - va yo'q, ehtimollik 232da bitta emas, balki ko'proq. CRC32 aloqa kanalidagi xatolarni aniqlash (va tuzatish) va shunga mos ravishda bu xususiyatlarni boshqalarning zarariga yaxshilash uchun mo'ljallanganligini kim esladi? Masalan, u baytlarni almashtirishga ahamiyat bermaydi: agar siz CRC32 ni ikkita satrdan hisoblasangiz, ikkinchisida siz birinchi 4 baytni keyingi 4 bayt bilan almashtirasiz - xuddi shunday bo'ladi. Agar bizda lotin alifbosidan matn satrlari (va ozgina tinish belgilari) kiritilganda va bu nomlar tasodifiy bo'lmasa, bunday almashtirish ehtimoli sezilarli darajada oshadi.

Aytgancha, u erda nima borligini kim tekshirdi albatta CRC32? Dastlabki manbalardan birida (hatto Uoltmandan oldin ham) har bir belgini 239 raqamiga ko'paytiruvchi xesh funktsiyasi mavjud edi, bu odamlar tomonidan juda yaxshi ko'rilgan, ha ha!

Nihoyat, biz konstruktorlar maydon turiga ega ekanligini tushundik Vector<int> и Vector<PolymorType> turli CRC32 bo'ladi. Va chiziqdagi taqdimot haqida nima deyish mumkin? Va nazariy jihatdan, turining bir qismiga aylanadimi? Aytaylik, biz o'n ming sonli massivni o'tkazamiz Vector<int> hamma narsa aniq, uzunligi va yana 40000 XNUMX bayt. Va agar bu Vector<Type2>, bu faqat bitta maydondan iborat int va bu turdagi yagona - biz 10000xabcdef0 ni 34 marta va keyin 4 baytni takrorlashimiz kerakmi? int, yoki til konstruktordan buni biz uchun KO'RSATA oladi fixedVec va o'rniga 80000 40000 bayt, yana faqat XNUMX XNUMX o'tkazish?

Bu umuman bo'sh nazariy savol emas - tasavvur qiling-a, siz guruh foydalanuvchilari ro'yxatini olasiz, ularning har birining identifikatori, ismi, familiyasi bor - mobil aloqa orqali uzatiladigan ma'lumotlar miqdoridagi farq sezilarli bo'lishi mumkin. Bizga e'lon qilingan Telegram serializatsiyasining samaradorligi.

Shunday qilib ...

Chiqarib bo'lmaydigan vektor

Agar siz kombinatorlar va taxminan tavsif sahifalarini ko'rib chiqishga harakat qilsangiz, vektor (va hatto matritsa) rasman kortejlar orqali bir nechta varaqlarni chiqarishga harakat qilayotganini ko'rasiz. Ammo oxir-oqibat ular bolg'alanadi, oxirgi bosqich o'tkazib yuboriladi va vektorning ta'rifi oddiygina beriladi, bu ham turga bog'liq emas. Bu yerda nima gap? tillarda dasturlash, ayniqsa funktsional, strukturani rekursiv tarzda tasvirlash odatiy holdir - dangasa baholash bilan kompilyator hamma narsani tushunadi va buni amalga oshiradi. Tilda ma'lumotlarni ketma-ketlashtirish lekin SAMARALIK kerak: shunchaki tasvirlab berish kifoya ro'yxat, ya'ni. ikkita elementdan iborat struktura - birinchisi ma'lumotlar elementi, ikkinchisi bir xil tuzilmaning o'zi yoki quyruq uchun bo'sh joy (paket). (cons) Lispda). Ammo bu aniq talab qiladi har birining element turini tavsiflash uchun qo'shimcha ravishda 4 bayt (TL uchun CRC32) sarflaydi. Massivni tasvirlash oson belgilangan o'lcham, lekin ilgari noma'lum uzunlikdagi massiv bo'lsa, biz uzamiz.

Shunday qilib, TL vektorni chiqarishga ruxsat bermagani uchun uni yon tomonga qo'shish kerak edi. Oxir-oqibat, hujjatlarda shunday deyilgan:

Seriyalashtirishda har doim bir xil konstruktor “vektor” (const 0x1cb5c415 = crc32 (“vektor t:Type # [ t ] = Vector t”) ishlatiladi, bu t tipidagi o‘zgaruvchining o‘ziga xos qiymatiga bog‘liq emas.

Ixtiyoriy t parametrining qiymati ketma-ketlashtirishda ishtirok etmaydi, chunki u natija turidan olingan (har doim ketma-ketlashtirishdan oldin ma'lum).

Yaqindan ko'rib chiqing: vector {t:Type} # [ t ] = Vector t - lekin hech qaerda ta'rifning o'zi birinchi raqam vektor uzunligiga teng bo'lishi kerakligini aytmaydi! Va u hech qayerdan ergashmaydi. Bu sizning qo'llaringiz bilan yodda tutishingiz va amalga oshirishingiz kerak bo'lgan berilgan. Boshqa joyda, hujjatlarda bu tur soxta ekanligi to'g'ri aytilgan:

Vektor t polimorf psevdotipi "turi" bo'lib, uning qiymati har qanday turdagi t qiymatlari ketma-ketligi bo'lib, qutilangan yoki yalang'och.

... lekin bunga e'tibor qaratmaydi. Matematikani chuqur o'rganishdan charchagan bo'lsangiz (ehtimol sizga universitet kursidan ma'lum bo'lgan bo'lsa ham) ball to'plashga va u bilan amalda qanday ishlashni kuzatishga qaror qilganingizda, miyangizda shunday taassurot qoladi: bu erda jiddiy matematika asoslanadi. , Shubhasiz Cool People (ikkita matematik - ACM g'olibi), va faqat hech kim emas. Maqsadga erishildi - xarajat qilish.

Aytgancha, raqam haqida. Eslab qoling # bu sinonimdir nat, natural son:

Turi ifodalar mavjud (typeexpr) va raqamli ifodalar (nat-expr). Biroq, ular bir xil tarzda belgilanadi.

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

lekin grammatikada ular xuddi shunday tasvirlangan, ya'ni. bu farqni yana eslab qolish va qo'lda amalga oshirish kerak.

Ha, shablon turlari (vector<int>, vector<User>) umumiy identifikatorga ega (#1cb5c415), ya'ni. qo'ng'iroq deb e'lon qilinganligini bilsangiz

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

unda siz nafaqat vektorni, balki foydalanuvchilarning vektorini kutmoqdasiz. Aniqroq aytganda, bo'lishi kerak kuting - haqiqiy kodda har bir element, agar yalang'och tur bo'lmasa, konstruktorga ega bo'ladi va amalga oshirishda yaxshi yo'l bilan tekshirish kerak bo'ladi - va biz ushbu vektorning har bir elementida aniq yuborilganmiz. o'sha turi? Va agar bu massiv turli elementlarda har xil turlarni o'z ichiga olishi mumkin bo'lgan PHP-ning qandaydir turi bo'lsa?

Shu nuqtada siz hayron bo'lasiz - bunday TL kerakmi? Balki, arava uchun o'sha paytda mavjud bo'lgan protobuf bo'lgan inson serializatoridan foydalanish mumkinmi? Bu nazariya edi, keling, amaliyotga qaraylik.

Koddagi mavjud TL ilovalari

TL Durovning ulushini sotish bilan mashhur voqealardan oldin ham VKontakte ichkarisida tug'ilgan va (albatta), Telegram rivojlanishidan oldin ham. Va ochiq manbada birinchi amalga oshirish manbalari juda ko'p kulgili tayoqchalarni topishingiz mumkin. Tilning oʻzi esa u yerda hozirgi Telegramdagidan koʻra toʻliqroq tatbiq etilgan. Misol uchun, sxemada xeshlar umuman ishlatilmaydi (deviant xatti-harakatlarga ega o'rnatilgan psevdotip (vektor kabi) degan ma'noni anglatadi). Yoki

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

ammo tafakkur devining evolyutsiyasini, ta'bir joiz bo'lsa, kuzatish uchun rasmni to'liqlik uchun ko'rib chiqaylik.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

Yoki bu go'zal:

    static const char *reserved_words_polymorhic[] = {

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

      };

Ushbu parcha shablonlarga tegishli, masalan:

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

Bu int - Type juftlarining vektori sifatida xashmap shablon turining ta'rifi. C++ da u shunday ko'rinadi:

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

shunday, alpha - kalit so'z! Lekin faqat C++ da siz T ni yozishingiz mumkin, lekin siz alfa, beta yozishingiz kerak ... Lekin 8 parametrdan ko'p emas, fantaziya tetada tugadi. Shunday qilib, bir vaqtlar Sankt-Peterburgda shunday dialoglar bo'lganga o'xshaydi:

-- Надо сделать в TL шаблоны
-- Бл... Ну пусть параметры зовут альфа, бета,... Какие там ещё буквы есть... О, тэта!
-- Грамматика? Ну потом напишем

-- Смотрите, какой я синтаксис придумал для шаблонов и вектора!
-- Ты долбанулся, как мы это парсить будем?
-- Да не ссыте, он там один в схеме, захаркодить -- и ок

Ammo bu "umuman" TLni birinchi marta amalga oshirish haqida edi. Haqiqiy Telegram mijozlarida amalga oshirishni ko'rib chiqishga o'tamiz.

Basilning so'zi:

Vasiliy, [09.10.18 17:07] Eng muhimi, ular bir qancha abstraksiyalarni buzib tashlaganlari, keyin esa bolt bilan bolg'a bilan urib, kodgegeratorni qo'ltiq tayoq bilan qoplaganliklari uchun eshak qiziydi.
Natijada, birinchi navbatda docklardan pilot.jpg
Keyin jekichan.webp kodidan

Albatta, algoritmlar va matematika bilan tanish bo'lgan odamlardan biz ular Aho, Ullmanni o'qiganliklarini va o'nlab yillar davomida o'zlarining DSL kompilyatorlarini yozish uchun de-fakto sanoat standart vositalarini bilishlarini kutishimiz mumkin, shunday emasmi? ..

Muallif telegram-cli Vitaliy Valtman, TLO formatining uning (cli) chegaralaridan tashqarida paydo bo'lishidan tushunish mumkinki, jamoa a'zosi - endi TLni tahlil qilish uchun kutubxona ajratilgan. alohida-alohidauning taassurotlari qanday TL tahlilchisi? ..

16.12 04:18 Vasiliy: menimcha, kimdir lex + yaccni o'zlashtirmagan
16.12 04:18 Vasiliy: aks holda men buni tushuntira olmayman
16.12 04:18 Vasiliy: yaxshi, yoki ular VKdagi qatorlar soni uchun to'langan.
16.12 04:19 Vasiliy: boshqalarning 3k+ satrlari<censored> parser o'rniga

Balki istisnodir? Keling, qanday qilib yo'qoladi bu RASMIY mijoz — Telegram ish stoli:

    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);

Python-da 1100 dan ortiq satrlar, bir nechta muntazam iboralar + vektor tipidagi maxsus holatlar, albatta, sxemada TL sintaksisi bo'yicha bo'lishi kerak deb e'lon qilinadi, lekin ular uni ushbu sintaksisga qo'yishadi, ko'proq tahlil qilishadi. ...Savol shuki, nega bu mo‘jizalar bilan ovoraиko'proq puff, agar hech kim uni hujjatlarga muvofiq tahlil qilmoqchi bo'lmasa ?!

Aytgancha ... CRC32 tekshiruvi haqida gaplashganimizni eslaysizmi? Shunday qilib, Telegram Desktop kod generatorida hisoblangan CRC32 turlari uchun istisnolar ro'yxati mavjud. mos kelmaydi diagrammada ko'rsatilganidek!

Vasiliy, [18.12 22:49] va bu erda siz bunday TL kerak yoki yo'qligini o'ylab ko'rishingiz kerak.
Agar men muqobil ilovalar bilan chalkashmoqchi bo'lsam, qatorlarni qo'yishni boshlayman, tahlilchilarning yarmi ko'p qatorli ta'riflarda buziladi
tdesktop ham

Bir-laynerlar haqidagi fikrni eslang, birozdan keyin unga qaytamiz.

Xo'sh, telegram-cli norasmiy, Telegram Desktop rasmiy, ammo boshqalar-chi? Va kim biladi?.. Android mijoz kodida sxemani tahlil qilish dasturi umuman yo'q edi (bu ochiq manba haqida savollar tug'diradi, lekin bu ikkinchi qism uchun), lekin yana bir nechta kulgili kod qismlari bor edi, lekin ular haqida quyida joylashgan bo'lim.

Serializatsiya amalda yana qanday savollarni tug'diradi? Masalan, ular, albatta, bit maydonlari va shartli maydonlar bilan o'ralgan:

vasiliy: flags.0? true
bayroq o'rnatilgan bo'lsa, maydon mavjud va rost ekanligini bildiradi

vasiliy: flags.1? int
maydon mavjud ekanligini va seriyani bekor qilish kerakligini bildiradi

Vasiliy: Eshak, kuyma, nima qilyapsan!
Vasiliy: Hujjatning biron bir joyida "true" nol uzunlikning yalang'och turi ekanligi haqida eslatib o'tilgan, ammo ularning hujjatlaridan biror narsa yig'ish haqiqatga to'g'ri kelmaydi.
Vasiliy: Ochiq ilovalarda ham bunday narsa yo'q, lekin tayoqchalar va rekvizitlar juda ko'p

Telemarafon haqida nima deyish mumkin? MTProto mavzusiga kelsak, misol - hujjatlarda bunday qismlar mavjud, ammo belgi % u faqat "berilgan yalang'och turga to'g'ri keladi" deb ta'riflanadi, ya'ni. quyidagi misollarda xato yoki hujjatsiz narsa:

Vasiliy, [22.06.18 18:38] Bir joyda:

msg_container#73f1f8dc messages:vector message = MessageContainer;

Boshqacha qilib aytganda:

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

Va bu ikkita katta farq, haqiqiy hayotda qandaydir yalang'och vektor keladi

Men yalang'och vektor ta'riflarini ko'rmadim va uni uchratmadim

Qo'lda telemafonda yozilgan tahlil

Uning sxemasi ta'rifni izohladi msg_container

Shunga qaramay, savol% haqida qolmoqda. Bu tasvirlanmagan.

Vadim Goncharov, [22.06.18 19:22] va ish stolida?

Vasiliy, [22.06.18 19:23] Lekin ularning regulyatorlardagi TL tahlilchisi ham uni yemaydi.

// parsed manually

TL - bu chiroyli abstraktsiya, uni hech kim to'liq amalga oshirmaydi

Va sxemaning ularning versiyasida% yo'q

Lekin bu erda hujjatlar o'ziga zid, shuning uchun xs

Bu grammatikada topilgan, ular semantikani tasvirlashni unutishlari mumkin edi

Xo'sh, siz TL dagi dockni ko'rdingiz, uni yarim litrsiz tushunolmaysiz

"Mayli, aytaylik," deydi boshqa o'quvchi, "siz hamma narsani tanqid qilasiz, shuning uchun uni kerak bo'lganda ko'rsating".

Vasiliy javob beradi: "Agar tahlilchiga kelsak, menga shunga o'xshash narsalar kerak

    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 ) ); }
            ;

qandaydir tarzda ko'proq o'xshaydi

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;
}

yoki

        # 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)

Bu BUTUN lekser:

    ---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];

bular. yumshoqroq qilib aytganda oddiyroqdir."

Umuman olganda, aslida ishlatilgan TL to'plami uchun tahlil qiluvchi va kod generatori taxminan 100 grammatik qatorga va generatorning ~ 300 qatoriga (shu jumladan barcha) mos keladi. printning yaratilgan kodi), shu jumladan turdagi shirinliklar, har bir sinfda introspeksiya uchun ma'lumotlarni kiriting. Har bir polimorf tip bo'sh mavhum tayanch sinfga aylantiriladi va konstruktorlar undan meros bo'lib, ketma-ketlashtirish va deserializatsiya usullariga ega.

Tip tilida turlarning etishmasligi

Kuchli yozish yaxshi, to'g'rimi? Yo'q, bu holivar emas (garchi men dinamik tillarni afzal ko'raman), lekin TL ichidagi postulat. Unga asoslanib, til biz uchun barcha turdagi cheklarni taqdim etishi kerak. Xo'sh, mayli, unga emas, balki amalga oshirishga ruxsat bering, lekin u hech bo'lmaganda ularni tasvirlashi kerak. Va biz qanday imkoniyatlarni xohlaymiz?

Birinchidan, cheklovlar. Bu erda biz fayllarni yuklash uchun hujjatlarni ko'ramiz:

Keyin faylning ikkilik tarkibi qismlarga bo'linadi. Barcha qismlar bir xil o'lchamga ega bo'lishi kerak ( part_size ) va quyidagi shartlar bajarilishi kerak:

  • part_size % 1024 = 0 (1KB ga bo'linadi)
  • 524288 % part_size = 0 (512KB qism_sizega teng boʻlinishi kerak)

Oxirgi qism ushbu shartlarga javob berishi shart emas, agar uning o'lchami part_size dan kichik bo'lsa.

Har bir qism tartib raqamiga ega bo'lishi kerak, file_part, qiymati 0 dan 2,999 gacha.

Fayl qismlarga bo'lingandan so'ng uni serverda saqlash usulini tanlashingiz kerak. foydalanish upload.saveBigFilePart faylning to'liq hajmi 10 MB dan ortiq bo'lsa va upload.saveFilePart kichikroq fayllar uchun.
[…] Quyidagi maʼlumotlarni kiritish xatolaridan biri qaytarilishi mumkin:

  • FILE_PARTS_INVALID - qismlar soni noto'g'ri. Qiymat orasida emas 1..3000

Ulardan birortasi sxemada mavjudmi? Bu qandaydir tarzda TL orqali ifodalanishi mumkinmi? Yo'q. Kechirasiz, hatto eski uslubdagi Turbo Paskal ham tomonidan berilgan turlarni tasvirlay olgan. diapazonlari. Va u yana bir ishni qila olardi, endi bu nom bilan mashhur enum - qat'iy (kichik) qiymatlar sonining ro'yxatidan iborat tur. C kabi tillarda - raqamli, shuni yodda tutingki, biz hozirgacha faqat turlar haqida gaplashdik. raqamlar. Ammo massivlar, satrlar ham bor... masalan, bu satrda faqat telefon raqami bo'lishi mumkinligini ta'riflash yaxshi bo'lardi, to'g'rimi?

Bularning hech biri TLda emas. Ammo, masalan, JSON sxemasida mavjud. Va agar kimdir 512 KB ning bo'linuvchanligiga e'tiroz bildirsa, bu kodda tekshirilishi kerak bo'lsa, mijoz oddiygina ekanligiga ishonch hosil qiling. qila olmadi raqamni diapazondan tashqariga yuboring 1..3000 (va tegishli xato yuzaga kelishi mumkin emas edi) bu mumkin edi, to'g'rimi? ..

Aytgancha, xatolar va qaytish qiymatlari haqida. Hatto TL bilan ishlaganlarning ham ko'zlari xiralashgan - bu bizga darhol tushmadi har biri TL dagi funksiya aslida nafaqat tasvirlangan qaytish turini, balki xatoni ham qaytarishi mumkin. Lekin buni TLning o'zi orqali chiqarib bo'lmaydi. Albatta, baribir tushunarli va amalda nafis kerak emas (aslida RPC turli yo'llar bilan amalga oshirilishi mumkin bo'lsa-da, biz bunga qaytamiz) - ammo samoviy mavhum turlarning matematikasi tushunchalarining pokligi haqida nima deyish mumkin? dunyo? .. Tutqichni ushlab oldi - shuning uchun mos keladi.

Va nihoyat, o'qish qobiliyati haqida nima deyish mumkin? Xo'sh, umuman olganda, men xohlayman tavsifi u sxemada to'g'ri bor (yana JSON sxemasida), lekin agar u allaqachon zo'rlangan bo'lsa, unda amaliy tomoni haqida nima deyish mumkin - hech bo'lmaganda yangilanishlar paytida farqlarni ko'rish juda oddiymi? O'zingiz ko'ring haqiqiy misollar:

-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;

yoki

-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;

Kimgadir yoqadi, lekin GitHub, masalan, bunday uzun qatorlar ichidagi o'zgarishlarni ta'kidlashni rad etadi. "10 ta farqni toping" o'yini va miya darhol ko'rgan narsa shundaki, ikkala misolda ham boshlanishi va oxiri bir xil, siz o'rtada biron bir joyda zerikarli o'qishingiz kerak ... Menimcha, bu faqat nazariy jihatdan emas, lekin faqat vizual ko'rinadi iflos va tartibsiz.

Aytgancha, nazariyaning sofligi haqida. Bit maydonlari nima uchun kerak? Ular shunday emasmi? hid tip nazariyasi nuqtai nazaridan yomonmi? Sxemaning oldingi versiyalarida tushuntirishni ko'rish mumkin. Avvaliga, ha, shunday edi, har bir aksirish uchun yangi tur yaratilgan. Ushbu rudimentlar hali ham ushbu shaklda mavjud, masalan:

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;

Ammo endi tasavvur qiling-a, agar sizning tuzilmangizda 5 ta ixtiyoriy maydon mavjud bo'lsa, unda barcha mumkin bo'lgan variantlar uchun 32 tur kerak bo'ladi. kombinatsion portlash. Shunday qilib, TL nazariyasining kristall sofligi yana bir bor ketma-ketlashtirishning qattiq haqiqatining quyma temir eshakka qarshi urildi.

Bundan tashqari, joylarda bu bolalar o'zlari yozishni buzadilar. Misol uchun, MTProto-da (keyingi bob) javob Gzip tomonidan siqilishi mumkin, hamma narsa oqilona - qatlamlar va sxemalarning buzilishi bundan mustasno. Bir marta, va RpcResultning o'zini emas, balki uning mazmunini o'rgandim. Xo'sh, nega buni qilish kerak? .. Siqish har qanday joyda ishlashi uchun men qo'ltiq tayoqchani kesishim kerak edi.

Yoki boshqa misol, biz bir marta xato topdik - yuborilgan InputPeerUser o'rniga InputUser. Yoki aksincha. Lekin u ishladi! Ya'ni, server turiga ahamiyat bermadi. Bu qanday bo'lishi mumkin? Javob, ehtimol, telegram-cli-dan kod bo'laklari tomonidan so'raladi:

  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);

Boshqacha qilib aytganda, bu erda serializatsiya amalga oshiriladi Qo'lda, kod yaratilmagan! Ehtimol, server xuddi shunday tarzda amalga oshirilgandir?.. Asosan, bu bir marta bajarilsa ishlaydi, lekin keyin uni yangilanishlar bilan qanday qo'llab-quvvatlash mumkin? Sxema shu uchun emasmi? Va keyin biz keyingi savolga o'tamiz.

Versiyalash. Qatlamlar

Nima uchun sxema versiyalari qatlamlar deb ataladi, faqat nashr etilgan sxemalar tarixiga asoslanib taxmin qilish mumkin. Ko'rinishidan, dastlab mualliflarga asosiy narsalarni o'zgarmas sxema bo'yicha bajarish mumkin bo'lib tuyuldi va faqat kerak bo'lganda, ular boshqa versiya bo'yicha amalga oshirilayotganligini aniq so'rovlarga ko'rsatadi. Aslida, hatto yaxshi g'oya ham - va yangi iroda, xuddi "aralashtirib" eski ustiga qo'yadi. Ammo keling, bu qanday amalga oshirilganini ko'rib chiqaylik. To'g'ri, boshidanoq qarash mumkin emas edi - bu kulgili, lekin asosiy qatlam sxemasi oddiygina mavjud emas. Qatlamlar 2 da boshlangan. Hujjatlar bizga maxsus TL xususiyati haqida gapirib beradi:

Agar mijoz 2-qatlamni qo'llab-quvvatlasa, quyidagi konstruktordan foydalanish kerak:

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

Amalda, bu har bir API chaqiruvidan oldin qiymatga ega int ekanligini anglatadi 0x289dd1f6 usul raqamidan oldin qo'shilishi kerak.

OK. Ammo keyin nima bo'ldi? Keyin keldi

invokeWithLayer3#b7475268 query:!X = X;

Xo'sh, keyingi nima? Chunki taxmin qilish oson

invokeWithLayer4#dea0d430 query:!X = X;

Qiziqmi? Yo'q, kulishga hali erta, nima haqida o'ylang har biri boshqa qatlamdan kelgan so'rovni shunday maxsus turga o'rash kerak - agar sizda ularning barchasi boshqacha bo'lsa, ularni qanday ajratish mumkin? Oldinga atigi 4 bayt qo'shish juda samarali usul. Shunday qilib

invokeWithLayer5#417a57ae query:!X = X;

Ammo bir muncha vaqt o'tgach, u qandaydir bakanaliyaga aylanishi aniq. Va yechim keldi:

Yangilash: 9-qavatdan boshlab yordamchi usullar invokeWithLayerN bilan birgalikda ishlatilishi mumkin initConnection

Xayr! 9 ta versiyadan so'ng, biz nihoyat 80-yillarda Internet protokollarida nima qilinganiga keldik - ulanish boshida bir marta versiya muzokaralari!

Xo'sh, keyingi nima?..

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

Va endi siz kulishingiz mumkin. Faqat yana 9 ta qatlamdan so'ng, nihoyat versiya raqamiga ega universal konstruktor qo'shildi, uni ulanish boshida faqat bir marta chaqirish kerak bo'ladi va qatlamlardagi ma'no yo'qolganga o'xshaydi, endi bu shunchaki shartli versiya, masalan boshqa hamma joyda. Muammo hal.

To'g'rimi?..

Vasiliy, [16.07.18 14:01] Juma kuni men o'yladim:
Teleserver voqealarni so'rovsiz yuboradi. So'rovlar InvokeWithLayer-ga o'ralishi kerak. Server yangilanishlarni o'ramaydi, javoblar va yangilanishlarni o'rash uchun tuzilma yo'q.

Bular. mijoz yangilanishni xohlagan qatlamni aniqlay olmaydi

Vadim Goncharov, [16.07.18 14:02] InvokeWithLayer printsipial jihatdan tayoq emasmi?

Vasiliy, [16.07.18 14:02] Bu yagona yo'l

Vadim Goncharov, [16.07.18 14:02] bu mohiyatan sessiya boshida qatlam bo'lishini anglatishi kerak

Aytgancha, shundan kelib chiqadiki, mijozning yangilanishi ta'minlanmaydi

Yangilanishlar, ya'ni. turi Updates sxema bo'yicha server mijozga API so'roviga javoban emas, balki voqea sodir bo'lganda o'z-o'zidan yuboradi. Bu boshqa postda muhokama qilinadigan murakkab mavzu, ammo hozircha server yangilanishlarni mijoz oflayn bo'lganda ham to'plashini bilish muhimdir.

Shunday qilib, o'rashdan bosh tortganda har birining paketi uning versiyasini ko'rsatadi, shuning uchun mantiqiy ravishda quyidagi mumkin bo'lgan muammolar paydo bo'ladi:

  • mijoz qaysi versiyani qo'llab-quvvatlashini aytishdan oldin server mijozga yangilanishlarni yuboradi
  • mijozni yangilagandan keyin nima qilish kerak?
  • kim? kafolatlarjarayonda serverning qatlam raqami haqidagi fikri o'zgarmaydi?

Sizningcha, bu faqat nazariy fikrlash va amalda bunday bo'lishi mumkin emas, chunki server to'g'ri yozilgan (har qanday holatda ham, u yaxshi sinovdan o'tgan)? Ha! Qanday bo'lmasin!

Avgust oyida aynan shu narsaga duch keldik. 14 avgust kuni Telegram serverlarida nimadir yangilanayotgani haqida xabarlar paydo bo‘ldi... keyin esa jurnallarda:

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.

va keyin bir necha megabayt stek izlari (yaxshi, bir vaqtning o'zida, ro'yxatga olish o'rnatildi). Oxir oqibat, agar sizning TL-da biror narsa tan olinmagan bo'lsa - bu imzolar bo'yicha ikkilik, keyin esa oqimda HAMMA ketadi, dekodlash imkonsiz bo'ladi. Bunday vaziyatda nima qilish kerak?

Xo'sh, har kimning xayoliga keladigan birinchi narsa - aloqani uzib, qayta urinib ko'rish. Yordam bermadi. Biz CRC32-ni google-da qidirdik - bular 73-sxemadagi ob'ektlar bo'lib chiqdi, garchi biz 82-sxema ustida ishlagan bo'lsak-da. Biz jurnallarga diqqat bilan qaraymiz - ikki xil sxemadan identifikatorlar mavjud!

Balki muammo faqat bizning norasmiy mijozimizdadir? Yo‘q, biz Telegram Desktop 1.2.17 dasturini ishga tushiramiz (versiyasi bir qator Linux distributivlarida taqdim etilgan), u Istisnolar jurnaliga yozadi: MTP kutilmagan turdagi identifikatori #b5223b0f MTPMessageMedia da o‘qiladi…

Telegram protokoli va tashkiliy yondashuvlarini tanqid qilish. 1-qism, texnik: mijozni noldan yozish tajribasi - TL, MT

Google shunga o'xshash muammo norasmiy mijozlardan birida sodir bo'lganligini ko'rsatdi, ammo keyin versiya raqamlari va shunga ko'ra, taxminlar boshqacha edi ...

Xo'sh, nima qilish kerak? Vasiliy va men bo'linib ketdik: u sxemani 91 ga yangilashga harakat qildi, men bir necha kun kutishga qaror qildim va 73 ga harakat qildim. Ikkala usul ham ishladi, lekin ular empirik bo'lgani uchun, qancha versiyani sakrash kerakligi haqida hech qanday tushuncha yo'q. yoki pastga, na qancha kutishingiz kerak.

Keyinchalik men vaziyatni qayta tiklashga muvaffaq bo'ldim: biz mijozni ishga tushiramiz, uni o'chirib qo'yamiz, sxemani boshqa qatlamga qayta kompilyatsiya qilamiz, qayta ishga tushiramiz, muammoni yana hal qilamiz, avvalgisiga qaytamiz - ey, sxemani o'zgartirib, mijozni bir necha marta qayta ishga tushirmaydi. daqiqalar yordam beradi. Siz turli qatlamlardan ma'lumotlar tuzilmalari aralashmasini olasiz.

Tushuntirish? Turli bilvosita alomatlardan taxmin qilganingizdek, server turli xil mashinalarda turli xil jarayonlardan iborat. Katta ehtimol bilan, "buferlash" uchun mas'ul bo'lgan serverlardan biri navbatga yuqoriroqlari bergan narsani qo'yadi va ular uni avlod davridagi sxema bo'yicha berishdi. Va bu navbat "chirigan" bo'lgunga qadar, bu haqda hech narsa qilish mumkin emas edi.

Magar... lekin bu dahshatli qo‘ltiq bo‘lsa?!.. Yo‘q, aqldan ozgan g‘oyalar haqida o‘ylashdan oldin, keling, rasmiy mijozlar kodini ko‘rib chiqaylik. Android versiyasida biz hech qanday TL tahlilchisini topa olmadik, lekin biz (de) seriyali faylni topamiz (github uni rang berishdan bosh tortadi). Mana kod parchalari:

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;

yoki

    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... aqldan ozgan ko'rinadi. Lekin, ehtimol, bu ishlab chiqarilgan kod, shundaymi? .. Lekin, albatta, barcha versiyalarni qo'llab-quvvatlaydi! To'g'ri, nima uchun hamma narsa bir uyumga, yashirin suhbatlarga va har xil narsalarga aralashganligi aniq emas. _old7 qaysidir ma'noda mashina ishlab chiqarishga o'xshamaydi ... Lekin, eng muhimi, men aqldan ozganman

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Bolalar, bir qatlam ichida qaror qabul qila olmaysizmi?! Xo'sh, mayli, "ikki", deylik, xato bilan chiqarildi, yaxshi, shunday bo'ladi, lekin UCH? .. Darhol yana o'sha rakeda? Bu qanday pornografiya, kechirasizmi? ..

Aytgancha, shunga o'xshash narsa Telegram Desktop manbalarida sodir bo'ladi - agar shunday bo'lsa va sxema bo'yicha ketma-ket bir nechta majburiyatlar uning qatlam raqamini o'zgartirmaydi, balki biror narsani tuzatadi. Sxema uchun rasmiy ma'lumot manbai bo'lmagan sharoitda, rasmiy mijoz manbalaridan tashqari, uni qayerdan olsam bo'ladi? Va siz uni u erdan olasiz, barcha usullarni sinab ko'rmaguningizcha, sxema to'liq to'g'ri ekanligiga ishonch hosil qila olmaysiz.

Buni qanday qilib sinab ko'rish mumkin? Umid qilamanki, birlik, funktsional va boshqa testlarning muxlislari sharhlarda baham ko'rishadi.

OK, keling, boshqa kod qismini ko'rib chiqaylik:

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;

Bu erda "qo'lda yaratilgan" sharhi shuni ko'rsatadiki, ushbu faylning faqat bir qismi qo'lda yozilgan (siz ta'mirlash dahshatli tushini tasavvur qila olasizmi?), qolgan qismi esa mashina tomonidan yaratilgan. Biroq, keyin yana bir savol tug'iladi - manbalar mavjud butunlay emas (Linux yadrosida GPL ostida la bloblar), lekin bu allaqachon ikkinchi qism uchun mavzu.

Lekin yetarli. Keling, ushbu ketma-ketlashtirishning barchasi ta'qib qilinadigan protokolga o'tamiz.

MT Proto

Shunday qilib, keling, ochaylik umumiy tavsif и protokolning batafsil tavsifi va biz qoqilgan birinchi narsa terminologiya. Va hamma narsaning ko'pligi bilan. Umuman olganda, bu Telegram’ning savdo belgisi bo‘lib ko‘rinadi – turli joylarda narsalarni turli yo‘llar bilan chaqirish yoki bir so‘z bilan turli narsalarni yoki aksincha (masalan, yuqori darajadagi API’da stikerlar to‘plamini ko‘rsangiz – bu siz o'ylagandek emas).

Masalan, "xabar" (xabar) va "sessiya" (sessiya) - bu erda ular Telegram mijozining odatiy interfeysidan farqli narsani anglatadi. Xo'sh, xabarda hamma narsa aniq, uni OOP nuqtai nazaridan talqin qilish mumkin yoki oddiygina "paket" so'zi deb ataladi - bu past, transport darajasi, interfeysdagi kabi xabarlar yo'q, juda ko'p. xizmat ko'rsatadiganlar. Lekin sessiya ... lekin birinchi narsa birinchi.

transport qatlami

Birinchi narsa - transport. Bizga 5 ta variant haqida aytib beramiz:

  • TCP
  • Veb-sayt
  • HTTPS orqali veb-soket
  • HTTP
  • HTTPS

Vasiliy, [15.06.18 15:04] UDP transporti ham bor, lekin u hujjatlashtirilmagan

Va TCP uchta variantda

Birinchisi TCP orqali UDP ga o'xshaydi, har bir paket ketma-ketlik raqami va crc ni o'z ichiga oladi
Nega aravada doklarni o'qish shunchalik og'riqli?

Xo'sh, hozir bor TCP allaqachon 4 ta variantda:

  • Qisqartirilgan
  • vositachi
  • to'ldirilgan oraliq
  • to'liq

OK, MTProxy uchun Padded intermediate, bu keyinchalik ma'lum voqealar tufayli qo'shilgan. Lekin nima uchun yana ikkita versiya (jami uchta), bittasini qilish mumkin edi? To'rttasi faqat asosiy MTProto-ning uzunligi va foydali yukini qanday o'rnatishda bir-biridan farq qiladi, ular keyinroq muhokama qilinadi:

  • Qisqartirilganda u 1 yoki 4 baytni tashkil qiladi, lekin tanadan 0xef emas
  • Intermediate da bu 4 bayt uzunlik va maydon bo'lib, mijoz birinchi marta yuborishi kerak 0xeeeeeeee Oraliq ekanligini bildirish uchun
  • In To'liq, eng o'ziga qaram bo'lib, tarmoqchi nuqtai nazaridan: uzunlik, tartib raqami va BIR EMAS, bu asosan MTProto, tana, CRC32. Ha, bularning barchasi TCP orqali. Bu bizni baytlarning ketma-ket oqimi ko'rinishida ishonchli transport bilan ta'minlaydi, hech qanday ketma-ketliklar, ayniqsa nazorat summalari kerak emas. OK, endi ular menga TCP 16 bitli nazorat summasiga ega ekanligiga e'tiroz bildiradilar, shuning uchun ma'lumotlarning buzilishi sodir bo'ladi. Ajoyib, bizda 16 baytdan ortiq xeshli kriptografik protokol mavjud bo'lsa, bu xatolarning barchasi va undan ham ko'proq - SHA mos kelmasligi yuqori darajada bo'ladi. CRC32 da bu borada hech qanday nuqta yo'q.

Keling, bir bayt uzunlik mumkin bo'lgan Qisqartirilganni Intermediate bilan taqqoslaylik, bu "4 baytlik ma'lumotlarni tekislash kerak bo'lganda" oqlaydi, bu juda bema'nilik. Nima, Telegram dasturchilari shunchalik bema'niki, ular rozetkadan ma'lumotlarni tekislangan buferga o'qiy olmaydilar, deb ishoniladimi? Siz buni hali ham qilishingiz kerak, chunki o'qish sizga har qanday baytni qaytarishi mumkin (va proksi-serverlar ham mavjud, masalan ...). Yoki, boshqa tomondan, agar bizda hali ham 16 baytdan katta hajmdagi to'ldirishlar mavjud bo'lsa, nima uchun Qisqartirilgan bilan bezovta qilish kerak - 3 baytni tejang. ba'zan ?

Nikolay Durov velosipedlarni, shu jumladan tarmoq protokollarini haqiqiy amaliy ehtiyojlarsiz ixtiro qilishni juda yaxshi ko'radi degan taassurot paydo bo'ladi.

Boshqa transport imkoniyatlari, shu jumladan. Veb va MTProxy, agar so'rov bo'lsa, biz hozir ko'rib chiqmaymiz, ehtimol boshqa postda. 2018 yilda chiqarilganidan so'ng, provayderlar tezda uni blokirovka qilishni o'rgangan MTProxy haqida hozir eslaymiz. blokni aylanib o'tish, o'n paket hajmi! Bundan tashqari, C tilida yozilgan (yana Uoltman tomonidan) MTProxy serveri Linux xususiyatlariga keraksiz bog'langanligi, garchi bu umuman talab qilinmasa ham (Fil Kulin tasdiqlaydi) va shunga o'xshash server Go yoki Node.js da mavjud. yuzdan kamroq qatorga mos keladi.

Ammo biz ushbu odamlarning texnik savodxonligi haqida boshqa masalalarni ko'rib chiqqandan keyin bo'lim oxirida xulosa qilamiz. Hozircha, keling, MTProto sessiyasini joylashtirgan 5-OSI qatlamiga, sessiyaga o'tamiz.

Kalitlar, xabarlar, seanslar, Diffie-Hellman

Ular uni to'liq to'g'ri qo'yishmagan ... Seans - bu Faol seanslar ostidagi interfeysda ko'rinadigan sessiya emas. Lekin tartibda.

Telegram protokoli va tashkiliy yondashuvlarini tanqid qilish. 1-qism, texnik: mijozni noldan yozish tajribasi - TL, MT

Bu erda biz transport qatlamidan ma'lum uzunlikdagi bayt qatorini oldik. Bu shifrlangan xabar yoki ochiq matn - agar biz hali ham asosiy muzokaralar bosqichida bo'lsak va buni amalga oshirayotgan bo'lsak. "Kalit" deb ataladigan tushunchalar to'plamining qaysi biri haqida gapiramiz? Keling, Telegram jamoasining o'zi uchun bu masalaga oydinlik kiritaylik (men o'z hujjatlarimni ingliz tilidan yoki ertalab soat 4 da charchagan miyaga tarjima qilganim uchun uzr so'rayman, ba'zi iboralarni o'z holicha qoldirish osonroq bo'ldi):

deb nomlangan ikkita ob'ekt mavjud sessiya - "joriy seanslar" ostidagi rasmiy mijozlar interfeysidagi bittasi, bunda har bir seans butun qurilma/OTga mos keladi.
Ikkinchi - MTProto sessiyasi, unda xabarlar tartib raqami (past darajadagi ma'noda) mavjud va qaysi turli TCP ulanishlari orasida davom etishi mumkin. Bir vaqtning o'zida bir nechta MTProto seanslarini o'rnatish mumkin, masalan, fayllarni yuklab olishni tezlashtirish.

Bu ikkisi o'rtasida sessiyalarni tushunchasidir ruxsat. Degeneratsiya holatida buni aytish mumkin UI sessiyasi bilan bir xil ruxsatAmmo afsuski, bu juda murakkab. Biz qaraymiz:

  • Yangi qurilmadagi foydalanuvchi avval yaratadi auth_key va uni hisob bilan bog'laydi, masalan, SMS orqali - shuning uchun ruxsat
  • Bu birinchisining ichida sodir bo'ldi MTProto sessiyasi, bor session_id o'zingizning ichingizda.
  • Ushbu bosqichda kombinatsiya ruxsat и session_id chaqirish mumkin edi misol - bu so'z ba'zi mijozlarning hujjatlari va kodlarida mavjud
  • Keyin mijoz ochishi mumkin bir nechta MTProto seanslari xuddi shu ostida auth_key - xuddi shu DC ga.
  • Keyin bir kun mijoz faylni so'rashi kerak boshqa DC - va bu DC uchun yangisi yaratiladi auth_key !
  • Tizimga bu ro'yxatdan o'tayotgan yangi foydalanuvchi emasligini aytish uchun, lekin bir xil ruxsat (UI sessiyasi), mijoz API chaqiruvlaridan foydalanadi auth.exportAuthorization uyda DC auth.importAuthorization yangi DCda.
  • Shunga qaramay, bir nechta ochiq bo'lishi mumkin MTProto seanslari (har biri o'ziga xos session_id) bu yangi DC ga, ostida uning auth_key.
  • Nihoyat, mijoz mukammal forward maxfiyligini xohlashi mumkin. Har auth_key edi Doimiy kalit - har bir DC - va mijoz qo'ng'iroq qilishi mumkin auth.bindTempAuthKey foydalanish uchun vaqtinchalik auth_key - va yana bitta temp_auth_key DC boshiga, hamma uchun umumiy MTProto seanslari bu DC ga.

e'tibor bering, bu tuz (va kelajakdagi tuzlar) ham bitta auth_key bular. hamma o'rtasida taqsimlangan MTProto seanslari bir xil DC ga.

"Turli TCP ulanishlari o'rtasida" nimani anglatadi? Bu shuni anglatadiki shunga o'xshash narsa veb-saytdagi avtorizatsiya cookie-fayllari - bu serverga ko'plab TCP ulanishlarini saqlab qoladi (omon qoladi), lekin bir kun u yomonlashadi. Faqat HTTP-dan farqli o'laroq, MTProto-da, sessiya ichida xabarlar ketma-ket raqamlanadi va tasdiqlanadi, ular tunnelga kirdi, ulanish uzildi - yangi ulanish o'rnatilgandan so'ng, server ushbu seansda etkazib bermagan hamma narsani iltimos qilib yuboradi. oldingi TCP ulanishi.

Biroq, yuqoridagi ma'lumotlar ko'p oylik sud jarayonlaridan keyin siqishdir. Ayni paytda biz mijozimizni noldan amalga oshiryapmizmi? - keling, boshiga qaytaylik.

Shunday qilib, biz hosil qilamiz auth_key haqida Telegram'dan Diffie-Hellman versiyalari. Keling, hujjatlarni tushunishga harakat qilaylik ...

Vasiliy, [19.06.18 20:05] data_with_hash := SHA1(maʼlumotlar) + maʼlumotlar + (har qanday tasodifiy baytlar); uzunligi 255 baytga teng bo'lishi uchun;
shifrlangan_ma'lumotlar := RSA (xeshli_ma'lumotlar, server_ommaviy_kalit); 255 bayt uzunlikdagi raqam (katta endian) kerakli modul orqali kerakli quvvatga ko'tariladi va natija 256 baytlik raqam sifatida saqlanadi.

Ularda DH doping bor

Sog'lom odamning DH ga o'xshamaydi
dx-da ikkita ochiq kalit mavjud emas

Oxir-oqibat, biz buni aniqladik, lekin cho'kindi qoldi - mijoz tomonidan raqamni faktorizatsiya qila olganligi ishining isboti. DoS hujumlaridan himoya turi. Va RSA kaliti faqat bir yo'nalishda, asosan shifrlash uchun ishlatiladi new_nonce. Ammo bu oddiy tuyulgan operatsiya muvaffaqiyatli bo'lsa-da, siz nimaga duch kelishingiz kerak?

Vasiliy, [20.06.18 00:26] Men hali ilova so'roviga erishmadim

Men DHga so'rov yubordim

Va transportdagi dokda 4 bayt xato kodi bilan javob berishi mumkinligi yozilgan. Va tamom

Xo'sh, u menga -404 dedi, nima?

Mana, men unga: "Falonchi barmoq izi bilan server kaliti bilan shifrlangan o'z efignani qo'lga oling, men DH istayman" va u ahmoqona javob beradi 404

Bunday server javobi haqida nima deb o'ylaysiz? Nima qilish kerak? Hech kim so'ramaydi (lekin ikkinchi qismda bu haqda ko'proq).

Bu yerda dock barcha qiziqish qilish

Boshqa qiladigan ishim yo'q, men faqat raqamlarni oldinga va orqaga aylantirishni orzu qilardim

Ikkita 32 bitli raqam. Men ham boshqalar kabi ularni to‘pladim

Lekin yo'q, aynan mana shu ikkitasi sizga birinchi navbatda BE sifatida kerak

Vadim Goncharov, [20.06.18 15:49] va shu sababli 404?

Vasiliy, [20.06.18 15:49] HA!

Vadim Goncharov, [20.06.18 15:50] shuning uchun u nimani "topa olmaganini" tushunmayapman

Vasiliy, [20.06.18 15:50] taxminan

Men oddiy bo'linuvchilarga bunday parchalanishni topmadim%)

Hatto xato haqida xabar berish ham o'zlashtirilmagan

Vasiliy, [20.06.18 20:18] Oh, MD5 ham bor. Allaqachon uch xil xesh

Kalit barmoq izi quyidagicha hisoblanadi:

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

SHA1 va sha2

Shunday qilib, keling auth_key Diffie-Hellmanga ko'ra biz 2048 bit o'lchamga ega bo'ldik. Keyin nima? Keyin biz ushbu kalitning pastki 1024 bitlari hech qanday tarzda ishlatilmasligini aniqlaymiz ... lekin hozircha bu haqda o'ylab ko'raylik. Ushbu bosqichda biz server bilan umumiy sirga egamiz. TLS seansining analogi o'rnatildi, bu juda qimmat protsedura. Ammo server bizning kimligimiz haqida hali hech narsa bilmaydi! Hali emas, aslida ruxsat. Bular. agar siz ilgari ICQ-da bo'lgani kabi "login-parol" yoki hech bo'lmaganda SSH-da bo'lgani kabi "login-kalit" deb o'ylagan bo'lsangiz (masalan, ba'zi gitlab / github-da). Biz anonim bo'ldik. Va agar server bizga "bu telefon raqamlari boshqa DC tomonidan xizmat qiladi" deb javob bersa? Yoki hatto "telefon raqamingiz taqiqlangan"mi? Biz qila oladigan eng yaxshi narsa, kalitni hali ham foydali bo'lib qoladi va shu paytgacha chirimaydi degan umidda saqlashdir.

Aytgancha, biz uni rezervatsiyalar bilan "oldik". Masalan, biz serverga ishonamizmi? U soxtami? Bizga kriptografik tekshiruvlar kerak:

Vasiliy, [21.06.18 17:53] Ular mobil mijozlarga 2kbit raqamni soddaligi uchun tekshirishni taklif qilishadi%)

Lekin bu umuman aniq emas, nafeijoa

Vasiliy, [21.06.18 18:02] Dok oddiy bo'lmasa nima qilish kerakligini aytmaydi

Aytilmagan. Keling, Android uchun rasmiy mijoz bu holatda nima qilishini ko'rib chiqaylik? A bu nima (va ha, butun fayl u erda qiziqarli) - ular aytganidek, men uni shu erda qoldiraman:

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

Yo'q, albatta biroz raqamning soddaligi uchun tekshiruvlar mavjud, lekin shaxsan men matematika bo'yicha etarli bilimga ega emasman.

OK, biz asosiy kalitni oldik. Tizimga kirish uchun, ya'ni. so'rovlarni yuborish uchun AES-dan foydalanib, keyingi shifrlashni amalga oshirish kerak.

Xabar kaliti xabar tanasining SHA128 ning 256 o'rta biti (shu jumladan seans, xabar identifikatori va boshqalar), shu jumladan avtorizatsiya kalitidan olingan 32 bayt bilan to'ldirilgan bayt sifatida aniqlanadi.

Vasiliy, [22.06.18 14:08] O'rtacha kaltaklar

Tushundim auth_key. Hammasi. Keyinchalik ular ... docklardan aniq emas. Ochiq manba kodini o'rganing.

E'tibor bering, MTProto 2.0 12 dan 1024 baytgacha to'ldirishni talab qiladi, natijada xabar uzunligi 16 baytga bo'linishi sharti bilan.

Xo'sh, qancha plomba qo'yish kerak?

Va ha, bu erda ham, xato bo'lsa, 404

Agar kimdir diagramma va hujjatlar matnini diqqat bilan o'rgangan bo'lsa, u erda MAC yo'qligini payqadi. Va bu AES boshqa joyda ishlatilmaydigan ba'zi IGE rejimida ishlatiladi. Ular, albatta, bu haqda o'zlarining tez-tez so'raladigan savollariga yozadilar... Bu erda, masalan, xabar kalitining o'zi bir vaqtning o'zida yaxlitligini tekshirish uchun ishlatiladigan shifrlangan ma'lumotlarning SHA xeshidir - va agar nomuvofiqlik bo'lsa, hujjatlar ba'zi sabablarga ko'ra jimgina ularni e'tiborsiz qoldirishni tavsiya qiladi (lekin xavfsizlik haqida nima desa, to'satdan bizni buzadimi?).

Men kriptograf emasman, ehtimol bu rejimda nazariy nuqtai nazardan hech qanday yomon narsa yo'q. Lekin men Telegram Desktop misolida amaliy muammoni aniq ayta olaman. U mahalliy keshni (barcha bu D877F783D5D3EF8C) MTProto-dagi xabarlar bilan bir xil tarzda shifrlaydi (faqat bu holda, 1.0 versiyasi), ya'ni. avval xabar kaliti, so'ngra ma'lumotlarning o'zi (va bir joyda asosiy katta auth_key 256 bayt, ularsiz msg_key foydasiz). Shunday qilib, muammo katta fayllarda sezilarli bo'ladi. Ya'ni, siz ma'lumotlarning ikkita nusxasini saqlashingiz kerak - shifrlangan va shifrlangan. Va agar megabaytlar yoki oqimli video bo'lsa, masalan? .. Shifrlangan matndan so'ng MAC bilan klassik sxemalar uni oqimli o'qish va darhol uzatish imkonini beradi. Va MTProto bilan siz kerak boshida butun xabarni shifrlash yoki parolini ochish, shundan keyingina uni tarmoqqa yoki diskka o'tkazish. Shuning uchun, Telegram ish stolining so'nggi versiyalarida keshda user_data boshqa format allaqachon ishlatilgan - CTR rejimida AES bilan.

Vasiliy, [21.06.18 01:27] Oh, men IGE nima ekanligini bilib oldim: IGE dastlab Kerberos uchun "autentifikatsiya qiluvchi shifrlash rejimi" ga birinchi urinish edi. Bu muvaffaqiyatsiz urinish edi (bu butunlikni himoya qilmaydi) va uni olib tashlash kerak edi. Bu yaqinda OCB va GCM kabi rejimlar bilan yakunlangan, ishlaydigan autentifikatsiya qiluvchi shifrlash rejimiga 20 yillik qidiruvning boshlanishi edi.

Va endi arava tomonidagi dalillar:

Nikolay Durov boshchiligidagi Telegram ortidagi jamoa oltita ACM chempionidan iborat bo‘lib, ularning yarmi matematika fanlari nomzodi. MTProto ning joriy versiyasini chiqarish uchun ularga taxminan ikki yil kerak bo'ldi.

Nimasi kulgili. Pastki darajaga ikki yil

Yoki biz faqat tls olishimiz mumkin

OK, deylik, biz shifrlash va boshqa nuanslarni bajardik. Biz nihoyat TL-seriyalashtirilgan so'rovlarni yubora olamizmi va javoblarni seriyadan chiqara olamizmi? Xo'sh, nima va qanday yuborilishi kerak? Mana usul initConnectionbalki shudir?

Vasiliy, [25.06.18 18:46] Ulanishni ishga tushiradi va foydalanuvchi qurilmasi va ilovasidagi ma'lumotlarni saqlaydi.

U app_id, device_model, system_version, app_version va lang_code ni qabul qiladi.

Va ba'zi so'rovlar

Har doimgidek hujjatlar. Ochiq manbani o'rganing

Agar invokeWithLayer bilan hamma narsa aniq bo'lsa, bu nima? Ma'lum bo'lishicha, bizda bor - mijoz allaqachon serverdan so'rashi kerak bo'lgan narsaga ega edi - biz yubormoqchi bo'lgan so'rov bor:

Vasiliy, [25.06.18 19:13] Kodga ko'ra, birinchi qo'ng'iroq bu axlatga o'ralgan va axlatning o'zi invokewithlayer ichida.

Nima uchun initConnection alohida qo'ng'iroq bo'lishi mumkin emas, lekin o'ram bo'lishi kerak? Ha, ma'lum bo'lishicha, bu asosiy kalitda bo'lgani kabi, bir martalik emas, balki har bir seansning boshida bajarilishi kerak. Lekin! Unga ruxsatsiz foydalanuvchi qo'ng'iroq qila olmaydi! Bu erda biz uni qo'llash mumkin bo'lgan bosqichga yetdik Bunisi hujjatlar sahifasi - va u bizga shuni aytadi ...

API usullarining faqat kichik bir qismi ruxsatsiz foydalanuvchilar uchun mavjud:

  • 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

Ulardan birinchisi auth.sendCode, va biz api_id va api_hash yuboradigan qimmatli birinchi so'rov bor va shundan so'ng biz kod bilan SMS olamiz. Va agar biz noto'g'ri DC ga kirgan bo'lsak (masalan, ushbu mamlakatning telefon raqamlariga boshqasi xizmat ko'rsatadi), unda biz kerakli DC raqami bilan xatoga duch kelamiz. DC raqami orqali qaysi IP-manzilga ulanishimiz kerakligini bilish uchun bizga yordam beradi help.getConfig. Bir vaqtlar atigi 5 ta yozuv bor edi, ammo 2018 yilgi taniqli voqealardan so'ng ularning soni sezilarli darajada oshdi.

Endi eslaylikki, biz ushbu bosqichda anonim serverga kirganmiz. Shunchaki IP manzilni olish juda qimmat emasmi? Nega buni va boshqa operatsiyalarni MTProto-ning shifrlanmagan qismida bajarmaslik kerak? Men e'tirozni eshitaman: "Qanday qilib siz RKN soxta manzillar bilan javob bermasligiga ishonch hosil qilishingiz mumkin?". Buning uchun biz, aslida, rasmiy mijozlarda ekanligini eslaymiz o'rnatilgan RSA kalitlari, ya'ni. faqat mumkin belgisi bu ma'lumot. Aslida, bu allaqachon mijozlar boshqa kanallar orqali oladigan qulflarni chetlab o'tish haqida ma'lumot olish uchun qilingan (buni MTProto-ning o'zida amalga oshirib bo'lmaydi, chunki siz hali ham qaerga ulanishni bilishingiz kerak).

Ha mayli. Mijozlarni avtorizatsiya qilishning ushbu bosqichida biz hali ruxsat etilmagan va arizamizni ro'yxatdan o'tkazmaganmiz. Biz hozircha server ruxsatsiz foydalanuvchi uchun mavjud usullarga qanday javob berishini ko'rmoqchimiz. Va bu erda…

Vasiliy, [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;

Sxemada birinchi, ikkinchisi keladi

Tdesktop sxemasida uchinchi qiymat

Ha, o'shandan beri, albatta, hujjatlar yangilandi. Garchi tez orada u yana ahamiyatsiz bo'lib qolishi mumkin. Ajam ishlab chiquvchi qanday bilishi kerak? Balki arizangizni ro'yxatdan o'tkazsangiz, sizga xabar berishar? Vasiliy buni qildi, lekin afsuski, unga hech narsa yuborilmadi (yana bu haqda ikkinchi qismda gaplashamiz).

... Siz allaqachon qandaydir tarzda API ga o'tganimizni payqadingiz, ya'ni. keyingi bosqichga o'ting va MTProto mavzusida biror narsani o'tkazib yubordingizmi? Hech narsa ajablanarli emas:

Vasiliy, [28.06.18 02:04] Mm, ular e2e-dagi ba'zi algoritmlarni ko'rib chiqishmoqda

Mtproto shifrlash algoritmlari va ikkala domen uchun kalitlarni, shuningdek, bir oz o'rash tuzilishini belgilaydi.

Lekin ular doimo turli stek darajalarini aralashtirib turishadi, shuning uchun mtproto qayerda tugashi va keyingi daraja boshlangani har doim ham aniq emas.

Ular qanday aralashgan? Xo'sh, bu erda PFS uchun bir xil vaqtinchalik kalit, masalan (Aytgancha, Telegram Desktop buni qanday qilishni bilmaydi). U API so'rovi bilan amalga oshiriladi auth.bindTempAuthKey, ya'ni. yuqori darajadan. Ammo shu bilan birga, u pastki darajadagi shifrlashga xalaqit beradi - undan keyin, masalan, siz buni yana qilishingiz kerak. initConnection va hokazo, bu emas faqatgina oddiy so'rov. Alohida-alohida, u shuningdek, maydon bo'lsa-da, DCda faqat BIR vaqtinchalik kalitga ega bo'lishingiz mumkinligini ta'minlaydi auth_key_id har bir xabarda hech bo'lmaganda har bir xabar kalitni o'zgartirishga imkon beradi va server istalgan vaqtda vaqtinchalik kalitni "unutish" huquqiga ega - bu holda nima qilish kerak, hujjatlarda aytilmagan ... yaxshi, nima uchun kelajakdagi tuzlar to'plamida bo'lgani kabi, bir nechta kalitlarga ega bo'lish mumkin emas, lekin?..

MTProto mavzusida e'tiborga olish kerak bo'lgan yana bir qancha narsalar mavjud.

Xabar xabarlari, msg_id, msg_seqno, minnatdorchiliklar, noto'g'ri yo'nalishdagi pinglar va boshqa o'ziga xosliklar

Nega ular haqida bilishingiz kerak? Chunki ular bir daraja yuqoriroq "oqish" qiladi va siz API bilan ishlashda ular haqida bilishingiz kerak. Aytaylik, bizni msg_key qiziqtirmaydi, pastki daraja biz uchun hamma narsani hal qildi. Ammo shifrlangan ma'lumotlar ichida bizda quyidagi maydonlar mavjud (shuningdek, to'ldirish qaerda ekanligini bilish uchun ma'lumotlar uzunligi, lekin bu muhim emas):

  • tuz-int64
  • session_id - int64
  • message_id - int64
  • seq_no-int32

Eslatib o'tamiz, tuz butun DC uchun bittadir. Nega u haqida bilasiz? Faqat so'rov borligi uchun emas get_future_salts, qaysi intervallar to'g'ri bo'lishini aytadi, lekin sizning tuzingiz "chirigan" bo'lsa, xabar (so'rov) shunchaki yo'qoladi. Albatta, server yangi tuz haqida xabar beradi new_session_created - lekin eskisi bilan, masalan, qandaydir tarzda qayta yuborishingiz kerak bo'ladi. Va bu savol dasturning arxitekturasiga ta'sir qiladi.

Serverga ko'p sabablarga ko'ra sessiyalarni butunlay to'xtatish va shu tarzda javob berishga ruxsat berilgan. Aslida, mijoz tomonidan MTProto sessiyasi nima? Bu ikkita raqam session_id и seq_no ushbu sessiya doirasidagi xabarlar. Albatta, va asosiy TCP ulanishi. Aytaylik, bizning mijozimiz hali ham ko'p narsalarni qanday qilishni bilmaydi, uzilib qolgan, qayta ulangan. Agar bu tezda sodir bo'lsa - eski sessiya yangi TCP ulanishida davom etdi, oshiring seq_no yana. Agar bu uzoq vaqt talab qilsa, server uni o'chirib tashlashi mumkin, chunki biz aniqlaganimizdek, uning tomonida u ham navbat.

Nima bo'lishi kerak seq_no? Oh, bu qiyin savol. Nimani nazarda tutganini halol tushunishga harakat qiling:

Kontentga oid xabar

Aniq tasdiqni talab qiladigan xabar. Bularga barcha foydalanuvchi va ko'plab xizmat xabarlari kiradi, konteynerlar va minnatdorchiliklar bundan mustasno.

Xabar tartib raqami (msg_seqno)

32 bitli raqam jo'natuvchi tomonidan ushbu xabardan oldin yaratilgan "tarkibga bog'liq" xabarlar sonining ikki barobariga teng (tasdiqni talab qiladiganlar va ayniqsa konteyner bo'lmaganlar) va agar joriy xabar yangi bo'lsa, keyinchalik bittaga oshiriladi. kontent bilan bog'liq xabar. Konteyner har doim butun tarkibidan keyin hosil bo'ladi; shuning uchun uning tartib raqami undagi xabarlarning tartib raqamlaridan katta yoki teng.

1 ga, keyin esa yana 2 ga oshgan bu qanday sirk? .. Men asl ma'no "ACK uchun past bit, qolgani raqam" edi, deb gumon qilaman, ammo natija unchalik to'g'ri emas - xususan, jo‘natish mumkinligi ma’lum bo‘ldi bir nechta bir xil bo'lgan tasdiqlar seq_no! Qanaqasiga? Xo'sh, masalan, server bizga biror narsa yuboradi, yuboradi va biz o'zimiz jim turamiz, biz faqat uning xabarlarini qabul qilish to'g'risida xizmatni tasdiqlash xabarlari bilan javob beramiz. Bunday holda, bizning chiquvchi tasdiqlarimiz bir xil chiquvchi raqamga ega bo'ladi. Agar siz TCP bilan tanish bo'lsangiz va bu aqldan ozgandek tuyuladi deb o'ylagan bo'lsangiz, lekin u unchalik vahshiy emasga o'xshaydi, chunki TCP-da seq_no o'zgarmaydi va tasdiqlash ga o'tadi seq_no boshqa tomon - keyin men xafa qilishga shoshilaman. Tasdiqlashlar MTProto-ga kelmoqda QAYD haqida seq_no, TCP da bo'lgani kabi, lekin msg_id !

Bu nima msg_id, bu sohalarning eng muhimi? Nomidan ko'rinib turibdiki, xabarning noyob identifikatori. Bu 64-bitli raqam sifatida aniqlanadi, ularning eng kam ahamiyatli bitlari yana server-server emas sehriga ega, qolganlari esa Unix vaqt tamg'asi bo'lib, kasr qismini o'z ichiga oladi, 32 bit chapga siljigan. Bular. vaqt tamg'asi (va vaqtlari juda boshqacha bo'lgan xabarlar server tomonidan rad etiladi). Bundan ma'lum bo'lishicha, umuman olganda, bu mijoz uchun global bo'lgan identifikator. Vaholanki - esda tuting session_id - biz kafolat beramiz: Hech qanday holatda bitta seansga mo'ljallangan xabarni boshqa seansga yuborish mumkin emas. Ya'ni, allaqachon borligi ma'lum bo'ldi uchta daraja — sessiya, sessiya raqami, xabar identifikatori. Nega bunday haddan tashqari murakkablik, bu sir juda ajoyib.

Va shunday qilib, msg_id uchun zarur…

RPC: so'rovlar, javoblar, xatolar. Tasdiqlashlar.

Siz sezganingizdek, javoblar mavjud bo'lsa-da, sxemaning biron bir joyida "RPC so'rovini yuborish" maxsus turi yoki funktsiyasi mavjud emas. Axir, bizda kontent bilan bog'liq xabarlar bor! Ya'ni, har qanday xabar so'rov bo'lishi mumkin! Yoki bo'lmang. Hammasidan keyin; axiyri, har birining bo'ladi msg_id. Va bu erda javoblar:

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

Bu qaysi xabarga javob ekanligi ko'rsatilgan. Shuning uchun, APIning yuqori darajasida siz so'rovingiz qanday raqamga ega ekanligini eslab qolishingiz kerak bo'ladi - menimcha, ish asinxron ekanligini tushuntirish shart emas va bir vaqtning o'zida bir nechta so'rovlar bo'lishi mumkin, ularga javoblar har qanday tartibda qaytarilishi mumkinmi? Asosan, bundan va ishchilar yo'qligi kabi xato xabarlaridan, buning orqasidagi arxitekturani kuzatish mumkin: siz bilan TCP ulanishini ta'minlaydigan server front-end muvozanatlashtiruvchisi bo'lib, so'rovlarni backendlarga yo'naltiradi va ularni qayta yig'adi. message_id. Hamma narsa aniq, mantiqiy va yaxshi ko'rinadi.

Ha?.. Va agar o'ylab ko'rsangiz? Axir, RPC javobining o'zi ham maydonga ega msg_id! Serverga "siz mening javobimga javob bermayapsiz!" Deb baqirishimiz kerakmi? Va ha, tasdiqlash haqida nima bor edi? Sahifa haqida xabarlar haqida xabarlar nima ekanligini bizga aytadi

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

va har bir tomon buni qilishi kerak. Lekin har doim emas! Agar siz RpcResult-ni olsangiz, u tasdiq sifatida xizmat qiladi. Ya'ni, server sizning so'rovingizga MsgsAck bilan javob berishi mumkin - "men uni oldim" kabi. RpcResultga darhol javob berishi mumkin. Bu ikkalasi ham bo'lishi mumkin.

Va ha, siz hali ham javobga javob berishingiz kerak! Tasdiqlash. Aks holda, server uni yetkazib berilmagan deb hisoblaydi va uni yana sizga tashlab yuboradi. Qayta ulanishdan keyin ham. Ammo bu erda, albatta, taym-aut haqida savol tug'iladi. Keling, ularni biroz keyinroq ko'rib chiqaylik.

Shu bilan birga, so'rovni bajarishda yuzaga kelishi mumkin bo'lgan xatolarni ko'rib chiqaylik.

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

Oh, kimdir xitob qiladi, bu erda ko'proq insoniy format - chiziq bor! Shoshilmang. Bu yerga xatolar ro'yxatilekin, albatta, to'liq emas. Undan biz kodning - ekanligini bilib olamiz shunga o'xshash narsa HTTP xatolar (albatta, javoblarning semantikasi hisobga olinmaydi, ba'zi joylarda ular tasodifiy kodlar bo'yicha taqsimlanadi) va satr shunday ko'rinadi CAPITAL_LETTERS_AND_NUMBERS. Masalan, PHONE_NUMBER_OCCUPIED yoki FILE_PART_X_MISSING. Xo'sh, ya'ni, siz hali ham bu qatorga egasiz tahlil qilish. Masalan, FLOOD_WAIT_3600 bir soat kutishingiz kerakligini anglatadi va PHONE_MIGRATE_5ushbu prefiksli telefon raqami 5-sonli shaharda ro'yxatdan o'tgan bo'lishi kerak. Bizda bir turdagi til bor, to'g'rimi? Bizga satrdan argument kerak emas, oddiy iboralar qiladi, cho.

Shunga qaramay, bu xizmat xabarlari sahifasida emas, lekin ushbu loyihada odatiy bo'lganidek, ma'lumotni topish mumkin boshqa hujjatlar sahifasida. Yoki shubha uyg'otish. Birinchidan, qarang, terish/qatlamlar buzilishi - RpcError investitsiya qilish mumkin RpcResult. Nega tashqarida emas? Nimani hisobga olmadik?.. Shunga ko'ra, kafolat qayerda RpcError investitsiya qilinmasligi mumkin RpcResult, lekin to'g'ridan-to'g'ri yoki boshqa turga joylashtirilganmi? yetishmaydi req_msg_id ? ..

Ammo keling, xizmat xabarlari haqida davom etaylik. Mijoz server uzoq vaqt davomida o'ylayotgan deb o'ylashi va shunday ajoyib so'rovni berishi mumkin:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Bunga uchta mumkin bo'lgan javob mavjud, ular yana tasdiqlash mexanizmi bilan kesishib, ular nima bo'lishi kerakligini tushunishga harakat qilish uchun (va umuman tasdiqlashni talab qilmaydigan turlar ro'yxati qanday), o'quvchi uy vazifasi sifatida qoldiriladi (eslatma: Telegram Desktop manbalaridagi ma'lumotlar to'liq emas).

Giyohvandlik: Xabar postlarining holati

Umuman olganda, TL, MTProto va Telegram-ning ko'p joylari o'jarlik tuyg'usini qoldiradi, ammo xushmuomalalik, xushmuomalalik va boshqalar. yumshoq ko'nikmalar biz bu haqda muloyimlik bilan sukut saqladik, dialoglardagi odobsizliklar esa senzura qilindi. Biroq, bu joyОhaqida sahifaning katta qismi xabarlar haqida xabarlar uzoq vaqtdan beri tarmoq protokollari bilan ishlagan va turli darajadagi egrilikdagi velosipedlarni ko'rgan men uchun ham zarba beradi.

Bu zararsiz, tasdiqlar bilan boshlanadi. Keyinchalik, bizga aytiladi

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;

Xo'sh, MTProto bilan ishlashni boshlagan har bir kishi "tuzatilgan - qayta tuzilgan - ishga tushirilgan" tsiklda ularga duch kelishi kerak bo'ladi, tahrirlash paytida chirigan raqamlar yoki tuzlarni olish odatiy holdir. Biroq, bu erda ikkita nuqta bor:

  1. Bundan kelib chiqadiki, asl xabar yo'qoladi. Biz ba'zi navbatlarni to'sib qo'yishimiz kerak, buni keyinroq ko'rib chiqamiz.
  2. Bu g'alati xato raqamlari nima? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... qolgan raqamlar qayerda, Tommi?

Hujjatlarda aytilishicha:

Maqsad shundaki, xato_kodi qiymatlari guruhlangan (xato_kodi >> 4): masalan, 0x40 - 0x4f kodlari konteyner parchalanishidagi xatolarga mos keladi.

lekin, birinchidan, boshqa yo'nalishda siljish, ikkinchidan, qolgan kodlar qayerda ekanligi muhim emasmi? Muallifning boshida?.. Biroq, bu mayda-chuydalar.

Giyohvandlik post holati xabarlari va post nusxalarida boshlanadi:

  • Xabar holati ma'lumotlarini so'rash
    Agar tomonlardan biri bir muncha vaqt davomida o'zining chiquvchi xabarlarining holati to'g'risida ma'lumot olmagan bo'lsa, u boshqa tomondan buni aniq so'rashi mumkin:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Xabarlar holati to'g'risida axborot xabari
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Bu yerda, info kiruvchi msg_ids roʻyxatidagi har bir xabar uchun aniq bir bayt xabar holatini oʻz ichiga olgan qatordir:

    • 1 = xabar haqida hech narsa ma'lum emas (msg_id juda past, boshqa tomon uni unutgan bo'lishi mumkin)
    • 2 = xabar qabul qilinmadi (msg_id saqlangan identifikatorlar doirasiga kiradi; ammo, boshqa tomon, albatta, bunday xabarni olmagan)
    • 3 = xabar qabul qilinmadi (msg_id juda baland, ammo boshqa tomon uni hali qabul qilmagan)
    • 4 = xabar qabul qilindi (esda tutingki, bu javob bir vaqtning o'zida kvitansiyani tasdiqlaydi)
    • +8 = xabar allaqachon tasdiqlangan
    • +16 = tasdiqlashni talab qilmaydigan xabar
    • +32 = Xabarda mavjud RPC so'rovi qayta ishlanmoqda yoki qayta ishlanmoqda
    • +64 = allaqachon yaratilgan xabarga kontent bilan bog'liq javob
    • +128 = boshqa tomon xabar allaqachon olinganligini aniq biladi
      Bu javob tan olishni talab qilmaydi. Bu o'z-o'zidan tegishli msgs_state_reqning tan olinishidir.
      E'tibor bering, agar to'satdan boshqa tomonda unga yuborilganga o'xshash xabar yo'qligi aniqlansa, xabar shunchaki qayta yuborilishi mumkin. Agar boshqa tomon bir vaqtning o'zida xabarning ikki nusxasini olishi kerak bo'lsa ham, dublikat e'tiborga olinmaydi. (Agar juda ko'p vaqt o'tgan bo'lsa va asl msg_id endi haqiqiy bo'lmasa, xabar msg_copy ichiga o'ralishi kerak).
  • Xabarlar holatini ixtiyoriy ravishda etkazish
    Har bir tomon ixtiyoriy ravishda boshqa tomon uzatgan xabarlarning holati to'g'risida boshqa tomonni xabardor qilishi mumkin.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Bitta xabarning holati to'g'risida kengaytirilgan ixtiyoriy xabar
    ...
    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;
  • Xabarlarni qayta yuborish uchun aniq so'rov
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    Masofaviy tomon darhol so'ralgan xabarlarni qayta yuborish orqali javob beradi [...]
  • Javoblarni qayta yuborish uchun aniq so'rov
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    Masofaviy tomon darhol qayta yuborish orqali javob beradi javoblar so'ralgan xabarlarga [...]
  • Xabar nusxalari
    Ba'zi hollarda, msg_id ko'rsatkichi bo'lgan eski xabarni qayta yuborish kerak bo'ladi. Keyin u nusxa ko'chirish idishiga o'raladi:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Qabul qilingandan so'ng, xabar xuddi o'ram bo'lmagandek qayta ishlanadi. Biroq, agar orig_message.msg_id xabari olinganligi aniq ma'lum bo'lsa, yangi xabar qayta ishlanmaydi (bir vaqtning o'zida u va orig_message.msg_id tasdiqlanadi). Orig_message.msg_id qiymati konteynerning msg_id qiymatidan past bo‘lishi kerak.

Keling, bu haqiqat haqida sukut saqlaylik msgs_state_info yana, tugallanmagan TL ning quloqlari chiqib turadi (bizga baytlar vektori kerak edi, va pastki ikki bitda enum va eski bitlarda bayroqlar kerak edi). Gap boshqa narsada. Bularning barchasi amalda nima uchun ekanligini kimdir tushunadi haqiqiy mijozda kerakmi?.. Qiyinchilik bilan, lekin agar odam disk raskadrovka bilan shug'ullansa va interaktiv rejimda bo'lsa, qandaydir foydani tasavvur qilishingiz mumkin - serverdan nima va qanday qilib so'rang. Ammo so'rovlar bu erda tasvirlangan borish va kelish yo'nalishida.

Bundan kelib chiqadiki, har bir tomon nafaqat xabarlarni shifrlashi va jo'natishi, balki ular haqidagi ma'lumotlarni, ularga javoblar va noma'lum vaqt davomida saqlashi kerak. Hujjatlar ushbu xususiyatlarning vaqtlarini yoki amaliy qo'llanilishini tasvirlamaydi. Yo'q. Eng ajablanarlisi shundaki, ular aslida rasmiy mijozlar kodida qo'llaniladi! Ko'rinishidan, ularga ochiq hujjatlarga kiritilmagan narsa aytilgan. Koddan tushuning nima uchun, endi TL misolidagi kabi oddiy emas - bu (qiyosiy) mantiqiy izolyatsiya qilingan qism emas, balki dastur arxitekturasiga bog'langan qism, ya'ni. dastur kodini tushunish uchun ko'proq vaqt kerak bo'ladi.

Pinglar va vaqtlar. Navbatlar.

Har bir narsadan, agar siz server arxitekturasi haqidagi taxminlarni eslayotgan bo'lsangiz (so'rovlarni backendlar bo'ylab taqsimlash), TCP-da etkazib berishning barcha kafolatlariga qaramay (ma'lumotlar etkazib berilgan yoki sizga xabar beriladi) juda zerikarli narsa. tanaffus, lekin ma'lumotlar muammo paydo bo'lgunga qadar etkazib beriladi), bu MTProto-ning o'zida tasdiqlanadi - kafolatlar yo'q. Server sizning xabaringizni osongina yo'qotishi yoki tashlab yuborishi mumkin va bu haqda hech narsa qilib bo'lmaydi, shunchaki har xil turdagi tayoqchalarni to'sish uchun.

Va birinchi navbatda - xabarlar navbatlari. Xo'sh, bir narsa, hamma narsa boshidanoq aniq edi - tasdiqlanmagan xabarni saqlash va qayta yuborish kerak. Va qaysi vaqtdan keyin? Va hazil uni taniydi. Ehtimol, o'sha giyohvandlik xizmati xabarlari qandaydir tarzda tayoqchalar bilan bu muammoni hal qiladi, aytaylik, Telegram ish stolida ularga mos keladigan 4 ga yaqin navbat mavjud (balki ko'proq, yuqorida aytib o'tilganidek, buning uchun siz uning kodi va arxitekturasini jiddiyroq o'rganishingiz kerak; shu bilan birga. vaqt, biz uni namuna sifatida olish mumkin emasligini bilamiz, unda MTProto sxemasidan ma'lum miqdordagi turlar ishlatilmaydi).

Nima uchun bu sodir bo'lmoqda? Ehtimol, server dasturchilari klaster ichida ishonchlilikni ta'minlay olmadilar yoki hech bo'lmaganda oldingi balanslagichda buferlashni ta'minlay olmadilar va bu muammoni mijozga o'tkazdilar. Vasiliy umidsizlikdan TCP algoritmlaridan foydalangan holda ikkita navbat bilan muqobil variantni amalga oshirishga harakat qildi - serverga RTTni o'lchash va tan olinmagan so'rovlar soniga qarab "oyna" hajmini (xabarlarda) sozlash. Ya'ni, server yukini hisoblash uchun bunday qo'pol evristik - bizning qancha so'rovimizni bir vaqtning o'zida chaynashi va yo'qotmasligi mumkin.

Xo'sh, tushundingizmi, to'g'rimi? Agar siz TCP orqali ishlaydigan protokol ustiga yana TCP ni qo'llashingiz kerak bo'lsa, bu juda yomon ishlab chiqilgan protokolni bildiradi.

Ha, nima uchun bir nechta navbat kerak va umuman olganda, bu yuqori darajadagi API bilan ishlaydigan odam uchun nimani anglatadi? Qarang, siz so'rov qilasiz, uni ketma-ketlashtirasiz, lekin ko'pincha uni darhol jo'natib bo'lmaydi. Nega? Chunki javob bo'ladi msg_id, bu vaqtinchalikаMen yorliqman, uni tayinlashni iloji boricha kechiktirgan ma'qul - to'satdan server biz va u o'rtamizdagi vaqt nomuvofiqligi sababli uni rad etadi (albatta, biz vaqtimizni hozirgidan o'zgartiradigan qo'ltiq tayoqchasini yaratishimiz mumkin) server javoblaridan hisoblangan delta qo'shish orqali server vaqtiga - rasmiy mijozlar buni qiladilar, ammo bu usul qo'pol va buferlash tufayli noto'g'ri). Shunday qilib, kutubxonadan mahalliy funktsiya chaqiruvi bilan so'rov yuborganingizda, xabar quyidagi bosqichlardan o'tadi:

  1. Xuddi shu navbatda yotadi va shifrlashni kutmoqda.
  2. Tayinlangan msg_id va xabar boshqa navbatga o'tdi - yo'naltirish mumkin; rozetkaga yuboring.
  3. a) Server MsgsAck deb javob berdi – xabar yetkazildi, biz uni “boshqa navbat”dan o‘chirib tashlaymiz.
    b) Yoki aksincha, unga biror narsa yoqmadi, u badmsg deb javob berdi - biz "boshqa navbatda" qayta yuboramiz
    c) Hech narsa ma'lum emas, xabarni boshqa navbatdan qayta yuborish kerak - lekin aniq qachon ma'lum emas.
  4. Server nihoyat javob berdi RpcResult - haqiqiy javob (yoki xato) - nafaqat etkazib berilgan, balki qayta ishlangan.

Ehtimol, konteynerlardan foydalanish muammoni qisman hal qilishi mumkin. Bunda bir nechta xabarlar bittaga to'plangan va server birdaniga bitta xabar bilan hammaga rozilik bildirgan. msg_id. Ammo, agar biror narsa noto'g'ri bo'lsa, u bu paketni ham rad etadi.

Va bu vaqtda texnik bo'lmagan fikrlar o'ynaydi. Tajribadan biz ko'plab tayoqchalarni ko'rdik va bundan tashqari, endi biz yomon maslahat va me'morchilikning ko'proq misollarini ko'ramiz - bunday sharoitda ishonish va bunday qarorlar qabul qilish kerakmi? Savol ritorik (albatta emas).

Biz nima haqida gapiryapmiz? Agar "xabarlarga qaram xabarlar" mavzusida siz hali ham "siz ahmoqsiz, bizning ajoyib fikrimizni tushunmadingiz!" kabi e'tirozlar bilan taxmin qilishingiz mumkin. (shuning uchun avval hujjatlarni oddiy odamlar kerak bo'lganda, mantiqiy va paket almashish misollari bilan yozing, keyin gaplashamiz), keyin vaqtlar/taym-autlar sof amaliy va o'ziga xos masala, bu erda hamma narsa uzoq vaqtdan beri ma'lum. Lekin hujjatlar bizga vaqt tugashi haqida nima deydi?

Server odatda mijozdan (odatda, RPC so'rovi) xabarni RPC javobidan foydalangan holda qabul qilganligini tasdiqlaydi. Agar javob uzoq vaqtdan beri kelsa, server avval kvitansiyani tasdiqlashi va birozdan keyin RPC javobining o'zi yuborishi mumkin.

Mijoz odatda serverdan xabar qabul qilinganligini (odatda RPC javobi), agar u juda kech uzatilmagan bo'lsa (agar u qabul qilinganidan keyin 60-120 soniya ichida yaratilgan bo'lsa) keyingi RPC so'roviga tasdiq qo'shish orqali tan oladi. serverdan kelgan xabar). Biroq, agar uzoq vaqt davomida serverga xabar jo'natish uchun hech qanday sabab bo'lmasa yoki serverdan ko'p sonli tan olinmagan xabarlar bo'lsa (masalan, 16 dan ortiq), mijoz mustaqil tasdiqni uzatadi.

...Tarjima qilaman: bu qancha va qanday zarurligini o‘zimiz ham bilmaymiz, mayli, shunday bo‘lsin, deb hisoblaylik.

Va pinglar haqida:

Ping xabarlari (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Javob odatda bir xil ulanishga qaytariladi:

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

Ushbu xabarlar tasdiqlashni talab qilmaydi. Pong faqat pingga javob sifatida uzatiladi, ping esa har ikki tomondan boshlanishi mumkin.

Kechiktirilgan ulanishni yopish + PING

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

Ping kabi ishlaydi. Bunga qo'shimcha ravishda, bu qabul qilingandan so'ng, server barcha oldingi taymerlarni avtomatik ravishda tiklaydigan bir xil turdagi yangi xabarni olmasa, joriy ulanishni disconnect_delay soniyadan keyin yopadigan taymerni ishga tushiradi. Agar mijoz bu pinglarni har 60 soniyada bir marta yuborsa, masalan, disconnect_delay ni 75 soniyaga tenglashtirishi mumkin.

Siz aqldan ozganmisiz?! 60 soniyadan so‘ng poyezd stansiyaga kiradi, yo‘lovchilarni tushiradi va olib ketadi va yana tunnelda aloqani yo‘qotadi. 120 soniyadan so'ng, siz aylanib yurganingizda, u boshqasiga keladi va ulanish uzilib qoladi. Oyoqlar qaerdan o'sishi aniq - "Men qo'ng'iroqni eshitdim, lekin qaerdaligini bilmayman", Nagle algoritmi va interaktiv ish uchun mo'ljallangan TCP_NODELAY opsiyasi mavjud. Ammo, afsuski, uning standart qiymatini kechiktiring - 200 Milliysoniya. Agar siz haqiqatan ham shunga o'xshash narsani tasvirlamoqchi bo'lsangiz va mumkin bo'lgan paketlarni tejashni istasangiz - uni kamida 5 soniyaga qo'ying yoki "Foydalanuvchi yozmoqda ..." xabarining kutish vaqti endi teng bo'ladi. Lekin boshqa yo'q.

Va nihoyat, pinglar. Ya'ni, TCP ulanishining jonliligini tekshirish. Bu kulgili, lekin taxminan 10 yil oldin men fakultetimiz yotoqxonasining messenjeri haqida tanqidiy matn yozgan edim - u erda mualliflar serverga mijozdan ping yuborishgan va aksincha. Ammo uchinchi kurs talabalari bir narsa, xalqaro ofis boshqa narsa, shunday emasmi? ..

Birinchidan, kichik ta'lim dasturi. TCP ulanishi, paket almashinuvi bo'lmasa, haftalar davomida yashashi mumkin. Bu maqsadga qarab ham yaxshi, ham yomon. Xo'sh, agar sizda serverga SSH ulanishi ochiq bo'lsa, siz kompyuteringizdan turdingiz, routerni qayta ishga tushirdingiz, o'z joyingizga qaytdingiz - ushbu server orqali sessiya buzilmadi (hech narsa yozmadi, paketlar yo'q edi), qulay. Agar serverda minglab mijozlar bo'lsa, bu yomon, ularning har biri resurslarni egallaydi (salom Postgres!) va mijoz hosti uzoq vaqt oldin qayta ishga tushirilgan bo'lishi mumkin - lekin biz bu haqda bilmaymiz.

Chat/IM tizimlari boshqa, qo'shimcha sabablarga ko'ra ikkinchi holatga tegishli - onlayn holatlar. Agar foydalanuvchi "yiqilib tushgan" bo'lsa, bu haqda suhbatdoshlarini xabardor qilish kerak. Aks holda, Jabber yaratuvchilari qilgan xato (va 20 yil davomida tuzatilgan) bo'ladi - foydalanuvchi uzilib qoldi, lekin ular u onlayn ekanligiga ishonib, unga xabar yozishni davom ettirmoqdalar (bu ham bir necha daqiqada butunlay yo'qolgan edi). tanaffus aniqlandi). Yo'q, TCP taymerlari qanday ishlashini tushunmaydigan ko'p odamlar istalgan joyda paydo bo'ladigan TCP_KEEPALIVE opsiyasi (o'nlab soniyalar kabi yovvoyi qiymatlarni o'rnatish orqali) bu erda yordam bermaydi - siz nafaqat OS yadrosi ekanligiga ishonch hosil qilishingiz kerak. foydalanuvchining mashinasi tirik, lekin ayni paytda normal ishlaydi, javob bera oladi va ilovaning o‘zi ham (sizningcha, u muzlatib qo‘ymaydimi? Ubuntu 18.04 da Telegram ish stoli men uchun qayta-qayta ishdan chiqdi).

Shuning uchun siz ping qilishingiz kerak server mijoz va aksincha emas - agar mijoz buni qilsa, ulanish buzilganda, ping yetkazilmaydi, maqsadga erishilmaydi.

Va biz Telegramda nimani ko'ramiz? Hammasi aksincha! Xo'sh, ya'ni. rasmiy ravishda, albatta, har ikki tomon bir-biriga ping qo'yishi mumkin. Amalda mijozlar tayoqchadan foydalanadilar ping_delay_disconnect, bu serverda taymerni ishga tushiradi. Kechirasiz, u yerda qancha vaqt pingsiz yashashni xohlash mijozning ishi emas. Server, yukiga qarab, yaxshiroq biladi. Ammo, albatta, agar siz resurslarga achinmasangiz, unda ular yomon Pinokkiodir va tayoq pastga tushadi ...

U qanday ishlab chiqilishi kerak edi?

O'ylaymanki, yuqoridagi faktlar Telegram / VKontakte jamoasining kompyuter tarmoqlarining transport (va quyi) darajasida unchalik yuqori emasligi va tegishli masalalarda past malakasi ekanligini aniq ko'rsatib turibdi.

Nega bu juda murakkab bo'lib chiqdi va Telegram arxitektorlari qanday qilib e'tiroz bildirishlari mumkin? Ular TCP ulanishi uzilib qolgan seansni o'tkazishga urinishlari, ya'ni biz hozir etkazib bermagan narsamizni keyinroq yetkazib beramiz. Ehtimol, ular UDP transportini amalga oshirishga harakat qilishgan, garchi ular qiyinchiliklarga duch kelishgan va undan voz kechishgan (shuning uchun hujjatlar bo'sh - maqtanadigan hech narsa yo'q edi). Ammo umuman tarmoqlar va xususan TCP qanday ishlashini, qayerga ishonishingiz mumkinligini va buni qayerda (va qanday qilib) o'zingiz qilishingiz kerakligini tushunmaslik va buni kriptografiya bilan birlashtirishga urinishlar tufayli "ikkitadan bitta zarba" bir toshli qushlar" - shunday jasad chiqdi.

Qanday bo'lishi kerak edi? Bunga asoslanib msg_id takroriy hujumlarning oldini olish uchun kriptografik jihatdan zarur bo'lgan vaqt tamg'asi bo'lib, unga noyob identifikator funksiyasini biriktirish xatodir. Shuning uchun, joriy arxitekturani keskin o'zgartirmasdan (Yangilanishlar to'plami tashkil etilganda, bu postlar seriyasining boshqa qismi uchun yuqori darajadagi API mavzusi), quyidagilarga to'g'ri keladi:

  1. Mijoz bilan TCP ulanishini ushlab turgan server javobgarlikni o'z zimmasiga oladi - agar siz rozetkadan ayirsangiz, xatolikni tan oling, qayta ishlang yoki qaytaring, yo'qotish yo'q. Keyin tasdiqlash identifikatorlarning vektori emas, balki oddiygina "oxirgi qabul qilingan seq_no" - TCP-dagi kabi shunchaki raqam (ikki raqam - o'z seq va tasdiqlangan). Biz har doim sessiyadamiz, shunday emasmi?
  2. Takroriy hujumlarning oldini olish uchun vaqt belgisi alohida maydonga aylanadi, a la nonce. Tekshirildi, lekin boshqa hech narsa ta'sir qilmaydi. Yetarli va uint32 - agar bizning tuzimiz kamida har yarim kunda o'zgarib tursa, biz 16 bitni joriy vaqtning butun qismining pastki bitlariga, qolganini - soniyaning kasr qismiga (hozirgidek) ajratishimiz mumkin.
  3. Qaytarildi msg_id umuman - backendlarda so'rovlarni farqlash nuqtai nazaridan, birinchidan, mijoz identifikatori, ikkinchidan, sessiya identifikatori mavjud va ularni birlashtiradi. Shunga ko'ra, so'rov identifikatori sifatida faqat bittasi etarli seq_no.

Bundan tashqari, eng yaxshi variant emas, to'liq tasodifiy identifikator bo'lib xizmat qilishi mumkin - bu, aytmoqchi, xabar yuborishda yuqori darajadagi APIda allaqachon amalga oshiriladi. Arxitekturani nisbiydan mutlaqga o'zgartirish yaxshiroq bo'lardi, lekin bu boshqa qism uchun mavzu, bu post emas.

API?

Ta-dam! Shunday qilib, og'riq va tayoqchalar bilan to'la yo'lni bosib o'tib, biz nihoyat serverga har qanday so'rov yuborish va ularga har qanday javob olish, shuningdek serverdan yangilanishlarni olish imkoniyatiga ega bo'ldik (so'rovga javoban emas, balki u bizga o'zini yuboradi, masalan, PUSH, agar kimdir juda aniqroq bo'lsa).

Diqqat, endi maqolada yagona Perl misoli bo'ladi! (sintaksis bilan tanish bo'lmaganlar uchun birinchi dalil ob'ektning ma'lumotlar tuzilishi, ikkinchisi esa uning sinfidir):

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' )
};

Ha, ayniqsa, spoyler ostida emas - agar siz uni o'qimagan bo'lsangiz, boring va bajaring!

Oh, vay~~… bu nimaga o'xshaydi? Juda tanish narsa... ehtimol bu JSON-dagi odatiy Web API-ning ma'lumotlar strukturasidir, bundan tashqari, sinflar ob'ektlarga biriktirilganmi?..

Shunday bo'ldi ... Bu nima, o'rtoqlar? .. Shuncha harakat - va biz veb-dasturchilar qaerda dam olishni to'xtatdik. endigina boshlanmoqda?.. HTTPS orqali faqat JSON oson emasmi?! Va evaziga nima oldik? Bu harakatlarga arziydimi?

Keling, TL+MTProto bizga nima berganini va qanday alternativalar mumkinligini baholaylik. Xo'sh, HTTP so'roviga javob berish yomon mos keladi, lekin hech bo'lmaganda TLS ustida biror narsa bormi?

ixcham seriyalash. JSONga o'xshash ushbu ma'lumotlar strukturasini ko'rib, uning ikkilik variantlari mavjudligi esga olinadi. Keling, MsgPack-ni yetarli darajada kengaytirilmagan deb belgilaymiz, ammo, masalan, CBOR mavjud - aytmoqchi, ushbu maqolada tasvirlangan standart. QRM 7049. belgilab berishi bilan diqqatga sazovordir teglar, kengaytirish mexanizmi sifatida va orasida allaqachon standartlashtirilgan lar bor:

  • 25 + 256 - takroriy satrlarni satr raqami ma'lumotnomasi bilan almashtirish, bunday arzon siqish usuli
  • 26 - sinf nomi va konstruktor argumentlari bilan seriyalashtirilgan Perl ob'ekti
  • 27 - tip nomi va konstruktor argumentlari bilan seriallashtirilgan tildan mustaqil ob'ekt

Xo'sh, men bir xil ma'lumotlarni TL va CBOR-da qatorlar va ob'ektlarni o'rash yoqilgan holda ketma-ketlashtirishga harakat qildim. Natija megabaytdan CBOR foydasiga farq qila boshladi:

cborlen=1039673 tl_len=1095092

Va shunday qilib, Xulosa: Sinxronizatsiya xatosi yoki noma'lum identifikator muammosiga duchor bo'lmagan ancha sodda formatlar mavjud, ular bilan solishtirish mumkin bo'lgan samaradorlik.

Tez ulanishni o'rnatish. Bu qayta ulanishdan so'ng nol RTT degan ma'noni anglatadi (kalit allaqachon bir marta yaratilgan bo'lsa) - birinchi MTProto xabaridan boshlab amal qiladi, lekin ba'zi shartlar bilan - ular bir xil tuzga tushib qolishdi, seans chirigan emas va hokazo. Buning evaziga TLS bizga nimani taklif qiladi? Tegishli iqtibos:

TLS da PFS dan foydalanilganda, TLS seans chiptalari (QRM 5077) kalitlarni qayta muhokama qilmasdan va asosiy ma'lumotlarni serverda saqlamasdan shifrlangan sessiyani davom ettirish. Birinchi ulanishni ochish va kalitlarni yaratishda server ulanish holatini shifrlaydi va uni mijozga yuboradi (sessiya chiptasi shaklida). Shunga ko'ra, ulanish qayta tiklanganda, mijoz serverga boshqa narsalar qatori seans kalitini o'z ichiga olgan sessiya chiptasini yuboradi. Chiptaning o'zi vaqtinchalik kalit (sessiya chiptasi kaliti) bilan shifrlangan bo'lib, u serverda saqlanadi va klasterli yechimlarda SSL bilan ishlovchi barcha frontend serverlarga tarqatilishi kerak.[10]. Shunday qilib, agar vaqtinchalik server kalitlari buzilgan bo'lsa, masalan, ular uzoq vaqt saqlanganda (OpenSSL, nginx, Apache sukut bo'yicha ularni dastur ishlayotgan butun vaqt davomida saqlaydi; mashhur saytlar) seans chiptasining kiritilishi PFSni buzishi mumkin. kalitni bir necha soat, kungacha foydalaning).

Bu erda RTT nolga teng emas, siz kamida ClientHello va ServerHello almashishingiz kerak, shundan so'ng Finished bilan birga mijoz allaqachon ma'lumotlarni yuborishi mumkin. Ammo bu erda shuni esda tutish kerakki, bizda yangi ochilgan ulanishlar to'plamiga ega bo'lgan Internet yo'q, lekin ulanishi ko'pincha bir yoki bir nechta yoki kamroq uzoq muddatli, Web-sahifalar uchun nisbatan qisqa so'rovlar bo'lgan messenjer - hamma narsa. ichida multiplekslangan. Ya'ni, agar biz juda yomon metro uchastkasiga duch kelmagan bo'lsak, bu juda maqbuldir.

Yana bir narsani unutdingizmi? Izohlarda yozing.

Davomi bor!

Ushbu postlar seriyasining ikkinchi qismida biz texnik masalalardan ko'ra tashkiliy masalalarni ko'rib chiqamiz - yondashuvlar, mafkura, interfeys, foydalanuvchilarga munosabat va boshqalar. Biroq, bu erda taqdim etilgan texnik ma'lumotlarga asoslanadi.

Uchinchi qism texnik komponent / ishlab chiqish tajribasini tahlil qilishni davom ettiradi. Siz xususan quyidagilarni o'rganasiz:

  • TL-turlarining xilma-xilligi bilan pandemoniumning davomi
  • kanallar va superguruhlar haqida noma'lum narsalar
  • dialoglardan ko'ra ro'yxatga qaraganda yomonroq
  • mutlaq va nisbiy xabarlarni adreslash haqida
  • fotosurat va rasm o'rtasidagi farq nima
  • emoji kursivli matnga qanday xalaqit beradi

va boshqa tayoqchalar! Yangiliklarni kuzatib boring, xabardor bo'lib boring; Biz bilan qoling!

Manba: www.habr.com

a Izoh qo'shish