Pagpuna sa protocol at mga diskarte sa organisasyon ng Telegram. Bahagi 1, teknikal: karanasan sa pagsulat ng isang kliyente mula sa simula - TL, MT

Kamakailan, nagsimulang lumabas nang mas madalas sa HabrΓ© ang mga post tungkol sa kung gaano kahusay ang Telegram, kung gaano katalino at karanasan ang magkapatid na Durov sa pagbuo ng mga network system, atbp. Kasabay nito, napakakaunting mga tao ang talagang nahuhulog ang kanilang sarili sa teknikal na aparato - sa karamihan, gumagamit sila ng medyo simple (at medyo naiiba sa MTProto) na nakabase sa JSON na Bot API, at kadalasan ay tinatanggap lamang sa pananampalataya lahat ng papuri at PR na umiikot sa messenger. Halos isang taon at kalahati na ang nakalilipas, ang aking kasamahan sa Eshelon NGO na si Vasily (sa kasamaang palad, ang kanyang account sa HabrΓ© ay nabura kasama ang draft) ay nagsimulang magsulat ng kanyang sariling Telegram client mula sa simula sa Perl, at kalaunan ay sumali ang may-akda ng mga linyang ito. Bakit Perl, may magtatanong agad? Dahil umiiral na ang mga ganitong proyekto sa ibang mga wika. Sa katunayan, hindi ito ang punto, maaaring mayroong iba pang wika kung saan walang nakahandang aklatan, at naaayon ang may-akda ay dapat pumunta sa lahat ng paraan mula sa simula. Bukod dito, ang cryptography ay isang bagay ng tiwala, ngunit i-verify. Sa isang produkto na naglalayong seguridad, hindi ka maaaring umasa sa isang handa na aklatan mula sa tagagawa at walang taros na pinagkakatiwalaan ito (gayunpaman, ito ay isang paksa para sa ikalawang bahagi). Sa ngayon, gumagana nang maayos ang library sa "average" na antas (nagbibigay-daan sa iyong gumawa ng anumang mga kahilingan sa API).

Gayunpaman, hindi magkakaroon ng maraming cryptography o matematika sa seryeng ito ng mga post. Ngunit magkakaroon ng maraming iba pang mga teknikal na detalye at mga saklay ng arkitektura (kapaki-pakinabang din para sa mga hindi magsusulat mula sa simula, ngunit gagamit ng aklatan sa anumang wika). Kaya, ang pangunahing layunin ay subukang ipatupad ang kliyente mula sa simula ayon sa opisyal na dokumentasyon. Iyon ay, ipagpalagay natin na ang source code ng mga opisyal na kliyente ay sarado (muli, sa ikalawang bahagi ay tatalakayin natin nang mas detalyado ang paksa ng katotohanan na ito ay totoo ito ang mangyayari kaya), ngunit, tulad ng noong unang panahon, halimbawa, mayroong isang pamantayan tulad ng RFC - posible bang magsulat ng isang kliyente ayon sa pagtutukoy lamang, "nang hindi tumitingin" sa source code, maging opisyal (Telegram Desktop, mobile), o hindi opisyal na Telethon?

Talaan ng nilalaman:

Documentation... meron naman diba? Totoo ba?..

Ang mga fragment ng mga tala para sa artikulong ito ay nagsimulang kolektahin noong nakaraang tag-init. Sa lahat ng oras na ito sa opisyal na website https://core.telegram.org Ang dokumentasyon ay noong Layer 23, i.e. natigil sa isang lugar noong 2014 (tandaan, wala pang mga channel noon?). Siyempre, sa teorya, ito ay dapat na nagpapahintulot sa amin na ipatupad ang isang kliyente na may functionality sa oras na iyon sa 2014. Ngunit kahit na sa ganitong estado, ang dokumentasyon ay, una, hindi kumpleto, at pangalawa, sa mga lugar na ito ay sumasalungat sa sarili nito. Mahigit isang buwan lang ang nakalipas, noong Setyembre 2019, ito ay nagkataon Natuklasan na mayroong malaking pag-update ng dokumentasyon sa site, para sa ganap na kamakailang Layer 105, na may tala na ngayon ay kailangang basahin muli ang lahat. Sa katunayan, maraming artikulo ang binago, ngunit marami ang nanatiling hindi nagbabago. Samakatuwid, kapag binabasa ang pagpuna sa ibaba tungkol sa dokumentasyon, dapat mong tandaan na ang ilan sa mga bagay na ito ay hindi na nauugnay, ngunit ang ilan ay medyo. Pagkatapos ng lahat, ang 5 taon sa modernong mundo ay hindi lamang isang mahabang panahon, ngunit napaka marami ng. Mula noong mga panahong iyon (lalo na kung hindi mo isinasaalang-alang ang mga itinapon at muling binuhay na mga geochat site mula noon), ang bilang ng mga pamamaraan ng API sa scheme ay lumago mula sa isang daan hanggang sa higit sa dalawang daan at limampu!

Saan magsisimula bilang isang batang may-akda?

Hindi mahalaga kung sumulat ka mula sa simula o gumamit, halimbawa, mga handa na aklatan tulad ng Telethon para sa Python o Madeline para sa PHP, sa anumang kaso, kakailanganin mo muna irehistro ang iyong aplikasyon - kumuha ng mga parameter api_id ΠΈ api_hash (kaagad na nauunawaan ng mga nagtrabaho sa VKontakte API) kung saan makikilala ng server ang application. Ito kailangang gawin ito para sa mga legal na dahilan, ngunit mas pag-uusapan natin kung bakit hindi ito mai-publish ng mga may-akda ng library sa ikalawang bahagi. Maaaring nasiyahan ka sa mga halaga ng pagsubok, kahit na ang mga ito ay napakalimitado - ang katotohanan ay maaari ka nang magparehistro isa lang app, kaya huwag magmadali dito.

Ngayon, mula sa isang teknikal na pananaw, dapat tayong maging interesado sa katotohanan na pagkatapos ng pagpaparehistro dapat tayong makatanggap ng mga abiso mula sa Telegram tungkol sa mga update sa dokumentasyon, protocol, atbp. Iyon ay, maaaring ipalagay ng isang tao na ang site na may mga pantalan ay inabandona lamang at nagpatuloy na partikular na gumana sa mga nagsimulang gumawa ng mga kliyente, dahil mas madali. Ngunit hindi, walang ganoong naobserbahan, walang impormasyon na dumating.

At kung sumulat ka mula sa simula, kung gayon ang paggamit ng nakuha na mga parameter ay talagang malayo pa rin. Bagaman https://core.telegram.org/ at pinag-uusapan ang mga ito sa Pagsisimula una sa lahat, sa katunayan, kailangan mo munang ipatupad Protocol ng MTProto - ngunit kung naniniwala ka layout ayon sa modelo ng OSI sa dulo ng pahina para sa isang pangkalahatang paglalarawan ng protocol, pagkatapos ito ay ganap na walang kabuluhan.

Sa katunayan, bago at pagkatapos ng MTProto, sa ilang mga antas nang sabay-sabay (tulad ng sinasabi ng mga dayuhang networker na nagtatrabaho sa OS kernel, paglabag sa layer), isang malaki, masakit at kakila-kilabot na paksa ang hahadlang...

Binary serialization: TL (Type Language) at ang scheme nito, at mga layer, at marami pang nakakatakot na salita

Ang paksang ito, sa katunayan, ang susi sa mga problema ng Telegram. At magkakaroon ng maraming kakila-kilabot na mga salita kung susubukan mong bungkalin ito.

Kaya, narito ang diagram. Kung ang salitang ito ang pumasok sa iyong isipan, sabihin mo, JSON Schema, Tama ang naisip mo. Ang layunin ay pareho: ilang wika upang ilarawan ang isang posibleng set ng ipinadalang data. Dito nagtatapos ang pagkakatulad. Kung mula sa pahina Protocol ng MTProto, o mula sa source tree ng opisyal na kliyente, susubukan naming buksan ang ilang schema, makakakita kami ng tulad ng:

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;

Ang isang taong nakakakita nito sa unang pagkakataon ay madaling makikilala ang bahagi lamang ng nakasulat - mabuti, ang mga ito ay tila mga istruktura (bagaman nasaan ang pangalan, sa kaliwa o sa kanan?), May mga patlang sa mga ito, pagkatapos na ang isang uri ay sumusunod pagkatapos ng isang tutuldok... malamang. Dito sa mga angle bracket ay malamang na may mga template tulad ng sa C++ (sa katunayan, hindi talaga). At ano ang ibig sabihin ng lahat ng iba pang mga simbolo, mga tandang pananong, mga tandang padamdam, mga porsyento, mga marka ng hash (at maliwanag na iba't ibang mga bagay ang ibig sabihin nito sa iba't ibang lugar), minsan ay naroroon at kung minsan ay hindi, mga numerong hexadecimal - at higit sa lahat, kung paano makukuha mula dito tama (na hindi tatanggihan ng server) byte stream? Kailangan mong basahin ang dokumentasyon (oo, may mga link sa schema sa bersyon ng JSON sa malapit - ngunit hindi nito ginagawang mas malinaw).

Buksan ang pahina Binary Data Serialization at sumisid sa mahiwagang mundo ng mga mushroom at discrete mathematics, isang bagay na katulad ng matan sa ika-4 na taon. Alpabeto, uri, halaga, combinator, functional combinator, normal na anyo, composite type, polymorphic type... at iyon lang ang unang pahina! Susunod na naghihintay sa iyo Wika ng TL, na, bagama't naglalaman na ito ng isang halimbawa ng isang maliit na kahilingan at tugon, ay hindi nagbibigay ng anumang sagot sa mas karaniwang mga kaso, na nangangahulugang kailangan mong dumaan sa muling pagsasalaysay ng matematika na isinalin mula sa Russian patungo sa Ingles sa isa pang walong naka-embed mga pahina!

Ang mga mambabasa na pamilyar sa mga functional na wika at awtomatikong uri ng inference ay, siyempre, makikita ang paglalarawan ng wika sa wikang ito, kahit na mula sa halimbawa, bilang mas pamilyar, at maaaring sabihin na ito ay talagang hindi masama sa prinsipyo. Ang mga pagtutol dito ay:

  • oo, layunin pakinggan, ngunit sayang, siya hindi nakamit
  • Ang edukasyon sa mga unibersidad ng Russia ay nag-iiba kahit na sa mga IT specialty - hindi lahat ay kumuha ng kaukulang kurso
  • Sa wakas, tulad ng makikita natin, sa pagsasanay ito ay hindi kinakailangan, dahil limitado lang ang subset ng kahit na ang TL na inilarawan ang ginagamit

Gaya ng sinabi LeoNerd sa channel #perl sa FreeNode IRC network, na sinubukang ipatupad ang isang gate mula sa Telegram hanggang Matrix (ang pagsasalin ng quote ay hindi tumpak mula sa memorya):

Pakiramdam nito ay may isang taong ipinakilala sa pag-type ng teorya sa unang pagkakataon, natuwa, at nagsimulang subukang paglaruan ito, hindi talaga nagmamalasakit kung kailangan ito sa pagsasanay.

Tingnan para sa iyong sarili, kung ang pangangailangan para sa mga walang laman na uri (int, mahaba, atbp.) bilang isang bagay na elementarya ay hindi naglalabas ng mga katanungan - sa huli ay dapat itong maipatupad nang manu-mano - halimbawa, subukan nating magmula sa kanila vector. Iyon ay, sa katunayan, РјР Β° Π‘ΠƒΠ‘ΠƒΠ Ρ‘Π Π†, kung tatawagin mo ang mga resultang bagay sa pamamagitan ng kanilang mga wastong pangalan.

Pero kanina

Isang maikling paglalarawan ng isang subset ng TL syntax para sa mga hindi nagbabasa ng opisyal na dokumentasyon

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;

Laging nagsisimula ang kahulugan designer, pagkatapos kung saan opsyonal (sa pagsasanay - palagi) sa pamamagitan ng simbolo # dapat CRC32 mula sa normalized na string ng paglalarawan ng ganitong uri. Susunod ay isang paglalarawan ng mga patlang; kung mayroon sila, ang uri ay maaaring walang laman. Ang lahat ng ito ay nagtatapos sa isang pantay na tanda, ang pangalan ng uri kung saan ang tagabuo na ito - iyon ay, sa katunayan, ang subtype - nabibilang. Ang lalaki sa kanan ng equals sign ay polymorphic - iyon ay, ilang partikular na uri ang maaaring tumutugma dito.

Kung ang kahulugan ay nangyayari pagkatapos ng linya ---functions---, kung gayon ang syntax ay mananatiling pareho, ngunit ang kahulugan ay magkakaiba: ang tagabuo ay magiging pangalan ng pag-andar ng RPC, ang mga patlang ay magiging mga parameter (mabuti, iyon ay, ito ay mananatiling eksakto ang parehong ibinigay na istraktura, tulad ng inilarawan sa ibaba , ito lang ang itinalagang kahulugan), at ang β€œpolymorphic type " - ang uri ng ibinalik na resulta. Totoo, mananatili pa rin itong polymorphic - tinukoy lang sa seksyon ---types---, ngunit ang constructor na ito ay "hindi isasaalang-alang". Overloading ang mga uri ng tinatawag na function sa pamamagitan ng kanilang mga argumento, i.e. Para sa ilang kadahilanan, maraming mga function na may parehong pangalan ngunit magkaibang mga lagda, tulad ng sa C++, ay hindi ibinigay para sa TL.

Bakit "constructor" at "polymorphic" kung hindi ito OOP? Sa katunayan, magiging mas madali para sa isang tao na isipin ito sa mga tuntunin ng OOP - isang uri ng polymorphic bilang isang abstract na klase, at ang mga konstruktor ay ang mga direktang descendant na klase nito, at final sa terminolohiya ng ilang mga wika. Sa katunayan, siyempre, dito lamang pagkakatulad na may totoong overloaded na mga pamamaraan ng constructor sa mga wikang programming ng OO. Dahil narito lamang ang mga istruktura ng data, walang mga pamamaraan (bagaman ang paglalarawan ng mga pag-andar at pamamaraan ay lubos na may kakayahang lumikha ng pagkalito sa ulo na umiiral ang mga ito, ngunit iyon ay ibang bagay) - maaari mong isipin ang isang tagabuo bilang isang halaga mula sa alin ay itinatayo mag-type kapag nagbabasa ng isang byte stream.

