Telegram-ın protokolu və təşkilati yanaşmalarının tənqidi. 1-ci hissə, texniki: müştərini sıfırdan yazmaq təcrübəsi - TL, MT

Son vaxtlar Habré-də Telegram-ın nə qədər yaxşı olması, Durov qardaşlarının şəbəkə sistemlərinin qurulmasında nə qədər parlaq və təcrübəli olması və s. haqqında yazılar daha tez-tez görünməyə başlayıb. Eyni zamanda, çox az adam həqiqətən texniki cihaza daxil olur - ən çox onlar kifayət qədər sadə (və MTProto-dan çox fərqli) JSON əsaslı Bot API-dən istifadə edirlər və adətən sadəcə qəbul edirlər. iman üzərində messenger ətrafında fırlanan bütün bu təriflər və PR. Demək olar ki, bir il yarım əvvəl NPO-dakı həmkarım Echelon Vasili (təəssüf ki, Habré-dəki hesabı layihə ilə birlikdə silindi) Perl-də sıfırdan öz Telegram müştərisini yazmağa başladı və sonra bu sətirlərin müəllifi qoşuldu. Niyə Perl, bəziləri dərhal soruşacaq? Çünki artıq başqa dillərdə belə layihələr var.Əslində məsələ bu deyil, harda başqa dil də ola bilər bitmiş kitabxana, və müvafiq olaraq müəllif bütün yolu getməlidir sıfırdan. Üstəlik, kriptoqrafiya belə bir şeydir - etibar edin, amma yoxlayın. Təhlükəsizliyə yönəlmiş məhsulla siz sadəcə satıcının hazır kitabxanasına güvənmək və kor-koranə buna inanmaq olmaz (lakin bu, ikinci hissədə daha çox mövzudur). Hazırda kitabxana “orta” səviyyədə olduqca yaxşı işləyir (istənilən API sorğularını etməyə imkan verir).

Bununla belə, bu yazılar silsiləsində kriptoqrafiya və riyaziyyat çox olmayacaq. Ancaq bir çox başqa texniki detallar və memarlıq qoltuqları olacaq (bu, sıfırdan yazmayacaq, kitabxanadan istənilən dildə istifadə edəcəklər üçün də faydalı olacaq). Beləliklə, əsas məqsəd müştərini sıfırdan həyata keçirməyə çalışmaq idi rəsmi sənədlərə əsasən. Yəni, tutaq ki, rəsmi müştərilərin mənbə kodu bağlanıb (yenə ikinci hissədə bunun əslində nə olduğu mövzusunu daha ətraflı açacağıq. olur belə), lakin, köhnə günlərdə olduğu kimi, məsələn, RFC kimi bir standart var - müştərini yalnız spesifikasiyaya uyğun olaraq, mənbə koduna "gözləmədən", hətta rəsmi (Telegram Desktop, mobil) yazmaq mümkündürmü? ), hətta qeyri-rəsmi Telethon?

Mündəricat:

Sənədləşmə... oradadır? Doğrudurmu?..

Bu yazı üçün qeydlərin fraqmentləri ötən ilin yayında toplanmağa başladı. Bütün bu müddət ərzində rəsmi saytda https://core.telegram.org sənədləşdirmə Layer 23-də idi, yəni. 2014-cü ildə bir yerdə ilişib qalmışdım (xatırlayın, o vaxtlar hələ kanallar belə yox idi?). Əlbəttə ki, nəzəri olaraq, bu, 2014-cü ildə o dövrdə funksionallığı olan bir müştərini həyata keçirməyə imkan verməli idi. Amma bu vəziyyətdə də sənədləşmə, birincisi, natamam idi, ikincisi, yerlərdə özü ilə ziddiyyət təşkil edirdi. Bir aydan bir az əvvəl, 2019-cu ilin sentyabrında oldu təsadüfən Məlum oldu ki, saytda tamamilə təzə Layer 105 üçün sənədlərin böyük bir yeniləməsi var və indi hər şeyin yenidən oxunması lazım olduğunu qeyd etdi. Doğrudan da, bir çox məqalələrə yenidən baxılıb, lakin bir çoxları dəyişməz qalıb. Buna görə də, sənədlərlə bağlı aşağıdakı tənqidi oxuyarkən, bunların bəzilərinin artıq aktual olmadığını, bəzilərinin isə hələ də kifayət qədər olduğunu nəzərə almalısınız. Axı, müasir dünyada 5 il sadəcə çox deyil, amma çox çoxlu. O vaxtdan bəri (xüsusilə də o vaxtdan bəri atılmış və dirilmiş geochatları nəzərə almırsınızsa), sxemdəki API metodlarının sayı yüzdən iki yüz əlliyə qədər artmışdır!

Gənc yazıçı kimi hardan başlayırsınız?

Sıfırdan yazmağınız və ya məsələn, kimi hazır kitabxanalardan istifadə etməyiniz fərq etməz Python üçün telethon və ya PHP üçün Madeline, hər halda, ilk sizə lazım olacaq ərizənizi qeydiyyatdan keçirin - parametrləri əldə edin api_id и api_hash (VKontakte API ilə işləyənlər dərhal başa düşür) hansı ki, server tətbiqi müəyyən edəcək. Bu var hüquqi səbəblərə görə, lakin biz ikinci hissədə kitabxana müəlliflərinin niyə dərc edə bilmədiyi haqqında daha çox danışacağıq. Yəqin ki, test qiymətləri sizi qane edəcək, baxmayaraq ki, onlar çox məhduddur - fakt budur ki, indi nömrənizdə qeydiyyatdan keçə bilərsiniz. yalnız bir tətbiqi, buna görə də başdan-başa tələsməyin.

İndi texniki nöqteyi-nəzərdən bizi maraqlandırmalı idi ki, qeydiyyatdan keçdikdən sonra Telegram-dan sənədlərdə, protokollarda və s. yeniliklər barədə bildirişlər almalıyıq. Yəni, güman etmək olar ki, dokları olan sayt sadəcə "qol aldı" və xüsusi olaraq müştəri yaratmağa başlayanlarla işləməyə davam etdi, çünki. daha asandır. Amma yox, belə bir şey müşahidə olunmayıb, heç bir məlumat gəlməyib.

Və sıfırdan yazsanız, alınan parametrlərin istifadəsi əslində hələ də uzaqdır. Baxmayaraq ki https://core.telegram.org/ və onlar haqqında ilk olaraq Başlarkən danışır, əslində ilk növbədə həyata keçirməlisiniz MTProto protokolu - amma inanırsansa OSI modelinə uyğun tərtibat səhifənin sonunda protokolun ümumi təsviri, sonra tamamilə boş yerə.

Əslində, həm MTProto-dan əvvəl, həm də sonra, eyni anda bir neçə səviyyədə (ƏS nüvəsində işləyən xarici şəbəkəçilərin dediyi kimi, təbəqənin pozulması) böyük, ağrılı və dəhşətli bir mövzu mane olacaq ...

Binar serializasiya: TL (Type Language) və onun sxemi, təbəqələri və bir çox başqa qorxulu sözlər

Bu mövzu əslində Telegramın problemlərinin açarıdır. Və onu araşdırmağa çalışsanız, çox dəhşətli sözlər olacaq.

Beləliklə, sxem. Bu sözü xatırlayırsınızsa, deyin JSON sxemiDüz düşündün. Məqsəd eynidir: ötürülən məlumatların mümkün dəstini təsvir etmək üçün bəzi dillər. Əslində oxşarlığın bitdiyi yer budur. Əgər səhifədən MTProto protokolu, və ya rəsmi müştərinin mənbə ağacından bəzi sxemləri açmağa çalışacağıq, belə bir şey görəcəyik:

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;

Bunu ilk dəfə görən insan intuitiv olaraq yazılanların yalnız bir hissəsini tanıyacaq - yaxşı, bunlar zahirən strukturlardır (baxmayaraq ki, ad haradadır, solda və ya sağda?), Onların içərisində sahələr var, bundan sonra tip kolondan keçir ... yəqin. Burada, bucaqlı mötərizədə, yəqin ki, C++-da olduğu kimi şablonlar var (əslində, həqiqətən deyil). Və bütün digər simvollar nə deməkdir, sual işarələri, nida nöqtələri, faizlər, qəfəslər (və açıq-aydın onlar müxtəlif yerlərdə fərqli şeylər deməkdir), bir yerdə təqdim olunur, lakin bir yerdə deyil, onaltılıq nömrələr - və ən əsası, bundan necə əldə etmək olar müntəzəm (server tərəfindən rədd edilməyəcək) bayt axını? Sənədləri oxumalısınız (Bəli, yaxınlıqda JSON versiyasında sxemə keçidlər var - lakin bu, onu aydınlaşdırmır).

Səhifənin açılması Binar verilənlərin seriyalaşdırılması və 4-cü ildə matan bənzər bir şey olan göbələklərin və diskret riyaziyyatın sehrli dünyasına qərq olun. Əlifba, tip, dəyər, kombinator, funksional kombinator, normal forma, kompozit tip, polimorf tip... və bu, sadəcə ilk səhifədir! Növbəti sizi gözləyir TL Dili, bu, artıq mənasız bir sorğu və cavab nümunəsini ehtiva etsə də, daha tipik hallara ümumiyyətlə cavab vermir, bu o deməkdir ki, daha səkkiz yuvada rus dilindən ingilis dilinə tərcümə edilmiş riyaziyyatın təkrarlanmasından keçməli olacaqsınız. səhifələr!

Funksional dillər və avtomatik tipli nəticə ilə tanış olan oxucular, əlbəttə ki, bu dildə təsvirləri, hətta bir nümunədən də daha çox tanış gördülər və bunun ümumiyyətlə prinsipcə pis olmadığını söyləyə bilərlər. Buna etirazlar:

  • Bəli, qol yaxşı səslənir, amma təəssüf ki əldə olunmayıb
  • Rusiya universitetlərində təhsil hətta İT ixtisasları arasında da dəyişir - hamı müvafiq kursu oxumur
  • Nəhayət, görəcəyimiz kimi, praktikada belədir tələb olunmur, çünki təsvir edilən TL-nin yalnız məhdud alt çoxluğu istifadə olunur

Deyildiyi kimi LeoNerd kanalda #perl FreeNode IRC şəbəkəsində Telegram-dan Matrix-ə bir qapı tətbiq etməyə çalışır (sitatın tərcüməsi yaddaşdan qeyri-dəqiqdir):

Tip nəzəriyyəsi ilə ilk dəfə tanış olmuş birisi kimi həyəcanlandı və onunla oynamağa çalışmağa başladı, praktikada bunun lazım olub-olmamasına əhəmiyyət vermədi.

Elementar bir şey kimi çılpaq növlərə (int, long və s.) ehtiyacın sual doğurmadığını özünüz görün - sonda onlar əl ilə həyata keçirilməlidir - məsələn, onlardan əldə etməyə cəhd edək. vektor. Yəni əslində massiv, nəticədə yaranan şeyləri öz adları ilə çağırsanız.

Amma əvvəl

Bilməyənlər üçün TL sintaksisinin alt dəstinin qısa təsviri… rəsmi sənədləri oxuyun

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;

Həmişə tərifə başlayır konstruktor, bundan sonra, isteğe bağlı olaraq (təcrübədə, həmişə) simvol vasitəsilə # lazımdır CRC32 verilmiş növün normallaşdırılmış təsvir sətirindən. Sonra sahələrin təsviri gəlir, əgər varsa - tip boş ola bilər. Hamısı bərabər işarə ilə başa çatır, verilmiş konstruktorun - yəni əslində alt tipin aid olduğu növün adı. Bərabər işarəsinin sağındakı tipdir polimorf - yəni bir neçə spesifik növə uyğun ola bilər.

Əgər tərif sətirdən sonra baş verirsə ---functions---, onda sintaksis eyni qalacaq, lakin məna fərqli olacaq: konstruktor RPC funksiyasının adı olacaq, sahələr parametrlərə çevriləcək (yaxşı, yəni aşağıda göstərildiyi kimi tam olaraq eyni verilmiş struktur olaraq qalacaq, sadəcə verilmiş məna olacaq) və "polimorfik tip" qaytarılan nəticənin növüdür. Düzdür, hələ də polimorfik olaraq qalacaq - yalnız bölmədə müəyyən edilmişdir ---types---, və bu konstruktor nəzərə alınmayacaq. Çağırılan funksiyaların həddindən artıq yüklənməsini arqumentləri ilə yazın, yəni. nədənsə TL-də C++-da olduğu kimi eyni adlı, lakin fərqli imzaya malik bir neçə funksiya təmin edilmir.

OOP deyilsə niyə "konstruktor" və "polimorf"? Yaxşı, əslində, kiminsə bu barədə OOP baxımından düşünməsi daha asan olacaq - mücərrəd sinif kimi polimorf tip və konstruktorlar onun birbaşa nəsil sinifləridir, üstəlik final bir sıra dillərin terminologiyasında. Əslində, əlbəttə, burada oxşarlıq OO proqramlaşdırma dillərində real həddən artıq yüklənmiş konstruktor metodları ilə. Burada sadəcə məlumat strukturları olduğundan, heç bir üsul yoxdur (baxmayaraq ki, aşağıda funksiyaların və metodların təsviri onların nə olduğu barədə başda çaşqınlıq yaratmağa qadirdir, lakin bu, başqa bir şeydir) - konstruktoru bir konstruktor kimi düşünə bilərsiniz. dəyəri olandan tikilir bayt axını oxuyarkən yazın.

Bu necə baş verir? Həmişə 4 bayt oxuyan deserializer dəyəri görür 0xcrc32 - və bundan sonra nə olacağını başa düşür field1 növü ilə int, yəni. növü ilə üst-üstə düşən bu sahədə tam olaraq 4 bayt oxuyur PolymorType oxumaq. Görür 0x2crc32 və başa düşür ki, daha iki sahə var, birincisi long, buna görə də 8 bayt oxuyuruq. Və sonra yenə eyni şəkildə sıradan çıxarılan mürəkkəb bir növ. Misal üçün, Type3 sxemdə müvafiq olaraq iki konstruktor elan oluna bilər, onda onlar ya uyğun gəlməlidirlər 0x12abcd34, bundan sonra başqa 4 bayt oxumaq lazımdır intVə ya 0x6789cdef, bundan sonra heç bir şey olmayacaq. Başqa bir şey - bir istisna atmaq lazımdır. Hər halda, bundan sonra biz 4 bayt oxumağa qayıdırıq int margins field_c в constructorTwo və bununla da bizim kitabımızı oxuyub bitiririk PolymorType.

Nəhayət, əgər tutulsa 0xdeadcrc uğrunda constructorThree, sonra işlər daha da mürəkkəbləşir. İlk sahəmiz bit_flags_of_what_really_present növü ilə # - əslində bu tip üçün sadəcə ləqəbdir nat"təbii ədəd" mənasını verir. Yəni əslində unsigned int yeganə haldır ki, yeri gəlmişkən, real sxemlərdə işarəsiz nömrələrə rast gəlinir. Beləliklə, növbəti sual işarəsi olan konstruksiyadır, yəni bu sahədir - o, yalnız istinad edilən sahədə müvafiq bit təyin edildikdə (təxminən üçlü operator kimi) teldə mövcud olacaqdır. Beləliklə, tutaq ki, bu bit açıq idi, sonra kimi bir sahə oxumaq lazımdır Type, bizim nümunəmizdə 2 konstruktor var. Biri boşdur (yalnız identifikatordan ibarətdir), digərində sahə var ids növü ilə ids:Vector<long>.

Həm şablonların, həm də generiklərin yaxşı və ya Java olduğunu düşünə bilərsiniz. Amma yox. Təxminən. Bu tək real sxemlərdə bucaq mötərizələri halıdır və o, YALNIZ Vektor üçün istifadə olunur. Bayt axınında bu, Vektor tipinin özü üçün 4 CRC32 bayt olacaq, həmişə eyni, sonra 4 bayt - massiv elementlərinin sayı, sonra isə bu elementlərin özləri.

Buna əlavə edin ki, seriallaşdırma həmişə 4 baytlıq sözlərdə baş verir, bütün növlər onun qatıdır - daxili tiplər də təsvir olunur. bytes и string uzunluğun əl ilə serializasiyası və bu hizalanma ilə 4 - yaxşı, normal və hətta nisbətən səmərəli səslənir? TL-nin effektiv ikili serializasiya olduğu iddia edilsə də, hər hansı bir şeyin, hətta boolean dəyərlərin və 4 bayta qədər tək simvollu sətirlərin genişlənməsi ilə cəhənnəmə JSON hələ də daha qalın olacaqmı? Baxın, hətta lazımsız sahələr bit bayraqları ilə keçə bilər, hər şey qaydasındadır və hətta gələcək üçün genişləndirilə bilər, sonradan konstruktora yeni isteğe bağlı sahələr əlavə etmisiniz?..

Amma yox, əgər mənim qısa təsvirimi deyil, tam sənədləri oxuyub həyata keçirməyi düşünsəniz. Birincisi, konstruktorun CRC32-si normallaşdırılmış sxem mətninin təsviri sətri ilə hesablanır (əlavə boşluqları çıxarın və s.) - belə ki, yeni sahə əlavə olunarsa, növün təsviri sətri və deməli, onun CRC32 və nəticə etibarilə seriallaşdırılması dəyişəcək. Köhnə müştəri yeni bayraqlar qoyulmuş bir sahə alsa nə edərdi, amma bundan sonra onlarla nə edəcəyini bilmirdi? ..

İkincisi, xatırlayaq CRC32, burada mahiyyətcə kimi istifadə olunur hash funksiyaları hansı növün seriyalaşdırıldığını unikal şəkildə müəyyən etmək. Burada biz toqquşma problemi ilə qarşılaşırıq - və yox, ehtimal 232-də bir deyil, daha çox. CRC32-nin rabitə kanalındakı səhvləri aşkar etmək (və düzəltmək) və müvafiq olaraq bu xüsusiyyətləri başqalarının zərərinə yaxşılaşdırmaq üçün nəzərdə tutulduğunu kim xatırladı? Məsələn, baytların dəyişdirilməsinə əhəmiyyət vermir: iki sətirdən CRC32-ni hesablasanız, ikincidə ilk 4 baytı növbəti 4 baytla əvəz edəcəksiniz - eyni olacaq. Giriş kimi Latın əlifbasından mətn sətirləri (və bir az durğu işarəsi) olduqda və bu adlar xüsusilə təsadüfi deyilsə, belə bir dəyişdirmə ehtimalı çox artır.

Yeri gəlmişkən, orada nə olduğunu kim yoxlayıb həqiqətən CRC32? Erkən mənbələrdən birində (hətta Waltmandan əvvəl) hər bir simvolu bu insanlar tərəfindən çox sevilən 239 nömrəsinə vuran bir hash funksiyası var idi, ha ha!

Nəhayət, tamam, biz başa düşdük ki, sahə tipli konstruktorlar Vector<int> и Vector<PolymorType> fərqli CRC32 olacaq. Bəs xəttdəki təqdimat haqqında nə demək olar? Və nəzəriyyə baxımından, növün bir hissəsinə çevrilir? Deyək ki, biz on min ədəddən ibarət bir sıra keçirik Vector<int> hər şey aydındır, uzunluq və başqa 40000 bayt. Və əgər bu Vector<Type2>, yalnız bir sahədən ibarətdir int və bu tipdə yeganədir - 10000xabcdef0-ü 34 dəfə və sonra 4 bayt təkrarlamalıyıq? int, ya da dil konstruktordan bunu bizim üçün GÖSTERƏ bilir fixedVec və 80000 bayt əvəzinə, yenidən yalnız 40000 köçürün?

Bu, heç də boş nəzəri sual deyil - təsəvvür edin ki, qrup istifadəçilərinin siyahısını əldə edirsiniz, onların hər birinin id, ad, soyad var - mobil əlaqə üzərindən ötürülən məlumatların həcmindəki fərq əhəmiyyətli ola bilər. Bizə reklam edilən Telegram serializasiyasının effektivliyidir.

Beləliklə ...

Çıxarılması mümkün olmayan vektor

Kombinatorların və təqribən təsvir səhifələrini gəzməyə çalışsanız, bir vektorun (və hətta matrisin) formal olaraq dəstlər vasitəsilə bir neçə vərəq çıxarmağa çalışdığını görəcəksiniz. Ancaq sonda onlar döyülür, son addım atlanır və vektorun tərifi sadəcə verilir, bu da bir növə bağlı deyil. Burda nə məsələ var? dillərdə proqramlaşdırma, xüsusilə funksional olanlar, strukturu rekursiv şəkildə təsvir etmək olduqca tipikdir - tənbəl qiymətləndirməsi ilə tərtibçi hər şeyi başa düşəcək və bunu edəcək. Dildə məlumatların seriallaşdırılması lakin SƏMƏRƏLİLİK lazımdır: sadəcə təsvir etmək kifayətdir siyahı, yəni. iki elementdən ibarət bir quruluş - birincisi məlumat elementidir, ikincisi eyni quruluşun özü və ya quyruq üçün boş yerdir (paket (cons) Lispdə). Ancaq bu, şübhəsiz ki, tələb edəcəkdir hər biri element növünü təsvir etmək üçün əlavə olaraq 4 bayt (TL vəziyyətində CRC32) sərf edir. Massivi təsvir etmək asandır sabit ölçü, lakin əvvəllər bilinməyən uzunluqlu bir massiv vəziyyətində biz ayrılırıq.

TL vektoru çıxarmağa imkan vermədiyi üçün onu yan tərəfə əlavə etmək lazım idi. Sonda sənədlərdə deyilir:

Serializasiya həmişə t tipli dəyişənin xüsusi dəyərindən asılı olmayan eyni konstruktor “vektor”dan (const 0x1cb5c415 = crc32 (“vektor t:Type # [ t ] = Vector t”) istifadə edir.

İsteğe bağlı t parametrinin dəyəri seriallaşdırmada iştirak etmir, çünki o, nəticə növündən əldə edilir (serializasiyadan əvvəl həmişə məlumdur).

Daha yaxından baxın: vector {t:Type} # [ t ] = Vector t - Amma heç bir yerdə tərifin özü ilk ədədin vektorun uzunluğuna bərabər olması lazım olduğunu söyləmir! Və heç bir yerdən gəlmir. Bu, yadda saxlamağınız və əllərinizlə həyata keçirməyiniz lazım olan bir şeydir. Başqa bir yerdə, sənədlər növün saxta olduğunu hətta vicdanla qeyd edir:

Vector t polimorfik psevdotipi "növ"dür, onun dəyəri qutulu və ya çılpaq hər hansı t növünün dəyərlər ardıcıllığıdır.

… lakin buna diqqət yetirmir. Riyaziyyat fənni ilə məşğul olmaqdan yorulduğunuz zaman (bəlkə də sizə universitet kursundan məlumdur), nəticə əldə etmək və onunla praktikada necə işləməyinizə baxmaq qərarına gəldikdə, təəssürat beyninizdə qalır: burada Ciddi Riyaziyyat əsaslanır. , açıq-aydın Cool People (iki riyaziyyatçı - ACM qalibi) və yalnız hər kəs deyil. Məqsəd - israf etmək - əldə edildi.

Yeri gəlmişkən, nömrə haqqında. Xatırla # sinonimdir nat, natural ədəd:

tipli ifadələr var (typeexpr) və ədədi ifadələr (nat-expr). Bununla belə, onlar eyni şəkildə müəyyən edilir.

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

lakin qrammatikada onlar eyni şəkildə təsvir edilir, yəni. bu fərq bir daha xatırlanmalı və əl ilə həyata keçirilməlidir.

Bəli, şablon növləri (vector<int>, vector<User>) ümumi identifikatoru var (#1cb5c415), yəni. zəng kimi elan edildiyini bilirsinizsə

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

onda siz sadəcə vektor deyil, istifadəçilərin vektorunu gözləyirsiniz. Daha dəqiq, olmalıdır gözləyin - real kodda, hər bir element, çılpaq tip olmasa, bir konstruktora sahib olacaq və həyata keçirərkən yaxşı bir şəkildə yoxlamaq lazım olacaq - və biz bu vektorun hər bir elementində tam olaraq göndərildik. o tip? Və əgər bu bir növ PHP idisə, hansı ki massivdə müxtəlif elementlərdə müxtəlif tiplər ola bilər?

Bu məqamda düşünməyə başlayırsınız - belə bir TL lazımdırmı? Bəlkə araba üçün o vaxt mövcud olan eyni protobuf olan insan serializatorundan istifadə etmək olardı? Bu nəzəriyyə idi, gəlin praktikaya baxaq.

Kodda mövcud TL tətbiqləri

TL, Durovun payının satışı ilə məşhur hadisələrdən əvvəl VKontakte-nin bağırsaqlarında doğuldu və (əlbəttə), Telegramın inkişafından əvvəl. Və açıq mənbədə ilk icra mənbələri bir çox gülməli balta tapa bilərsiniz. Dilin özü də indi Telegram-da olduğundan daha dolğun şəkildə orada tətbiq olundu. Məsələn, sxemdə heşlər ümumiyyətlə istifadə edilmir (deviant davranışa malik daxili psevdotip (vektor kimi) nəzərdə tutulur). Və ya

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

amma tamlıq naminə Fikir Nəhənginin təkamülünü, belə demək mümkünsə, izləmək üçün mənzərəni nəzərdən keçirək.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

Ya da bu gözəl:

    static const char *reserved_words_polymorhic[] = {

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

      };

Bu fraqment şablonlar haqqındadır, məsələn:

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

Bu, int - Tip cütlərinin vektoru kimi həshmap şablon növünün tərifidir. C++-da bu belə görünür:

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

belə ki, alpha - açar söz! Amma yalnız C++ dilində T yaza bilərsən, amma alfa, beta yazmalısan... Amma 8 parametrdən çox deyil, fantaziya tetada bitdi. Belə görünür ki, bir dəfə Sankt-Peterburqda təxminən aşağıdakı dialoqlar olub:

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

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

Lakin söhbət TL-nin "ümumiyyətlə" ilk qoyulmuş tətbiqindən gedirdi. Gəlin real Telegram müştərilərində tətbiqlərin nəzərdən keçirilməsinə keçək.

Basilin sözü:

Vasili, [09.10.18 17:07] Ən əsası, onların bir dəstə abstraksiyanı yıxıb, sonra da onların üzərinə bolt vurub, kodekeratoru qoltuq dəyənəkləri ilə örtdükləri üçün eşşək isti olur.
Nəticədə doklardan ilk olaraq pilot.jpg
Sonra jekichan.webp kodundan

Əlbəttə ki, alqoritmlər və riyaziyyatla tanış olan insanlardan gözləyə bilərik ki, onlar Aho, Ullmanı oxudular və onilliklər ərzində öz DSL-ləri üçün tərtibçilər yazmaq üçün sənayedə faktiki standarta çevrilmiş alətlərlə tanış oldular, elə deyilmi? ..

Tərəfindən telegram-cli Vitaliy Valtmandır, TLO formatının onun (cli) hüdudlarından kənarda baş verməsindən də anlaşıldığı kimi, komandanın üzvü - indi TL-nin təhlili üçün kitabxana ayrılıb. ayrı-ayrıonun haqqında təəssürat nədir TL analizatoru? ..

16.12 04:18 Vasili: məncə, kimsə lex + yacc-ı mənimsəməyib
16.12 04:18 Vasili: əks halda izah edə bilmərəm
16.12 04:18 Vasili: yaxşı, yoxsa VK-da sətirlərin sayına görə ödəniliblər
16.12 04:19 Vasili: başqalarının 3k+ sətirləri<censored> təhlilçi yerinə

Bəlkə istisna? Gəlin görək necə dələduz bu RƏSMİ müştəridir — Telegram Desktop:

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

Python-da 1100+ sətir, bir neçə müntəzəm ifadə + vektor tipli xüsusi hallar, əlbəttə ki, TL sintaksisinə uyğun olaraq sxemdə elan olunur, lakin onu bu sintaksisə qoyurlar, daha çox təhlil edirlər. ...Məsələ ondadır ki, bütün bu möcüzə ilə niyə narahat olursunuz?иdaha çox puf, əgər heç kim sənədlərə uyğun olaraq təhlil etməyəcəksə ?!

Yeri gəlmişkən... CRC32 çekindən danışdığımızı xatırlayırsınız? Beləliklə, Telegram Desktop kod generatorunda hesablanmış CRC32-nin olduğu növlər üçün istisnaların siyahısı var. uyğun gəlmir diaqramda göstərildiyi kimi!

Vasili, [18.12 22:49] və burada belə bir TL-yə ehtiyac olub olmadığını düşünməlisiniz.
Alternativ tətbiqlərlə qarışmaq istəsəm, sətir fasilələri daxil etməyə başlayardım, təhlilçilərin yarısı çox sətirli təriflərdə pozulacaq
tdesktop da

Bir laynerlə bağlı məqamı xatırlayın, bir az sonra ona qayıdacağıq.

Yaxşı, telegram-cli qeyri-rəsmidir, Telegram Desktop rəsmidir, bəs digərləri? Və kim bilir?.. Android müştəri kodunda ümumiyyətlə sxem təhlili yox idi (bu açıq mənbə ilə bağlı suallar yaradır, lakin bu ikinci hissə üçündür), lakin bir neçə başqa gülməli kod parçaları var idi, lakin onlar haqqında aşağıdakı alt bölmə.

Seriallaşdırma praktikada başqa hansı sualları doğurur? Məsələn, əlbəttə ki, bit sahələri və şərti sahələr ilə vidalaşdılar:

vasiliy: flags.0? true
sahənin mövcud olduğunu və bayraq qoyulubsa doğru olduğunu bildirir

vasiliy: flags.1? int
sahənin mövcud olduğunu və sıradan çıxarılması lazım olduğunu bildirir

Vasili: Göt, yanma, nə edirsən!
Vasili: Sənəddə bir yerdə belə bir qeyd var ki, true sıfır uzunluqda çılpaq tipdir, lakin onların sənədlərindən nəsə toplamaq qeyri-realdır.
Vasili: Açıq tətbiqlərdə də belə bir şey yoxdur, amma çoxlu qoltuq və dayaqlar var

Telemarafon haqqında necə? MTProto mövzusuna baxaraq, bir nümunə - sənədlərdə belə parçalar var, lakin işarə % yalnız "verilmiş çılpaq tipə uyğun" kimi təsvir olunur, yəni. aşağıdakı nümunələrdə ya səhv, ya da sənədsiz bir şey:

Vasili, [22.06.18/18/38 XNUMX:XNUMX] Bir yerdə:

msg_container#73f1f8dc messages:vector message = MessageContainer;

Fərqli şəkildə:

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

Və bunlar iki böyük fərqdir, real həyatda bir növ çılpaq vektor gəlir

Mən çılpaq vektor tərifləri görməmişəm və buna rast gəlməmişəm

Əl ilə telemarafonda yazılmış analiz

Onun sxemi tərifi şərh etdi msg_container

Yenə sual% haqqında qalır. Bu təsvir edilmir.

Vadim Qonçarov, [22.06.18 19:22] və tdesktopda?

Vasili, [22.06.18/19/23 XNUMX:XNUMX] Amma onların tənzimləyicilər üzərindəki TL analizatoru da yəqin ki, onu yeməyəcək

// parsed manually

TL gözəl abstraksiyadır, heç kim onu ​​tamamilə həyata keçirmir

Və onların sxem versiyasında % yoxdur

Ancaq burada sənədləşmə özü ilə ziddiyyət təşkil edir, buna görə də xs

Qrammatikada tapıldı, onlar sadəcə semantikanı təsvir etməyi unuda bilərdilər

Yaxşı, TL-də dok gördünüz, yarım litr olmadan başa düşə bilməzsiniz

"Yaxşı, deyək ki," başqa bir oxucu deyəcək, "sən hər şeyi tənqid edirsən, ona görə də lazım olduğu kimi göstər."

Vasili cavab verir: “Ayrışdırıcıya gəlincə, mənə belə şeylər lazımdır

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

nədənsə daha çox oxşayır

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

və ya

        # 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 BÜTÜN lexerdir:

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

olanlar. daha sadə, yumşaq desək."

Ümumiyyətlə, nəticədə TL-nin faktiki istifadə olunan alt dəsti üçün təhliledici və kod generatoru təxminən 100 qrammatika sətirinə və generatorun ~ 300 sətirinə (bütün daxil olmaqla) uyğun gəlir. printyaradılan kod), o cümlədən növ ləzzətləri, hər sinifdə introspeksiya üçün məlumat yazın. Hər bir polimorf tip boş mücərrəd baza sinfinə çevrilir və konstruktorlar ondan miras qalır və seriallaşdırma və sıradan çıxarma üsullarına malikdir.

Tip dilində tiplərin olmaması

Güclü yazmaq yaxşıdır, elə deyilmi? Xeyr, bu holivar deyil (baxmayaraq ki, dinamik dillərə üstünlük verirəm), TL daxilində postulatdır. Buna əsaslanaraq, dil bizim üçün hər cür çek təmin etməlidir. Yaxşı, tamam, ona deyil, həyata keçirilməsinə icazə verin, amma heç olmasa onları təsvir etməlidir. Və biz hansı imkanları istəyirik?

Hər şeydən əvvəl, məhdudiyyətlər. Burada faylları yükləmək üçün sənədlərdə görürük:

Faylın ikili məzmunu daha sonra hissələrə bölünür. Bütün hissələr eyni ölçüdə olmalıdır ( hissə_ölçüsü ) və aşağıdakı şərtlər yerinə yetirilməlidir:

  • part_size % 1024 = 0 (1KB-ə bölünür)
  • 524288 % part_size = 0 (512KB hissə_ölçüsünə bərabər bölünməlidir)

Ölçüsü part_size-dən kiçik olduqda, sonuncu hissənin bu şərtlərə cavab verməsi lazım deyil.

Hər bir hissənin ardıcıl nömrəsi olmalıdır, fayl_hissəsi, dəyəri 0-dan 2,999-a qədərdir.

Fayl bölündükdən sonra onu serverdə saxlamaq üçün bir üsul seçməlisiniz. istifadə edin upload.saveBigFilePart faylın tam ölçüsü 10 MB-dan çox olduqda və upload.saveFilePart kiçik fayllar üçün.
[…] aşağıdakı məlumat daxil etmə xətalarından biri qaytarıla bilər:

  • FILE_PARTS_INVALID - Yanlış hissələrin sayı. Qiymət arasında deyil 1..3000

Bunlardan hər hansı biri sxemdə varmı? Bunu TL ilə ifadə etmək olarmı? Yox. Bağışlayın, hətta köhnə dəbli Turbo Paskal belə verdiyi növləri təsvir edə bildi. diapazonları. Və o, indi daha yaxşı tanınan bir şey edə bilərdi enum - sabit (kiçik) sayda dəyərlərin sadalanmasından ibarət tip. C kimi dillərdə - rəqəmsal, nəzərə alın ki, biz indiyə qədər yalnız növlər haqqında danışdıq. nömrələri. Amma massivlər, sətirlər də var... məsələn, bu sətirdə yalnız telefon nömrəsi ola biləcəyini təsvir etmək gözəl olardı, elə deyilmi?

Bunların heç biri TL-də deyil. Lakin, məsələn, JSON Schema-da var. Əgər başqası 512 KB-nin bölünə bilməsi ilə bağlı etiraz edə bilərsə ki, bunun hələ də kodda yoxlanılması lazımdır, onda əmin olun ki, müştəri sadəcə olaraq bilmədi nömrəni diapazondan kənara göndərin 1..3000 (və müvafiq səhv yarana bilməzdi) mümkün olardı, elə deyilmi? ..

Yeri gəlmişkən, səhvlər və qaytarılan dəyərlər haqqında. Göz hətta TL ilə işləyənlər üçün də bulanıqdır - bu, bizə dərhal aydın olmadı hər biri TL-dəki funksiya əslində təkcə təsvir edilmiş qaytarma növünü deyil, həm də xətanı qaytara bilər. Lakin bu TL-nin özü ilə çıxılmazdır. Əlbəttə ki, başa düşüləndir və praktikada nafiq lazım deyil (baxmayaraq ki, RPC müxtəlif yollarla edilə bilər, biz buna qayıdacağıq) - bəs səmavi dünyadan Abstrakt Tiplərin Riyaziyyatının anlayışlarının Saflığı haqqında nə demək olar? .. Yedək tutdu - belə uyğun.

Və nəhayət, oxunaqlılıq haqqında nə demək olar? Yaxşı, orada, ümumiyyətlə, istərdim təsvir o, sxemdə düzdür (yenə də JSON sxemindədir), amma artıq onunla gərgindirsə, onda praktik tərəfi haqqında nə demək olar - ən azı yeniləmələr zamanı fərqləri izləmək sadəlövhlükdür? Özünüz baxın real nümunələr:

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

və ya

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

Kiminsə xoşuna gəlir, amma GitHub, məsələn, belə uzun sətirlər daxilində dəyişiklikləri vurğulamaqdan imtina edir. Oyun "10 fərq tap" və beynin dərhal gördüyü şey, hər iki nümunədə başlanğıc və sonların eyni olmasıdır, ortada bir yerdə yorucu oxumaq lazımdır ... Məncə, bu, yalnız nəzəri cəhətdən deyil, ancaq sırf vizual görünür çirkli və səliqəsiz.

Yeri gəlmişkən, nəzəriyyənin saflığı haqqında. Bit sahələri niyə lazımdır? Deyəsən, elə deyillər iy tip nəzəriyyəsi baxımından pisdir? İzahı sxemin əvvəlki versiyalarında görmək olar. Əvvəlcə, bəli, belə idi, hər asqırma üçün yeni bir növ yaradıldı. Bu rudimentlər hələ də bu formada mövcuddur, məsələn:

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;

Ancaq indi təsəvvür edin ki, strukturunuzda 5 isteğe bağlı sahəniz varsa, o zaman bütün mümkün variantlar üçün 32 növ lazımdır. kombinator partlayışı. Beləliklə, TL nəzəriyyəsinin kristal saflığı bir daha serializasiyanın sərt reallığının çuqun eşşəyinə çırpıldı.

Bundan əlavə, yerlərdə bu uşaqlar özləri öz yazılarını pozurlar. Məsələn, MTProto-da (növbəti fəsil) cavab Gzip tərəfindən sıxıla bilər, hər şey həssasdır - təbəqələrin və sxemlərin pozulması istisna olmaqla. Bir dəfə və RpcResult-un özünü deyil, məzmununu biçdi. Yaxşı, niyə belə edirsən?.. Mən qoltuqağacı kəsməli oldum ki, sıxılma hər yerdə işləsin.

Və ya başqa bir nümunə, bir dəfə bir səhv tapdıq - göndərildi InputPeerUser əvəzinə InputUser. Və ya əksinə. Amma işlədi! Yəni server tipinə əhəmiyyət vermirdi. Bu necə ola bilər? Cavab, bəlkə də, telegram-cli-dən kod fraqmentləri ilə izah ediləcək:

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

Başqa sözlə, burada seriallaşdırma aparılır EL İLE, kod yaradılmayıb! Bəlkə server oxşar şəkildə həyata keçirilir?.. Prinsipcə, bu, bir dəfə edilsə, işləyəcək, amma daha sonra yeniləmələrlə bunu necə dəstəkləmək olar? Sxem bunun üçün deyildimi? Və sonra növbəti suala keçirik.

Versiyalaşdırma. qatlar

Sxema versiyalarının niyə təbəqə adlandırıldığını yalnız dərc edilmiş sxemlərin tarixinə əsasən təxmin etmək olar. Göründüyü kimi, əvvəlcə müəlliflərə elə gəldi ki, əsas şeylər dəyişməz bir sxem üzrə edilə bilər və yalnız lazım olduqda, fərqli bir versiyaya uyğun olaraq həyata keçirildiyini xüsusi sorğulara göstərin. Prinsipcə, hətta yaxşı bir fikirdir - və yeni, sanki, köhnənin üstünə "qarışdırılır". Ancaq gəlin bunun necə edildiyinə baxaq. Düzdür, əvvəldən baxmaq mümkün deyildi - gülməli, amma əsas təbəqənin sxemi sadəcə mövcud deyil. Qatlar 2-də başladı. Sənədlər bizə xüsusi TL xüsusiyyətindən xəbər verir:

Əgər müştəri Layer 2-ni dəstəkləyirsə, onda aşağıdakı konstruktordan istifadə edilməlidir:

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

Praktikada bu o deməkdir ki, hər API çağırışından əvvəl dəyəri olan bir int 0x289dd1f6 metod nömrəsindən əvvəl əlavə edilməlidir.

Yaxşı səslənir. Bəs sonra nə oldu? Sonra gəldi

invokeWithLayer3#b7475268 query:!X = X;

Beləliklə, növbəti nədir? Təxmin etmək asan olduğu üçün

invokeWithLayer4#dea0d430 query:!X = X;

Gülməli? Yox, gülmək hələ tezdir, nə düşün hər biri başqa bir təbəqədən gələn sorğu belə bir xüsusi növə bükülməlidir - əgər onların hamısı fərqlidirsə, onları başqa necə ayırd etmək olar? Ön tərəfə cəmi 4 bayt əlavə etmək olduqca səmərəli üsuldur. Belə ki

invokeWithLayer5#417a57ae query:!X = X;

Amma bir müddət sonra bunun hansısa bakanaliyaya çevriləcəyi göz qabağındadır. Və həll gəldi:

Yeniləmə: Layer 9-dan başlayaraq köməkçi üsullar invokeWithLayerN ilə birlikdə istifadə edilə bilər initConnection

Yaşasın! 9 versiyadan sonra, nəhayət, 80-ci illərdə İnternet protokollarında nə edildiyinə gəldik - bağlantının əvvəlində bir dəfə versiya danışıqları!

Sonra nə olacaq?..

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

Və indi gülə bilərsiniz. Yalnız daha 9 qatdan sonra nəhayət versiya nömrəsi olan universal konstruktor əlavə edildi, onu əlaqənin əvvəlində yalnız bir dəfə çağırmaq lazımdır və təbəqələrdəki məna yoxa çıxmış kimi görünür, indi sadəcə şərti versiyadır, məsələn başqa hər yerdə. Problem həll edildi.

Düzdü?..

Vasili, [16.07.18 14:01] Cümə günü düşündüm:
Teleserver hadisələri sorğusuz göndərir. Sorğular InvokeWithLayer-ə bükülməlidir. Server yeniləmələri əhatə etmir, cavabları və yeniləmələri bağlamaq üçün heç bir struktur yoxdur.

Bunlar. müştəri yeniləmələri istədiyi təbəqəni təyin edə bilməz

Vadim Qonçarov, [16.07.18 14:02] InvokeWithLayer prinsipcə qoltuqağacı deyilmi?

Vasili, [16.07.18 14:02] Bu yeganə yoldur

Vadim Qonçarov, [16.07.18 14:02] bu, mahiyyət etibarilə sessiyanın əvvəlində təbəqələşməni nəzərdə tutmalıdır.

Yeri gəlmişkən, bundan belə nəticə çıxır ki, müştərinin aşağı səviyyəsi təmin edilmir

Yeniləmələr, yəni. növü Updates sxemdə bu, serverin müştəriyə API sorğusuna cavab olaraq deyil, hadisə baş verdikdə öz başına göndərdiyi şeydir. Bu, başqa yazıda müzakirə olunacaq mürəkkəb mövzudur, lakin hələlik müştəri oflayn olduqda belə serverin Yeniləmələri yığdığını bilmək vacibdir.

Beləliklə, bükməkdən imtina edərkən hər biri paketinin versiyasını göstərin, buna görə də məntiqi olaraq aşağıdakı mümkün problemlər yaranır:

  • müştəri hansı versiyanı dəstəklədiyini bildirməzdən əvvəl server müştəriyə yeniləmələr göndərir
  • müştərini təkmilləşdirdikdən sonra nə edilməlidir?
  • kim zəmanət verirserverin təbəqə nömrəsi ilə bağlı fikri prosesdə dəyişməyəcək?

Sizcə, bu, sırf nəzəri düşüncədir və praktikada bu baş verə bilməz, çünki server düzgün yazılmışdır (hər halda, yaxşı sınaqdan keçirilir)? ha! Necə olursa olsun!

Bu, avqust ayında qarşılaşdığımız şeydir. Avqustun 14-də Telegram serverlərində nəyinsə yeniləndiyi barədə mesajlar parladı... və sonra 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.

və sonra bir neçə meqabayt yığın izləri (yaxşı, eyni zamanda, giriş düzəldildi). Axı, TL-də bir şey tanınmayıbsa - bu, imzalarla ikili, daha sonra axın içərisindədir HAMISI gedir, deşifrləmə qeyri-mümkün olacaq. Belə bir vəziyyətdə nə etməli?

Yaxşı, hər kəsin ağlına gələn ilk şey əlaqəni kəsib yenidən cəhd etməkdir. Kömək etmədi. CRC32-ni google-da axtardıq - bunlar 73-cü sxemdən olan obyektlər oldu, baxmayaraq ki, biz sxem 82 üzərində işləmişik. Biz jurnallara diqqətlə baxırıq - iki fərqli sxemdən identifikatorlar var!

Bəlkə problem sırf bizim qeyri-rəsmi müştərimizdədir? Xeyr, biz Telegram Desktop 1.2.17 (bir sıra Linux paylamaları ilə təchiz edilmiş versiya) işlədirik, o, İstisna jurnalına yazır: MTP gözlənilməz tip id #b5223b0f MTPMessageMedia-da oxunur…

Telegram-ın protokolu və təşkilati yanaşmalarının tənqidi. 1-ci hissə, texniki: müştərini sıfırdan yazmaq təcrübəsi - TL, MT

Google, oxşar problemin qeyri-rəsmi müştərilərdən birində artıq baş verdiyini göstərdi, lakin sonra versiya nömrələri və müvafiq olaraq, fərziyyələr fərqli idi ...

Beləliklə, nə etməli? Vasili ilə mən ayrıldıq: o, sxemi 91-ə yeniləməyə çalışdı, mən bir neçə gün gözləmək və 73-ə cəhd etmək qərarına gəldim. Hər iki üsul işlədi, lakin onlar empirik olduğundan, neçə versiyanı atlamağınız lazım olduğunu başa düşmək mümkün deyil. və ya aşağı, nə də nə qədər gözləmək lazımdır.

Daha sonra vəziyyəti yenidən yaratmağa müvəffəq oldum: müştərini işə salırıq, onu söndürürük, sxemi başqa bir təbəqəyə yığırıq, yenidən başladın, problemi yenidən həll edirik, əvvəlki birinə qayıdırıq - oops, sxemi dəyişdirmək və müştərini bir neçə dəfə yenidən başlatmaq olmaz. dəqiqə kömək edəcək. Siz müxtəlif təbəqələrdən məlumat strukturlarının qarışığını alacaqsınız.

İzahat? Müxtəlif dolayı simptomlardan təxmin edə bildiyiniz kimi, server müxtəlif maşınlarda çoxlu müxtəlif növ proseslərdən ibarətdir. Çox güman ki, "buferləşdirmə" üçün cavabdeh olan serverlərdən biri, daha yüksəklərin verdiyini növbəyə qoydu və onlar nəsil zamanı olan sxemdə verdilər. Və bu növbə "çürük" olana qədər bununla bağlı heç nə etmək mümkün deyildi.

Bir halda ki... amma bu dəhşətli qoltuqaltıdır?!.. Yox, sərsəm fikirlər haqqında düşünməzdən əvvəl rəsmi müştərilərin koduna baxaq. Android versiyasında biz heç bir TL analizatoru tapmırıq, lakin (de) seriyalı bir fayl tapırıq (github onu rəngləndirməkdən imtina edir). Budur kod parçaları:

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;

və ya

    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... dəli görünür. Amma yəqin ki, bu yaradılan koddur, bəs?.. Amma o, şübhəsiz ki, bütün versiyaları dəstəkləyir! Düzdür, niyə hər şeyin bir yığında qarışdığı, gizli söhbətlərin və hər cür _old7 nədənsə maşın nəslinə bənzəmir ... Ancaq ən çox mən dəli oldum

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Uşaqlar, siz hətta bir təbəqə daxilində qərar verə bilməzsiniz?! Yaxşı, yaxşı, "iki", tutaq ki, xəta ilə buraxıldı, yaxşı, olur, amma ÜÇ?.. Dərhal yenə eyni dırmıqda? Bu hansı pornoqrafiyadır, bağışlayın? ..

Yeri gəlmişkən, oxşar şey Telegram Desktop mənbələrində baş verir - əgər belədirsə və sxemə bir neçə öhdəçilik onun təbəqə nömrəsini dəyişmir, lakin nəyisə düzəldir. Sxem üçün rəsmi məlumat mənbəyi olmadığı bir şəraitdə, rəsmi müştəri mənbələri istisna olmaqla, onu haradan əldə edə bilərəm? Və oradan götürürsən, bütün üsulları sınaqdan keçirməyincə sxemin tamamilə düzgün olduğuna əmin ola bilməzsən.

Bunu necə sınaqdan keçirmək olar? Ümid edirəm ki, vahid, funksional və digər testlərin pərəstişkarları şərhlərdə paylaşacaqlar.

Yaxşı, başqa bir kod parçasına baxaq:

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;

Buradakı "əllə yaradılmış" şərhi bu faylın yalnız bir hissəsinin əl ilə yazıldığını (təsərrüfat kabusunu təsəvvür edə bilərsinizmi?), qalan hissəsinin isə maşın tərəfindən yaradıldığını göstərir. Ancaq sonra başqa bir sual yaranır - mənbələrin olması tamamilə deyil (Linux nüvəsindəki GPL altında bir la blobs), lakin bu artıq ikinci hissənin mövzusudur.

Amma kifayətdir. Gəlin bütün bu serializasiyanın təqib etdiyi protokola keçək.

MT Proto

Beləliklə, açaq ümumi təsviri и protokolun ətraflı təsviri və ilk səhv etdiyimiz şey terminologiyadır. Və hər şeyin bolluğu ilə. Ümumiyyətlə, bu, Telegram-ın ticarət nişanı kimi görünür - müxtəlif yerlərdə olan əşyaları müxtəlif yollarla və ya fərqli şeyləri bir sözlə çağırmaq və ya əksinə (məsələn, stiker paketi görsəniz, yüksək səviyyəli API-də - bu düşündüyünüz kimi deyil).

Məsələn, "mesaj" (mesaj) və "sessiya" (sessiya) - burada onlar Telegram müştərisinin adi interfeysindən fərqli bir şey deməkdir. Yaxşı, mesajla hər şey aydındır, OOP baxımından şərh edilə bilər və ya sadəcə "paket" sözü adlandırıla bilər - bu aşağı, nəqliyyat səviyyəsidir, interfeysdə olduğu kimi eyni mesajlar yoxdur, çox şey var xidmət olanlardan. Ancaq sessiya ... amma ilk şeylər.

nəqliyyat təbəqəsi

İlk şey nəqliyyatdır. Bizə 5 variant haqqında məlumat veriləcək:

  • TCP
  • Veb sayt
  • HTTPS üzərindən Websocket
  • HTTP
  • HTTPS

Vasili, [15.06.18 15:04] Və UDP nəqliyyatı da var, lakin sənədləşdirilməyib

Və üç variantda TCP

Birincisi, TCP üzərindən UDP-yə bənzəyir, hər paketə ardıcıllıq nömrəsi və crc daxildir
Səbətdə dokları oxumaq niyə bu qədər ağrılıdır?

Yaxşı indi var TCP artıq 4 variantda:

  • ixtisarlı
  • Aralıq
  • yastıqlı aralıq
  • Full

Ok, MTProxy üçün Padded intermediate, bu daha sonra məlum hadisələrə görə əlavə edildi. Bəs niyə daha iki versiya (cəmi üç), biri edə bilərdi? Dördü mahiyyətcə yalnız əsas MTProto-nun özünün uzunluğunu və faydalı yükünü necə təyin etməkdə fərqlənir, daha sonra müzakirə ediləcək:

  • Qısaldılmışda bu 1 və ya 4 baytdır, lakin bədəndən sonra 0xef deyil
  • Intermediate-də bu, 4 bayt uzunluq və sahədir və ilk dəfə müştəri göndərməlidir 0xeeeeeeee Orta səviyyədə olduğunu göstərmək üçün
  • Tam olaraq, şəbəkəçi nöqteyi-nəzərindən ən çox asılılıq yaradır: uzunluq, ardıcıllıq nömrəsi və əsasən MTProto, bədən, CRC32 olan BİR DEYİL. Bəli, bütün bunlar TCP üzərindədir. Hansı ki, bizə baytların ardıcıl axını şəklində etibarlı nəqliyyat təmin edir, heç bir ardıcıllığa ehtiyac yoxdur, xüsusən də yoxlama məbləğləri. Yaxşı, indi onlar mənə etiraz edəcəklər ki, TCP-nin 16 bitlik yoxlama məbləği var, buna görə də məlumatların pozulması baş verir. Əla, 16 baytdan çox heşləri olan kriptoqrafik protokolumuzun olması istisna olmaqla, bütün bu səhvlər - və hətta daha çox - daha yüksək səviyyədə SHA uyğunsuzluğunda tutulacaq. CRC32-də bununla bağlı heç bir məqam yoxdur.

Bir bayt uzunluğun mümkün olduğu Qısaldılmışı "4 baytlıq məlumatların uyğunlaşdırılması lazım olduqda" əsaslandıran Intermediate ilə müqayisə edək ki, bu olduqca cəfəngiyyatdır. Nə, belə hesab olunur ki, Telegram proqramçıları o qədər yöndəmsizdirlər ki, məlumatları rozetkadan uyğunlaşdırılmış buferə oxuya bilmirlər? Bunu hələ də etməlisiniz, çünki oxumaq sizə istənilən sayda bayt qaytara bilər (və həmçinin proxy serverlər də var, məsələn ...). Və ya digər tərəfdən, üstdə hələ də 16 baytdan böyük paddinglər varsa, niyə Qısaldılmış ilə narahat olaq - 3 bayta qənaət edin bəzən ?

Belə bir təəssürat yaranır ki, Nikolay Durov heç bir praktik ehtiyac olmadan velosipedlər, o cümlədən şəbəkə protokolları icad etməyi çox sevir.

Digər nəqliyyat variantları, o cümlədən. Web və MTProxy, biz indi nəzərdən keçirməyəcəyik, bəlkə başqa bir yazıda, bir sorğu olarsa. Biz yalnız indi bu MTProxy haqqında xatırlayacağıq ki, 2018-ci ildə buraxıldıqdan qısa müddət sonra provayderlər onu tam olaraq bloklamağı öyrəndilər. blok bypassIlə paket ölçüsü! Həm də C-də yazılmış (yenə Waltman tərəfindən) MTProxy serverinin Linux xüsusiyyətlərinə ehtiyac olmadan bağlı olması, buna heç bir ehtiyac olmasa da (Phil Kulin təsdiq edəcək) və oxşar serverin ya Go, ya da Node.js-də olması. yüz sətirdən az uyğun.

Amma biz bu insanların texniki savadları haqqında digər məsələləri nəzərdən keçirdikdən sonra bölmənin sonunda nəticə çıxaracağıq. Hələlik MTProto sessiyasını yerləşdirdikləri 5-ci OSI qatına, sessiyaya keçək.

Açarlar, mesajlar, sessiyalar, Diffie-Hellman

Onu tam olaraq düzgün qoymayıblar... Sessiya, Aktiv seanslar altında interfeysdə görünən eyni sessiya deyil. Amma qaydada.

Telegram-ın protokolu və təşkilati yanaşmalarının tənqidi. 1-ci hissə, texniki: müştərini sıfırdan yazmaq təcrübəsi - TL, MT

Burada nəqliyyat qatından məlum uzunluqda bayt sətirini almışıq. Bu ya şifrələnmiş mesajdır, ya da açıq mətndir - əgər biz hələ də əsas danışıqlar mərhələsindəyiksə və bunu həqiqətən ediriksə. "Açar" adlanan anlayışlar dəstəsindən hansı haqqında danışırıq? Gəlin Telegram komandasının özü üçün bu məsələyə aydınlıq gətirək (səhər saat 4-də öz sənədlərimi ingilis dilindən ya yorğun beyinə tərcümə etdiyim üçün üzr istəyirəm, bəzi ifadələri olduğu kimi buraxmaq daha asan oldu):

adlı iki qurum var Sessiya - "cari sessiyalar" altında rəsmi müştərilərin UI-də biri, burada hər seans bütöv bir cihaza / ƏS-ə uyğundur.
İkincisi MTProto sessiyası, içində mesaj sıra nömrəsi (aşağı səviyyəli mənada) olan və hansı müxtəlif TCP əlaqələri arasında davam edə bilər. Eyni zamanda bir neçə MTProto seansı qurmaq olar, məsələn, faylların endirilməsini sürətləndirmək üçün.

Bu ikisi arasında sessiyaları anlayışdır icazə. Degenerativ vəziyyətdə bunu demək olar UI sessiyası ilə eynidir icazəAmma təəssüf ki, mürəkkəbdir. Baxırıq:

  • Yeni cihazdakı istifadəçi əvvəlcə yaradır auth_key və hesaba bağlayır, məsələn, SMS ilə - buna görə də icazə
  • Birincinin içərisində baş verdi MTProto sessiyası, olan session_id öz içində.
  • Bu mərhələdə birləşmə icazə и session_id çağırmaq olar Məsələn - bu söz bəzi müştərilərin sənədlərində və kodunda olur
  • Sonra müştəri aça bilər bir MTProto seansları eyni altında auth_key - eyni DC-yə.
  • Sonra bir gün müştəri bir fayl tələb etməlidir başqa bir DC - və bu DC üçün yenisi yaradılacaq auth_key !
  • Sistemə bildirmək ki, bu, qeydiyyatdan keçən yeni istifadəçi deyil, eynidir icazə (UI sessiyası), müştəri API zənglərindən istifadə edir auth.exportAuthorization evdə DC auth.importAuthorization yeni DC-də.
  • Eyni zamanda, bir neçə açıq ola bilər MTProto seansları (hər biri öz session_id) bu yeni DC-yə, altında onun auth_key.
  • Nəhayət, müştəri Mükəmməl İrəli Məxfilik istəyə bilər. Hər auth_key oldu daimi açar - DC başına - və müştəri zəng edə bilər auth.bindTempAuthKey istifadə üçün müvəqqəti auth_key - və yenə, yalnız bir temp_auth_key hər DC üçün, hamı üçün ümumi MTProto seansları bu DC-yə.

Qeyd edin ki duz (və gələcək duzlar) da bir auth_key olanlar. hamı arasında paylaşdı MTProto seansları eyni DC-yə.

"Müxtəlif TCP əlaqələri arasında" nə deməkdir? Bu o deməkdir ki, bu kimi bir şey vebsaytda avtorizasiya kukisi - bu serverə bir çox TCP bağlantılarını saxlayır (sağ qalır), lakin bir gün o, pisləşəcək. Yalnız HTTP-dən fərqli olaraq, MTProto-da, sessiya daxilində mesajlar ardıcıl olaraq nömrələnir və təsdiqlənir, onlar tunelə daxil olurlar, əlaqə pozulur - yeni bir əlaqə qurduqdan sonra server lütfən bu sessiyada çatdırmadığı hər şeyi göndərir. əvvəlki TCP bağlantısı.

Bununla belə, yuxarıda göstərilən məlumatlar bir çox aylarla davam edən məhkəmə çəkişmələrindən sonra bir sıxıntıdır. Bu arada biz müştərimizi sıfırdan həyata keçiririkmi? - gəlin əvvəlinə qayıdaq.

Beləliklə, biz yaradırıq auth_key haqqında Telegram-dan Diffie-Hellman versiyaları. Gəlin sənədləri anlamağa çalışaq...

Vasili, [19.06.18/20/05 1:255] data_with_hash := SHAXNUMX(data) + data + (istənilən təsadüfi bayt); uzunluğu XNUMX bayta bərabər olsun;
şifrələnmiş_məlumat := RSA(hash_ilə_məlumat, server_ümumi_açar); 255 baytlıq uzun nömrə (böyük endian) tələb olunan modul üzərində lazımi gücə qaldırılır və nəticə 256 baytlıq nömrə kimi saxlanılır.

Bir az narkotik DH aldılar

Sağlam insanın DH-yə bənzəmir
dx-də iki açıq açar yoxdur

Yaxşı, sonda biz bunu başa düşdük, amma çöküntü qaldı - işin sübutu müştəri tərəfindən nömrəni faktorlara ayıra bildiyi üçün edilir. DoS hücumlarına qarşı qorunma növü. Və RSA açarı yalnız bir istiqamətdə, əsasən şifrələmə üçün istifadə olunur new_nonce. Bəs bu sadə görünən əməliyyat uğurlu olsa da, nə ilə üzləşməli olacaqsınız?

Vasili, [20.06.18/00/26 XNUMX:XNUMX] Mən hələ əlavə sorğuya çatmamışam

DH-yə sorğu göndərdim

Və nəqliyyatın dokunda onun 4 bayt səhv kodu ilə cavab verə biləcəyi yazılıb. Və bu qədər

Yaxşı, mənə -404 dedi, bəs nə?

Budur, mən ona: “filan barmaq izi ilə server açarı ilə şifrələnmiş efignanı tut, mən DH istəyirəm” və o, axmaqcasına cavab verir 404

Belə bir server cavabı haqqında nə düşünürsünüz? Nə etməli? Soruşacaq heç kim yoxdur (amma bu barədə ikinci hissədə daha çox).

Burada bütün maraq dok etməkdir

Başqa işim yoxdur, mən yalnız rəqəmləri irəli-geri çevirməyi xəyal edirdim

İki 32 bitlik nömrə. Hamı kimi mən də onları qablaşdırdım

Xeyr, BE kimi bir cərgədə ilk ehtiyacınız olan bu ikisidir

Vadim Qonçarov, [20.06.18 15:49] və buna görə 404?

Vasili, [20.06.18 15:49] BƏLİ!

Vadim Qonçarov, [20.06.18 15:50] ona görə də onun nəyi “tapmadığını” anlamıram

Vasili, [20.06.18 15:50] təxminən

Sadə bölücülərə belə bir parçalanma tapmadım%)

Hətta səhv hesabatı da mənimsənilməyib

Vasili, [20.06.18 20:18] Oh, MD5 də var. Artıq üç fərqli hash

Əsas barmaq izi aşağıdakı kimi hesablanır:

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

SHA1 və sha2

Beləliklə, qoyaq auth_key Diffie-Hellman-a görə əldə etdiyimiz 2048 bit ölçüsü. Sonra nə var? Sonra bu açarın aşağı 1024 bitinin heç bir şəkildə istifadə olunmadığını öyrənirik ... amma hələlik bu barədə düşünək. Bu addımda serverlə ortaq bir sirrimiz var. TLS sessiyasının analoqu yaradılmışdır, bu çox bahalı prosedurdur. Amma server bizim kim olduğumuz haqqında hələ heç nə bilmir! Hələ yox, əslində icazə. Bunlar. Əgər əvvəllər ICQ-da olduğu kimi “giriş-parol” və ya heç olmasa SSH-də olduğu kimi (məsələn, bəzi gitlab / github-da) “giriş açarı” baxımından düşünürsünüzsə. Anonim olduq. Və server bizə "bu telefon nömrələri başqa bir DC tərəfindən xidmət göstərir" cavabını verirsə? Və ya hətta "telefon nömrəniz qadağandır"? Edə biləcəyimiz ən yaxşı şey, açarın hələ də faydalı olacağı və o vaxta qədər çürüməyəcəyi ümidi ilə saxlamaqdır.

Yeri gəlmişkən, biz bunu rezervasiyalarla “qəbul etmişik”. Məsələn, serverə etibar edirikmi? O saxtadır? Bizə kriptoqrafik yoxlamalar lazımdır:

Vasili, [21.06.18/17/53 2:XNUMX] Onlar mobil müştərilərə sadəlik üçün XNUMXkbit nömrəni yoxlamağı təklif edirlər%)