Paano ito nangyayari? Ang deserializer, na palaging nagbabasa ng 4 byte, ay nakikita ang halaga 0xcrc32 - at naiintindihan kung ano ang susunod na mangyayari field1 may uri int, ibig sabihin. nagbabasa ng eksaktong 4 bytes, dito ang nakapatong na field na may uri PolymorType basahin. Nakita 0x2crc32 at nauunawaan na may dalawang patlang pa, una long, na nangangahulugang nagbabasa tayo ng 8 bytes. At pagkatapos ay muli ang isang kumplikadong uri, na kung saan ay deserialized sa parehong paraan. Halimbawa, Type3 ay maaaring ideklara sa circuit sa lalong madaling dalawang konstruktor, ayon sa pagkakabanggit, pagkatapos ay dapat silang magkakilala 0x12abcd34, pagkatapos nito kailangan mong magbasa ng 4 pang byte intO 0x6789cdef, pagkatapos nito ay wala na. Anumang bagay - kailangan mong magtapon ng isang pagbubukod. Anyway, pagkatapos nito bumalik tayo sa pagbabasa ng 4 bytes int mga patlang field_c Π² constructorTwo and with that we finish reading our PolymorType.

Sa wakas, kung mahuli ka 0xdeadcrc para sa constructorThree, kung gayon ang lahat ay nagiging mas kumplikado. Ang aming unang larangan ay bit_flags_of_what_really_present may uri # - sa katunayan, ito ay isang alyas lamang para sa uri nat, ibig sabihin ay "natural na numero". Iyon ay, sa katunayan, ang unsigned int ay, sa pamamagitan ng paraan, ang tanging kaso kapag ang mga unsigned na numero ay nangyari sa mga totoong circuit. Kaya, ang susunod ay isang konstruksiyon na may tandang pananong, ibig sabihin na ang field na ito - ito ay naroroon lamang sa wire kung ang kaukulang bit ay nakatakda sa field na tinutukoy (humigit-kumulang tulad ng isang ternary operator). Kaya, ipagpalagay natin na ang bit na ito ay itinakda, na nangangahulugang kailangan pa nating basahin ang isang field na tulad nito Type, na sa aming halimbawa ay may 2 constructor. Ang isa ay walang laman (binubuo lamang ng identifier), ang isa ay may field ids may uri ids:Vector<long>.

Maaari mong isipin na ang parehong mga template at generic ay nasa pro o Java. Pero hindi. halos. Ito solong kaso ng paggamit ng mga angle bracket sa mga totoong circuit, at ito ay ginagamit LAMANG para sa Vector. Sa isang byte stream, ang mga ito ay magiging 4 CRC32 byte para sa mismong uri ng Vector, palaging pareho, pagkatapos ay 4 na byte - ang bilang ng mga elemento ng array, at pagkatapos ay ang mga elementong ito mismo.

Idagdag dito ang katotohanan na ang serialization ay palaging nangyayari sa mga salita ng 4 na bait, lahat ng mga uri ay maramihang nito - ang mga built-in na uri ay inilarawan din bytes ΠΈ string na may manu-manong serialization ng haba at ang pagkakahanay na ito sa pamamagitan ng 4 - mabuti, ito ay tila normal at kahit medyo epektibo? Bagama't ang TL ay sinasabing isang epektibong binary serialization, sa impiyerno sa kanila, sa pagpapalawak ng halos kahit ano, kahit na ang mga Boolean na halaga at mga single-character na string hanggang 4 na byte, magiging mas makapal pa ba ang JSON? Tingnan mo, kahit na ang mga hindi kinakailangang field ay maaaring laktawan gamit ang mga bit flag, lahat ay maganda, at kahit na mapalawak para sa hinaharap, kaya bakit hindi magdagdag ng mga bagong opsyonal na field sa constructor sa ibang pagkakataon?..

Ngunit hindi, kung basahin mo hindi ang aking maikling paglalarawan, ngunit ang buong dokumentasyon, at isipin ang tungkol sa pagpapatupad. Una, ang CRC32 ng constructor ay kinakalkula ayon sa normalized na linya ng paglalarawan ng teksto ng scheme (alisin ang dagdag na whitespace, atbp.) - kaya kung may idinagdag na bagong field, magbabago ang uri ng linya ng paglalarawan, at dahil dito ang CRC32 at , dahil dito, serialization. At ano ang gagawin ng lumang kliyente kung nakatanggap siya ng field na may mga bagong flag na nakatakda, at hindi niya alam kung ano ang susunod na gagawin sa mga ito?..

Pangalawa, tandaan natin CRC32, na pangunahing ginagamit dito bilang pag-andar ng hash upang natatanging matukoy kung anong uri ang (de)serialized. Narito tayo ay nahaharap sa problema ng mga banggaan - at hindi, ang posibilidad ay hindi isa sa 232, ngunit mas malaki. Sino ang nakaalala na ang CRC32 ay idinisenyo upang makita (at itama) ang mga error sa channel ng komunikasyon, at naaayon ay pinapabuti ang mga katangiang ito sa kapinsalaan ng iba? Halimbawa, wala itong pakialam sa muling pagsasaayos ng mga byte: kung kinakalkula mo ang CRC32 mula sa dalawang linya, sa pangalawa ay ipagpalit mo ang unang 4 na byte sa susunod na 4 na byte - magiging pareho ito. Kapag ang aming input ay mga string ng teksto mula sa alpabetong Latin (at isang maliit na bantas), at ang mga pangalang ito ay hindi partikular na random, ang posibilidad ng gayong muling pagsasaayos ay tumataas nang malaki.

Siya nga pala, sino ang nag-check kung ano ang nandoon? talaga CRC32? Ang isa sa mga naunang source code (kahit bago ang Waltman) ay may hash function na pinarami ang bawat karakter sa bilang na 239, na minamahal ng mga taong ito, ha ha!

Sa wakas, okay, napagtanto namin na ang mga konstruktor na may uri ng field Vector<int> ΠΈ Vector<PolymorType> magkakaroon ng ibang CRC32. Paano ang tungkol sa online na pagganap? At mula sa teoretikal na pananaw, nagiging bahagi ba ito ng uri? Sabihin nating pumasa tayo sa isang hanay ng sampung libong numero, na rin sa Vector<int> lahat ay malinaw, ang haba at isa pang 40000 bytes. At kung ito Vector<Type2>, na binubuo lamang ng isang field int at ito ay nag-iisa sa uri - kailangan ba nating ulitin ang 10000xabcdef0 34 beses at pagkatapos ay 4 na bait int, o ang wika ay magagawang INDEPEND para sa amin mula sa constructor fixedVec at imbes na 80000 bytes, 40000 lang ang transfer ulit?

Ito ay hindi isang idle na teoretikal na tanong - isipin na nakatanggap ka ng isang listahan ng mga user ng grupo, bawat isa ay may id, pangalan, apelyido - ang pagkakaiba sa dami ng data na inilipat sa isang mobile na koneksyon ay maaaring maging makabuluhan. Ito ay tiyak na ang pagiging epektibo ng Telegram serialization na na-advertise sa amin.

Kaya…

Vector, na hindi kailanman inilabas

Kung susubukan mong lumakad sa mga pahina ng paglalarawan ng mga combinator at iba pa, makikita mo na ang isang vector (at kahit isang matrix) ay pormal na sinusubukang i-output sa pamamagitan ng mga tuple ng ilang mga sheet. Ngunit sa huli ay nakalimutan nila, ang huling hakbang ay nilaktawan, at ang isang kahulugan ng isang vector ay ibinigay lamang, na hindi pa nakatali sa isang uri. Anong problema? Sa mga wika programa, lalo na ang mga functional, medyo tipikal na ilarawan ang istraktura nang recursively - ang compiler na may tamad na pagsusuri ay mauunawaan at gagawin ang lahat mismo. Sa wika serialization ng data ang kailangan ay EFFICIENCY: sapat na ang simpleng paglalarawan listahan, ibig sabihin. istraktura ng dalawang elemento - ang una ay isang elemento ng data, ang pangalawa ay ang parehong istraktura mismo o isang walang laman na espasyo para sa buntot (pack (cons) sa Lisp). Ngunit ito ay malinaw na mangangailangan ng bawat isa Ang elemento ay gumugugol ng karagdagang 4 na byte (CRC32 sa kaso sa TL) upang ilarawan ang uri nito. Ang isang array ay maaari ding madaling ilarawan takdang sukat, ngunit sa kaso ng isang hanay ng hindi kilalang haba nang maaga, kami ay humiwalay.

Samakatuwid, dahil hindi pinapayagan ng TL ang pag-output ng isang vector, kailangan itong idagdag sa gilid. Sa huli ang dokumentasyon ay nagsasabi:

Palaging ginagamit ng serialization ang parehong constructor na β€œvector” (const 0x1cb5c415 = crc32(β€œvector t:Type # [t ] = Vector t”) na hindi nakadepende sa partikular na value ng variable ng type t.

Ang halaga ng opsyonal na parameter t ay hindi kasama sa serialization dahil ito ay hinango sa uri ng resulta (palaging kilala bago ang deserialization).

Tingnang mabuti: vector {t:Type} # [ t ] = Vector t - ngunit wala kahit saan Ang kahulugan na ito mismo ay hindi nagsasabi na ang unang numero ay dapat na katumbas ng haba ng vector! At hindi ito nanggaling kahit saan. Ito ay isang ibinigay na kailangang isaisip at ipatupad gamit ang iyong mga kamay. Sa ibang lugar, matapat na binanggit ng dokumentasyon na ang uri ay hindi totoo:

Ang Vector t polymorphic pseudotype ay isang "uri" na ang halaga ay isang pagkakasunud-sunod ng mga halaga ng anumang uri t, naka-box man o walang laman.

... ngunit hindi tumutok dito. Kapag ikaw, pagod na sa pagtawid sa kahabaan ng matematika (maaaring kilala mo pa mula sa isang kurso sa unibersidad), ay nagpasya na sumuko at talagang tingnan kung paano ito gagawin sa pagsasanay, ang natitirang impresyon sa iyong ulo ay na ito ay Seryoso Mathematics at the core, ito ay malinaw na naimbento ng Cool People (dalawang mathematician - ACM winner), at hindi kung sino lang. Ang layunin - upang ipakita ang - ay nakamit.

Sa pamamagitan ng paraan, tungkol sa numero. Paalalahanan ka namin # ito ay isang kasingkahulugan nat, natural na numero:

May mga uri ng expression (type-expr) at mga numeric na expression (nat-expr). Gayunpaman, ang mga ito ay tinukoy sa parehong paraan.

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

ngunit sa gramatika sila ay inilarawan sa parehong paraan, i.e. Ang pagkakaibang ito ay dapat na muling alalahanin at isasagawa nang manu-mano.

Well, oo, mga uri ng template (vector<int>, vector<User>) ay may karaniwang identifier (#1cb5c415), ibig sabihin. kung alam mo na ang tawag ay inihayag bilang

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

pagkatapos ay hindi ka na naghihintay para sa isang vector lamang, ngunit isang vector ng mga gumagamit. Mas tiyak, dapat maghintay - sa totoong code, bawat elemento, kung hindi isang hubad na uri, ay magkakaroon ng isang tagabuo, at sa isang mahusay na paraan sa pagpapatupad ay kinakailangan upang suriin - ngunit kami ay ipinadala nang eksakto sa bawat elemento ng vector na ito yung tipong? Paano kung ito ay isang uri ng PHP, kung saan ang isang array ay maaaring maglaman ng iba't ibang uri sa iba't ibang elemento?

Sa puntong ito magsisimula kang mag-isip - kailangan ba ang gayong TL? Siguro para sa cart posible na gumamit ng isang serializer ng tao, ang parehong protobuf na umiiral na noon? Iyon ang teorya, tingnan natin ang pagsasanay.

Mga kasalukuyang pagpapatupad ng TL sa code

Ang TL ay ipinanganak sa kalaliman ng VKontakte kahit na bago ang mga sikat na kaganapan sa pagbebenta ng bahagi ni Durov at (tiyak), bago pa man magsimula ang pagbuo ng Telegram. At sa open source source code ng unang pagpapatupad makakahanap ka ng maraming nakakatawang saklay. At ang wika mismo ay ipinatupad doon nang mas ganap kaysa sa ngayon sa Telegram. Halimbawa, ang mga hash ay hindi ginagamit sa scheme (ibig sabihin ay isang built-in na pseudotype (tulad ng isang vector) na may lihis na pag-uugali). O kaya

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

ngunit isaalang-alang natin, alang-alang sa pagkakumpleto, upang masubaybayan, wika nga, ang ebolusyon ng Higante ng Pag-iisip.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

O ito maganda:

    static const char *reserved_words_polymorhic[] = {

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

      };

Ang fragment na ito ay tungkol sa mga template tulad ng:

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

Ito ang kahulugan ng isang uri ng template ng hashmap bilang isang vector ng mga pares ng int - Uri. Sa C++ ito ay magiging ganito:

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

kaya, alpha - keyword! Ngunit sa C++ lamang maaari kang sumulat ng T, ngunit dapat mong isulat ang alpha, beta... Ngunit hindi hihigit sa 8 mga parameter, doon nagtatapos ang pantasya. Tila noong unang panahon sa St. Petersburg naganap ang ilang mga diyalogong tulad nito:

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

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

Ngunit ito ay tungkol sa unang nai-publish na pagpapatupad ng TL "sa pangkalahatan". Magpatuloy tayo sa pagsasaalang-alang sa mga pagpapatupad sa mga kliyente ng Telegram mismo.

Salita kay Vasily:

Vasily, [09.10.18 17:07] Higit sa lahat, mainit ang asno dahil lumikha sila ng isang grupo ng mga abstraction, at pagkatapos ay pinartilyo ang mga ito ng bolt, at tinakpan ng saklay ang code generator
Bilang resulta, una mula sa dock pilot.jpg
Pagkatapos ay mula sa code na dzhekichan.webp

Siyempre, mula sa mga taong pamilyar sa mga algorithm at matematika, maaari nating asahan na nabasa nila ang Aho, Ullmann, at pamilyar sa mga tool na naging de facto na pamantayan sa industriya sa loob ng mga dekada para sa pagsulat ng kanilang mga DSL compiler, tama ba?..

Sa pamamagitan ng may-akda telegrama-cli ay si Vitaly Valtman, gaya ng mauunawaan mula sa paglitaw ng TLO format sa labas ng (cli) na mga hangganan nito, isang miyembro ng team - ngayon ay isang library para sa TL parsing ay inilaan na hiwa-hiwalay, ano ang impression sa kanya TL parser? ..

16.12 04:18 Vasily: Sa palagay ko ay may hindi nakakabisado ng lex+yacc
16.12 04:18 Vasily: Hindi ko maipaliwanag kung hindi man
16.12 04:18 Vasily: mabuti, o binayaran sila para sa bilang ng mga linya sa VK
16.12 04:19 Vasily: 3k+ na linya atbp<censored> sa halip na isang parser

Baka isang exception? Tingnan natin kung paano ay Ito ang OPISYAL na kliyente - Telegram Desktop:

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

1100+ na linya sa Python, isang pares ng mga regular na expression + mga espesyal na kaso tulad ng isang vector, na, siyempre, ay idineklara sa scheme na dapat ayon sa syntax ng TL, ngunit umasa sila sa syntax na ito upang mai-parse ito... Ang tanong, bakit naging himala ang lahat?ΠΈIto ay mas layered kung walang sinuman ang mag-parse nito ayon sa dokumentasyon pa rin?!

Siyanga pala... Tandaan na napag-usapan natin ang tungkol sa pagsuri ng CRC32? Kaya, sa Telegram Desktop code generator mayroong isang listahan ng mga pagbubukod para sa mga uri kung saan ang kinakalkula na CRC32 hindi tumutugma gamit ang nakasaad sa diagram!

Vasily, [18.12/22 49:XNUMX] at dito ko iisipin kung kailangan ba ng ganoong TL
kung gusto kong guluhin ang mga alternatibong pagpapatupad, magsisimula akong magpasok ng mga line break, kalahati ng mga parser ay masisira sa mga multi-line na kahulugan
tdesktop, gayunpaman, masyadong

Tandaan ang punto tungkol sa one-liner, babalikan natin ito mamaya.

Okay, ang telegram-cli ay hindi opisyal, ang Telegram Desktop ay opisyal, ngunit paano ang iba? Sino ang nakakaalam?.. Sa Android client code, walang schema parser (na naglalabas ng mga tanong tungkol sa open source, ngunit ito ay para sa ikalawang bahagi), ngunit mayroong ilang iba pang mga nakakatawang piraso ng code, ngunit higit pa sa mga ito sa subsection sa ibaba.

Anong iba pang mga tanong ang itinataas ng serialization sa pagsasanay? Halimbawa, marami silang ginawa, siyempre, kasama ang mga bit field at conditional na field:

Vasily: flags.0? true
nangangahulugan na ang field ay naroroon at katumbas ng totoo kung ang bandila ay nakatakda

Vasily: flags.1? int
nangangahulugan na ang field ay naroroon at kailangang deserialized

Vasily: Ass, huwag kang mag-alala sa ginagawa mo!
Vasily: May binanggit sa isang lugar sa doc na ang true ay isang bare zero-length na uri, ngunit imposibleng mag-assemble ng anuman mula sa kanilang doc
Vasily: Sa mga open source na pagpapatupad ay hindi rin ito ang kaso, ngunit mayroong isang grupo ng mga saklay at suporta

Paano ang Telethon? Inaasahan ang paksa ng MTProto, isang halimbawa - sa dokumentasyon mayroong mga naturang piraso, ngunit ang tanda % ito ay inilarawan lamang bilang "naaayon sa isang ibinigay na hubad-uri", i.e. sa mga halimbawa sa ibaba mayroong alinman sa isang error o isang bagay na hindi dokumentado:

Vasily, [22.06.18 18:38] Sa isang lugar:

msg_container#73f1f8dc messages:vector message = MessageContainer;

Sa ibang:

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

At ito ay dalawang malaking pagkakaiba, sa totoong buhay may ilang uri ng hubad na vector na dumarating

Hindi ko nakita ang isang hubad na kahulugan ng vector at hindi pa ako nakatagpo ng isa

Ang pagsusuri ay nakasulat sa pamamagitan ng kamay sa telethon

Sa kanyang diagram ang kahulugan ay nagkomento msg_container

Muli, ang tanong ay nananatiling tungkol sa%. Hindi ito inilarawan.

Vadim Goncharov, [22.06.18 19:22] at sa tdesktop?

Vasily, [22.06.18 19:23] Ngunit ang kanilang TL parser sa mga regular na makina ay malamang na hindi rin ito kakainin

// parsed manually

Ang TL ay isang magandang abstraction, walang ganap na nagpapatupad nito

At ang % ay wala sa kanilang bersyon ng scheme

Ngunit dito ang dokumentasyon ay sumasalungat sa sarili nito, kaya idk

Natagpuan ito sa gramatika, nakalimutan lang nilang ilarawan ang semantika

Nakita mo ang dokumento sa TL, hindi mo maiisip ito nang walang kalahating litro

"Buweno, sabihin natin," sasabihin ng isa pang mambabasa, "pinupuna mo ang isang bagay, kaya ipakita sa akin kung paano ito dapat gawin."

Sumagot si Vasily: "Kung tungkol sa parser, gusto ko ang mga bagay tulad

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

kahit papaano mas gusto ito kaysa

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

o

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

ito ang BUONG lexer:

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

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

mga. ang mas simple ay ang paglalagay nito nang mahinahon."

Sa pangkalahatan, bilang resulta, ang parser at code generator para sa aktwal na ginamit na subset ng TL ay umaangkop sa humigit-kumulang 100 linya ng grammar at ~300 linya ng generator (bilang lahat printnabuong code), kabilang ang mga uri ng impormasyon buns para sa pagsisiyasat ng sarili sa bawat klase. Ang bawat uri ng polymorphic ay nagiging isang walang laman na abstract base class, at ang mga constructor ay nagmamana mula dito at may mga pamamaraan para sa serialization at deserialization.

Kakulangan ng mga uri sa uri ng wika

Ang malakas na pag-type ay isang magandang bagay, tama ba? Hindi, hindi ito holivar (bagaman mas gusto ko ang mga dynamic na wika), ngunit isang postulate sa loob ng balangkas ng TL. Batay dito, ang wika ay dapat magbigay ng lahat ng uri ng mga pagsusuri para sa amin. Well, okay, siguro hindi siya mismo, ngunit ang pagpapatupad, ngunit dapat niyang ilarawan ang mga ito. At anong uri ng mga pagkakataon ang gusto natin?

Una sa lahat, mga hadlang. Dito makikita natin sa dokumentasyon para sa pag-upload ng mga file:

Ang binary na nilalaman ng file ay nahahati sa mga bahagi. Ang lahat ng mga bahagi ay dapat na may parehong laki ( bahagi_laki ) at ang mga sumusunod na kondisyon ay dapat matugunan:

  • part_size % 1024 = 0 (nahahati ng 1KB)
  • 524288 % part_size = 0 (Ang 512KB ay dapat na pantay na mahahati sa part_size)

Ang huling bahagi ay hindi kailangang matugunan ang mga kundisyong ito, sa kondisyon na ang laki nito ay mas mababa sa part_size.

Ang bawat bahagi ay dapat may sequence number, file_part, na may halagang mula 0 hanggang 2,999.

Matapos mahati ang file kailangan mong pumili ng paraan para sa pag-save nito sa server. Gamitin upload.saveBigFilePart kung sakaling ang buong laki ng file ay higit sa 10 MB at upload.saveFilePart para sa mas maliliit na file.
[...] maaaring ibalik ang isa sa mga sumusunod na data input error:

  • FILE_PARTS_INVALID β€” Di-wastong bilang ng mga bahagi. Ang halaga ay hindi sa pagitan 1..3000

Mayroon ba nito sa diagram? Ito ba ay kahit papaano ay nasasabi gamit ang TL? Hindi. Pero excuse me, kahit ang Turbo Pascal ni lolo ay nagawang ilarawan ang mga uri na tinukoy mga saklaw. At isa pang bagay ang alam niya, ngayon ay mas kilala bilang enum - isang uri na binubuo ng isang enumeration ng isang nakapirming (maliit) na bilang ng mga halaga. Sa mga wika tulad ng C - numeric, tandaan na sa ngayon ay pinag-uusapan lang natin ang tungkol sa mga uri numero. Ngunit mayroon ding mga array, mga string... halimbawa, magandang ilarawan na ang string na ito ay maaari lamang maglaman ng numero ng telepono, tama?

Wala sa TL ito. Ngunit mayroong, halimbawa, sa JSON Schema. At kung ang ibang tao ay maaaring magtaltalan tungkol sa divisibility ng 512 KB, na kailangan pa rin itong suriin sa code, pagkatapos ay siguraduhin na ang kliyente ay simple. hindi magawa magpadala ng numero sa labas ng saklaw 1..3000 (at ang katumbas na error ay hindi maaaring lumitaw) ito ay posible, tama?..

Sa pamamagitan ng paraan, tungkol sa mga error at pagbabalik ng mga halaga. Kahit na ang mga nakatrabaho sa TL ay nanlalabo ang kanilang mga mata - hindi ito kaagad napagtanto sa amin bawat isa ang isang function sa TL ay maaaring aktwal na ibalik hindi lamang ang inilarawan na uri ng pagbabalik, kundi pati na rin ang isang error. Ngunit hindi ito mahihinuha sa anumang paraan gamit ang TL mismo. Siyempre, ito ay malinaw na at hindi na kailangan ng anumang bagay sa pagsasanay (bagaman sa katunayan, ang RPC ay maaaring gawin sa iba't ibang paraan, babalikan natin ito mamaya) - ngunit ano ang tungkol sa Kadalisayan ng mga konsepto ng Matematika ng Mga Uri ng Abstract mula sa makalangit na mundo?.. Kinuha ko ang paghatak - kaya itugma mo ito.

At sa wakas, paano naman ang pagiging madaling mabasa? Well, doon, sa pangkalahatan, gusto ko paglalarawan tama ba ito sa schema (sa JSON schema, muli, ito ay), ngunit kung nahihirapan ka na dito, paano naman ang praktikal na bahagi - kahit na maliit na pagtingin sa mga pagkakaiba sa panahon ng mga update? Tingnan para sa iyong sarili sa tunay na mga halimbawa:

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

o

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

Depende ito sa lahat, ngunit ang GitHub, halimbawa, ay tumangging i-highlight ang mga pagbabago sa loob ng gayong mahabang linya. Ang larong "hanapin ang 10 pagkakaiba", at kung ano ang agad na nakikita ng utak ay ang simula at pagtatapos sa parehong mga halimbawa ay pareho, kailangan mong nakakapagod na basahin sa isang lugar sa gitna... Sa aking opinyon, ito ay hindi lamang sa teorya, pero puro biswal marumi at palpak.

Sa pamamagitan ng paraan, tungkol sa kadalisayan ng teorya. Bakit kailangan natin ng mga bit field? Hindi ba parang sila amoy masama mula sa punto ng view ng uri ng teorya? Ang paliwanag ay makikita sa mga naunang bersyon ng diagram. Sa una, oo, ganoon talaga, para sa bawat pagbahin ay may nalikhang bagong uri. Ang mga simulaing ito ay umiiral pa rin sa form na ito, halimbawa:

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;

Ngunit ngayon isipin, kung mayroon kang 5 opsyonal na mga patlang sa iyong istraktura, kakailanganin mo ng 32 na uri para sa lahat ng posibleng opsyon. Kombinatoryal na pagsabog. Kaya, ang kristal na kadalisayan ng teorya ng TL ay muling nabasag laban sa cast-iron na asno ng malupit na katotohanan ng serialization.

Bilang karagdagan, sa ilang mga lugar ang mga taong ito mismo ay lumalabag sa kanilang sariling tipolohiya. Halimbawa, sa MTProto (susunod na kabanata) ang tugon ay maaaring i-compress ng Gzip, lahat ay maayos - maliban na ang mga layer at circuit ay nilabag. Muli, hindi ang RpcResult mismo ang inani, kundi ang nilalaman nito. Well, bakit gagawin ito?.. Kinailangan kong putulin ang isang saklay upang ang compression ay gumana kahit saan.

O isa pang halimbawa, minsan kaming nakatuklas ng error - ipinadala ito InputPeerUser sa halip ng InputUser. O vice versa. Ngunit ito ay gumana! Ibig sabihin, walang pakialam ang server sa uri. Paanong nangyari to? Ang sagot ay maaaring ibigay sa amin ng mga fragment ng code mula sa telegram-cli:

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

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

Sa madaling salita, dito ginagawa ang serialization MANUAL, hindi nabuong code! Siguro ang server ay ipinatupad sa isang katulad na paraan?.. Sa prinsipyo, ito ay gagana kung tapos na nang isang beses, ngunit paano ito susuportahan sa ibang pagkakataon sa panahon ng pag-update? Ito ba ang dahilan kung bakit naimbento ang pamamaraan? At narito na tayo sa susunod na tanong.

Pag-bersyon. Mga layer

Kung bakit ang mga bersyon ng eskematiko ay tinatawag na mga layer ay maaari lamang hulaan batay sa kasaysayan ng nai-publish na mga eskematiko. Tila, noong una ay naisip ng mga may-akda na ang mga pangunahing bagay ay maaaring gawin gamit ang hindi nabagong pamamaraan, at kung kinakailangan lamang, para sa mga partikular na kahilingan, ay nagpapahiwatig na ang mga ito ay ginagawa gamit ang ibang bersyon. Sa prinsipyo, kahit na isang magandang ideya - at ang bago ay, bilang ito ay, "halo-halong", layered sa ibabaw ng luma. Ngunit tingnan natin kung paano ito ginawa. Totoo, hindi ko ito matingnan mula pa sa simula - ito ay nakakatawa, ngunit ang diagram ng base layer ay hindi umiiral. Nagsimula ang mga layer sa 2. Sinasabi sa amin ng dokumentasyon ang tungkol sa isang espesyal na feature ng TL:

Kung sinusuportahan ng isang kliyente ang Layer 2, dapat gamitin ang sumusunod na constructor:

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

Sa pagsasagawa, nangangahulugan ito na bago ang bawat tawag sa API, isang int na may halaga 0x289dd1f6 dapat idagdag bago ang numero ng pamamaraan.

Parang normal. Ngunit ano ang sumunod na nangyari? Pagkatapos ay lumitaw

invokeWithLayer3#b7475268 query:!X = X;

Kaya ano ang susunod? Tulad ng maaari mong hulaan,

invokeWithLayer4#dea0d430 query:!X = X;

Nakakatawa? Hindi, masyado pang maaga para tumawa, isipin mo ang katotohanang iyon bawat ang isang kahilingan mula sa isa pang layer ay kailangang balot sa isang espesyal na uri - kung lahat sila ay iba para sa iyo, paano mo pa sila makikilala? At ang pagdaragdag lamang ng 4 na bait sa harap ay isang medyo mahusay na pamamaraan. Kaya,

invokeWithLayer5#417a57ae query:!X = X;

Ngunit ito ay malinaw na pagkatapos ng ilang sandali ito ay magiging isang uri ng bacchanalia. At dumating ang solusyon:

Update: Simula sa Layer 9, helper method invokeWithLayerN maaaring gamitin lamang kasama ng initConnection

Hooray! Pagkatapos ng 9 na bersyon, sa wakas ay narating namin ang ginawa sa mga protocol ng Internet noong 80s - sumang-ayon sa bersyon nang isang beses sa simula ng koneksyon!

Kaya ano ang susunod?..

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

Pero ngayon pwede ka pa ring tumawa. Pagkatapos lamang ng isa pang 9 na layer, ang isang unibersal na tagabuo na may isang numero ng bersyon ay sa wakas ay naidagdag, na kailangang tawagan nang isang beses lamang sa simula ng koneksyon, at ang kahulugan ng mga layer ay tila nawala, ngayon ito ay isang kondisyon na bersyon lamang, tulad ng kahit saan pa. Nalutas ang problema.

eksakto?..

Vasily, [16.07.18 14:01] Kahit noong Biyernes naisip ko:
Ang teleserver ay nagpapadala ng mga kaganapan nang walang kahilingan. Ang mga kahilingan ay dapat na nakabalot sa InvokeWithLayer. Hindi binabalot ng server ang mga update; walang istraktura para sa pagbabalot ng mga tugon at update.

Yung. hindi matukoy ng kliyente ang layer kung saan gusto niya ng mga update

Vadim Goncharov, [16.07.18 14:02] hindi ba ang InvokeWithLayer ay isang saklay sa prinsipyo?

Vasily, [16.07.18 14:02] Ito ang tanging paraan

Vadim Goncharov, [16.07.18 14:02] na dapat ay nangangahulugang pagsang-ayon sa layer sa simula ng session

Sa pamamagitan ng paraan, ito ay sumusunod na ang pag-downgrade ng kliyente ay hindi ibinigay

Mga update, ibig sabihin. uri Updates sa scheme, ito ang ipinapadala ng server sa kliyente hindi bilang tugon sa isang kahilingan sa API, ngunit nang nakapag-iisa kapag may nangyaring kaganapan. Ito ay isang kumplikadong paksa na tatalakayin sa isa pang post, ngunit sa ngayon mahalagang malaman na ang server ay nagse-save ng Mga Update kahit na ang kliyente ay offline.

Kaya, kung tumanggi kang balutin ng bawat isa package upang ipahiwatig ang bersyon nito, ito ay lohikal na humahantong sa mga sumusunod na posibleng problema:

  • nagpapadala ang server ng mga update sa kliyente bago pa man ipaalam ng kliyente kung aling bersyon ang sinusuportahan nito
  • ano ang dapat kong gawin pagkatapos mag-upgrade ng kliyente?
  • sino garantiyana ang opinyon ng server tungkol sa numero ng layer ay hindi magbabago sa panahon ng proseso?

Sa palagay mo ba ito ay puro teoretikal na haka-haka, at sa pagsasagawa ay hindi ito maaaring mangyari, dahil ang server ay nakasulat nang tama (hindi bababa sa, ito ay nasubok na mabuti)? Ha! Hindi mahalaga kung paano ito ay!

Ganito talaga ang nasagasaan namin noong Agosto. Noong Agosto 14, may mga mensahe na may ina-update sa mga server ng Telegram... at pagkatapos ay sa mga log:

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

at pagkatapos ay ilang megabytes ng stack traces (well, sa parehong oras ang pag-log ay naayos). Pagkatapos ng lahat, kung ang isang bagay ay hindi nakilala sa iyong TL, ito ay binary sa pamamagitan ng lagda, sa ibaba ng linya LAHAT napupunta, ang pag-decode ay magiging imposible. Ano ang dapat mong gawin sa ganoong sitwasyon?

Well, ang unang bagay na pumapasok sa isip ng sinuman ay ang idiskonekta at subukang muli. Hindi nakatulong. Nag-google kami ng CRC32 - ang mga ito ay naging mga bagay mula sa scheme 73, kahit na nagtrabaho kami sa 82. Tinitingnan namin nang mabuti ang mga log - may mga identifier mula sa dalawang magkaibang mga scheme!

Baka puro sa unofficial client natin ang problema? Hindi, inilulunsad namin ang Telegram Desktop 1.2.17 (ibinigay na bersyon sa isang bilang ng mga distribusyon ng Linux), nagsusulat ito sa Exception log: MTP Hindi inaasahang uri id #b5223b0f nabasa sa MTPMessageMedia…

Pagpuna sa protocol at mga diskarte sa organisasyon ng Telegram. Bahagi 1, teknikal: karanasan sa pagsulat ng isang kliyente mula sa simula - TL, MT

Ipinakita ng Google na ang isang katulad na problema ay nangyari na sa isa sa mga hindi opisyal na kliyente, ngunit pagkatapos ay ang mga numero ng bersyon at, nang naaayon, ang mga pagpapalagay ay naiiba...

So anong dapat nating gawin? Naghiwalay kami ni Vasily: sinubukan niyang i-update ang circuit sa 91, nagpasya akong maghintay ng ilang araw at subukan ang 73. Ang parehong mga pamamaraan ay nagtrabaho, ngunit dahil ang mga ito ay empirical, walang pag-unawa sa kung gaano karaming mga bersyon pataas o pababa ang kailangan mo tumalon, o gaano katagal kailangan mong maghintay .

Nang maglaon ay nagawa kong kopyahin ang sitwasyon: inilunsad namin ang kliyente, i-off ito, muling i-compile ang circuit sa isa pang layer, i-restart, saluhin muli ang problema, bumalik sa nauna - oops, walang halaga ng circuit switching at client restart para sa isang ilang minuto ay makakatulong. Makakatanggap ka ng isang halo ng mga istruktura ng data mula sa iba't ibang mga layer.

Paliwanag? Tulad ng maaari mong hulaan mula sa iba't ibang mga hindi direktang sintomas, ang server ay binubuo ng maraming mga proseso ng iba't ibang uri sa iba't ibang mga makina. Malamang, ang server na responsable para sa "buffering" ay inilagay sa pila kung ano ang ibinigay ng mga nakatataas dito, at ibinigay nila ito sa pamamaraan na nasa lugar sa panahon ng henerasyon. At hanggang sa "bulok" ang pila na ito, walang magagawa tungkol dito.

Marahil... ngunit ito ay isang kahila-hilakbot na saklay?!.. Hindi, bago mag-isip tungkol sa mga nakatutuwang ideya, tingnan natin ang code ng mga opisyal na kliyente. Sa bersyon ng Android wala kaming mahanap na TL parser, ngunit nakahanap kami ng isang mabigat na file (tumanggi ang GitHub na hawakan ito) na may (de)serialization. Narito ang mga snippet ng code:

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;

o

    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... mukhang wild. Ngunit, malamang, ito ay nabuong code, pagkatapos ay okay?.. Ngunit tiyak na sinusuportahan nito ang lahat ng mga bersyon! Totoo, hindi malinaw kung bakit pinaghalo ang lahat, mga lihim na pakikipag-chat, at lahat ng uri _old7 kahit papaano ay hindi mukhang machine generation... Gayunpaman, higit sa lahat ako ay natangay

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Guys, hindi ba kayo makapagpasya kung ano ang nasa loob ng isang layer?! Well, okay, let's say β€œtwo” were released with a error, well, it happens, pero TATLO?.. Kaagad, the same rake again? Anong klaseng pornograpiya ito, sorry?..

Sa source code ng Telegram Desktop, sa pamamagitan ng paraan, isang katulad na bagay ang mangyayari - kung gayon, maraming mga commit sa isang hilera sa scheme ay hindi nagbabago sa numero ng layer nito, ngunit ayusin ang isang bagay. Sa mga kondisyon kung saan walang opisyal na mapagkukunan ng data para sa scheme, saan ito makukuha, maliban sa source code ng opisyal na kliyente? At kung kukunin mo ito mula doon, hindi ka makatitiyak na ang pamamaraan ay ganap na tama hanggang sa subukan mo ang lahat ng mga pamamaraan.

Paano ito masusubok? Umaasa ako na ang mga tagahanga ng unit, functional at iba pang mga pagsubok ay magbahagi sa mga komento.

Okay, tingnan natin ang isa pang piraso ng code:

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;

Ang komentong ito na "manual na ginawa" ay nagmumungkahi na bahagi lamang ng file na ito ang manu-manong isinulat (maiisip mo ba ang buong bangungot sa pagpapanatili?), at ang iba ay binuo ng makina. Gayunpaman, pagkatapos ay lumitaw ang isa pang tanong - na ang mga mapagkukunan ay magagamit hindi kumpleto (a la GPL blobs sa Linux kernel), ngunit isa na itong paksa para sa ikalawang bahagi.

Ngunit sapat na. Lumipat tayo sa protocol kung saan tumatakbo ang lahat ng serialization na ito.

MT Proto

Kaya, buksan natin Pangkalahatang paglalarawan ΠΈ detalyadong paglalarawan ng protocol at ang una nating natitisod ay ang terminolohiya. At sa kasaganaan ng lahat. Sa pangkalahatan, ito ay tila isang pagmamay-ari na feature ng Telegram - iba ang pagtawag sa mga bagay sa iba't ibang lugar, o iba't ibang bagay gamit ang isang salita, o kabaliktaran (halimbawa, sa isang mataas na antas ng API, kung makakita ka ng sticker pack, hindi ito ano ang inisip mo).

Halimbawa, iba ang ibig sabihin ng "mensahe" at "session" dito kaysa sa karaniwang interface ng kliyente ng Telegram. Well, ang lahat ay malinaw sa mensahe, maaari itong bigyang-kahulugan sa mga termino ng OOP, o simpleng tinatawag na salitang "packet" - ito ay isang mababa, antas ng transportasyon, walang parehong mga mensahe tulad ng sa interface, mayroong maraming mga mensahe ng serbisyo . Pero yung session... but first things first.

layer ng transportasyon

Ang unang bagay ay transportasyon. Sasabihin nila sa amin ang tungkol sa 5 mga pagpipilian:

  • TCP
  • Websocket
  • Websocket sa HTTPS
  • HTTP
  • HTTPS

Vasily, [15.06.18 15:04] Mayroon ding UDP transport, ngunit hindi ito dokumentado

At TCP sa tatlong variant

Ang una ay katulad ng UDP sa TCP, ang bawat packet ay may kasamang sequence number at crc
Bakit napakasakit magbasa ng mga dokumento sa cart?

Well, ayan na ngayon Nasa 4 na variant na ang TCP:

  • Pinaikling
  • Panggitna
  • Padded intermediate
  • Ganap

Well, ok, Padded intermediate para sa MTProxy, ito ay naidagdag sa ibang pagkakataon dahil sa mga kilalang kaganapan. Ngunit bakit dalawa pang bersyon (tatlo sa kabuuan) kung kaya mo namang makayanan ang isa? Ang lahat ng apat ay mahalagang naiiba lamang sa kung paano itakda ang haba at kargamento ng pangunahing MTProto, na tatalakayin pa:

  • sa Abridged ito ay 1 o 4 bytes, ngunit hindi 0xef, pagkatapos ay ang katawan
  • sa Intermediate ito ay 4 bytes ang haba at isang field, at sa unang pagkakataon na dapat magpadala ang kliyente 0xeeeeeeee upang ipahiwatig na ito ay Intermediate
  • sa Buong pinaka nakakahumaling, mula sa punto ng view ng isang networker: haba, numero ng pagkakasunud-sunod, at HINDI ANG ISA na pangunahing MTProto, katawan, CRC32. Oo, lahat ng ito ay nasa ibabaw ng TCP. Na nagbibigay sa amin ng maaasahang transportasyon sa anyo ng isang sequential byte stream; walang mga sequence ang kailangan, lalo na ang mga checksum. Okay, ngayon may tututol sa akin na ang TCP ay may 16-bit na checksum, kaya nangyayari ang data corruption. Mahusay, ngunit mayroon talaga kaming cryptographic protocol na may mga hash na mas mahaba kaysa sa 16 byte, lahat ng mga error na ito - at higit pa - ay mahuhuli ng hindi pagkakatugma ng SHA sa mas mataas na antas. Walang punto sa CRC32 sa itaas nito.

Ihambing natin ang Abridged, kung saan posible ang isang byte ng haba, sa Intermediate, na nagbibigay-katwiran sa "Kung sakaling kailanganin ang 4-byte na pag-align ng data," na medyo walang kapararakan. Ano, pinaniniwalaan na ang mga programmer ng Telegram ay walang kakayahan na hindi nila mabasa ang data mula sa isang socket patungo sa isang nakahanay na buffer? Kailangan mo pa ring gawin ito, dahil ang pagbabasa ay maaaring magbalik sa iyo ng anumang bilang ng mga byte (at mayroon ding mga proxy server, halimbawa...). O sa kabilang banda, bakit i-block ang Abridged kung magkakaroon pa rin tayo ng mabigat na padding sa itaas ng 16 bytes - makatipid ng 3 bytes kung minsan ?

Nakukuha ng isang tao ang impresyon na talagang gusto ni Nikolai Durov na muling likhain ang mga gulong, kabilang ang mga protocol ng network, nang walang anumang tunay na praktikal na pangangailangan.

Iba pang mga opsyon sa transportasyon, kasama. Web at MTProxy, hindi natin isasaalang-alang ngayon, baka sa ibang post, kung may kahilingan. Tungkol sa parehong MTProxy na ito, tandaan lamang natin ngayon na ilang sandali matapos itong ilabas noong 2018, mabilis na natutunan ng mga provider na harangan ito, na nilayon para sa bypass blockingSa pamamagitan ng laki ng pakete! At gayundin ang katotohanan na ang server ng MTProxy na isinulat (muli ni Waltman) sa C ay labis na nakatali sa mga partikular na Linux, bagama't hindi ito kinakailangan (kukumpirmahin ng Phil Kulin), at ang isang katulad na server sa Go o Node.js ay gagawin. magkasya sa wala pang isang daang linya.

Ngunit gagawa kami ng mga konklusyon tungkol sa teknikal na literacy ng mga taong ito sa dulo ng seksyon, pagkatapos isaalang-alang ang iba pang mga isyu. Sa ngayon, lumipat tayo sa OSI layer 5, session - kung saan nila inilagay ang MTProto session.

Mga susi, mensahe, session, Diffie-Hellman

Inilagay nila ito doon nang hindi ganap nang tama... Ang isang session ay hindi ang parehong session na nakikita sa interface sa ilalim ng Mga aktibong session. Ngunit sa pagkakasunud-sunod.

Pagpuna sa protocol at mga diskarte sa organisasyon ng Telegram. Bahagi 1, teknikal: karanasan sa pagsulat ng isang kliyente mula sa simula - TL, MT

Kaya nakatanggap kami ng byte string na alam ang haba mula sa transport layer. Ito ay alinman sa isang naka-encrypt na mensahe o plaintext - kung tayo ay nasa pangunahing yugto ng kasunduan at aktwal na ginagawa ito. Alin sa grupo ng mga konsepto na tinatawag na "susi" ang pinag-uusapan natin? Linawin natin ang isyung ito para sa Telegram team mismo (humihingi ako ng paumanhin para sa pagsasalin ng sarili kong dokumentasyon mula sa Ingles na may pagod na utak sa 4 am, mas madaling mag-iwan ng ilang mga parirala tulad ng mga ito):

Mayroong dalawang entity na tinatawag Sesyon - isa sa UI ng mga opisyal na kliyente sa ilalim ng "kasalukuyang mga session", kung saan ang bawat session ay tumutugma sa isang buong device / OS.
Ang pangalawa ay Sesyon ng MTProto, na mayroong sequence number ng mensahe (sa mababang antas na kahulugan) dito, at alin maaaring tumagal sa pagitan ng iba't ibang koneksyon sa TCP. Maraming mga sesyon ng MTProto ang maaaring mai-install nang sabay-sabay, halimbawa, upang mapabilis ang pag-download ng file.

Sa pagitan ng dalawang ito session may konsepto pahintulot. Sa degenerate case, masasabi natin iyan Sesyon ng UI ay kapareho ng pahintulot, ngunit sayang, ang lahat ay kumplikado. Tingnan natin:

  • Ang user sa bagong device ay unang bumubuo auth_key at i-bound ito sa account, halimbawa sa pamamagitan ng SMS - kaya naman pahintulot
  • Nangyari ito sa loob ng una Sesyon ng MTProto, na mayroon session_id sa loob mo.
  • Sa hakbang na ito, ang kumbinasyon pahintulot ΠΈ session_id maaaring tawagan halimbawa - lumilitaw ang salitang ito sa dokumentasyon at code ng ilang kliyente
  • Pagkatapos, ang kliyente ay maaaring magbukas ilan Mga sesyon ng MTProto sa ilalim ng pareho auth_key - sa parehong DC.
  • Pagkatapos, isang araw kakailanganin ng kliyente na hilingin ang file mula sa isa pang DC - at para sa DC na ito ay bubuo ng bago auth_key !
  • Upang ipaalam sa system na hindi bagong user ang nagrerehistro, ngunit pareho pahintulot (Sesyon ng UI), gumagamit ang kliyente ng mga tawag sa API auth.exportAuthorization sa bahay DC auth.importAuthorization sa bagong DC.
  • Ang lahat ay pareho, marami ang maaaring bukas Mga sesyon ng MTProto (bawat isa ay may sariling session_id) sa bagong DC na ito, sa ilalim kanya auth_key.
  • Sa wakas, maaaring gusto ng kliyente ang Perfect Forward Secrecy. Bawat auth_key noon ay permanente key - bawat DC - at maaaring tumawag ang kliyente auth.bindTempAuthKey para gamitin pansamantala auth_key - at muli, isa lamang temp_auth_key bawat DC, karaniwan sa lahat Mga sesyon ng MTProto sa DC na ito.

Tandaan na asin (at mga hinaharap na asin) ay isa din auth_key mga. ibinahagi sa pagitan ng lahat Mga sesyon ng MTProto sa parehong DC.

Ano ang ibig sabihin ng "sa pagitan ng iba't ibang koneksyon sa TCP"? So ibig sabihin nito isang bagay tulad ng cookie ng awtorisasyon sa isang website - nagpapatuloy ito (nakaligtas) ng maraming koneksyon sa TCP sa isang partikular na server, ngunit isang araw ay masira ito. Tanging hindi tulad ng HTTP, sa MTProto ang mga mensahe sa loob ng isang session ay sunud-sunod na binibilang at nakumpirma; kung sila ay pumasok sa tunnel, ang koneksyon ay nasira - pagkatapos magtatag ng isang bagong koneksyon, ang server ay magiliw na ipapadala ang lahat sa session na ito na hindi nito naihatid sa nakaraang Koneksyon ng TCP.

Gayunpaman, ang impormasyon sa itaas ay ibinubuod pagkatapos ng maraming buwan ng pagsisiyasat. Samantala, ipinapatupad ba natin ang ating kliyente mula sa simula? - balik tayo sa umpisa.

Kaya mag-generate tayo auth_key sa Mga bersyon ng Diffie-Hellman mula sa Telegram. Subukan nating unawain ang dokumentasyon...

Vasily, [19.06.18 20:05] data_with_hash := SHA1(data) + data + (anumang random byte); tulad na ang haba ay katumbas ng 255 bytes;
encrypted_data := RSA(data_with_hash, server_public_key); ang isang 255-byte na mahabang numero (malaking endian) ay itinaas sa kinakailangang kapangyarihan sa kinakailangang modulus, at ang resulta ay iniimbak bilang isang 256-byte na numero.

Mayroon silang ilang dope DH

Hindi mukhang DH ng isang malusog na tao
Walang dalawang pampublikong susi sa dx

Buweno, sa huli ito ay inayos, ngunit isang nalalabi ang nanatili - patunay ng trabaho ay ginawa ng kliyente na nagawa niyang i-factor ang numero. Uri ng proteksyon laban sa mga pag-atake ng DoS. At ang RSA key ay ginagamit lamang ng isang beses sa isang direksyon, mahalagang para sa pag-encrypt new_nonce. Ngunit habang ang tila simpleng operasyon na ito ay magtatagumpay, ano ang kailangan mong harapin?

Vasily, [20.06.18/00/26 XNUMX:XNUMX] Hindi pa ako nakakarating sa appid request

Ipinadala ko ang kahilingang ito sa DH

At, sa transport dock sinasabi nito na maaari itong tumugon sa 4 bytes ng isang error code. Iyon lang

Well, sinabi niya sa akin -404, kaya ano?

Kaya sinabi ko sa kanya: "Mahuli ang iyong kalokohan na naka-encrypt gamit ang isang susi ng server na may fingerprint na tulad nito, gusto ko ng DH," at tumugon ito ng isang hangal na 404

Ano ang maiisip mo sa tugon ng server na ito? Anong gagawin? Walang magtatanong (ngunit higit pa doon sa ikalawang bahagi).

Dito ginagawa ang lahat ng interes sa pantalan

Wala naman akong ibang gagawin, nangarap lang akong mag-convert ng mga numero pabalik-balik

Dalawang 32 bit na numero. Inimpake ko sila tulad ng iba

Ngunit hindi, ang dalawang ito ay kailangang idagdag muna sa linya bilang BE

Vadim Goncharov, [20.06.18 15:49] at dahil dito 404?

Vasily, [20.06.18 15:49] OO!

Vadim Goncharov, [20.06.18 15:50] kaya hindi ko maintindihan kung ano ang "hindi niya mahanap"

Vasily, [20.06.18 15:50] humigit-kumulang

Hindi ko mahanap ang ganoong decomposition sa prime factor%)

Ni hindi namin pinamahalaan ang pag-uulat ng error

Vasily, [20.06.18 20:18] Naku, meron ding MD5. Mayroon nang tatlong magkakaibang hash

Ang key fingerprint ay kinakalkula gaya ng sumusunod:

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

SHA1 at sha2

Kaya ilagay natin auth_key nakatanggap kami ng 2048 bits sa laki gamit ang Diffie-Hellman. Anong susunod? Susunod na natuklasan namin na ang mas mababang 1024 bits ng key na ito ay hindi ginagamit sa anumang paraan... ngunit pag-isipan muna natin ito sa ngayon. Sa hakbang na ito, mayroon kaming nakabahaging lihim sa server. Ang isang analogue ng session ng TLS ay naitatag, na isang napakamahal na pamamaraan. Ngunit wala pa ring alam ang server tungkol sa kung sino tayo! Hindi pa, actually. awtorisasyon. Yung. kung naisip mo sa mga tuntunin ng "login-password", tulad ng dati mong ginawa sa ICQ, o hindi bababa sa "login-key", tulad ng sa SSH (halimbawa, sa ilang gitlab/github). Nakatanggap kami ng anonymous. Paano kung sabihin sa amin ng server na "ang mga numero ng teleponong ito ay sineserbisyuhan ng isa pang DC"? O kahit na "ang iyong numero ng telepono ay pinagbawalan"? Ang pinakamahusay na magagawa natin ay panatilihin ang susi sa pag-asa na ito ay magiging kapaki-pakinabang at hindi mabulok sa oras na iyon.

Sa pamamagitan ng paraan, "natanggap" namin ito na may mga reserbasyon. Halimbawa, pinagkakatiwalaan ba natin ang server? Paano kung peke? Kakailanganin ang mga pagsusuri sa cryptographic:

Vasily, [21.06.18 17:53] Nag-aalok sila ng mga mobile client na suriin ang isang 2kbit na numero para sa primality%)

Ngunit ito ay hindi malinaw sa lahat, nafeijoa

Vasily, [21.06.18 18:02] Ang dokumento ay hindi nagsasabi kung ano ang gagawin kung ito ay lumabas na hindi simple

Hindi sinabi. Tingnan natin kung ano ang ginagawa ng opisyal na Android client sa kasong ito? A yan yan (at oo, ang buong file ay kawili-wili) - tulad ng sinasabi nila, iiwan ko lang ito dito:

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

Hindi, siyempre nandiyan pa rin ang ilan May mga pagsubok para sa primality ng isang numero, ngunit sa personal ay wala na akong sapat na kaalaman sa matematika.

Okay, nakuha namin ang master key. Upang mag-log in, i.e. magpadala ng mga kahilingan, kailangan mong magsagawa ng karagdagang pag-encrypt, gamit ang AES.

Ang message key ay tinukoy bilang ang 128 middle bits ng SHA256 ng message body (kabilang ang session, message ID, atbp.), kasama ang padding byte, na prepended ng 32 byte na kinuha mula sa authorization key.

Vasily, [22.06.18 14:08] Average, bitch, bits

Nakuha mo na auth_key. Lahat. Higit pa sa kanila... hindi malinaw sa dokumento. Huwag mag-atubiling pag-aralan ang open source code.

Tandaan na ang MTProto 2.0 ay nangangailangan ng mula 12 hanggang 1024 bytes ng padding, napapailalim pa rin sa kondisyon na ang magreresultang haba ng mensahe ay mahahati ng 16 bytes.

Kaya gaano karaming padding ang dapat mong idagdag?

At oo, mayroon ding 404 kung sakaling magka-error

Kung may maingat na pinag-aralan ang diagram at teksto ng dokumentasyon, napansin nila na walang MAC doon. At ang AES na iyon ay ginagamit sa isang partikular na IGE mode na hindi ginagamit kahit saan pa. Sila, siyempre, ay nagsusulat tungkol dito sa kanilang FAQ... Dito, tulad ng, ang message key mismo ay ang SHA hash din ng na-decrypt na data, na ginagamit upang suriin ang integridad - at sa kaso ng hindi pagkakatugma, ang dokumentasyon para sa ilang kadahilanan Inirerekomenda ng tahimik na huwag pansinin ang mga ito (ngunit paano ang tungkol sa seguridad, paano kung masira nila tayo?).

Hindi ako isang cryptographer, marahil walang mali sa mode na ito sa kasong ito mula sa isang teoretikal na pananaw. Ngunit maaari kong malinaw na pangalanan ang isang praktikal na problema, gamit ang Telegram Desktop bilang isang halimbawa. Ini-encrypt nito ang lokal na cache (lahat ng D877F783D5D3EF8C na ito) sa parehong paraan tulad ng mga mensahe sa MTProto (sa kasong ito bersyon 1.0 lamang), i.e. una ang susi ng mensahe, pagkatapos ay ang data mismo (at sa tabi-tabi ang pangunahing malaki auth_key 256 bytes, kung wala ito msg_key walang silbi). Kaya, ang problema ay nagiging kapansin-pansin sa malalaking file. Ibig sabihin, kailangan mong panatilihin ang dalawang kopya ng data - naka-encrypt at naka-decrypt. At kung mayroong mga megabytes, o streaming video, halimbawa?.. Ang mga klasikong scheme na may MAC pagkatapos ng ciphertext ay nagbibigay-daan sa iyo na basahin ito ng stream, kaagad na ipinadala ito. Ngunit sa MTProto kakailanganin mong gawin sa una i-encrypt o i-decrypt ang buong mensahe, pagkatapos lamang ilipat ito sa network o sa disk. Samakatuwid, sa pinakabagong mga bersyon ng Telegram Desktop sa cache sa user_data Ang isa pang format ay ginagamit din - na may AES sa CTR mode.

Vasily, [21.06.18 01:27] Oh, nalaman ko kung ano ang IGE: IGE ang unang pagtatangka sa isang "mode ng pag-encrypt ng pagpapatunay," na orihinal para sa Kerberos. Ito ay isang nabigong pagtatangka (hindi ito nagbibigay ng proteksyon sa integridad), at kailangang alisin. Iyon ang simula ng isang 20 taong paghahanap para sa isang nagpapatunay na mode ng pag-encrypt na gumagana, na kamakailan ay nagtapos sa mga mode tulad ng OCB at GCM.

At ngayon ang mga argumento mula sa gilid ng cart:

Ang koponan sa likod ng Telegram, na pinamumunuan ni Nikolai Durov, ay binubuo ng anim na ACM champion, kalahati sa kanila ay Ph.D sa math. Tumagal sila ng humigit-kumulang dalawang taon upang mailunsad ang kasalukuyang bersyon ng MTProto.

Nakakatawa iyan. Dalawang taon sa mababang antas

O maaari kang kumuha ng tls

Okay, sabihin nating nagawa na namin ang pag-encrypt at iba pang mga nuances. Posible bang magpadala ng mga kahilingang naka-serialize sa TL at deserialize ang mga tugon? Kaya ano at paano mo dapat ipadala? Narito, sabihin natin, ang pamamaraan initConnection, baka ito na?

Vasily, [25.06.18 18:46] Nagsisimula ng koneksyon at nagse-save ng impormasyon sa device at application ng user.

Tumatanggap ito ng app_id, device_model, system_version, app_version at lang_code.

At ilang katanungan

Dokumentasyon gaya ng dati. Huwag mag-atubiling pag-aralan ang open source

Kung ang lahat ay tinatayang malinaw sa invokeWithLayer, ano ang mali dito? Lumalabas, sabihin nating mayroon na kami - ang kliyente ay mayroon nang itatanong sa server tungkol sa - may kahilingan na gusto naming ipadala:

Vasily, [25.06.18 19:13] Sa paghusga sa code, ang unang tawag ay nakabalot sa crap na ito, at ang crap mismo ay nakabalot sa invokewithlayer

Bakit hindi maaaring maging hiwalay na tawag ang initConnection, ngunit dapat ay isang wrapper? Oo, tulad ng nangyari, dapat itong gawin sa bawat oras sa simula ng bawat sesyon, at hindi isang beses, tulad ng sa pangunahing susi. Ngunit! Hindi ito matatawag ng hindi awtorisadong gumagamit! Ngayon ay umabot na tayo sa yugto kung saan ito ay naaangkop Itong isa pahina ng dokumentasyon - at sinasabi nito sa amin na...

Maliit na bahagi lamang ng mga pamamaraan ng API ang magagamit sa mga hindi awtorisadong user:

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

Ang pinaka una sa kanila, auth.sendCode, at mayroong pinakamahalagang unang kahilingan kung saan ipinapadala namin ang api_id at api_hash, at pagkatapos nito ay nakatanggap kami ng SMS na may code. At kung kami ay nasa maling DC (halimbawa, ang mga numero ng telepono sa bansang ito ay pinaglilingkuran ng iba), pagkatapos ay makakatanggap kami ng isang error sa numero ng nais na DC. Upang malaman kung aling IP address sa pamamagitan ng numero ng DC ang kailangan mong kumonekta, tulungan kami help.getConfig. Sa isang pagkakataon mayroon lamang 5 mga entry, ngunit pagkatapos ng mga sikat na kaganapan ng 2018, ang bilang ay tumaas nang malaki.

Ngayon tandaan natin na nakarating tayo sa yugtong ito sa server nang hindi nagpapakilala. Hindi ba masyadong mahal ang pagkuha lang ng IP address? Bakit hindi gawin ito, at iba pang mga operasyon, sa hindi naka-encrypt na bahagi ng MTProto? Naririnig ko ang pagtutol: "paano natin matitiyak na hindi RKN ang sasagot ng mga maling address?" Dito naaalala namin na, sa pangkalahatan, ang mga opisyal na kliyente Ang mga RSA key ay naka-embed, ibig sabihin. pwede ba tanda impormasyong ito. Sa totoo lang, ginagawa na ito para sa impormasyon sa pag-bypass ng blocking na natatanggap ng mga kliyente sa pamamagitan ng ibang mga channel (logically, hindi ito magagawa sa MTProto mismo; kailangan mo ring malaman kung saan kumonekta).

OK. Sa yugtong ito ng awtorisasyon ng kliyente, hindi pa kami awtorisado at hindi pa namin nairehistro ang aming aplikasyon. Gusto lang naming makita sa ngayon kung ano ang tugon ng server sa mga pamamaraan na magagamit sa isang hindi awtorisadong gumagamit. At dito…

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

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

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

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

Sa scheme, ang una ay pumapangalawa

Sa tdesktop schema ang pangatlong halaga ay

Oo, mula noon, siyempre, ang dokumentasyon ay na-update. Bagama't sa lalong madaling panahon maaari itong maging hindi nauugnay muli. Paano dapat malaman ng isang baguhan na developer? Siguro kung irehistro mo ang iyong aplikasyon, ipaalam nila sa iyo? Ginawa ito ni Vasily, ngunit sayang, wala silang ipinadala sa kanya (muli, pag-uusapan natin ito sa pangalawang bahagi).

...Napansin mo na kahit papaano ay lumipat na kami sa API, i.e. sa susunod na antas, at may napalampas sa paksa ng MTProto? Walang sorpresa:

Vasily, [28.06.18 02:04] Mm, hinahalukay nila ang ilan sa mga algorithm sa e2e

Tinutukoy ng Mtproto ang mga algorithm at key ng pag-encrypt para sa parehong mga domain, pati na rin ang kaunting istraktura ng wrapper

Ngunit patuloy silang naghahalo ng iba't ibang antas ng stack, kaya hindi laging malinaw kung saan natapos ang mtproto at nagsimula ang susunod na antas

Paano sila naghahalo? Well, narito ang parehong pansamantalang susi para sa PFS, halimbawa (sa pamamagitan ng paraan, hindi ito magagawa ng Telegram Desktop). Ito ay isinasagawa sa pamamagitan ng isang kahilingan sa API auth.bindTempAuthKey, ibig sabihin. mula sa pinakamataas na antas. Ngunit sa parehong oras nakakasagabal ito sa pag-encrypt sa mas mababang antas - pagkatapos nito, halimbawa, kailangan mong gawin itong muli initConnection atbp., hindi ito lamang normal na kahilingan. Ang espesyal din ay maaari kang magkaroon lamang ng ISANG pansamantalang susi sa bawat DC, kahit na ang field auth_key_id sa bawat mensahe ay nagbibigay-daan sa iyo na baguhin ang susi ng hindi bababa sa bawat mensahe, at ang server ay may karapatang "kalimutan" ang pansamantalang susi anumang oras - hindi sinasabi ng dokumentasyon kung ano ang gagawin sa kasong ito... mabuti, bakit Hindi ba mayroon kang ilang mga susi, tulad ng isang hanay ng mga hinaharap na asin, at ?..

Mayroong ilang iba pang mga bagay na dapat tandaan tungkol sa tema ng MTProto.

Mga mensahe ng mensahe, msg_id, msg_seqno, mga pagkumpirma, pag-ping sa maling direksyon at iba pang mga idiosyncrasie

Bakit kailangan mong malaman ang tungkol sa kanila? Dahil "tumagas" sila sa mas mataas na antas, at kailangan mong malaman ang mga ito kapag nagtatrabaho sa API. Ipagpalagay natin na hindi tayo interesado sa msg_key; ang mas mababang antas ay na-decrypt ang lahat para sa atin. Ngunit sa loob ng naka-decrypt na data mayroon kaming mga sumusunod na field (gayundin ang haba ng data, kaya alam namin kung nasaan ang padding, ngunit hindi iyon mahalaga):

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

Paalalahanan ka namin na mayroon lamang isang asin para sa buong DC. Bakit alam ang tungkol sa kanya? Hindi lang dahil may hiling get_future_salts, na nagsasabi sa iyo kung aling mga pagitan ang magiging wasto, ngunit dahil din kung ang iyong asin ay "bulok", kung gayon ang mensahe (kahilingan) ay mawawala lang. Siyempre, iuulat ng server ang bagong asin sa pamamagitan ng pag-isyu new_session_created - ngunit sa luma, kakailanganin mong ipadala muli ito kahit papaano, halimbawa. At ang isyung ito ay nakakaapekto sa arkitektura ng application.

Ang server ay pinapayagang mag-drop ng mga session nang buo at tumugon sa ganitong paraan para sa maraming dahilan. Sa totoo lang, ano ang isang MTProto session mula sa panig ng kliyente? Ito ay dalawang numero session_id ΠΈ seq_no mga mensahe sa loob ng session na ito. Well, at ang pinagbabatayan na koneksyon ng TCP, siyempre. Sabihin nating hindi pa rin alam ng aming kliyente kung paano gumawa ng maraming bagay, nadiskonekta siya at muling nakipag-ugnayan. Kung mabilis itong nangyari - nagpatuloy ang lumang session sa bagong koneksyon sa TCP, tumaas seq_no karagdagang. Kung magtatagal ito, maaaring tanggalin ito ng server, dahil sa gilid nito ay isang pila rin ito, tulad ng nalaman namin.

Ano ba dapat seq_no? Oh, nakakalito na tanong iyan. Subukang tapat na maunawaan kung ano ang ibig sabihin:

Mensahe na may kaugnayan sa nilalaman

Isang mensahe na nangangailangan ng tahasang pagkilala. Kabilang dito ang lahat ng user at maraming mensahe ng serbisyo, halos lahat maliban sa mga lalagyan at pagkilala.

Numero ng Sequence ng Mensahe (msg_seqno)

Isang 32-bit na numero na katumbas ng dalawang beses ang bilang ng mga mensaheng "kaugnay ng nilalaman" (ang mga nangangailangan ng pagkilala, at partikular ang mga hindi lalagyan) na ginawa ng nagpadala bago ang mensaheng ito at pagkatapos ay dinagdagan ng isa kung ang kasalukuyang mensahe ay isang mensaheng nauugnay sa nilalaman. Ang isang lalagyan ay palaging nabuo pagkatapos ng buong nilalaman nito; samakatuwid, ang sequence number nito ay mas malaki kaysa o katumbas ng sequence number ng mga mensaheng nakapaloob dito.

Anong uri ng sirko ito na may pagtaas ng 1, at pagkatapos ay isa pa ng 2?.. Pinaghihinalaan ko na sa simula ay ang ibig nilang sabihin ay β€œthe least significant bit for ACK, the rest is a number”, ngunit ang resulta ay hindi magkapareho - sa partikular, ito ay lumabas, maaaring ipadala ilan mga kumpirmasyon na may pareho seq_no! Paano? Buweno, halimbawa, ang server ay nagpapadala sa amin ng isang bagay, ipinapadala ito, at kami mismo ay nananatiling tahimik, tumutugon lamang sa mga mensahe ng serbisyo na nagpapatunay sa pagtanggap ng mga mensahe nito. Sa kasong ito, ang aming mga papalabas na kumpirmasyon ay magkakaroon ng parehong papalabas na numero. Kung pamilyar ka sa TCP at naisip mo na ito ay parang ligaw, ngunit tila hindi masyadong ligaw, dahil sa TCP seq_no ay hindi nagbabago, ngunit ang kumpirmasyon ay napupunta sa seq_no sa kabilang banda, bibilisan kita para magalit. Ang mga kumpirmasyon ay ibinibigay sa MTProto HINDI sa seq_no, tulad ng sa TCP, ngunit sa pamamagitan ng msg_id !

Ano ito msg_id, ang pinakamahalaga sa mga field na ito? Isang natatanging identifier ng mensahe, gaya ng iminumungkahi ng pangalan. Ito ay tinukoy bilang isang 64-bit na numero, ang pinakamababang bits nito ay muling mayroong magic na "server-not-server", at ang iba ay isang Unix timestamp, kasama ang fractional na bahagi, na inilipat ang 32 bits sa kaliwa. Yung. timestamp per se (at ang mga mensahe na may mga oras na masyadong naiiba ay tatanggihan ng server). Mula dito lumalabas na sa pangkalahatan ito ay isang identifier na pandaigdigan para sa kliyente. Given na - tandaan natin session_id - kami ay garantisadong: Sa ilalim ng anumang pagkakataon ay maaaring maipadala ang isang mensahe para sa isang session sa ibang session. Ibig sabihin, meron na pala tatlo antas - session, numero ng session, id ng mensahe. Bakit sobrang komplikasyon, napakahusay ng misteryong ito.

Kaya, msg_id kailangan para...

RPC: mga kahilingan, tugon, mga error. Mga kumpirmasyon.

Gaya ng napansin mo, walang espesyal na uri o function na "gumawa ng RPC request" saanman sa diagram, bagama't may mga sagot. Pagkatapos ng lahat, mayroon kaming mga mensaheng nauugnay sa nilalaman! Yan ay, kahit ano ang mensahe ay maaaring isang kahilingan! O hindi maging. Pagkatapos ng lahat, ng bawat isa mayroon msg_id. Ngunit may mga sagot:

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

Dito ipinapahiwatig kung saang mensahe ito katugon. Samakatuwid, sa pinakamataas na antas ng API, kailangan mong tandaan kung ano ang bilang ng iyong kahilingan - Sa palagay ko ay hindi na kailangang ipaliwanag na ang gawain ay asynchronous, at maaaring mayroong ilang mga kahilingan na isinasagawa sa parehong oras, ang mga sagot na maaaring ibalik sa anumang pagkakasunud-sunod? Sa prinsipyo, mula dito at mga mensahe ng error na tulad ng walang mga manggagawa, ang arkitektura sa likod nito ay maaaring masubaybayan: ang server na nagpapanatili ng koneksyon sa TCP sa iyo ay isang front-end balancer, ipinapasa nito ang mga kahilingan sa mga backend at kinokolekta ang mga ito pabalik sa pamamagitan ng message_id. Tila ang lahat dito ay malinaw, lohikal at mabuti.

Oo?.. At kung iisipin mo? Pagkatapos ng lahat, ang tugon ng RPC mismo ay mayroon ding isang larangan msg_id! Kailangan ba nating sumigaw sa server ng "hindi mo sinasagot ang sagot ko!"? At oo, ano ang mayroon tungkol sa mga kumpirmasyon? Tungkol sa pahina mga mensahe tungkol sa mga mensahe nagsasabi sa amin kung ano ang

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

at dapat itong gawin ng bawat panig. Ngunit hindi palagi! Kung nakatanggap ka ng RpcResult, ito mismo ang nagsisilbing kumpirmasyon. Iyon ay, maaaring tumugon ang server sa iyong kahilingan gamit ang MsgsAck - tulad ng, "Natanggap ko ito." Maaaring tumugon kaagad ang RpcResult. Maaaring pareho.

At oo, kailangan mo pa ring sagutin ang sagot! Kumpirmasyon. Kung hindi, ituturing ng server na hindi ito maihahatid at ibabalik itong muli sa iyo. Kahit na pagkatapos ng muling pagkakakonekta. Ngunit dito, siyempre, ang isyu ng mga timeout ay lumitaw. Tingnan natin ang mga ito sa ibang pagkakataon.

Pansamantala, tingnan natin ang mga posibleng error sa pagpapatupad ng query.

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

Naku, may magbubulalas, narito ang mas makataong pormat - may linya! Huwag kang mag-madali. Dito listahan ng mga error, pero syempre hindi kumpleto. Mula dito natutunan natin na ang code ay isang bagay tulad ng Mga error sa HTTP (mabuti, siyempre, ang mga semantika ng mga tugon ay hindi iginagalang, sa ilang mga lugar sila ay ibinahagi nang random sa mga code), at ang linya ay parang CAPITAL_LETTERS_AND_NUMBERS. Halimbawa, PHONE_NUMBER_OCCUPIED o FILE_PART_Π₯_MISSING. Well, iyon ay, kakailanganin mo pa rin ang linyang ito pag-parse. Halimbawa FLOOD_WAIT_3600 ay nangangahulugan na kailangan mong maghintay ng isang oras, at PHONE_MIGRATE_5, na ang isang numero ng telepono na may prefix na ito ay dapat na nakarehistro sa 5th DC. Mayroon tayong isang uri ng wika, tama ba? Hindi namin kailangan ng argumento mula sa isang string, gagawin ng mga regular, okay.

Muli, wala ito sa pahina ng mga mensahe ng serbisyo, ngunit, gaya ng nakasanayan na sa proyektong ito, mahahanap ang impormasyon sa isa pang pahina ng dokumentasyon. O kaya magbigay ng hinala. Una, tingnan, paglabag sa pagta-type/layer - RpcError maaaring pugad RpcResult. Bakit hindi sa labas? Ano ang hindi natin isinaalang-alang?.. Alinsunod dito, nasaan ang garantiya na RpcError maaaring HINDI naka-embed sa RpcResult, ngunit direkta o naka-nest sa ibang uri?.. At kung hindi ito magagawa, bakit wala ito sa pinakamataas na antas, i.e. ito ay nawawala req_msg_id ? ..

Ngunit magpatuloy tayo tungkol sa mga mensahe ng serbisyo. Maaaring isipin ng kliyente na ang server ay nag-iisip nang mahabang panahon at gumawa ng napakagandang kahilingang ito:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Mayroong tatlong posibleng sagot sa tanong na ito, muling sumasalubong sa mekanismo ng kumpirmasyon; sinusubukang unawain kung ano dapat ang mga ito (at kung ano ang pangkalahatang listahan ng mga uri na hindi nangangailangan ng kumpirmasyon) ay iniiwan sa mambabasa bilang takdang-aralin (tandaan: ang impormasyon sa hindi kumpleto ang source code ng Telegram Desktop).

Pagkagumon sa droga: mga katayuan ng mensahe

Sa pangkalahatan, maraming lugar sa TL, MTProto at Telegram sa pangkalahatan ay nag-iiwan ng pakiramdam ng katigasan ng ulo, ngunit dahil sa pagiging magalang, taktika at iba pa malambot na kasanayan Magalang kaming tumahimik tungkol dito, at sininsor ang mga kahalayan sa mga diyalogo. Gayunpaman, ang lugar na itoОkaramihan sa page ay tungkol sa mga mensahe tungkol sa mga mensahe Nakakagulat kahit para sa akin, na nagtatrabaho sa mga protocol ng network sa loob ng mahabang panahon at nakakita ng mga bisikleta na may iba't ibang antas ng baluktot.

Nagsisimula ito nang hindi nakapipinsala, na may mga kumpirmasyon. Susunod na sasabihin nila sa amin

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;

Buweno, lahat ng nagsimulang magtrabaho kasama ang MTProto ay kailangang harapin ang mga ito; sa ikot ng "naitama - muling pinagsama - inilunsad", ang pagkuha ng mga error sa numero o asin na nagawang maging masama sa panahon ng mga pag-edit ay isang pangkaraniwang bagay. Gayunpaman, mayroong dalawang punto dito:

  1. Nangangahulugan ito na ang orihinal na mensahe ay nawala. Kailangan nating gumawa ng ilang pila, titingnan natin iyon mamaya.
  2. Ano ang mga kakaibang numero ng error na ito? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... nasaan na ang ibang numero, Tommy?

Ang dokumentasyon ay nagsasaad:

Ang layunin ay ang mga halaga ng error_code ay pinagsama-sama (error_code >> 4): halimbawa, ang mga code 0x40 β€” 0x4f ay tumutugma sa mga error sa pagkabulok ng lalagyan.

ngunit, una, isang paglilipat sa kabilang direksyon, at pangalawa, hindi mahalaga, nasaan ang iba pang mga code? Sa ulo ng may-akda?.. Gayunpaman, ang mga ito ay walang kabuluhan.

Nagsisimula ang pagkagumon sa mga mensahe tungkol sa mga status ng mensahe at mga kopya ng mensahe:

  • Kahilingan para sa Impormasyon sa Katayuan ng Mensahe
    Kung ang alinmang partido ay hindi nakatanggap ng impormasyon sa katayuan ng mga papalabas na mensahe nito sa loob ng ilang sandali, maaari itong tahasang humiling nito mula sa kabilang partido:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Mensaheng Pang-impormasyon tungkol sa Katayuan ng Mga Mensahe
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Dito, info ay isang string na naglalaman ng eksaktong isang byte ng status ng mensahe para sa bawat mensahe mula sa papasok na listahan ng msg_ids:

    • 1 = walang alam tungkol sa mensahe (masyadong mababa ang msg_id, maaaring nakalimutan ito ng kabilang partido)
    • 2 = hindi natanggap ang mensahe (napapaloob ang msg_id sa hanay ng mga nakaimbak na identifier; gayunpaman, ang kabilang partido ay tiyak na hindi nakatanggap ng mensaheng ganoon)
    • 3 = hindi natanggap ang mensahe (masyadong mataas ang msg_id; gayunpaman, tiyak na hindi pa ito natatanggap ng kabilang partido)
    • 4 = natanggap na mensahe (tandaan na ang tugon na ito ay kasabay din ng pagkilala sa resibo)
    • +8 = kinikilala na ang mensahe
    • +16 = mensaheng hindi nangangailangan ng pagkilala
    • +32 = RPC query na nakapaloob sa mensaheng pinoproseso o kumpleto na ang pagproseso
    • +64 = tugon na nauugnay sa nilalaman sa mensaheng nabuo na
    • +128 = alam ng ibang partido na natanggap na ang mensahe
      Ang tugon na ito ay hindi nangangailangan ng pagkilala. Ito ay isang pagkilala sa nauugnay na msgs_state_req, sa sarili nito.
      Tandaan na kung biglang lumabas na ang kabilang partido ay walang mensahe na mukhang ipinadala dito, ang mensahe ay maaaring ipadalang muli. Kahit na ang kabilang partido ay dapat makatanggap ng dalawang kopya ng mensahe sa parehong oras, ang duplicate ay hindi papansinin. (Kung masyadong maraming oras ang lumipas, at ang orihinal na msg_id ay hindi na wasto, ang mensahe ay balot sa msg_copy).
  • Kusang Komunikasyon ng Katayuan ng mga Mensahe
    Maaaring boluntaryong ipaalam ng alinmang partido sa kabilang partido ang katayuan ng mga mensaheng ipinadala ng kabilang partido.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Pinahabang Kusang Komunikasyon ng Katayuan ng Isang Mensahe
    ...
    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;
  • Tahasang Kahilingan na Muling Magpadala ng Mga Mensahe
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    Ang malayong partido ay agad na tumugon sa pamamagitan ng muling pagpapadala ng mga hiniling na mensahe […]
  • Tahasang Kahilingan na Muling Ipadala ang Mga Sagot
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    Ang malayong partido ay agad na tumugon sa pamamagitan ng muling pagpapadala sagot sa mga hiniling na mensahe […]
  • Mga Kopya ng Mensahe
    Sa ilang mga sitwasyon, ang isang lumang mensahe na may msg_id na hindi na wasto ay kailangang muling ipadala. Pagkatapos, ito ay nakabalot sa isang lalagyan ng kopya:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Kapag natanggap, ang mensahe ay pinoproseso na parang wala doon ang wrapper. Gayunpaman, kung tiyak na alam na ang mensaheng orig_message.msg_id ay natanggap, ang bagong mensahe ay hindi naproseso (habang kasabay nito, ito at ang orig_message.msg_id ay kinikilala). Ang halaga ng orig_message.msg_id ay dapat na mas mababa kaysa sa msg_id ng container.

Tahimik pa nga tayo kung ano msgs_state_info muli ang mga tainga ng hindi natapos na TL ay lumalabas (kailangan namin ng isang vector ng mga byte, at sa ibabang dalawang bit ay mayroong isang enum, at sa mas mataas na dalawang bit ay may mga flag). Iba ang punto. Mayroon bang nakakaunawa kung bakit ang lahat ng ito ay nasa pagsasanay? sa totoong kliyente kailangan?.. Sa kahirapan, ngunit maaari isa isipin ang ilang mga benepisyo kung ang isang tao ay nakikibahagi sa pag-debug, at sa isang interactive na mode - tanungin ang server kung ano at paano. Ngunit dito inilarawan ang mga kahilingan Papunta at pabalik.

Sinusunod nito na ang bawat partido ay hindi lamang dapat mag-encrypt at magpadala ng mga mensahe, ngunit mag-imbak din ng data tungkol sa kanilang sarili, tungkol sa mga tugon sa kanila, para sa hindi kilalang tagal ng panahon. Hindi inilalarawan ng dokumentasyon ang alinman sa mga timing o ang praktikal na pagkakalapat ng mga feature na ito. sa hindi pahintulutang paraan. Ano ang pinaka-kamangha-manghang ay na ang mga ito ay aktwal na ginagamit sa code ng mga opisyal na kliyente! Tila may sinabi sa kanila na hindi kasama sa pampublikong dokumentasyon. Unawain mula sa code bakit, ay hindi na kasing simple ng kaso ng TL - hindi ito isang (medyo) lohikal na nakahiwalay na bahagi, ngunit isang piraso na nakatali sa arkitektura ng application, i.e. mangangailangan ng mas maraming oras upang maunawaan ang code ng aplikasyon.

Mga ping at timing. Mga pila.

Mula sa lahat, kung naaalala natin ang mga hula tungkol sa arkitektura ng server (pamamahagi ng mga kahilingan sa mga backend), isang medyo malungkot na bagay ang sumusunod - sa kabila ng lahat ng mga garantiya sa paghahatid sa TCP (alinman ang data ay naihatid, o ipaalam sa iyo ang tungkol sa puwang, ngunit ang data ay ihahatid bago mangyari ang problema), na ang mga kumpirmasyon sa MTProto mismo - walang garantiya. Ang server ay madaling mawala o itapon ang iyong mensahe, at walang magagawa tungkol dito, gumamit lamang ng iba't ibang uri ng saklay.

At una sa lahat - mga pila ng mensahe. Buweno, sa isang bagay ay halata ang lahat mula pa sa simula - isang hindi nakumpirmang mensahe ay dapat na itago at magalit. At pagkatapos ng anong oras? At kilala siya ng jester. Marahil ang mga nakakahumaling na mensahe ng serbisyo sa anumang paraan ay malulutas ang problemang ito sa mga saklay, sabihin nating, sa Telegram Desktop mayroong mga 4 na pila na naaayon sa kanila (marahil higit pa, tulad ng nabanggit na, para dito kailangan mong suriin ang code at arkitektura nito nang mas seryoso; sa parehong oras, alam namin na hindi ito maaaring kunin bilang isang sample; isang tiyak na bilang ng mga uri mula sa pamamaraan ng MTProto ay hindi ginagamit dito).

Bakit ito nangyayari? Marahil, ang mga programmer ng server ay hindi matiyak ang pagiging maaasahan sa loob ng kumpol, o kahit na buffering sa front balancer, at inilipat ang problemang ito sa kliyente. Dahil sa kawalan ng pag-asa, sinubukan ni Vasily na magpatupad ng alternatibong opsyon, na may dalawang pila lamang, gamit ang mga algorithm mula sa TCP - pagsukat ng RTT sa server at pagsasaayos ng laki ng "window" (sa mga mensahe) depende sa bilang ng mga hindi kumpirmadong kahilingan. Iyon ay, ang isang magaspang na heuristic para sa pagtatasa ng load ng server ay kung gaano karami sa aming mga kahilingan ang maaari nitong nguyain nang sabay at hindi mawawala.

Well, iyon ay, naiintindihan mo, tama? Kung kailangan mong ipatupad muli ang TCP sa ibabaw ng isang protocol na tumatakbo sa TCP, ito ay nagpapahiwatig ng isang napakagandang disenyong protocol.

Oh oo, bakit kailangan mo ng higit sa isang pila, at ano ang ibig sabihin nito para sa isang taong nagtatrabaho sa isang mataas na antas ng API pa rin? Tingnan mo, gumawa ka ng isang kahilingan, i-serialize ito, ngunit madalas hindi mo ito maipadala kaagad. Bakit? Dahil ang magiging sagot msg_id, na pansamantalaΠ°Ako ay isang label, ang pagtatalaga kung saan ay pinakamahusay na ipagpaliban hanggang sa huli hangga't maaari - kung sakaling tanggihan ito ng server dahil sa hindi pagkakatugma ng oras sa pagitan namin at niya (siyempre, maaari tayong gumawa ng saklay na nagbabago ng ating oras mula sa kasalukuyan sa server sa pamamagitan ng pagdaragdag ng delta na kinakalkula mula sa mga tugon ng server - ginagawa ito ng mga opisyal na kliyente, ngunit ito ay krudo at hindi tumpak dahil sa buffering). Samakatuwid, kapag humiling ka gamit ang lokal na function na tawag mula sa library, ang mensahe ay dumaan sa mga sumusunod na yugto:

  1. Ito ay nasa isang pila at naghihintay ng pag-encrypt.
  2. Hinirang msg_id at ang mensahe ay napunta sa isa pang pila - posibleng pagpapasa; ipadala sa socket.
  3. a) Ang server ay tumugon sa MsgsAck - ang mensahe ay naihatid, tinanggal namin ito mula sa "ibang pila".
    b) O vice versa, may hindi siya nagustuhan, sumagot siya ng badmsg - resend mula sa "isa pang pila"
    c) Walang alam, ang mensahe ay kailangang mainis mula sa isa pang pila - ngunit hindi ito eksaktong alam kung kailan.
  4. Sa wakas ay tumugon ang server RpcResult - ang aktwal na tugon (o error) - hindi lamang naihatid, ngunit naproseso din.