Amma heç də aydın deyil, nafeijoa

Vasili, [21.06.18/18/02 XNUMX:XNUMX] Dok sadə olmadığı ortaya çıxsa nə edəcəyini demir

Deyil. Gəlin görək Android üçün rəsmi müştəri bu halda nə edir? A budur (və bəli, bütün fayl orada maraqlıdır) - necə deyərlər, onu burada buraxacağam:

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

Yox, təbii ki, var bəzi bir ədədin sadəliyi üçün yoxlamalar var, lakin şəxsən mənim artıq riyaziyyatda kifayət qədər biliyim yoxdur.

Yaxşı, əsas açarı aldıq. Daxil olmaq üçün, yəni. sorğu göndərmək üçün artıq AES istifadə edərək əlavə şifrələmə aparmaq lazımdır.

Mesaj açarı, avtorizasiya açarından götürülmüş 128 baytla qabaqcadan yazılmış doldurma baytları da daxil olmaqla, mesaj orqanının SHA256-nın 32 orta biti (sessiya, mesaj ID və s. daxil olmaqla) kimi müəyyən edilir.

Vasili, [22.06.18 14:08] Orta qancıqlar

Götürdüm auth_key. Hamısı. Onları daha da ... doklardan aydın deyil. Açıq mənbə kodunu öyrənməkdən çekinmeyin.

Nəzərə alın ki, MTProto 2.0 12 baytdan 1024 bayta qədər doldurma tələb edir və nəticədə mesaj uzunluğunun 16 bayta bölünməsi şərtilə.

Beləliklə, nə qədər padding qoymaq lazımdır?

Və bəli, burada da bir səhv halında 404

Kimsə diaqramı və sənədlərin mətnini diqqətlə öyrənsəydi, orada MAC olmadığını gördü. Və bu AES başqa heç bir yerdə istifadə olunmayan bəzi IGE rejimində istifadə olunur. Onlar, əlbəttə ki, bu barədə öz tez-tez verilən suallarda yazırlar... Burada, məsələn, mesaj açarının özü, eyni zamanda, bütövlüyünü yoxlamaq üçün istifadə edilən şifrəsi açılmış məlumatların SHA hashıdır - uyğunsuzluq olduqda isə sənədlər nədənsə səssizcə onlara məhəl qoymamağı tövsiyə edir (amma təhlükəsizlik haqqında nə demək olar, birdən bizi qırır?).

Mən kriptoqraf deyiləm, bəlkə də bu rejimdə bu halda nəzəri baxımdan heç nə yoxdur. Ancaq Telegram Desktop nümunəsindən istifadə edərək, mütləq praktik bir problem adlandıra bilərəm. O, yerli önbelleği (bütün bu D877F783D5D3EF8C) MTProto-dakı mesajlarla eyni şəkildə şifrələyir (yalnız bu halda, versiya 1.0), yəni. əvvəlcə mesaj açarı, sonra məlumatın özü (və əsas böyük auth_key 256 bayt, onsuz msg_key faydasız). Beləliklə, problem böyük fayllarda nəzərə çarpır. Məhz, məlumatların iki nüsxəsini saxlamalısınız - şifrələnmiş və şifrəsi açılmış. Məsələn, meqabaytlar və ya axın videosu varsa? .. Şifrəli mətndən sonra MAC ilə klassik sxemlər onu axınla oxumağa, dərhal köçürməyə imkan verir. Və MTProto ilə bunu etməlisiniz ilk növbədə bütün mesajı şifrələyin və ya deşifrə edin, yalnız bundan sonra onu şəbəkəyə və ya diskə köçürün. Buna görə də, Telegram Desktop-un ən son versiyalarında keş yaddaşda user_data başqa format artıq istifadə olunur - CTR rejimində AES ilə.

Vasili, [21.06.18/01/27 20:XNUMX] Oh, mən IGE-nin nə olduğunu öyrəndim: IGE ilk olaraq Kerberos üçün "autentifikasiya şifrələmə rejimi" üçün ilk cəhd idi. Bu, uğursuz cəhd idi (bütünlüyün qorunmasını təmin etmir) və silinməli idi. Bu, işləyən identifikasiya şifrələmə rejimi üçün XNUMX illik axtarışın başlanğıcı idi və bu yaxınlarda OCB və GCM kimi rejimlərlə yekunlaşdı.

İndi araba tərəfdən arqumentlər:

Nikolay Durovun rəhbərlik etdiyi Telegram-ın arxasında duran komanda altı ACM çempionundan ibarətdir, onların yarısı riyaziyyat üzrə fəlsəfə doktorudur. MTProto-nun hazırkı versiyasını yaymaq üçün onlara təxminən iki il lazım oldu.

Nə gülməli. Aşağı səviyyəyə iki il

Və ya sadəcə TL ala bilərik

Yaxşı, deyək ki, biz şifrələmə və digər nüansları etdik. Nəhayət TL seriyalı sorğular göndərə və cavabları silə bilərikmi? Beləliklə, nə və necə göndərilməlidir? Budur üsul initConnectionbəlkə budur?

Vasili, [25.06.18/18/46 XNUMX:XNUMX] Bağlantını işə salır və istifadəçinin cihazında və tətbiqində məlumatı saxlayır.

O, app_id, device_model, system_version, app_version və lang_code qəbul edir.

Və bəzi sorğular

Həmişə olduğu kimi sənədləşmə. Açıq mənbəni öyrənməkdən çekinmeyin

Əgər invokeWithLayer ilə hər şey təxminən aydın idisə, bu nədir? Belə çıxır ki, tutaq ki, müştərinin serverdən soruşacağı bir şey var idi - göndərmək istədiyimiz bir sorğu var:

Vasili, [25.06.18/19/13 XNUMX:XNUMX] Koda əsasən, ilk zəng bu zibilliyə bükülür və zibil özü invokewithlayer-dədir.

Niyə initConnection ayrıca zəng ola bilməz, lakin sarğı olmalıdır? Bəli, göründüyü kimi, əsas açarda olduğu kimi birdəfəlik deyil, hər sessiyanın əvvəlində bunu etmək lazımdır. Amma! Onu icazəsiz istifadəçi çağıra bilməz! Burada onun tətbiq oluna biləcəyi mərhələyə çatdıq Bu bir sənədləşmə səhifəsi - və o bizə deyir ki...

API metodlarının yalnız kiçik bir hissəsi icazəsiz istifadəçilər üçün əlçatandır:

  • 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