Marahil, ang paggamit ng mga lalagyan ay maaaring bahagyang malutas ang problema. Ito ay kapag ang isang grupo ng mga mensahe ay naka-pack sa isa, at ang server ay tumugon sa isang kumpirmasyon sa lahat ng mga ito nang sabay-sabay, sa isang msg_id. Ngunit tatanggihan din niya ang paketeng ito, kung may nangyaring mali, sa kabuuan nito.

At sa puntong ito pumapasok ang mga hindi teknikal na pagsasaalang-alang. Mula sa karanasan, nakakita tayo ng maraming saklay, at bilang karagdagan, makikita natin ngayon ang higit pang mga halimbawa ng masamang payo at arkitektura - sa ganitong mga kondisyon, sulit ba ang pagtitiwala at paggawa ng mga naturang desisyon? Ang tanong ay retorika (siyempre hindi).

Ano ang ating Pinag-uusapan? Kung sa paksang "mga mensahe ng droga tungkol sa mga mensahe" maaari ka pa ring mag-isip ng mga pagtutol tulad ng "tanga ka, hindi mo naintindihan ang aming napakatalino na plano!" (kaya isulat muna ang dokumentasyon, gaya ng dapat na mga normal na tao, na may katwiran at mga halimbawa ng pagpapalitan ng packet, pagkatapos ay pag-uusapan natin), pagkatapos ay ang mga timing/timeout ay isang praktikal at tiyak na tanong, lahat ng bagay dito ay kilala sa mahabang panahon. Ano ang sinasabi sa amin ng dokumentasyon tungkol sa mga timeout?