Onlardan ən birincisi auth.sendCode, və api_id və api_hash göndərəcəyimiz dəyərli ilk sorğu var və bundan sonra kodla SMS alırıq. Yanlış DC-yə çatsaq (məsələn, bu ölkənin telefon nömrələrinə başqası xidmət göstərir), onda istədiyiniz DC nömrəsi ilə səhv alacağıq. DC nömrəsi ilə hansı IP ünvanına qoşulmalı olduğumuzu öyrənmək üçün bizə kömək edəcək help.getConfig. Bir vaxtlar cəmi 5 giriş var idi, lakin 2018-ci ilin məlum hadisələrindən sonra onların sayı xeyli artdı.

İndi anonim serverdə bu mərhələdə olduğumuzu xatırlayaq. Sadəcə IP ünvanı almaq çox baha deyilmi? Niyə bunu və digər əməliyyatları MTProto-nun şifrələnməmiş hissəsində etməyək? Etiraz eşidirəm: “Sən necə əmin ola bilərsən ki, saxta ünvanlarla cavab verən RKN deyil?”. Bunun üçün biz xatırlayırıq ki, əslində, rəsmi müştərilərdə quraşdırılmış RSA açarları, yəni. sadəcə bilərsən imza bu məlumat. Əslində, bu, müştərilərin digər kanallar vasitəsilə qəbul etdiyi kilidləri keçmək haqqında məlumat üçün edilir (məntiqi budur ki, MTProto-nun özündə bunu etmək olmaz, çünki hələ də hara qoşulacağını bilməlisiniz).

TAMAM. Müştəri avtorizasiyasının bu mərhələsində biz hələ icazə almamışıq və ərizəmizi qeydiyyatdan keçirməmişik. Biz sadəcə olaraq serverin icazəsiz istifadəçi üçün mövcud üsullara nə cavab verdiyini görmək istəyirik. Və burada…

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

Sxemdə birinci, ikincisi gəlir

tdesktop sxemində üçüncü dəyərdir

Bəli, o vaxtdan bəri, əlbəttə ki, sənədlər yeniləndi. Baxmayaraq ki, tezliklə yenidən əhəmiyyətsiz ola bilər. Və təcrübəsiz bir tərtibatçı necə bilməlidir? Bəlkə ərizənizi qeydiyyatdan keçirsəniz, sizə məlumat verərlər? Vasili bunu etdi, amma təəssüf ki, ona heç nə göndərilmədi (yenidən bu barədə ikinci hissədə danışacağıq).

... Diqqət etdiniz ki, biz artıq bir şəkildə API-yə keçmişik, yəni. növbəti səviyyəyə keçdi və MTProto mövzusunda nəyisə qaçırdınız? Təəccüblü heç nə yoxdur:

Vasili, [28.06.18/02/04 2:XNUMX] Mm, onlar eXNUMXe-də bəzi alqoritmləri araşdırırlar.

Mtproto hər iki domen üçün şifrələmə alqoritmlərini və açarlarını, həmçinin bir qədər sarğı strukturunu müəyyən edir.

Lakin onlar daim müxtəlif yığın səviyyələrini qarışdırırlar, buna görə də mtproto-nun harada bitdiyi və növbəti səviyyənin harada başladığı həmişə aydın deyil.

Onlar necə qarışdırılır? Yaxşı, burada PFS üçün eyni müvəqqəti açar, məsələn (yeri gəlmişkən, Telegram Desktop bunu necə edəcəyini bilmir). API sorğusu ilə yerinə yetirilir auth.bindTempAuthKey, yəni. yuxarı səviyyədən. Ancaq eyni zamanda, aşağı səviyyədə şifrələməyə mane olur - ondan sonra, məsələn, yenidən etməlisiniz initConnection və s., bu deyil yalnız normal tələb. Ayrı-ayrılıqda, o, həmçinin sahəyə baxmayaraq, DC-də yalnız BİR müvəqqəti açarın ola biləcəyini təmin edir auth_key_id hər mesajda açarı ən azı hər mesajda dəyişdirməyə imkan verir və serverin istənilən vaxt müvəqqəti açarı "unutmaq" hüququ var - bu halda nə etməli, sənədlərdə deyilmir ... yaxşı, niyə gələcək duzlar dəsti kimi bir neçə açara sahib olmaq mümkün olmazdı, amma ?..

MTProto mövzusunda qeyd etməyə dəyər bir neçə başqa şey var.

Mesaj mesajları, msg_id, msg_seqno, təşəkkürlər, yanlış istiqamətdə pinglər və digər özəlliklər

Niyə onlar haqqında bilmək lazımdır? Çünki onlar bir səviyyə yuxarı “sızırlar” və API ilə işləyərkən onlar haqqında bilmək lazımdır. Tutaq ki, msg_key ilə maraqlanmırıq, aşağı səviyyə bizim üçün hər şeyi deşifrə etdi. Lakin şifrəsi açılmış məlumatların içərisində bizdə aşağıdakı sahələr var (həmçinin dolğunluğun harada olduğunu bilmək üçün məlumatların uzunluğu, lakin bu vacib deyil):

  • duz-int64
  • session_id - int64
  • message_id - int64
  • seq_no-int32

Xatırladaq ki, bütün DC üçün yalnız bir duz var. Niyə bu barədə bilirsiniz? Təkcə ona görə yox ki, müraciət var get_future_salts, hansı intervalların etibarlı olacağını bildirir, həm də ona görə ki, əgər duzunuz “çürük”dürsə, mesaj (sorğu) sadəcə itəcək. Server, əlbəttə ki, yeni duz haqqında məlumat verəcəkdir new_session_created - amma köhnəsi ilə, məsələn, birtəhər yenidən göndərməli olacaqsınız. Və bu sual tətbiqin arxitekturasına təsir göstərir.

Serverə bir çox səbəbə görə seansları tamamilə tərk etməyə və bu şəkildə cavab verməyə icazə verilir. Əslində, müştəri tərəfdən MTProto sessiyası nədir? Bunlar iki rəqəmdir session_id и seq_no bu sessiya daxilində mesajlar. Əlbəttə ki, əsas TCP bağlantısı. Tutaq ki, müştərimiz hələ də bir çox şeyi necə edəcəyini bilmir, əlaqəsi kəsilib, yenidən qoşulub. Bu tez baş verərsə - köhnə sessiya yeni TCP bağlantısında davam etdi, artırın seq_no daha. Çox vaxt apararsa, server onu silə bilər, çünki bildiyimiz kimi onun tərəfində də növbə var.

Nə olmalıdır seq_no? Oh, bu çətin sualdır. Nə demək istədiyini səmimi şəkildə başa düşməyə çalışın:

Məzmunla əlaqəli mesaj

Açıq etiraf tələb edən mesaj. Bunlara bütün istifadəçi və bir çox xidmət mesajları daxildir, demək olar ki, hamısı konteynerlər və təşəkkürlər istisna olmaqla.

Mesaj ardıcıllığı nömrəsi (msg_seqno)

Göndərən tərəfindən bu mesajdan əvvəl yaradılan və cari mesaj bir mesajdırsa, sonradan bir artırılan "məzmunla əlaqəli" mesajların (təsdiq tələb edənlər və xüsusən də konteyner olmayanlar) ikiqat sayına bərabər olan 32 bitlik nömrə. məzmunla əlaqəli mesaj. Konteyner həmişə bütün məzmunundan sonra yaradılır; buna görə də onun sıra nömrəsi onun içindəki mesajların ardıcıl nömrələrindən böyük və ya ona bərabərdir.

1, sonra başqa 2 artımla bu hansı sirkdir? .. Mən şübhələnirəm ki, ilkin məna “ACK üçün aşağı bit, qalanı rəqəmdir” idi, lakin nəticə tamamilə düzgün deyil - xüsusən, göndərilə biləcəyi məlum olur bir eyni olan təsdiqlər seq_no! Necə? Yaxşı, məsələn, server bizə bir şey göndərir, göndərir və biz özümüz susuruq, yalnız onun mesajlarını qəbul etmək barədə xidmət təsdiq mesajları ilə cavab veririk. Bu halda, gedən təsdiqlərimiz eyni gedən nömrəyə malik olacaq. Əgər siz TCP ilə tanışsınızsa və bunun çılğın səsləndiyini düşünürsünüzsə, lakin bu, çox da vəhşi deyil, çünki TCP-də seq_no dəyişmir və təsdiq gedir seq_no o biri tərəfi - sonra üzülməyə tələsirəm. MTProto-ya təsdiqlər gəlir EDİLMƏDİ haqqında seq_no, TCP-də olduğu kimi, lakin msg_id !

Bu nədir msg_id, bu sahələrdən ən əhəmiyyətlisi? Adından da göründüyü kimi mesajın unikal ID-si. Bu, 64 bitlik nömrə kimi müəyyən edilir, ən az əhəmiyyətli bitləri yenə server-server olmayan sehrə malikdir, qalan hissəsi isə Unix vaxt damğasıdır, o cümlədən fraksiya hissəsi 32 bit sola sürüşdürülmüşdür. Bunlar. vaxt damğası (və çox fərqli vaxtları olan mesajlar server tərəfindən rədd ediləcək). Buradan belə çıxır ki, ümumiyyətlə, bu, müştəri üçün qlobal olan identifikatordur. Hələ - xatırlayın session_id - zəmanət verilir: Heç bir halda bir seans üçün nəzərdə tutulan mesaj başqa sessiyaya göndərilə bilməz. Yəni məlum olur ki, artıq var üç səviyyə — sessiya, sessiya nömrəsi, mesaj id. Niyə belə bir həddindən artıq mürəkkəblik, bu sirr çox böyükdür.

Belə ki, msg_id üçün lazımdır...

RPC: sorğular, cavablar, səhvlər. Təsdiqlər.

Diqqət etdiyiniz kimi, cavablar olsa da, sxemin heç bir yerində "RPC sorğusu et" xüsusi bir növü və ya funksiyası yoxdur. Axı, məzmunla əlaqəli mesajlarımız var! Yəni, hər hansı mesaj istək ola bilər! Ya da olma. Hər şeydən sonra, hər biri yoxdur msg_id. Və burada cavablar:

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

Bunun hansı mesaja cavab olduğu burada göstərilir. Buna görə də, API-nin yuxarı səviyyəsində sorğunuzun hansı nömrə olduğunu xatırlamalı olacaqsınız - düşünürəm ki, işin asinxron olduğunu izah etmək lazım deyil və eyni zamanda bir neçə sorğu ola bilər, cavablar istənilən qaydada qaytarmaq olar? Prinsipcə, bundan və heç bir işçi kimi səhv mesajlarından, bunun arxasındakı arxitektura izlənilə bilər: sizinlə TCP əlaqəsini saxlayan server qabaqcıl balanslaşdırıcıdır, sorğuları arxa tərəflərə yönləndirir və onları geri toplayır. message_id. Burada hər şey aydın, məntiqli və yaxşı görünür.

Bəli?.. Bəs fikirləşsəniz? Axı, RPC cavabının özü də bir sahəyə malikdir msg_id! Serverə “cavabıma cavab vermirsən!” deyə qışqırmalıyıqmı? Bəli, təsdiqlə bağlı nə var idi? Səhifə haqqında mesajlar haqqında mesajlar nə olduğunu bizə bildirir

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

və hər tərəf bunu etməlidir. Ancaq həmişə deyil! RpcResult alsanız, bu, öz-özünə təsdiq kimi xidmət edir. Yəni, server sorğunuza MsgsAck ilə cavab verə bilər - kimi, "Mən onu aldım". Dərhal RpcResult cavab verə bilər. İkisi də ola bilər.

Bəli, hələ də cavaba cavab verməlisiniz! Təsdiq. Əks halda, server onu çatdırılmamış hesab edəcək və yenidən sizə atacaq. Yenidən qoşulduqdan sonra da. Amma burada təbii ki, fasilələrlə bağlı sual yaranacaq. Onlara bir az sonra baxaq.

Bu arada sorğunun icrasında mümkün səhvləri nəzərdən keçirək.

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

Oh, kimsə qışqıracaq, burada daha insani format var - bir xətt var! İşində ol. Budur səhvlərin siyahısılakin, əlbəttə ki, tam deyil. Ondan kodun - olduğunu öyrənirik kimi bir şey HTTP səhvləri (əlbəttə ki, cavabların semantikasına hörmət edilmir, bəzi yerlərdə təsadüfi kodlarla paylanır) və sətir belə görünür BÜYÜK_HƏRFLƏR_AND_NÖMRƏLƏR. Məsələn, PHONE_NUMBER_OCCUPIED və ya FILE_PART_X_MISSING. Yaxşı, yəni hələ də bu xəttə sahibsiniz təhlil etmək. Məsələn, FLOOD_WAIT_3600 bir saat gözləmək lazım olduğunu ifadə edəcək və PHONE_MIGRATE_5ki, bu prefiksli telefon nömrəsi 5-ci DC-də qeydiyyatdan keçməlidir. Tipik bir dilimiz var, elə deyilmi? Bizə sətirdən arqument lazım deyil, müntəzəm ifadələr edəcək, cho.

Yenə də bu, xidmət mesajları səhifəsində deyil, lakin bu layihə ilə artıq adət etdiyimiz kimi, məlumat tapıla bilər. başqa bir sənəd səhifəsində. Və ya şübhə oyatmaq. Birincisi, baxın, yazın/qatların pozulması - RpcError sərmayə qoymaq olar RpcResult. Niyə çöldə olmasın? Nəyi nəzərə almamışıq?.. Buna uyğun olaraq, zəmanət haradadır ki RpcError investisiya edilə bilməz RpcResult, lakin birbaşa və ya başqa bir növdə iç içə? çatışmır req_msg_id ? ..

Ancaq xidmət mesajlarına davam edək. Müştəri hesab edə bilər ki, server uzun müddətdir düşünür və belə bir gözəl sorğu verə bilər:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Ona üç mümkün cavab var, yenidən təsdiqləmə mexanizmi ilə kəsişir, onların nə olması lazım olduğunu anlamağa çalışmaq üçün (və ümumiyyətlə təsdiq tələb etməyən növlərin siyahısı nədir), oxucuya ev tapşırığı verilir (qeyd: Telegram Desktop mənbələrindəki məlumatlar tam deyil).

Asılılıq: Mesaj Post Statusları

Ümumiyyətlə, TL, MTProto və Telegram-da bir çox yer inadkarlıq hissi buraxır, lakin nəzakət, nəzakət və s. yumşaq bacarıqları bu haqda nəzakətlə susduq, dialoqlardakı nalayiq sözlər senzuraya məruz qaldı. Halbuki bu yerОhaqqında səhifənin çox hissəsi mesajlar haqqında mesajlar hətta uzun müddət şəbəkə protokolları ilə işləyən və müxtəlif əyrilik dərəcələrində velosipedlər görən mənim üçün şoka səbəb olur.

Təsdiqlərlə zərərsiz başlayır. Sonrakı, bizə danışılır

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;

Yaxşı, MTProto ilə işləməyə başlayan hər kəs onlarla qarşılaşmalı olacaq, "düzəldilmiş - yenidən tərtib edilmiş - işə salınmış" dövrədə, redaktə zamanı çürümüş nömrə səhvləri və ya duz almaq adi bir şeydir. Ancaq burada iki məqam var:

  1. Bundan belə nəticə çıxır ki, orijinal mesaj itir. Bəzi növbələri hasarlamalıyıq, bunu daha sonra nəzərdən keçirəcəyik.
  2. Bu qəribə səhv nömrələri nədir? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... qalan nömrələr haradadır, Tommi?

Sənədlərdə deyilir:

Məqsəd odur ki, xəta_kodu dəyərləri qruplaşdırılır (xəta_kodu >> 4): məsələn, 0x40 - 0x4f kodları konteynerin parçalanmasındakı səhvlərə uyğundur.

amma, birincisi, digər istiqamətə keçid, ikincisi, qalan kodların harada olmasının əhəmiyyəti yoxdur? Müəllifin başında?.. Halbuki bunlar xırda şeylərdir.

Asılılıq post status mesajlarında və poçt nüsxələrində başlayır:

  • Mesaj Status Məlumatı üçün Sorğu
    Tərəflərdən hər hansı biri bir müddət ərzində göndərilən mesajlarının statusu haqqında məlumat almamışdırsa, o, digər tərəfdən bunu açıq şəkildə tələb edə bilər:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Mesajların statusu ilə bağlı məlumat mesajı
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Burada info daxil olan msg_ids siyahısından hər mesaj üçün tam olaraq bir bayt mesaj statusu ehtiva edən sətirdir:

    • 1 = mesaj haqqında heç nə məlum deyil (msg_id çox aşağıdır, qarşı tərəf onu unutmuş ola bilər)
    • 2 = mesaj alınmadı (msg_id saxlanılan identifikatorların diapazonuna düşür; lakin qarşı tərəf, şübhəsiz ki, belə bir mesaj almayıb)
    • 3 = mesaj alınmadı (msg_id çox yüksək; lakin qarşı tərəf onu hələ almayıb)
    • 4 = mesaj alındı ​​(qeyd edək ki, bu cavab eyni zamanda qəbz təsdiqidir)
    • +8 = mesaj artıq qəbul edilib
    • +16 = təsdiq tələb etməyən mesaj
    • +32 = Mesajda olan RPC sorğusu emal olunur və ya artıq tamamlanır
    • +64 = artıq yaradılan mesaja məzmunla bağlı cavab
    • +128 = digər tərəf mesajın artıq qəbul edildiyini bilir
      Bu cavab təsdiq tələb etmir. Bu, özlüyündə müvafiq msgs_state_req-in təsdiqidir.
      Qeyd edək ki, birdən qarşı tərəfin ona göndərilmiş kimi görünən mesajı olmadığı üzə çıxarsa, mesaj sadəcə olaraq yenidən göndərilə bilər. Qarşı tərəf eyni anda mesajın iki nüsxəsini alsa belə, dublikat nəzərə alınmayacaq. (Əgər çox vaxt keçibsə və orijinal msg_id artıq etibarlı deyilsə, mesaj msg_copy-ə sarılmalıdır).
  • Mesajların statusunun könüllü şəkildə bildirilməsi
    Tərəflərdən hər biri digər tərəfin ötürdüyü mesajların statusu barədə könüllü olaraq digər tərəfə məlumat verə bilər.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Bir Mesajın Vəziyyətinin Genişləndirilmiş Könüllü Rabitəsi
    ...
    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;
  • Mesajları Yenidən Göndərmək üçün Açıq Tələb
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    Uzaq tərəf dərhal tələb olunan mesajları yenidən göndərməklə cavab verir [...]
  • Cavabları Yenidən Göndərmək üçün Açıq Sorğu
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    Uzaq tərəf dərhal yenidən göndərməklə cavab verir cavab tələb olunan mesajlara […]
  • Mesaj Kopiyaları
    Bəzi hallarda, artıq etibarlı olmayan msg_id ilə köhnə mesaj yenidən göndərilməlidir. Sonra surət qabına bükülür:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Qəbul edildikdən sonra mesaj sanki sarğı orada deyilmiş kimi işlənir. Bununla belə, orig_message.msg_id mesajının alındığı dəqiq bilinirsə, o zaman yeni mesaj emal edilmir (eyni zamanda o və orig_message.msg_id təsdiq edilir). orig_message.msg_id dəyəri konteynerin msg_id-dən aşağı olmalıdır.

İçəridə olan faktlara belə susaq msgs_state_info yenə də yarımçıq TL-nin qulaqları çıxır (bizə bayt vektoru, aşağı iki enum bitində və köhnə bitlərdə bayraq lazım idi). Məsələ başqa şeydir. Bütün bunların niyə praktikada olduğunu başa düşən varmı real müştəridə lazımdır?.. Çətinliklə, ancaq bir şəxs ayıklama ilə məşğul olarsa və interaktiv rejimdə olarsa, hansısa faydanı təsəvvür edə bilərsiniz - serverdən nə və necə soruşun. Ancaq istəklər burada təsvir edilmişdir gediş-gəliş.

Buradan belə çıxır ki, hər bir tərəf yalnız mesajları şifrələməli və göndərməməli, həm də onlar haqqında, onlara verilən cavablar haqqında məlumatları və naməlum müddət ərzində saxlamalıdır. Sənədlər bu xüsusiyyətlərin vaxtlarını və ya praktiki tətbiqini təsvir etmir. heç bir. Ən təəccüblüsü odur ki, onlar əslində rəsmi müştərilərin kodunda istifadə olunur! Görünür, onlara açıq sənədləşmədə olmayan bir şey deyilib. Koddan anlayın niyə?, artıq TL vəziyyətində olduğu kimi sadə deyil - bu (müqayisəli) məntiqi cəhətdən təcrid olunmuş hissə deyil, proqram arxitekturasına bağlı bir parçadır, yəni. proqram kodunu başa düşmək üçün daha çox vaxt tələb olunacaq.

Pinqlər və vaxtlar. Növbələr.

Hər şeydən, server arxitekturası ilə bağlı təxminləri xatırlayırsınızsa (sorğuların arxa tərəflər arasında paylanması), TCP-də çatdırılmanın bütün zəmanətlərinə baxmayaraq (ya məlumat çatdırılıb, ya da sizə məlumat veriləcək) olduqca darıxdırıcı bir şey var. fasilə, lakin məlumat problem anına qədər çatdırılacaq), MTProto-nun özündə təsdiqləmələr - zəmanət yoxdur. Server asanlıqla mesajınızı itirə və ya atıb ata bilər və bununla bağlı heç nə edilə bilməz, sadəcə olaraq müxtəlif növ qoltuqağaqları hasara almaq üçün.

Və ilk növbədə - mesaj növbələri. Yaxşı, bir şey üçün, hər şey əvvəldən aydın idi - təsdiqlənməmiş bir mesaj saxlanılmalı və yenidən göndərilməlidir. Və nə vaxtdan sonra? Zarafatcı da onu tanıyır. Ola bilsin ki, bu narkomaniya xidmət mesajları qoltuqaltılarla bu problemi birtəhər həll edir, deyək ki, Telegram Desktop-da onlara uyğun gələn təxminən 4 növbə var (bəlkə də daha çox, artıq qeyd edildiyi kimi, bunun üçün onun kodunu və arxitekturasını daha ciddi araşdırmaq lazımdır; eyni zamanda zaman, biz bilirik ki, nümunə kimi götürülə bilməz, MTProto sxemindən müəyyən sayda növlər istifadə edilmir).

Bu niyə baş verir? Yəqin ki, server proqramçıları klaster daxilində etibarlılığı təmin edə bilməyiblər və ya heç olmasa ön balanslaşdırıcıda buferləşə bilməyiblər və bu problemi müştərinin üzərinə keçiriblər. Çarəsizlikdən Vasili TCP alqoritmlərindən istifadə edərək, yalnız iki növbə ilə alternativ bir seçim tətbiq etməyə çalışdı - RTT-ni serverə ölçmək və qəbul edilməmiş sorğuların sayından asılı olaraq "pəncərə" ölçüsünü (mesajlarda) tənzimləmək. Yəni, server yükünü təxmin etmək üçün belə bir kobud heuristik - eyni anda neçə sorğumuzu çeynəyə bilər və itirməyəcək.

Yaxşı, başa düşürsən, elə deyilmi? TCP üzərində işləyən protokolun üzərinə yenidən TCP-ni tətbiq etməlisinizsə, bu, çox zəif dizayn edilmiş protokolu göstərir.

Bəli, niyə birdən çox növbə lazımdır və ümumiyyətlə, yüksək səviyyəli API ilə işləyən bir insan üçün bu nə deməkdir? Baxın, sorğu edirsiniz, seriallaşdırırsınız, amma onu dərhal göndərmək çox vaxt mümkün olmur. Niyə? Çünki cavab olacaq msg_id, bu müvəqqətidirаMən bir etiketəm, onun təyin edilməsini mümkün qədər gec təxirə salmaq daha yaxşıdır - birdən server bizimlə aramızda olan vaxt uyğunsuzluğuna görə onu rədd edəcək (əlbəttə ki, vaxtımızı indiki vaxtdan dəyişdirən bir qoltuqağacı edə bilərik. server cavablarından hesablanmış delta əlavə edərək server vaxtına - rəsmi müştərilər bunu edir, lakin buferləmə səbəbindən bu üsul kobud və qeyri-dəqiqdir). Beləliklə, kitabxanadan yerli funksiya çağırışı ilə sorğu göndərdiyiniz zaman mesaj aşağıdakı mərhələlərdən keçir:

  1. Eyni növbədədir və şifrələməni gözləyir.
  2. Təyin edildi msg_id və mesaj başqa növbəyə keçdi - mümkün yönləndirmə; rozetkaya göndərin.
  3. a) Server MsgsAck cavabını verdi - mesaj çatdırıldı, biz onu "digər növbə"dən silirik.
    b) Və ya əksinə, nəyisə bəyənmədi, badmsg cavab verdi - "digər növbədən" yenidən göndəririk
    c) Heç nə məlum deyil, mesajı başqa növbədən yenidən göndərmək lazımdır - amma dəqiq nə vaxt məlum deyil.
  4. Server nəhayət cavab verdi RpcResult - faktiki cavab (və ya xəta) - yalnız çatdırılmır, həm də işlənir.

Bəlkə, qabların istifadəsi problemi qismən həll edə bilər. Bu, bir qrup mesajın birinə yığıldığı zamandır və server bir anda hamısına bir etirafla cavab verir. msg_id. Ancaq bir şey səhv getsə, bu paketi də rədd edəcək.

Və bu nöqtədə qeyri-texniki mülahizələr meydana çıxır. Təcrübədən biz çoxlu qoltuqağacı gördük və bundan əlavə, indi daha çox pis məsləhət və memarlıq nümunələrini görəcəyik - belə şəraitdə etibar etməyə və belə qərarlar qəbul etməyə dəyərmi? Sual ritorikdir (əlbəttə ki, yox).

Biz nədən danışırıq? Əgər “mesajlar haqqında aludəçilik mesajları” mövzusunda hələ də “sən axmaqsan, bizim parlaq fikrimizi başa düşmədin!” kimi etirazlarla fərziyyə edə bilirsən. (buna görə də ilk öncə sənədləri normal insanların lazım olduğu kimi, məntiq və paket mübadiləsi nümunələri ilə yazın, sonra danışarıq), onda vaxtlar/taym-autlar sırf praktiki və konkret məsələdir, burada hər şey çoxdan məlumdur. Bəs sənədlər bizə fasilələr haqqında nə deyir?

Bir server adətən RPC cavabından istifadə edərək müştəridən (adətən, RPC sorğusu) mesajın alınmasını qəbul edir. Əgər cavab uzun müddətdirsə, server əvvəlcə qəbz təsdiqini, bir qədər sonra isə RPC cavabının özü göndərə bilər.

Müştəri adətən serverdən mesajın alınmasını (ümumiyyətlə, RPC cavabı) növbəti RPC sorğusuna bildiriş əlavə etməklə təsdiq edir, əgər o, çox gec ötürülməyibsə (məsələn, alındıqdan sonra 60-120 saniyə ərzində yaradılıbsa). serverdən gələn mesaj). Bununla belə, uzun müddət ərzində serverə mesaj göndərmək üçün heç bir səbəb yoxdursa və ya serverdən çoxlu sayda qəbul edilməmiş mesajlar varsa (məsələn, 16-dan çox), müştəri təkbaşına təsdiqi ötürür.

...Tərcümə edirəm: bunun nə qədər və necə lazım olduğunu özümüz də bilmirik, yaxşı, təxmin edək ki, belə olsun.

Və pinglər haqqında:

Ping Mesajları (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Cavab adətən eyni əlaqəyə qaytarılır:

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

Bu mesajlar təsdiq tələb etmir. Tennis yalnız pingə cavab olaraq ötürülür, ping isə hər iki tərəfdən başlaya bilər.

Təxirə salınmış Bağlantının Bağlanması + PING

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

Ping kimi işləyir. Bundan əlavə, bu qəbul edildikdən sonra server bütün əvvəlki taymerləri avtomatik olaraq sıfırlayan eyni tipli yeni mesaj almadığı halda, cari əlaqəni disconnect_delay saniyə sonra bağlayacaq taymer işə salır. Əgər müştəri bu pingləri hər 60 saniyədə bir dəfə göndərirsə, məsələn, disconnect_delay 75 saniyəyə bərabər ola bilər.

Ağlını itirmisən?! 60 saniyədən sonra qatar stansiyaya girəcək, sərnişinləri düşürüb götürəcək və yenidən tuneldə əlaqəni itirəcək. 120 saniyədən sonra siz ətrafda gəzdiyiniz zaman o, başqasına çatacaq və çox güman ki, əlaqə kəsiləcək. Bəli, ayaqların haradan böyüdüyü aydındır - "Zəng eşitdim, amma harada olduğunu bilmirəm", Nagle alqoritmi və interaktiv iş üçün nəzərdə tutulmuş TCP_NODELAY seçimi var. Lakin, bağışlayın, onun standart dəyərini gecikdirin - 200 Millisaniyə. Əgər həqiqətən oxşar bir şeyi təsvir etmək və mümkün bir cüt paketə qənaət etmək istəyirsinizsə - yaxşı, ən azı 5 saniyə təxirə salın və ya "İstifadəçi yazır ..." mesajının vaxtı nə olursa olsun indi bərabərdir. Amma daha yox.

Və nəhayət, pinglər. Yəni, TCP bağlantısının canlılığını yoxlamaq. Gülməli, amma təxminən 10 il əvvəl fakültəmizin yataqxanasının messenceri haqqında tənqidi mətn yazmışdım - orada müəlliflər serverə müştəridən də ping atmışdılar, əksinə yox. Ancaq üçüncü kurs tələbələri bir şeydir, beynəlxalq ofis başqa şeydir, elə deyilmi? ..

Birincisi, kiçik bir təhsil proqramı. TCP bağlantısı, paket mübadiləsi olmadıqda, həftələrlə yaşaya bilər. Bu məqsəddən asılı olaraq həm yaxşı, həm də pisdir. Yaxşı, serverə SSH bağlantınız varsa, kompüterinizdən qalxdınız, güc yönləndiricisini yenidən başladın, yerinizə qayıtdınız - bu server vasitəsilə sessiya pozulmadı (heç nə yazmadı, paketlər yox idi), rahat. Serverdə minlərlə müştəri varsa, pisdir, hər biri resurs götürür (salam Postgres!) və müştəri hostu çoxdan yenidən işə düşmüş ola bilər - lakin biz bu barədə bilməyəcəyik.

Çat/IM sistemləri başqa, əlavə səbəbə görə ikinci işə aiddir - onlayn statuslar. İstifadəçi “yıxılıbsa” bu barədə həmsöhbətlərinə məlumat vermək lazımdır. Əks təqdirdə, Jabber yaradıcılarının etdiyi səhv (və 20 il ərzində düzəldilmiş) olacaq - istifadəçi əlaqəni kəsdi, lakin onun onlayn olduğuna inanaraq ona mesajlar yazmağa davam etdilər (bu da bir neçə dəqiqə əvvəl tamamilə itirildi). fasilə aşkar edilmişdir). Xeyr, TCP taymerlərinin necə işlədiyini başa düşməyən bir çox insanın hər yerdə göründüyü TCP_KEEPALIVE seçimi (onlarla saniyə kimi vəhşi dəyərlər təyin etməklə) burada kömək etməyəcək - əmin olmalısınız ki, yalnız OS nüvəsi deyil. istifadəçinin maşını canlıdır, həm də normal işləyir, cavab verə bilir və tətbiqin özü (sizcə o, dondura bilməz? Ubuntu 18.04-də Telegram Desktop mənim üçün dəfələrlə qəzaya uğradı).

Buna görə də ping etməlisiniz server müştəri və əksinə deyil - əgər müştəri bunu edərsə, əlaqə pozulduqda, ping çatdırılmayacaq, məqsədə nail olunmur.

Bəs biz Telegramda nə görürük? Hər şey tam əksinədir! Yaxşı, yəni. formal olaraq, təbii ki, hər iki tərəf bir-birinə ping ata bilər. Praktikada müştərilər qoltuqdan istifadə edirlər ping_delay_disconnect, serverdə taymeri işə salır. Bağışlayın, ping olmadan orada nə qədər yaşamaq istədiyinə qərar vermək müştərinin işi deyil. Server, yükünə əsaslanaraq, daha yaxşı bilir. Ancaq təbii ki, resurslara təəssüflənmirsinizsə, o zaman pis Pinocchio özləridir və qoltuq aşağı düşəcək ...

Necə dizayn edilməli idi?

İnanıram ki, yuxarıda göstərilən faktlar Telegram / VKontakte komandasının kompüter şəbəkələrinin nəqliyyat (və daha aşağı) səviyyəsi sahəsində o qədər də yüksək olmayan səriştəsini və müvafiq məsələlərdə aşağı keyfiyyətini açıq şəkildə göstərir.

Niyə bu qədər mürəkkəb oldu və Telegram memarları buna necə etiraz edə bilər? Onların TCP bağlantısı kəsilməsindən xilas olan bir seans etməyə çalışdıqları, yəni indi çatdırmadıqlarımızı daha sonra çatdıracağıq. Çox güman ki, UDP nəqliyyatını da etməyə çalışdılar, baxmayaraq ki, çətinliklə üzləşdilər və onu tərk etdilər (buna görə də sənədlər boşdur - öyünməyə heç nə yox idi). Ancaq ümumiyyətlə şəbəkələrin və xüsusən də TCP-nin necə işlədiyini, hara etibar edə biləcəyinizi və bunu özünüz harada (və necə) etməli olduğunuzu başa düşməməniz və bunu kriptoqrafiya ilə birləşdirməyə cəhdlər səbəbindən “iki atışdan bir bir daşla quşlar” - belə bir cəsəd çıxdı.

Necə olmalı idi? Buna əsaslanaraq msg_id təkrar hücumların qarşısını almaq üçün kriptoqrafik olaraq zəruri olan vaxt damğasıdır, ona unikal identifikator funksiyasının əlavə edilməsi xətasıdır. Buna görə də, cari arxitekturanı kəskin şəkildə dəyişmədən (Yeniləmələr mövzusu yarandıqda, bu, bu yazılar seriyasının başqa bir hissəsi üçün yüksək səviyyəli API mövzusudur), aşağıdakıları etmək lazımdır:

  1. Müştəri ilə TCP bağlantısını saxlayan server məsuliyyət daşıyır - əgər siz rozetkadan çıxmısınızsa, zəhmət olmasa, xətanı təsdiqləyin, emal edin və ya qaytarın, itki yoxdur. Sonra təsdiq id-nin vektoru deyil, sadəcə olaraq "son qəbul edilən seq_no" - TCP-də olduğu kimi sadəcə bir rəqəmdir (iki rəqəm - öz ardıcıllığınız və təsdiqlənmiş). Biz həmişə sessiyadayıq, elə deyilmi?
  2. Təkrar hücumların qarşısını almaq üçün vaxt damğası ayrı bir sahəyə çevrilir. Yoxlanılıb, amma başqa heç nə təsirlənmir. Kifayət qədər və uint32 - duzumuz heç olmasa yarım gündə bir dəyişirsə, 16 biti cari vaxtın tam hissəsinin aşağı bitlərinə, qalanını isə saniyənin kəsr hissəsinə (indiki kimi) ayıra bilərik.
  3. Silinir msg_id ümumiyyətlə - backendlərdə sorğuların fərqləndirilməsi nöqteyi-nəzərindən, birincisi, müştəri id-si, ikincisi, sessiya id-si var və onları birləşdirin. Müvafiq olaraq, sorğu identifikatoru olaraq yalnız biri kifayətdir seq_no.

Həm də ən yaxşı seçim deyil, tam təsadüfi bir identifikator kimi xidmət edə bilər - bu, yeri gəlmişkən, mesaj göndərərkən yüksək səviyyəli API-də artıq edilir. Arxitekturanı nisbidən mütləqə dəyişdirmək daha yaxşı olardı, lakin bu, bu yazının deyil, başqa hissənin mövzusudur.

API?

Ta-daam! Beləliklə, ağrı və balta ilə dolu bir yoldan keçərək nəhayət serverə istənilən sorğu göndərə və onlara hər hansı cavab ala bildik, həmçinin serverdən yeniləmələri ala bildik (sorğuya cavab olaraq deyil, lakin bizə özü göndərir, məsələn, PUSH kimi, əgər kimsə bu qədər aydındırsa).

Diqqət, indi məqalədə yeganə Perl nümunəsi olacaq! (sintaksislə tanış olmayanlar üçün xeyir-dua vermək üçün ilk arqument obyektin məlumat strukturudur, ikincisi isə onun 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' )
};

Bəli, xüsusi olaraq spoylerin altında deyil - oxumamısınızsa, gedin və edin!

Oh, wai~~… bu nə kimi görünür? Çox tanış bir şey... bəlkə bu, JSON-da tipik Veb API-nin məlumat strukturudur, ola bilsin ki, obyektlərə siniflər əlavə olunub?..

Beləliklə, belə çıxır ... Bu nədir, yoldaşlar? .. Bu qədər səy - və veb proqramçıların olduğu yerdə dincəlmək üçün dayandıq. yeni başlayır?.. Yalnız HTTPS üzərindən JSON asan olmazmı?! Və əvəzində nə əldə etdik? Bu səylərə dəyərmi?

Gəlin TL+MTProto-nun bizə nə verdiyini və hansı alternativlərin mümkün olduğunu qiymətləndirək. Yaxşı, HTTP sorğu-cavabı pis uyğun gəlir, lakin heç olmasa TLS-in üstündə bir şey varmı?

kompakt serializasiya. JSON-a bənzər bu məlumat strukturunu görəndə onun ikili variantlarının olduğu xatırlanır. MsgPack-i kifayət qədər genişlənən kimi qeyd edək, lakin, məsələn, CBOR var - yeri gəlmişkən, burada təsvir edilən standart RFC 7049. müəyyən etməsi ilə diqqət çəkir etiketlər, uzatma mexanizmi kimi və arasında artıq standartlaşdırılıb var:

  • 25 + 256 - dublikat xətləri sətir nömrəsi istinadı ilə əvəz etmək, belə ucuz sıxılma üsulu
  • 26 - sinif adı və konstruktor arqumentləri ilə seriallaşdırılmış Perl obyekti
  • 27 - tip adı və konstruktor arqumentləri ilə seriallaşdırılmış dildən asılı olmayan obyekt

Yaxşı, mən sətirlərin və obyektlərin qablaşdırılması ilə eyni məlumatları TL və CBOR-da seriallaşdırmağa çalışdım. Nəticə bir meqabaytdan CBOR-un xeyrinə fərqlənməyə başladı:

cborlen=1039673 tl_len=1095092

Belə ki, nəticə: Sinxronizasiya nasazlığına və ya naməlum identifikator probleminə məruz qalmayan, müqayisə edilə bilən səmərəliliyi ilə əhəmiyyətli dərəcədə sadə formatlar var.

Sürətli əlaqənin qurulması. Bu, yenidən qoşulduqdan sonra sıfır RTT deməkdir (açar artıq bir dəfə yaradıldıqda) - ilk MTProto mesajından tətbiq olunur, lakin bəzi qeyd-şərtlərlə - onlar eyni duza girdilər, sessiya çürümədi və s. TLS əvəzində bizə nə təklif edir? Əlaqədar sitat:

TLS-də PFS istifadə edərkən TLS sessiya biletləri (RFC 5077) açarları yenidən müzakirə etmədən və əsas məlumatı serverdə saxlamadan şifrələnmiş sessiyanı davam etdirmək. Birinci əlaqəni açarkən və açarlar yaradanda server əlaqənin vəziyyətini şifrələyir və onu müştəriyə göndərir (sessiya bileti şəklində). Müvafiq olaraq, əlaqə bərpa edildikdə, müştəri serverə başqa şeylər arasında, sessiya açarını ehtiva edən sessiya biletini göndərir. Biletin özü müvəqqəti açar (sessiya bileti açarı) ilə şifrələnmişdir, bu açar serverdə saxlanılır və klaster həllərində SSL-i idarə edən bütün frontend serverlərə paylanmalıdır.[10]. Beləliklə, müvəqqəti server açarları, məsələn, uzun müddət saxlanıldıqda, sessiya biletinin tətbiqi PFS-ni poza bilər (OpenSSL, nginx, Apache onları proqramın işlədiyi bütün müddət ərzində standart olaraq saxlayır; populyar saytlar açarı bir neçə saat, günlərə qədər istifadə edin).

Burada RTT sıfır deyil, ən azı ClientHello və ServerHello mübadiləsi etməlisiniz, bundan sonra Finished ilə birlikdə müştəri artıq məlumat göndərə bilər. Ancaq burada xatırlamaq lazımdır ki, bizdə yeni açılmış əlaqələr dəstəsi olan İnternet yoxdur, lakin əlaqəsi tez-tez bir və ya daha çox və ya daha az uzunömürlü, Web səhifələri üçün nisbətən qısa sorğular olan bir messencerimiz var - hər şey var. içərisində multipleksləşmişdir. Yəni, metronun çox pis hissəsinə rast gəlməmişiksə, bu, kifayət qədər məqbuldur.

Başqa bir şey unutmusunuz? Şərhlərdə yazın.

Ardı var!

Bu yazılar silsiləsinin ikinci hissəsində texniki deyil, təşkilati məsələlərə - yanaşmalar, ideologiya, interfeys, istifadəçilərə münasibət və s. Bununla belə, burada təqdim olunan texniki məlumatlara əsaslanır.

Üçüncü hissə texniki komponentin / inkişaf təcrübəsinin təhlilini davam etdirəcəkdir. Xüsusilə öyrənəcəksiniz:

  • TL-növlərinin müxtəlifliyi ilə pandemonun davamı
  • kanallar və super qruplar haqqında bilinməyən şeylər
  • dialoqlardan daha siyahıdan daha pisdir
  • mütləq və nisbi mesaj ünvanlanması haqqında
  • şəkil və şəkil arasındakı fərq nədir
  • emoji kursivlə yazılmış mətnə ​​necə müdaxilə edir

və digər qoltuqaltılar! Bizimlə qalın!

Mənbə: www.habr.com

Добавить комментарий