Karaniwang kinikilala ng isang server ang pagtanggap ng isang mensahe mula sa isang kliyente (karaniwan, isang query sa RPC) gamit ang isang tugon ng RPC. Kung matagal nang darating ang isang tugon, maaaring magpadala muna ang isang server ng isang pagkilala sa resibo, at sa ibang pagkakataon, ang tugon mismo ng RPC.

Karaniwang kinikilala ng isang kliyente ang pagtanggap ng isang mensahe mula sa isang server (kadalasan, isang tugon ng RPC) sa pamamagitan ng pagdaragdag ng isang pagkilala sa susunod na query ng RPC kung hindi ito naipadala nang huli (kung ito ay nabuo, halimbawa, 60-120 segundo pagkatapos ng resibo ng isang mensahe mula sa server). Gayunpaman, kung sa mahabang panahon ay walang dahilan upang magpadala ng mga mensahe sa server o kung mayroong isang malaking bilang ng mga hindi kinikilalang mensahe mula sa server (sabihin, higit sa 16), ang kliyente ay nagpapadala ng isang stand-alone na pagkilala.

... I translate: tayo mismo ay hindi alam kung magkano at kung paano natin ito kailangan, kaya ipagpalagay natin na hayaan itong maging ganito.

At tungkol sa mga ping:

Mga Mensahe sa Ping (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Karaniwang ibinabalik ang isang tugon sa parehong koneksyon:

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

Ang mga mensaheng ito ay hindi nangangailangan ng mga pagkilala. Ang isang pong ay ipinapadala lamang bilang tugon sa isang ping habang ang isang ping ay maaaring simulan ng magkabilang panig.

Ipinagpaliban ang Pagsara ng Koneksyon + PING

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

Gumagana tulad ng ping. Bilang karagdagan, pagkatapos na matanggap ito, magsisimula ang server ng timer na magsasara ng kasalukuyang koneksyon disconnect_delay segundo mamaya maliban kung makatanggap ito ng bagong mensahe ng parehong uri na awtomatikong nagre-reset sa lahat ng nakaraang timer. Kung ipinapadala ng kliyente ang mga ping na ito isang beses bawat 60 segundo, halimbawa, maaari itong magtakda ng disconnect_delay na katumbas ng 75 segundo.

Baliw ka ba?! Sa loob ng 60 segundo, ang tren ay papasok sa istasyon, bababa at susunduin ang mga pasahero, at muling mawawalan ng kontak sa lagusan. Sa loob ng 120 segundo, habang naririnig mo ito, darating ito sa isa pa, at malamang na masira ang koneksyon. Buweno, malinaw kung saan nanggagaling ang mga binti - "Nakarinig ako ng tugtog, ngunit hindi ko alam kung nasaan ito", mayroong algorithm ng Nagl at ang opsyon na TCP_NODELAY, na nilayon para sa interactive na trabaho. Ngunit, ipagpaumanhin mo, hawakan ang default na halaga nito - 200 Millisegundo Kung talagang gusto mong ilarawan ang isang katulad na bagay at makatipid sa ilang posibleng packet, pagkatapos ay ipagpaliban ito ng 5 segundo, o anuman ang "Nagta-type ng User..." ang timeout ng mensahe ngayon. Pero wala na.

At sa wakas, mga ping. Iyon ay, sinusuri ang kasiglahan ng koneksyon ng TCP. Nakakatawa, ngunit humigit-kumulang 10 taon na ang nakalilipas nagsulat ako ng kritikal na teksto tungkol sa messenger ng dorm ng aming faculty - ang mga may-akda doon ay nag-ping din sa server mula sa kliyente, at hindi kabaliktaran. Ngunit ang mga mag-aaral sa 3rd year ay isang bagay, at ang isang pang-internasyonal na tanggapan ay isa pa, tama?..

Una, isang maliit na programang pang-edukasyon. Ang isang koneksyon sa TCP, sa kawalan ng packet exchange, ay maaaring mabuhay nang ilang linggo. Ito ay kapwa mabuti at masama, depende sa layunin. Mabuti kung mayroon kang koneksyon sa SSH na bukas sa server, bumangon ka mula sa computer, na-reboot ang router para sa kapangyarihan, bumalik sa iyong lugar - ang session sa pamamagitan ng server na ito ay hindi napunit (hindi ka nag-type ng anuman, walang packet), ito ay maginhawa. Masama kung mayroong libu-libong kliyente sa server, bawat isa ay kumukuha ng mga mapagkukunan (hello, Postgres!), at ang host ng kliyente ay maaaring matagal nang nag-reboot - ngunit hindi namin malalaman ang tungkol dito.

Ang mga chat/IM system ay nasa pangalawang kaso para sa isang karagdagang dahilan - mga online na katayuan. Kung ang gumagamit ay "nahulog", kailangan mong ipaalam sa kanyang mga kausap ang tungkol dito. Kung hindi, magkakaroon ka ng isang pagkakamali na ginawa ng mga tagalikha ng Jabber (at itinama sa loob ng 20 taon) - ang user ay nadiskonekta, ngunit patuloy silang sumulat ng mga mensahe sa kanya, na naniniwalang siya ay online (na ganap ding nawala sa mga ito. ilang minuto bago natuklasan ang pagkakadiskonekta). Hindi, ang opsyong TCP_KEEPALIVE, na kung saan maraming tao na hindi nakakaintindi kung paano gumagana ang mga TCP timer, ay hindi makakatulong dito - kailangan mong tiyakin na hindi lamang ang OS kernel ng makina ng gumagamit ay buhay, ngunit gumagana din nang normal, sa kakayahang tumugon, at ang application mismo (sa palagay mo ba ay hindi ito maaaring mag-freeze? Ang Telegram Desktop sa Ubuntu 18.04 ay nagyelo para sa akin nang higit sa isang beses).

Kaya naman kailangan mong mag-ping server client, at hindi vice versa - kung gagawin ito ng kliyente, kung nasira ang koneksyon, hindi maihahatid ang ping, hindi makakamit ang layunin.

Ano ang nakikita natin sa Telegram? Ito ay eksaktong kabaligtaran! Well, iyon ay. Sa pormal, siyempre, ang magkabilang panig ay maaaring mag-ping sa isa't isa. Sa pagsasagawa, ang mga kliyente ay gumagamit ng saklay ping_delay_disconnect, na nagtatakda ng timer sa server. Well, excuse me, hindi nakasalalay sa kliyente kung gaano katagal niya gustong manirahan doon nang walang ping. Ang server, batay sa pagkarga nito, ay mas nakakaalam. Ngunit, siyempre, kung hindi mo iniisip ang mga mapagkukunan, ikaw ang magiging iyong sariling masamang Pinocchio, at isang saklay ang gagawa...

Paano dapat ito ay dinisenyo?

Naniniwala ako na ang mga katotohanan sa itaas ay malinaw na nagpapahiwatig na ang Telegram/VKontakte team ay hindi masyadong may kakayahan sa larangan ng transportasyon (at mas mababang) antas ng mga network ng computer at ang kanilang mababang kwalipikasyon sa mga nauugnay na bagay.

Bakit naging napakakomplikado nito, at paano masusubukan ng mga arkitekto ng Telegram na tumutol? Ang katotohanan na sinubukan nilang gumawa ng session na nakaligtas sa mga break ng koneksyon sa TCP, ibig sabihin, kung ano ang hindi naihatid ngayon, ihahatid namin sa ibang pagkakataon. Marahil ay sinubukan din nilang gumawa ng UDP transport, ngunit nakatagpo sila ng mga paghihirap at iniwan ito (kaya't walang laman ang dokumentasyon - walang dapat ipagmalaki). Ngunit dahil sa kakulangan ng pag-unawa sa kung paano gumagana ang mga network sa pangkalahatan at TCP sa partikular, kung saan maaari kang umasa dito, at kung saan kailangan mong gawin ito sa iyong sarili (at kung paano), at isang pagtatangka na pagsamahin ito sa cryptography "dalawang ibon na may isang bato”, ito ang resulta.

Paano ito kinailangan? Batay sa katotohanan na msg_id ay isang timestamp na kinakailangan mula sa isang cryptographic na punto ng view upang maiwasan ang mga pag-atake ng replay, ito ay isang pagkakamali na mag-attach ng isang natatanging identifier function dito. Samakatuwid, nang walang pangunahing pagbabago sa kasalukuyang arkitektura (kapag nabuo ang stream ng Mga Update, iyon ay isang mataas na antas na paksa ng API para sa isa pang bahagi ng serye ng mga post na ito), kakailanganin ng isa na:

  1. Ang server na may hawak ng TCP na koneksyon sa kliyente ay may pananagutan - kung ito ay nabasa mula sa socket, mangyaring kilalanin, iproseso o ibalik ang isang error, walang pagkawala. Kung gayon ang kumpirmasyon ay hindi isang vector ng mga id, ngunit simpleng "ang huling natanggap na seq_no" - isang numero lamang, tulad ng sa TCP (dalawang numero - ang iyong seq at ang nakumpirma). Lagi tayong nasa session, di ba?
  2. Ang timestamp para maiwasan ang pag-atake ng replay ay nagiging isang hiwalay na field, a la nonce. Sinusuri ito, ngunit hindi nakakaapekto sa anumang bagay. Sapat na at uint32 - kung ang aming asin ay nagbabago ng hindi bababa sa bawat kalahating araw, maaari kaming maglaan ng 16 na bit sa mga low-order na bit ng isang integer na bahagi ng kasalukuyang oras, ang natitira - sa isang fractional na bahagi ng isang segundo (tulad ng ngayon).
  3. Inalis msg_id sa lahat - mula sa punto ng view ng pagkilala sa mga kahilingan sa mga backend, mayroong, una, ang client id, at pangalawa, ang session id, pagsamahin ang mga ito. Alinsunod dito, isang bagay lamang ang sapat bilang isang identifier ng kahilingan seq_no.

Hindi rin ito ang pinakamatagumpay na opsyon; ang isang kumpletong random ay maaaring magsilbi bilang isang identifier - ito ay ginagawa na sa mataas na antas ng API kapag nagpapadala ng mensahe, nga pala. Mas mainam na ganap na gawing muli ang arkitektura mula sa kamag-anak hanggang sa ganap, ngunit ito ay isang paksa para sa isa pang bahagi, hindi ang post na ito.

API?

Ta-daam! Kaya, sa pamamagitan ng pakikibaka sa isang landas na puno ng sakit at saklay, sa wakas ay nakapagpadala kami ng anumang mga kahilingan sa server at nakatanggap ng anumang mga sagot sa kanila, pati na rin makatanggap ng mga update mula sa server (hindi bilang tugon sa isang kahilingan, ngunit ito mismo nagpapadala sa amin, tulad ng PUSH, kung sinuman ay mas malinaw sa ganoong paraan).

Pansin, ngayon ay magkakaroon ng tanging halimbawa sa Perl sa artikulo! (para sa mga hindi pamilyar sa syntax, ang unang argumento ng bless ay ang istraktura ng data ng object, ang pangalawa ay ang klase nito):

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

Oo, hindi sinasadyang spoiler - kung hindi mo pa ito nababasa, sige at gawin mo!

Oh, wai~~... ano ang hitsura nito? Isang bagay na napakapamilyar... marahil ito ang istraktura ng data ng isang tipikal na Web API sa JSON, maliban na ang mga klase ay naka-attach din sa mga bagay?..

Kaya ganito ang lumabas... Tungkol saan ang lahat, mga kasama?.. Napakaraming pagsisikap - at huminto kami upang magpahinga kung saan ang mga programmer sa Web nagsisimula pa lang?..Hindi ba mas simple lang ang JSON sa HTTPS?! Ano ang nakuha namin bilang kapalit? Worth it ba ang effort?

Suriin natin kung ano ang ibinigay sa atin ng TL+MTProto at kung anong mga alternatibo ang posible. Well, ang HTTP, na nakatutok sa modelo ng paghiling-tugon, ay hindi angkop, ngunit hindi bababa sa isang bagay sa itaas ng TLS?

Compact na serialization. Nakikita ang istraktura ng data na ito, katulad ng JSON, naaalala ko na may mga binary na bersyon nito. Markahan natin ang MsgPack bilang hindi sapat na mapalawak, ngunit mayroong, halimbawa, CBOR - sa pamamagitan ng paraan, isang pamantayang inilarawan sa RFC 7049. Ito ay kapansin-pansin para sa katotohanan na ito ay tumutukoy mga tag, bilang mekanismo ng pagpapalawak, at kabilang sa standardized na magagamit:

  • 25 + 256 - pinapalitan ang mga paulit-ulit na linya na may reference sa numero ng linya, tulad ng murang paraan ng compression
  • 26 - serialized Perl object na may pangalan ng klase at mga argumento ng constructor
  • 27 - serialized language-independent object na may type name at constructor arguments

Well, sinubukan kong i-serialize ang parehong data sa TL at sa CBOR na may pinaganang string at object packing. Ang resulta ay nagsimulang mag-iba pabor sa CBOR sa isang lugar mula sa isang megabyte:

cborlen=1039673 tl_len=1095092

Kaya, konklusyon: Mayroong mas simpleng mga format na hindi napapailalim sa problema ng pagkabigo sa pag-synchronize o hindi kilalang identifier, na may maihahambing na kahusayan.

Mabilis na pagtatatag ng koneksyon. Nangangahulugan ito na zero RTT pagkatapos ng muling pagkonekta (kapag ang susi ay nabuo nang isang beses) - naaangkop mula sa pinakaunang mensahe ng MTProto, ngunit sa ilang mga reserbasyon - pindutin ang parehong asin, ang session ay hindi bulok, atbp. Ano ang inaalok sa amin ng TLS sa halip? Quote sa paksa:

Kapag gumagamit ng PFS sa TLS, TLS session ticket (RFC 5077) upang ipagpatuloy ang isang naka-encrypt na sesyon nang hindi muling nakikipagnegosasyon sa mga susi at hindi nag-iimbak ng pangunahing impormasyon sa server. Kapag binubuksan ang unang koneksyon at lumilikha ng mga susi, ini-encrypt ng server ang estado ng koneksyon at ipinapadala ito sa kliyente (sa anyo ng isang tiket ng session). Alinsunod dito, kapag ang koneksyon ay ipinagpatuloy, ang kliyente ay nagpapadala ng isang tiket ng session, kasama ang session key, pabalik sa server. Ang ticket mismo ay naka-encrypt gamit ang isang pansamantalang key (session ticket key), na naka-imbak sa server at dapat na ipamahagi sa lahat ng frontend server na nagpoproseso ng SSL sa mga clustered na solusyon.[10]. Kaya, ang pagpapakilala ng isang tiket ng session ay maaaring lumabag sa PFS kung ang mga pansamantalang susi ng server ay nakompromiso, halimbawa, kapag sila ay nakaimbak nang mahabang panahon (OpenSSL, nginx, Apache ay nag-iimbak ng mga ito bilang default para sa buong tagal ng programa; ginagamit ng mga sikat na site ang susi sa loob ng ilang oras, hanggang sa mga araw).

Dito ang RTT ay hindi zero, kailangan mong makipagpalitan ng hindi bababa sa ClientHello at ServerHello, pagkatapos nito ang kliyente ay maaaring magpadala ng data kasama ng Tapos na. Ngunit narito dapat nating tandaan na wala tayong Web, kasama ang grupo ng mga bagong bukas na koneksyon, ngunit isang mensahero, na ang koneksyon ay madalas na isa at higit pa o mas mahaba ang buhay, medyo maikling mga kahilingan sa mga Web page - lahat ay multiplexed panloob. Iyon ay, medyo katanggap-tanggap kung hindi kami nakatagpo ng isang talagang masamang seksyon ng subway.

May ibang nakalimutan? Sumulat sa mga komento.

Upang magpatuloy!

Sa ikalawang bahagi ng serye ng mga post na ito ay isasaalang-alang namin hindi teknikal, ngunit mga isyu sa organisasyon - mga diskarte, ideolohiya, interface, saloobin sa mga gumagamit, atbp. Batay, gayunpaman, sa teknikal na impormasyon na ipinakita dito.

Ang ikatlong bahagi ay patuloy na susuriin ang teknikal na bahagi / karanasan sa pag-unlad. Matututo ka, lalo na:

  • pagpapatuloy ng pandemonium na may iba't ibang uri ng TL
  • hindi kilalang mga bagay tungkol sa mga channel at supergroup
  • bakit mas masahol pa sa roster ang mga dialog
  • tungkol sa absolute vs relative message addressing
  • ano ang pagkakaiba ng larawan at larawan
  • kung paano nakakasagabal ang emoji sa italic text

at iba pang saklay! Manatiling nakatutok!

Pinagmulan: www.habr.com

Magdagdag ng komento