Telegramen protokoloaren eta antolaketa planteamenduaren kritika. 1. zatia, teknikoa: bezero bat hutsetik idazteko esperientzia - TL, MT

Duela gutxi, Telegram-en zein ona den, Durov anaiek sare-sistemak eraikitzen duten distira eta eskarmentua duten argitalpenak gehiago hasi dira HabrΓ©-n agertzen. Aldi berean, oso jende gutxi benetan murgildu da gailu teknikoan - gehienez ere, nahiko sinplea (eta MTProto-tik nahiko desberdina) JSON-en oinarritutako Bot API bat erabiltzen dute, eta normalean onartzen dute. fedearen gainean mezulariaren inguruan dabiltzan laudorio eta PR guztiak. Duela ia urte eta erdi, Vasily Eshelon GKEko nire lankidea (zoritxarrez, zirriborroarekin batera HabrΓ©-ri buruzko kontua ezabatu zen) bere Telegram bezeroa hutsetik idazten hasi zen Perl-en, eta geroago lerro hauen egilea sartu zen. Zergatik Perlek, berehala galdetuko dute batzuek? Horrelako proiektuak beste hizkuntza batzuetan existitzen direlako.Izan ere, hori ez da kontua, egon liteke beste hizkuntzarik ez dagoenean. prest egindako liburutegia, eta horren arabera egileak bide osoa egin behar du hutsetik. Gainera, kriptografia konfiantzazko kontua da, baina egiaztatu. Segurtasunera zuzendutako produktu batekin, ezin duzu fabrikatzailearen prest egindako liburutegi batean fidatu eta itsu-itsuan fidatu (hala ere, bigarren zatiko gaia da hau). Momentuz, liburutegiak nahiko ondo funtzionatzen du "batez besteko" mailan (edozein API eskaera egiteko aukera ematen du).

Hala ere, ez da kriptografia edo matematika handirik egongo mezu sorta honetan. Baina beste hainbat xehetasun tekniko eta makulu arkitektonikoak egongo dira (hutsetik idatziko ez dutenentzat ere baliagarriak, baina liburutegia edozein hizkuntzatan erabiliko dutenentzat). Beraz, bezeroa hutsetik inplementatzen saiatzea zen helburu nagusia dokumentazio ofizialaren arabera. Hau da, demagun bezero ofizialen iturburu-kodea itxita dagoela (berriz ere, bigarren zatian hori egia den gaia zehatzago landuko dugu. gertatzen da beraz), baina, garai zaharrean bezala, adibidez, RFC bezalako estandar bat dago - posible al da bezero bat zehaztapenaren arabera bakarrik idaztea, iturburu kodeari "begiratu gabe", ofiziala izan (Telegram mahaigaina, mugikorra), edo Telethon ez-ofiziala?

Edukien taula:

Dokumentazioa... existitzen da, ezta? Egia da?..

Artikulu honetarako oharren zatiak joan den udan hasi ziren biltzen. Denbora hau guztia webgune ofizialean https://core.telegram.org Dokumentazioa 23. geruzakoa zen, hau da. 2014an nonbait itsatsita (gogoratu, orduan ez zeuden kanalik ere?). Noski, teorian, horrek 2014ko une horretan funtzionaltasuna zuen bezero bat inplementatzeko aukera eman behar zigun. Baina egoera horretan ere, dokumentazioa, lehenik, osatugabea zen, eta, bestetik, leku batzuetan bere burua kontraesanean zegoen. Duela hilabete pasatxo, 2019ko irailean, hala izan zen ustekabean Deskubritu zen gunean dokumentazioaren eguneraketa handi bat zegoela, nahiko berria den Layer 105erako, orain dena berriro irakurri behar dela ohar batekin. Izan ere, artikulu asko berrikusi ziren, baina asko aldatu gabe geratu ziren. Hori dela eta, dokumentazioari buruzko beheko kritikak irakurtzean, kontuan izan behar duzu gauza horietako batzuk jada ez direla garrantzitsuak, baina beste batzuk nahiko direla. Azken finean, mundu modernoan 5 urte ez dira denbora luzea, baizik Oso asko. Garai haietatik (batez ere orduz geroztik baztertutako eta berpiztutako geotxat guneak kontuan hartzen ez badituzu), eskeman API metodoen kopurua ehunetik berrehun eta berrogeita hamar baino gehiagora hazi da!

Nondik hasi egile gazte gisa?

Berdin du hutsetik idazten duzun edo, adibidez, prest egindako liburutegiak erabiltzea Telethon Python-erako edo Madeline PHPrako, edonola ere, lehenik beharko duzu erregistratu zure eskaera - parametroak lortu api_id ΠΈ api_hash (VKontakte APIarekin lan egin dutenek berehala ulertzen dute) zerbitzariak aplikazioa identifikatuko duen. Hau behar dute egin ezazu legezko arrazoiengatik, baina gehiago hitz egingo dugu liburutegiko egileek zergatik ezin duten argitaratu bigarren zatian. Baliteke proba-balioekin pozik egotea, nahiz eta oso mugatuak izan; kontua da orain izena eman dezakezula bakarra aplikazioa, beraz, ez sartu presarik.

Orain, ikuspuntu teknikotik, interesatu beharko litzaiguke erregistratu ondoren Telegramen jakinarazpenak jaso beharko genituzkeela dokumentazioaren, protokoloaren eta abarren eguneratzeen inguruan. Hau da, pentsa liteke kaiak zituen gunea besterik gabe abandonatuta zegoela eta bezeroak egiten hasi zirenekin lan egiten jarraitu zuela, izan ere errazagoa da. Baina ez, horrelakorik ez zen ikusi, ez zen informaziorik iritsi.

Eta hutsetik idazten baduzu, lortutako parametroak erabiltzea oso urrun dago oraindik. Nahiz eta https://core.telegram.org/ eta haiei buruz hitz egiten du Lehenik eta behin, lehenik eta behin, inplementatu beharko duzu MTProto protokoloa - baina sinistu bazenu diseinua OSI ereduaren arabera orriaren amaieran protokoloaren deskribapen orokorra egiteko, orduan guztiz alferrik da.

Izan ere, bai MTProtoren aurretik bai ondoren, hainbat mailatan aldi berean (OS nukleoan lan egiten duten atzerriko saregileek esaten duten bezala, geruzaren urraketa), gai handi, mingarri eta ikaragarri batek oztopo egingo du...

Serializazio bitarra: TL (Type Language) eta bere eskema, eta geruzak eta beste hainbat hitz beldurgarri

Gai hau, hain zuzen ere, Telegramen arazoen gakoa da. Eta hitz ikaragarri asko egongo dira horretan sakontzen saiatzen bazara.

Beraz, hona hemen diagrama. Hitz hau burura etortzen bazaizu, esan: JSON eskema, Ondo pentsatu duzu. Helburua bera da: transmititutako datu multzo posible bat deskribatzeko hizkuntzaren bat. Hor amaitzen dira antzekotasunak. Orrialdetik bada MTProto protokoloa, edo bezero ofizialaren iturburu-zuhaitzetik, eskema batzuk irekitzen saiatuko gara, honelako zerbait ikusiko dugu:

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;

Hau lehen aldiz ikusten duenak intuizioz idatzitakoaren zati bat bakarrik antzeman ahal izango du - ba, itxuraz egiturak dira (nahiz eta non dagoen izena, ezkerrean ala eskuinean?), eremuak daude horietan, horren ondoren, bi punturen atzetik mota batek jarraitzen du... ziurrenik. Hemen parentesi angeluetan C++ bezalako txantiloiak egongo dira ziurrenik (hain zuzen ere, ez benetan). Eta zer esan nahi dute beste sinbolo guztiek, galdera ikurrak, harridura ikurrak, ehunekoak, hash markak (eta, jakina, gauza desberdinak esan nahi dituzte leku ezberdinetan), batzuetan presente eta beste batzuetan ez, zenbaki hamaseimalak - eta garrantzitsuena, honetatik nola atera. eskuineko (zerbitzariak ez du baztertuko) byte stream? Dokumentazioa irakurri beharko duzu (bai, JSON bertsioan eskemaren estekak daude gertu - baina horrek ez du argiago uzten).

Ireki orria Datu bitarren serializazioa eta murgildu perretxikoen eta matematika diskretuen mundu magikoan, 4. mailan matanen antzeko zerbait. Alfabetoa, mota, balioa, konbinatzailea, konbinatzaile funtzionala, forma normala, mota konposatua, mota polimorfikoa... eta hori guztia lehen orrialdea baino ez da! Hurrengoa zure zain dago TL Hizkuntza, zeinak, eskaera eta erantzun hutsal baten adibidea badu ere, ez die batere erantzunik ematen kasu tipikoagoei, eta horrek esan nahi du errusieratik ingelesera itzulitako matematikaren berrikusketa batean ibili beharko duzula beste zortzi txertatu batean. orriak!

Hizkuntza funtzionalak eta mota automatikoak inferentzia ezagutzen dituzten irakurleek, noski, deskribapen-hizkuntza hizkuntza honetan ikusiko dute, nahiz eta adibidetik begiratuta, askoz ere ezagunagoa den, eta esan dezakete printzipioz hori ez dela txarra. Honen aurkako eragozpenak hauek dira:

  • bai, helburua ona dirudi, baina ai, berak ez da lortu
  • Errusiako unibertsitateetan hezkuntza aldatu egiten da informatika espezialitateen artean ere - denek ez dute dagokion ikastaroa egin
  • Azkenik, ikusiko dugunez, praktikan hala da ez da beharrezkoa, deskribatu zen TL-aren azpimultzo mugatu bat baino ez baita erabiltzen

Esan bezala LeonNerd kanalean #perl FreeNode IRC sarean, Telegrametik Matrixera ate bat ezartzen saiatu zena (aipamenaren itzulpena ez da zehatza memoriatik):

Sentitzen da norbait lehen aldiz idatzitako teorian sartu zela, hunkitu egin zela eta horrekin jolasten saiatzen hasi zela, praktikan beharrezkoa zen ala ez axola gabe.

Ikus ezazu zeure burua, mota biluzien (int, long, etab.) oinarrizko zerbaiten beharrak ez badu galderarik sortzen - azken finean, eskuz inplementatu behar dira - adibidez, har dezagun saiakera horietatik ateratzeko. bektorea. Hau da, hain zuzen ere, array bat, sortzen diren gauzei izen propioz deitzen badiezu.

Baina aurretik

TL sintaxiaren azpimultzo baten deskribapen laburra dokumentazio ofiziala irakurtzen ez dutenentzat

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;

Definizioa beti hasten da diseinatzaile, ondoren aukeran (praktikan - beti) sinboloaren bidez # beharko lukete CRC32 mota honetako deskribapen kate normalizatutik. Ondoren, eremuen deskribapena dator; egonez gero, baliteke mota hutsik egotea. Hau guztia berdin zeinu batekin amaitzen da, eraikitzaile hau zein motaren izena - hau da, hain zuzen ere, azpimota -. Berdintasun zeinuaren eskuinean dagoen mutila da polimorfikoa - hau da, hainbat mota zehatz egon daitezke.

Definizioa lerroaren ondoren gertatzen bada ---functions---, orduan sintaxia berdina izango da, baina esanahia ezberdina izango da: eraikitzailea RPC funtzioaren izen bihurtuko da, eremuak parametro bihurtuko dira (beno, hau da, zehatz-mehatz emandako egitura berdina mantenduko da, behean deskribatzen den moduan). , esleitutako esanahia besterik ez da izango) eta "mota polimorfikoa" - itzulitako emaitzaren mota. Egia da, oraindik ere polimorfoa izango da - atalean definitu berri dena ---types---, baina eraikitzaile hau "ez da kontuan hartuko". Deitutako funtzio motak haien argumentuen bidez gainkargatzea, hau da. Arrazoiren batengatik, izen bereko baina sinadura ezberdineko hainbat funtzio, C++-n bezala, ez daude TL-n aurreikusita.

Zergatik "eraikitzailea" eta "polimorfikoa" ez bada OOP? Beno, egia esan, errazagoa izango da norbaitek hau OOP terminoetan pentsatzea - ​​mota polimorfoa klase abstraktu gisa, eta eraikitzaileak bere ondorengo klase zuzenak dira, eta final hainbat hizkuntzaren terminologian. Izan ere, noski, hemen bakarrik Antzekotasun OO programazio lengoaietan gainkargatutako eraikitzaile-metodo errealekin. Hemen datu-egiturak besterik ez direnez, ez dago metodorik (nahiz eta funtzioen eta metodoen deskribapena aurrerago nahasmena sortzeko gai den buruan existitzen direnez, baina hori beste kontu bat da) - eraikitzaile bat balio gisa pentsa dezakezu. zeina eraikitzen ari da idatzi byte korronte bat irakurtzean.

Nola gertatzen da hau? Deserializatzaileak, beti 4 byte irakurtzen dituenak, balioa ikusten du 0xcrc32 - eta gero zer gertatuko den ulertzen du field1 motarekin int, hau da. Zehazki 4 byte irakurtzen ditu, honen gainean mota duen eremua PolymorType irakurri. Ikusten du 0x2crc32 eta ulertzen du bi eremu gehiago daudela, lehenengoa long, hau da, 8 byte irakurtzen ditugu. Eta berriro ere mota konplexu bat, modu berean deserializatuta dagoena. Adibidez, Type3 zirkuituan deklaratu ahal izango dira bi eraikitzaile bezain laster, hurrenez hurren, orduan bata zein bestea bete behar dute 0x12abcd34, ondoren 4 byte gehiago irakurri behar dituzu intEdo 0x6789cdef, ondoren ez da ezer egongo. Beste ezer - salbuespen bat bota behar duzu. Dena den, honen ondoren 4 byte irakurtzera itzultzen gara int eremuak field_c Π² constructorTwo eta horrekin amaitzen dugu gure irakurtzen PolymorType.

Azkenik, harrapatzen bazara 0xdeadcrc egiteko constructorThree, orduan dena zaildu egiten da. Gure lehen eremua da bit_flags_of_what_really_present motarekin # - Izan ere, hau motaren alias bat besterik ez da nat, "zenbaki naturala" esan nahi duena. Hau da, hain zuzen ere, unsigned int da, bide batez, zeinurik gabeko zenbakiak zirkuitu errealetan gertatzen diren kasu bakarra. Beraz, hurrengo galdera ikurra duen eraikuntza bat dago, hau da, eremu hau - alanbrean egongo da soilik dagokion bit-a aipatzen den eremuan ezartzen bada (gutxi gorabehera, ternario-operadore bat bezala). Beraz, demagun bit hau ezarrita dagoela, eta horrek esan nahi du gehiago irakurri behar dugula bezalako eremu bat Type, gure adibidean 2 eraikitzaile dituena. Bata hutsik dago (identifikatzaileaz bakarrik osatuta dago), besteak eremu bat du ids motarekin ids:Vector<long>.

Pentsa dezakezu txantiloiak eta generikoak pros edo Javan daudela. Baina ez. Ia. Hau bakarra Zirkuitu errealetan parentesi angeluak erabiltzearen kasua, eta Bektorerako BAKARRIK erabiltzen da. Byte-korronte batean, hauek 4 CRC32 byte izango dira Bektore motarako, beti berdinak, gero 4 byte - array-elementuen kopurua, eta gero elementu horiek beraiek.

Gehitu honi serializazioa beti 4 byteko hitzetan gertatzen dela, mota guztiak horren multiploak direla - barne motak ere deskribatzen dira. bytes ΠΈ string luzeraren eskuzko serializazioarekin eta lerrokatze hau 4rekin - ba, badirudi normala eta nahiko eraginkorra dela? TL serializazio bitar eraginkorra dela esaten den arren, pikutara, ia edozerren hedapenarekin, nahiz eta balio boolearrak eta karaktere bakarreko kateak 4 bytetara, JSON oraindik ere askoz lodiagoa izango al da? Begira, beharrezkoak ez diren eremuak ere salta daitezke bit-markekin, dena nahiko ona da, eta baita etorkizunerako hedagarria ere, beraz, zergatik ez gehitu aukerako eremu berriak eraikitzaileari geroago?...

Baina ez, nire deskribapen laburra ez, dokumentazio osoa irakurri eta inplementazioan pentsatzen baduzu. Lehenik eta behin, eraikitzailearen CRC32 eskemaren testu-deskribapenaren lerro normalizatuaren arabera kalkulatzen da (kendu zuriune gehigarriak, etab.); beraz, eremu berri bat gehitzen bada, motaren deskribapen-lerroa aldatuko da, eta, beraz, CRC32 eta , ondorioz, serializazioa. Eta zer egingo luke bezero zaharrak bandera berriak ezarrita dituen eremu bat jasoko balu eta ez badaki zer egin haiekin?...

Bigarrenik, gogora dezagun CRC32, hemen funtsean bezala erabiltzen dena hash funtzioak zer mota (des)serializatzen ari den modu esklusiboan zehazteko. Hemen talken arazoaren aurrean gaude -eta ez, probabilitatea ez da 232tik bat, askoz handiagoa baizik. Nork gogoratu du CRC32 komunikazio kanalean akatsak detektatzeko (eta zuzentzeko) diseinatuta dagoela, eta horren ondorioz propietate horiek hobetzen dituela besteen kaltetan? Esaterako, ez du axola byteak berrantolatzea: CRC32 bi lerrotatik kalkulatzen baduzu, bigarrenean lehenengo 4 byteak hurrengo 4 byteekin aldatzen dituzu - berdina izango da. Gure sarrera latindar alfabetoko testu-kateak direnean (eta puntuazio apur bat), eta izen horiek bereziki ausazkoak ez direnean, berrantolaketa hori izateko probabilitatea asko handitzen da.

Bide batez, nork egiaztatu zuen zer zegoen? benetan CRC32? Hasierako iturburu-kodeetako batek (baita Waltman baino lehen ere) karaktere bakoitza 239 zenbakiz biderkatzen zuen hash funtzioa zuen, hain maitea den pertsona hauek, ja, ja!

Azkenik, ados, eremu mota bat duten eraikitzaileak konturatu gara Vector<int> ΠΈ Vector<PolymorType> CRC32 desberdinak izango ditu. Zer gertatzen da lineako errendimenduaz? Eta ikuspegi teorikotik, hau motaren parte bihurtzen al da? Demagun hamar mila zenbakiko array bat pasatzen dugula, ondo Vector<int> dena argi dago, luzera eta beste 40000 byte. Zer gertatzen da hau Vector<Type2>, eremu bakarraz osatua int eta motan bakarrik dago - 10000xabcdef0 34 aldiz errepikatu behar dugu eta gero 4 byte int, edo lengoaia eraikitzailetik INDEPENDUA gai da fixedVec eta 80000 byteren ordez, berriro transferitu 40000 bakarrik?

Hau ez da batere galdera teoriko hutsa - imajinatu talde-erabiltzaileen zerrenda bat jasotzen duzula, haietako bakoitzak ID, izen-abizenak dituela - mugikorrentzako konexio baten bidez transferitutako datu-kopuruaren aldea nabarmena izan daiteke. Hain zuzen, Telegram serializazioaren eraginkortasuna da iragartzen zaiguna.

Beraz ...

Vector, inoiz kaleratu ez zena

Konbinatzaileen deskribapen-orrietan eta abarretan barrena ibiltzen saiatzen bazara, ikusiko duzu bektore bat (eta baita matrize bat ere) formalki hainbat orritako tupla bidez ateratzen saiatzen ari dela. Baina azkenean ahaztu egiten dute, azken urratsa saltatzen da, eta bektore baten definizioa besterik ez da ematen, oraindik mota bati lotuta ez dagoena. Zein da ba arazoa? Hizkuntzetan programazioa, batez ere funtzionalak, nahiko tipikoa da egitura errekurtsiboki deskribatzea - ​​konpilatzaileak bere ebaluazio alferrarekin dena ulertu eta egingo du. Hizkuntzan datuen serializazioa behar dena ERAGINKORTASUNA da: nahikoa da deskribatzea besterik gabe zerrenda, hau da. bi elementuren egitura - lehenengoa datu-elementu bat da, bigarrena egitura bera da edo buztanerako espazio huts bat (pack (cons) Lisp-en). Baina hori beharrezkoa izango da, jakina bakoitzeko elementuak 4 byte gehiago gastatzen ditu (CRC32 kasuan TL-n) bere mota deskribatzeko. Array bat ere erraz deskriba daiteke tamaina finkoa, baina aldez aurretik luzera ezezaguna duen array baten kasuan, hausten dugu.

Hori dela eta, TL-k bektorerik ateratzen uzten ez duenez, alboan gehitu behar izan zen. Azken finean, dokumentazioa dio:

Serializazioak beti erabiltzen du "bektorea" eraikitzaile bera (const 0x1cb5c415 = crc32 ("vector t:Type # [ t ] = Vector t"), t motako aldagaiaren balio zehatzaren menpe ez dagoena.

t hautazko parametroaren balioa ez da parte hartzen serializazioan, emaitza motatik eratortzen baita (beti ezagutzen da deserializazioaren aurretik).

Begiratu hurbilagotik: vector {t:Type} # [ t ] = Vector t - baina inon Definizio honek berak ez du esaten lehen zenbakia bektorearen luzeraren berdina izan behar duenik! Eta ez dator inondik. Kontuan izan eta eskuekin inplementatu beharreko datua da. Gainerakoan, dokumentazioak zintzotasunez aipatzen du mota ez dela benetakoa:

Bektore t pseudotipo polimorfoa "mota" bat da, zeinaren balioa edozein t motatako balio-segida bat den, kutxadun edo biluzik.

... baina ez du horretan zentratzen. Zuk, matematikaren luzapenean barrena ibiltzeaz nekatuta (agian unibertsitateko ikastaro batetik ezagutzen duzuna ere), amore ematea erabakitzen duzunean eta praktikan nola lan egin aztertzea erabakitzen duzunean, zure buruan utzitako inpresioa da Larria dela. Matematika oinarrian, Cool People-ek asmatu zuen (bi matematikari - ACM irabazlea), eta ez edonork. Helburua - erakustea - lortu da.

Bide batez, kopuruari buruz. Gogora dezagun hori # sinonimo bat da nat, zenbaki naturala:

Mota esamoldeak daude (mota-espr) eta zenbakizko adierazpenak (nat-espr). Hala ere, modu berean definitzen dira.

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

baina gramatikan era berean deskribatzen dira, hau da. Desberdintasun hori berriro gogoratu eta eskuz ezarri behar da.

Tira, bai, txantiloi motak (vector<int>, vector<User>) identifikatzaile komun bat dute (#1cb5c415), hau da. deialdia bezala iragarrita dagoela badakizu

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

orduan jada ez zaude bektore baten zain, erabiltzaileen bektore baten zain baizik. Zehatzago, muztioa itxaron - benetako kodean, elementu bakoitzak, mota hutsa ez bada, eraikitzaile bat izango du, eta inplementazioan modu onean egiaztatu beharko litzateke - baina bektore honen elementu guztietan bidali gintuzten zehatz-mehatz mota hori? Zer gertatzen da PHP motaren bat izango balitz, zeinetan array batek mota desberdinak eduki ditzake elementu ezberdinetan?

Une honetan pentsatzen hasten zara - beharrezkoa al da horrelako TL bat? Agian gurdirako giza serializatzaile bat erabiltzea posible izango litzateke, orduan zegoen protobuf bera? Hori zen teoria, ikus diezaiogun praktikari.

Lehendik dauden TL inplementazioak kodean

TL VKontakte-ren sakonean jaio zen Durov-en akzioen salmentarekin eta gertaera ospetsuen aurretik ere (ziur aski), Telegramen garapena hasi aurretik ere. Eta kode irekian lehen ezarpenaren iturburu-kodea makulu dibertigarri asko aurki ditzakezu. Eta hizkuntza bera Telegramen orain baino beteago ezarri zen bertan. Esate baterako, hash-ak ez dira batere erabiltzen eskeman (jokaera desbideratua duen pseudotipo barneratua (bektore bat bezalakoa) esan nahi du). Edo

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

baina kontuan izan dezagun, osotasunaren mesedetan, nolabait esateko, Pentsamenduaren Erraldoiaren bilakaera.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

Edo eder hau:

    static const char *reserved_words_polymorhic[] = {

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

      };

Zati hau honelako txantiloiei buruzkoa da:

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

Hau da hashmap txantiloi mota baten definizioa int - Mota bikoteen bektore gisa. C++-n honelako itxura izango luke:

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

beraz, alpha - gakoa! Baina C++-n bakarrik T idatz dezakezu, baina alfa, beta idatzi beharko zenuke... Baina ez 8 parametro baino gehiago, hor amaitzen da fantasia. Badirudi behinola San Petersburgon horrelako elkarrizketa batzuk gertatu zirela:

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

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

Baina hau "oro har" TL-ren argitaratutako lehen inplementazioari buruzkoa izan zen. Goazen Telegram bezeroen inplementazioak aztertzera.

Vasily-ri hitza:

Vasily, [09.10.18 17:07] Batez ere, ipurdia beroa da, abstrakzio mordoa sortu zutelako, eta gero torloju bat mailukatu eta kode-sorgailua makuluekin estali.
Ondorioz, lehenengo dock pilot.jpg-tik
Ondoren, dzhekichan.webp kodeatik

Noski, algoritmoak eta matematikak ezagutzen dituzten pertsonengandik, espero dezakegu Aho, Ullmann irakurri dutela eta hamarkadetan zehar industrian de facto estandar bihurtu diren tresnak ezagutzen dituztela DSL konpiladoreak idazteko, ezta?...

Nork telegrama-cli Vitaly Valtman da, bere (cli) mugetatik kanpo TLO formatuaren agerpenetik uler daitekeenez, taldeko kide bat - orain TL analizatzeko liburutegia esleitu da bereizita, zein da bere inpresioa TL analizatzailea? ..

16.12 04:18 Vasily: Uste dut norbaitek ez zuela lex+yacc menperatzen
16.12 04:18 Vasily: Ezin dut bestela azaldu
16.12 04:18 Vasily: beno, edo VKko linea kopuruagatik ordaindu zuten
16.12 04:19 Vasily: 3k+ lerro etab.<censored> analizatzaile baten ordez

Salbuespena agian? Ikus dezagun nola marka Hau da bezero OFIZIALA - 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+ lerro Python-en, adierazpen erregular pare bat + bektore bat bezalako kasu bereziak, zeina, noski, eskeman TL sintaxiaren arabera behar den bezala deklaratzen dena, baina sintaxi horretan oinarritzen ziren analizatzeko... Galdera sortzen da, zergatik izan zen dena miraria?ΠΈGeruzatuagoa da, hala ere, inork dokumentazioaren arabera analizatuko ez badu?!

Bide batez... Gogoratzen al duzu CRC32 egiaztapenari buruz hitz egin genuela? Beraz, Telegram mahaigaineko kode-sorgailuan, kalkulatutako CRC32 mota horietako salbuespenen zerrenda dago. ez dator bat diagraman adierazitakoarekin!

Vasily, [18.12/22 49:XNUMX] eta hemen pentsatuko nuke horrelako TL behar den ala ez
inplementazio alternatiboekin nahastu nahi banu, lerro-jauziak txertatzen hasiko nintzateke, analizatzaileen erdiak lerro anitzeko definizioetan hautsiko dira
tdesktop, ordea, ere bai

Gogoratu lerro bakarreko puntua, beranduago itzuliko gara.

Ados, telegram-cli ez-ofiziala da, Telegram Desktop ofiziala da, baina zer gertatzen da besteekin? Nork daki?... Android bezero-kodean ez zegoen eskema analizatzailerik (horrek kode irekiari buruzko galderak sortzen ditu, baina hau bigarren zatirako da), baina beste hainbat kode dibertigarri zeuden, baina horiei buruz gehiago. azpiatala.

Zer beste galdera planteatzen ditu serializazioak praktikan? Adibidez, gauza asko egin zituzten, noski, bit-eremuekin eta baldintza-eremuekin:

Vasily: flags.0? true
eremua presente dagoela eta egia berdina dela esan nahi du bandera ezarrita badago

Vasily: flags.1? int
eremua presente dagoela eta deserializatu behar dela esan nahi du

Vasily: Ipurdia, ez kezkatu egiten ari zarenaz!
Vasily: Dokumentuan nonbait aipatzen da egia zero-luzera mota bat dela, baina ezinezkoa da haien dokumentutik ezer muntatzea.
Vasily: Kode irekiko inplementazioetan ere ez da horrela, baina makulu eta euskarri ugari daude

Zer gertatzen da Telethon? MTProtoren gaiari begira, adibide bat - dokumentazioan horrelako piezak daude, baina seinalea % "mota biluzi jakin bati dagokiona" bakarrik deskribatzen da, hau da. beheko adibideetan errore bat edo dokumenturik gabeko zerbait dago:

Vasily, [22.06.18 18:38] Leku bakarrean:

msg_container#73f1f8dc messages:vector message = MessageContainer;

Beste batean:

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

Eta hauek bi desberdintasun handi dira, bizitza errealean bektore biluzi motaren bat dator

Ez dut bektore definizio hutsik ikusi eta ez dut topatu

Analisia eskuz idazten da telethonean

Bere diagraman definizioa iruzkintzen da msg_container

Berriz ere, galdera % ingurukoa izaten jarraitzen du. Ez da deskribatzen.

Vadim Goncharov, [22.06.18 19:22] eta tdesktop-en?

Vasily, [22.06.18 19:23] Baina motor arruntetan duten TL analizatzaileak ziurrenik ez du hau ere jango

// parsed manually

TL abstrakzio ederra da, inork ez du erabat inplementatzen

Eta % ez dago eskemaren bertsioan

Baina hemen dokumentazioa kontraesanean dago, beraz, idk

Gramatikan aurkitu zen, semantika deskribatzea besterik ez zuten ahaztu

Dokumentua TL-n ikusi duzu, ezin duzu asmatu litro erdi gabe

"Beno, demagun", esango du beste irakurle batek, "zerbait kritikatzen duzula, beraz, erakutsi nola egin behar den".

Vasilyk erantzun dio: β€œAnalizatzaileari dagokionez, gustatzen zaizkit horrelako gauzak

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

nolabait hobeto baino

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

edo

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

hau da lexera OSOA:

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

horiek. sinpleagoa da emeki esateaΒ».

Oro har, ondorioz, benetan erabilitako TL azpimultzorako analizatzailea eta kode-sorgailua 100 gramatika lerrotan eta sorgailuaren ~ 300 lerrotan sartzen dira (guztiak zenbatuta). print'k sortutako kodea), klase bakoitzean barneratzeko informazio motak barne. Mota polimorfiko bakoitza oinarrizko klase abstraktu huts batean bihurtzen da, eta eraikitzaileek hortik oinordetzen dute eta serializazio eta deserializazio metodoak dituzte.

Moten falta hizkuntza motan

Idazketa sendoa gauza ona da, ezta? Ez, hau ez da holibar bat (hizkuntza dinamikoak nahiago ditudan arren), TL-ren esparruko postulatu bat baizik. Horretan oinarrituta, hizkuntzak era guztietako egiaztapenak eman behar dizkigu. Tira, ados, agian ez bera, ezartzea baizik, baina gutxienez deskribatu beharko lituzke. Eta nolako aukerak nahi ditugu?

Lehenik eta behin, mugak. Hona hemen fitxategiak igotzeko dokumentazioan ikusten dugu:

Ondoren, fitxategiaren eduki bitarra zatitan banatzen da. Zati guztiek tamaina berdina izan behar dute ( zati_tamaina ) eta baldintza hauek bete behar dira:

  • part_size % 1024 = 0 (1KBz zatigarria)
  • 524288 % part_size = 0 (512KB berdin zatitu behar da zati_tamainaren arabera)

Azken zatiak ez ditu baldintza hauek bete behar, baldin eta bere tamaina part_size baino txikiagoa bada.

Zati bakoitzak sekuentzia zenbaki bat izan behar du, fitxategi_zatia, 0tik 2,999ra bitarteko balioarekin.

Fitxategia zatitu ondoren zerbitzarian gordetzeko metodo bat aukeratu behar duzu. Erabili upload.saveBigFilePart fitxategiaren tamaina osoa 10 MB baino gehiagokoa bada eta upload.saveFilePart fitxategi txikiagoetarako.
[…] Datuak sartzeko errore hauetako bat itzul daiteke:

  • FILE_PARTS_INVALID β€” Pieza kopuru baliogabea. Balioa ez dago artean 1..3000

Hauetako bat al dago diagraman? Hau nolabait adierazgarria al da TL erabiliz? Ez. Baina barkatu, aitonaren Turbo Pascal ere gai izan zen zehaztutako motak deskribatzeko barrutiak. Eta gauza bat gehiago zekien, orain izenez ezagunagoa enum - balio-kopuru finko (txiki) zenbaketaz osatutako mota bat. C - numerikoa bezalako hizkuntzetan, kontuan izan orain arte motei buruz bakarrik hitz egin dugula zenbakiak. Baina matrizeak, kateak... ere badaude adibidez, ondo legoke deskribatzea kate horrek telefono zenbaki bat bakarrik eduki dezakeela, ezta?

Horietako ezer ez dago TL-n. Baina badago, adibidez, JSON Schema-n. Eta beste norbaitek 512 KBren zatigarritasunari buruz eztabaidatzen badu, hori oraindik kodean egiaztatu behar dela, ziurtatu bezeroak besterik gabe. Ezin bidali zenbaki bat barrutitik kanpo 1..3000 (eta dagokion akatsa ezin zen sortu) posible izango zen, ezta?...

Bide batez, erroreei eta itzulerari buruzko balioei buruz. TL-rekin lan egin dutenek ere begiak lausotzen dituzte - ez zitzaigun berehala ohartu bakoitza TL-ko funtzio batek deskribatutako itzulera mota ez ezik, errore bat ere itzul dezake. Baina hori ezin da inola ere ondorioztatu TL bera erabiliz. Jakina, dagoeneko argi dago eta praktikan ez dago ezertarako beharrik (nahiz eta, egia esan, RPC modu ezberdinetan egin daitekeen, aurrerago hona itzuliko gara) - baina zer gertatzen da Mota Abstraktuen Matematika kontzeptuen Garbitasunaz. zeruko mundutik?.. Sokatira jaso nuen - beraz, lotu.

Eta azkenik, irakurgarritasunari buruz? Tira, hor, orokorrean, gustatuko litzaidake deskribapena ondo daukazu eskeman (JSON eskeman, berriro ere bai), baina dagoeneko tentsioa baduzu, zer gertatzen da alde praktikoarekin - eguneratzeetan zehar desberdintasunak begiratuz behintzat? Ikus ezazu zeure burua at benetako adibideak:

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

edo

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

Guztion araberakoa da, baina GitHub-ek, adibidez, uko egiten du halako lerro luzeen barruan aldaketak nabarmentzeari. Jolasa β€œaurkitu 10 diferentzia”, eta garunak berehala ikusten duena da bi adibideetan hasierak eta amaierak berdinak direla, lapurtera irakurri behar da erdian nonbait... Nire ustez, hau ez da teorian bakarrik, baina ikusmen hutsa zikina eta zabarra.

Bide batez, teoriaren garbitasunari buruz. Zergatik behar ditugu bit eremuak? Ez al du ematen haiek usaina txarra motaren teoriaren ikuspuntutik? Azalpena diagramaren aurreko bertsioetan ikus daiteke. Hasieran, bai, horrela zen, doministiku bakoitzeko mota berri bat sortzen zen. Oinarri hauek oraindik ere forma honetan existitzen dira, adibidez:

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;

Baina orain imajinatu, zure egituran aukerako 5 eremu badituzu, orduan 32 mota beharko dituzu aukera posible guztietarako. Leherketa konbinatorioa. Horrela, TL teoriaren kristal garbitasuna berriro ere apurtu zen seriazioaren errealitate gogorraren burdinurtuzko ipurdia.

Horrez gain, leku batzuetan mutil hauek beraiek beren tipologia urratzen dute. Adibidez, MTProto-n (hurrengo kapituluan) erantzuna Gzip-en bidez konprimitu daiteke, dena ondo dago, geruzak eta zirkuitua urratzen direla izan ezik. Berriro ere, ez zen RpcResult bera bildu zena, bere edukia baizik. Tira, zergatik egin hau?... Makulu batean moztu behar izan nuen, konpresioak edonon funtziona zezan.

Edo beste adibide bat, behin errore bat aurkitu genuen - bidali zen InputPeerUser ordez InputUser. Edo alderantziz. Baina funtzionatu zuen! Hau da, zerbitzariari ez zitzaion mota axola. Nola izan daiteke hau? Erantzuna telegram-cli-ko kode zatiek eman diezagukete:

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

Beste era batera esanda, hor serializazioa egiten da ESKUZ, ez da sortu kodea! Agian zerbitzaria antzeko moduan inplementatuta dago?... Printzipioz, hau behin egiten bada funtzionatuko du, baina nola onar daiteke geroago eguneratzeetan? Horregatik asmatu zen eskema? Eta hemen hurrengo galderara pasatzen gara.

Bertsioa. Geruzak

Bertsio eskematikoei zergatik deitzen zaien geruzak argitaratutako eskemaren historian oinarrituta soilik espekulatu daiteke. Antza denez, hasiera batean egileek pentsatu zuten oinarrizko gauzak egin zitezkeela aldaketarik gabeko eskema erabiliz, eta behar den tokian bakarrik, eskaera zehatzetarako, beste bertsio bat erabiliz egiten ari zirela adierazten dute. Printzipioz, ideia ona ere bada - eta berria, nolabait, "nahastua" izango da, zaharraren gainean geruzatuta. Baina ikus dezagun nola egin zen. Egia da, ezin izan nuen hasiera-hasieratik begiratu - dibertigarria da, baina oinarrizko geruzaren diagrama ez da existitzen. Geruzak 2rekin hasi ziren. Dokumentazioak TL ezaugarri berezi bati buruz hitz egiten digu:

Bezero batek 2. geruza onartzen badu, eraikitzaile hau erabili behar da:

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

Praktikan, horrek esan nahi du API dei bakoitzaren aurretik int bat balioarekin 0x289dd1f6 metodo zenbakiaren aurretik gehitu behar da.

Normala dirudi. Baina zer gertatu zen gero? Gero agertu zen

invokeWithLayer3#b7475268 query:!X = X;

Orduan, zer da hurrengoa? Asma dezakezun bezala,

invokeWithLayer4#dea0d430 query:!X = X;

Dibertigarria? Ez, goizegi da barre egiteko, pentsa hori bakoitzeko beste geruza baten eskaera mota berezi batean bildu behar da - zuretzako guztiak desberdinak badira, nola bereiz ditzakezu bestela? Eta aurrean 4 byte besterik ez gehitzea metodo nahiko eraginkorra da. Beraz,

invokeWithLayer5#417a57ae query:!X = X;

Baina agerikoa da denbora baten buruan hau bakanalia moduko bat bihurtuko dela. Eta irtenbidea etorri zen:

Eguneraketa: 9. geruzatik hasita, metodo laguntzaileak invokeWithLayerN batera bakarrik erabil daiteke initConnection

Aupa! 9 bertsioren ostean, azkenean 80ko hamarkadan Interneteko protokoloetan egiten zena iritsi ginen - konexioaren hasieran behin bertsioa adostuz!

Orduan, zer da hurrengoa?...

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

Baina orain oraindik barre egin dezakezu. Beste 9 geruzen ondoren, azkenean bertsio-zenbaki bat duen eraikitzaile unibertsala gehitu zen, konexioaren hasieran behin bakarrik deitu behar zaiona, eta geruzen esanahia desagertu egin zela zirudien, orain baldintzazko bertsio bat besterik ez da, adibidez. beste leku guztietan. Arazoa konponduta.

Zehazki?...

Vasily, [16.07.18 14:01] Ostiralean ere pentsatu nuen:
Telezerbitzariak eskaerarik gabe bidaltzen ditu gertaerak. Eskaerak InvokeWithLayer-en bildu behar dira. Zerbitzariak ez ditu eguneraketak biltzen; ez dago erantzunak eta eguneraketak biltzeko egiturarik.

Horiek. bezeroak ezin du zehaztu zein geruza nahi dituen eguneraketak

Vadim Goncharov, [16.07.18 14:02] ez al da printzipioz InvokeWithLayer makulu bat?

Vasily, [16.07.18 14:02] Hau da bide bakarra

Vadim Goncharov, [16.07.18 14:02] horrek, funtsean, saioaren hasieran geruza adostea esan nahi du

Bide batez, ondorioztatzen da bezeroaren maila jaitsi ez dela ematen

Eguneraketak, hau da. mota Updates eskeman, hau da zerbitzariak bezeroari bidaltzen diona ez API eskaera bati erantzunez, gertaera bat gertatzen denean modu independentean baizik. Gai konplexua da, beste post batean eztabaidatuko dena, baina oraingoz garrantzitsua da jakitea zerbitzariak Eguneraketak gordetzen dituela bezeroa lineaz kanpo dagoenean ere.

Horrela, biltzeari uko egiten badiozu bakoitzeko paketea bere bertsioa adierazteko, honek logikoki arazo posible hauek dakartza:

  • zerbitzariak eguneraketak bidaltzen dizkio bezeroari, bezeroak zein bertsio onartzen duen jakinarazi aurretik ere
  • zer egin behar dut bezeroa berritu ondoren?
  • nor bermeakzerbitzariak geruza-zenbakiari buruz duen iritzia ez dela aldatuko prozesuan zehar?

Uste duzu hori espekulazio teoriko hutsa dela, eta praktikan ezin dela gertatu, zerbitzaria behar bezala idatzita dagoelako (bederen, ondo probatuta dago)? Ja! Ez du axola nola den!

Hauxe da abuztuan topo egin genuena. Abuztuaren 14an Telegram zerbitzarietan zerbait eguneratzen ari zelako mezuak zeuden... eta gero erregistroetan:

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.

eta gero hainbat megabyte pila-arrastoak (beno, aldi berean erregistroa konpondu zen). Azken finean, zure TLn zerbait ezagutzen ez bada, sinaduraz bitarra da, lerroan beherago GUZTIAK doa, deskodetzea ezinezkoa izango da. Zer egin beharko zenuke horrelako egoera batean?

Bada, inori burura etortzen zaion lehen gauza deskonektatzea eta berriro saiatzea da. Ez zuen lagundu. Googlen CRC32 bilatzen dugu - 73. eskemako objektuak izan ziren, 82an lan egin genuen arren. Arretaz begiratzen ditugu erregistroak - bi eskema ezberdinetako identifikatzaileak daude!

Agian arazoa gure bezero ez ofizialean dago? Ez, Telegram Desktop 1.2.17 abiarazten dugu (Linux banaketa batzuetan hornitutako bertsioa), Salbuespenen erregistroan idazten du: MTP Ustekabeko ID mota #b5223b0f irakurri MTPMessageMedia-n...

Telegramen protokoloaren eta antolaketa planteamenduaren kritika. 1. zatia, teknikoa: bezero bat hutsetik idazteko esperientzia - TL, MT

Google-k erakutsi zuen lehendik ere antzeko arazo bat gertatu zitzaiola bezero ez ofizialetako bati, baina gero bertsio-zenbakiak eta, horren arabera, hipotesiak desberdinak ziren...

Beraz, zer egin behar dugu? Vasily eta biok banatu ginen: zirkuitua 91ra eguneratzen saiatu zen, egun batzuk itxarotea eta 73ra probatzea erabaki nuen. Bi metodoek funtzionatu zuten, baina enpirikoak direnez, ez dago ulertzen zenbat bertsio behar dituzun gora edo behera. salto egiteko, edo zenbat denbora itxaron behar duzun.

Geroago egoera erreproduzitu ahal izan nuen: bezeroa abiarazten dugu, itzaltzen dugu, zirkuitua beste geruza batera konpilatzen dugu, berrabiarazi, arazoa berriro harrapatzen dugu, aurrekora itzultzen dugu - oops, ez dago zirkuitu aldaketarik eta bezeroa berrabiarazi egiten dugu. minutu batzuk lagunduko dizu. Geruza ezberdinetako datu-egituren nahasketa jasoko duzu.

Azalpena? Zeharkako sintoma ezberdinetatik asma dezakezun bezala, zerbitzariak mota ezberdinetako prozesu asko ditu makina ezberdinetan. Seguruenik, "buffering"-az arduratzen den zerbitzariak bere nagusiek emandakoa ilaran jarri zuen, eta sorrera garaian zegoen eskeman eman zuten. Eta ilara hau β€œusteldu arte”, ezin izan da ezer egin.

Beharbada... baina makulu ikaragarria da hau?!.. Ez, ideia zoroetan pentsatu baino lehen, ikus dezagun bezero ofizialen kodea. Android bertsioan ez dugu TL analizatzailerik aurkitzen, baina fitxategi handi bat aurkitzen dugu (GitHub-ek ukitzeari uko egiten dio) (des)serializazioarekin. Hona hemen kode zatiak:

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;

edo

    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... basatia dirudi. Baina, ziurrenik, sortutako kodea da, orduan ados?... Baina zalantzarik gabe, bertsio guztiak onartzen ditu! Egia da, ez dago argi zergatik den guztia nahasten den, ezkutuko berriketaldiak eta era guztietakoak _old7 nolabait ez dirudi makina-sorkuntza... Hala ere, batez ere harrituta geratu nintzen

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Mutilak, ezin duzu erabaki geruza baten barruan zer dagoen?! Beno, ados, demagun "bi" akats batekin kaleratu zirela, ba, gertatzen da, baina HIRU?... Berehala, berriz ere rake berdina? Zer motatako pornografia da hau, barkatu?...

Telegram Desktop-en iturburu-kodean, bide batez, antzeko gauza bat gertatzen da; hala bada, eskemari jarraian egindako hainbat konpromisok ez dute bere geruza-zenbakia aldatzen, zerbait konpontzen baizik. Eskemarako datu-iturri ofizialik ez dagoen baldintzetan, nondik lor daiteke, bezero ofizialaren iturburu-kodea izan ezik? Eta hortik hartzen baduzu, ezin duzu ziurtatu eskema guztiz zuzena denik metodo guztiak probatu arte.

Nola probatu daiteke hori? Unitate, funtzional eta beste proben zaleek iruzkinetan partekatzea espero dut.

Ados, ikus dezagun beste kode bat:

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;

"Eskuz sortutako" iruzkin honek iradokitzen du fitxategi honen zati bat bakarrik eskuz idatzi dela (imagina al duzu mantentze-amesgaizto osoa?), eta gainerakoa makinaz sortu dela. Hala ere, orduan beste galdera bat sortzen da: iturriak eskuragarri daudela ez guztiz (Linux nukleoan GPL blob-ak), baina hori dagoeneko bigarren zatiko gaia da.

Baina nahikoa. Goazen serializazio hori guztia exekutatzen den protokolora.

MT Proto

Beraz, ireki dezagun deskribapen orokorra ΠΈ protokoloaren deskribapen zehatza eta estropezu egiten dugun lehenengo gauza terminologia da. Eta denetarik ugariarekin. Orokorrean, badirudi Telegram-en jabetzako ezaugarri bat dela: leku ezberdinetan gauzei modu ezberdinean deitzea, edo hitz batekin gauza desberdinak deitzea, edo alderantziz (adibidez, goi-mailako API batean, eranskailu pakete bat ikusten baduzu, ez da zer pentsatu duzu).

Adibidez, "mezua" eta "saioa" Telegram bezeroaren ohiko interfazean baino zerbait desberdina da hemen. Beno, dena argi dago mezuarekin, OOP terminoetan interpretatu daiteke edo, besterik gabe, "pakete" hitza deitu daiteke - hau garraio-maila baxua da, ez daude interfazean bezalako mezuak, zerbitzu-mezu asko daude. . Baina saioa... baina lehenik eta behin.

Garraio-geruza

Lehenengo gauza garraioa da. 5 aukeraren berri emango digute:

  • TCP
  • Webgunea
  • Websocket HTTPS bidez
  • HTTP
  • HTTPS

Vasily, [15.06.18 15:04] UDP garraioa ere badago, baina ez dago dokumentatuta

Eta TCP hiru aldaeratan

Lehenengoa UDP-ren antzekoa da TCP bidez, pakete bakoitzak sekuentzia-zenbaki bat eta crc barne hartzen ditu
Zergatik da hain mingarria dokumentuak gurdi batean irakurtzea?

Tira, hor dago orain TCP dagoeneko 4 aldaeratan:

  • laburra
  • Bitarteko
  • Tarteko betegarria
  • Osoa

Beno, ados, MTProxyrako betegarria tarteko, hau geroago gehitu zen gertaera ezagunengatik. Baina zergatik beste bi bertsio (hiru guztira) batekin aurrera egin dezakezunean? Funtsean, laurak MTProto nagusiaren luzera eta karga karga nola ezarri baino ez dira desberdinak, gehiago eztabaidatuko dena:

  • Laburtua-n 1 edo 4 byte da, baina ez 0xef, orduan gorputza
  • Bitartekoan hau 4 byte-ko luzera eta eremu bat da, eta bezeroak bidali behar duen lehen aldia 0xeeeeeeee Tartekoa dela adierazteko
  • Osorik adiktiboena, saregile baten ikuspuntutik: luzera, sekuentzia-zenbakia, eta EZ da nagusiki MTProto, gorputza, CRC32. Bai, hau guztia TCPren gainean dago. Horrek garraio fidagarria eskaintzen digu byte-korronte sekuentzial baten moduan; ez da sekuentziarik behar, batez ere checksumak. Ados, orain norbaitek aurka egingo dit TCPk 16 biteko checksum bat duela, beraz, datuen ustelkeria gertatzen da. Bikaina, baina benetan 16 byte baino luzeagoak diren hashekin protokolo kriptografiko bat dugu, akats horiek guztiak -eta are gehiago- maila altuagoko SHA desegokitze batek harrapatuko ditu. CRC32-n ez dago punturik honen gainean.

Konpara dezagun Laburtua, zeinetan byte bateko luzera posible den, Tartekoarekin, "4 byteko datuen lerrokatzea beharrezkoa bada" justifikatzen duena, eta hori nahiko zentzugabea da. Zer, uste da Telegrameko programatzaileak hain ezgai direla, ezen ezin dituztela socket bateko datuak lerrokatuko buffer batean irakurri? Oraindik egin behar duzu, irakurtzeak edozein byte itzul ditzakelako (eta proxy zerbitzariak ere badaude, adibidez...). Edo, bestetik, zergatik blokeatu Laburtua 16 byteen gainean betegarri handia izango badugu - gorde 3 byte batzuetan ?

Nikolai Durov-ek gurpilak berrasmatzea oso gustuko duela iruditzen zaio, sareko protokoloak barne, benetako behar praktikorik gabe.

Beste garraio aukera batzuk, barne. Web eta MTProxy, ez dugu orain kontuan hartuko, agian beste post batean, eskaerarik badago. MTProxy honi buruz, gogoratu dezagun orain 2018an kaleratu eta gutxira hornitzaileek azkar ikasi zutela blokeatzen, helburutzat. saihesbidea blokeatzeaBy paketearen tamaina! Eta baita C-n idatzitako MTProxy zerbitzaria (berriro ere Waltman-ek) Linux-en zehaztasunekin gehiegi lotua zegoela, hori batere beharrezkoa ez zen arren (Phil Kulin-ek baieztatuko du), eta antzeko zerbitzari batek Go edo Node.js-en egingo luke. ehun lerro baino gutxiagotan sartzen da.

Baina pertsona horien alfabetatze teknikoari buruzko ondorioak aterako ditugu atalaren amaieran, beste gai batzuk aztertu ondoren. Oraingoz, pasa gaitezen OSI 5. geruzara, saiora, zeinean MTProto saioa jarri zuten.

Gakoak, mezuak, saioak, Diffie-Hellman

Han jarri zuten ez guztiz behar bezala... Saio bat ez da Saio aktiboen azpian interfazean ikusgai dagoen saio bera. Baina ordenan.

Telegramen protokoloaren eta antolaketa planteamenduaren kritika. 1. zatia, teknikoa: bezero bat hutsetik idazteko esperientzia - TL, MT

Beraz, garraio geruzaren luzera ezaguna duen byte-kate bat jaso dugu. Hau enkriptatutako mezua edo testu arrunta da - oraindik gako-akordioaren fasean bagaude eta benetan egiten ari bagara. "giltza" izeneko kontzeptu sortatik zeini buruz ari gara? Argi diezaiogun gai hau Telegram taldeari berari (Barkamena eskatzen dizut neure dokumentazioa ingelesetik itzultzeagatik 4:XNUMXetan garun nekatuta, errazagoa zen esaldi batzuk dauden bezala uztea):

Bi entitate daude deituta saioa - bat bezero ofizialen UI-n "uneko saioen" azpian, non saio bakoitza gailu / OS oso bati dagokion.
Bigarrena da MTProto saioa, mezuaren sekuentzia-zenbakia (maila baxuko zentzuan) daukana, eta zein TCP konexio desberdinen artean iraun dezake. Hainbat MTProto saio instalatu daitezke aldi berean, adibidez, fitxategien deskarga azkartzeko.

Bi hauen artean saioak kontzeptu bat dago baimen. Degeneratu kasuan, hori esan dezakegu UI saioa berdina da baimen, baina ai, dena da konplikatua. Ikus dezagun:

  • Gailu berriko erabiltzaileak lehenik sortzen du auth_key eta kontuetara lotzen du, SMS bidez adibidez, horregatik baimen
  • Lehenengoaren barruan gertatu zen MTProto saioa, daukana session_id zeure barnean.
  • Urrats honetan, konbinazioa baimen ΠΈ session_id deitu liteke Esate - hitz hau bezero batzuen dokumentazioan eta kodean agertzen da
  • Ondoren, bezeroak ireki dezake hainbat MTProto saioak beraren azpian auth_key - DC berera.
  • Orduan, egunen batean bezeroak fitxategia eskatu beharko dio beste DC bat - eta DC honetarako berri bat sortuko da auth_key !
  • Sistemari jakinaraztea ez dela erabiltzaile berria erregistratzen ari dena, bera baizik baimen (UI saioa), bezeroak API deiak erabiltzen ditu auth.exportAuthorization DC etxean auth.importAuthorization DC berrian.
  • Dena berdina da, hainbat irekita egon daitezke MTProto saioak (bakoitza berearekin session_id) DC berri honi, azpian bere auth_key.
  • Azkenik, bezeroak Perfect Forward Secrecy nahi izan dezake. Bakoitzak auth_key It zen iraunkorra gakoa - DC bakoitzeko - eta bezeroak deitu dezake auth.bindTempAuthKey erabiltzeko aldi baterako auth_key - eta berriro, bakarra temp_auth_key DC bakoitzeko, guztientzat komuna MTProto saioak DC honi.

Kontuan hartu gatza (eta etorkizuneko gatzak) ere bat da auth_key horiek. guztion artean partekatua MTProto saioak DC berari.

Zer esan nahi du "TCP konexio desberdinen artean"? Beraz, honek esan nahi du antzeko zerbait baimen-cookiea webgune batean - zerbitzari jakin batera TCP konexio asko irauten du (bizirik irauten du), baina egun batean txarto egiten da. HTTP ez bezala, MTProto-n saio bateko mezuak sekuentzialki zenbakituta eta berresten dira; tunelean sartuz gero, konexioa hautsi egin zen - konexio berri bat ezarri ondoren, zerbitzariak saio honetan aurrekoan eman ez zuen guztia bidaliko du. TCP konexioa.

Hala ere, goiko informazioa hilabete askoren ikerketaren ondoren laburbiltzen da. Bitartean, gure bezeroa hutsetik ezartzen al dugu? - itzul gaitezen hasierara.

Beraz, sor gaitezen auth_key on Telegramen Diffie-Hellman bertsioak. Saia gaitezen dokumentazioa ulertzen...

Vasily, [19.06.18 20:05] data_with_hash := SHA1 (datuak) + datuak + (ausazko byte guztiak); hala nola, luzera 255 byte berdina da;
datu_zifratuak := RSA (datuak_hasharekin, zerbitzari_gako_publikoa); 255 byte luzeko zenbaki bat (big endian) behar den moduluaren gainean behar den potentziara igotzen da, eta emaitza 256 byteko zenbaki gisa gordetzen da.

Dope DH batzuk dituzte

Ez du pertsona osasuntsu baten DH itxurarik
Ez dago bi gako publiko dx-n

Beno, azkenean hori konpondu zen, baina hondakin bat geratu zen - bezeroak egiten du lanaren froga kopurua faktorizatu ahal izan zuela. DoS erasoen aurkako babes mota. Eta RSA gakoa behin bakarrik erabiltzen da norabide batean, funtsean enkriptatzeko new_nonce. Baina itxuraz sinplea den eragiketa honek arrakasta izango duen arren, zeri aurre egin beharko diozu?

Vasily, [20.06.18/00/26 XNUMX:XNUMX] Oraindik ez dut aplikazioaren eskaerara iritsi

Eskaera hau DH-ri bidali nion

Eta, garraio kaian esaten du errore-kode baten 4 byterekin erantzun dezakeela. Hori da dena

Tira, -404 esan zidan, zer?

Beraz, esan nion: "Harrapa ezazu zure zezenkeriak zerbitzariaren gako batekin zifratuta honelako hatz-marka batekin, DH nahi dut", eta 404 ergel batekin erantzun zidan.

Zer pentsatuko zenuke zerbitzariaren erantzun honi buruz? Zer egin? Ez dago inor galdetzeko (baina bigarren zatian gehiago).

Hemen interes guztia kaian egiten da

Ez daukat beste ezer egiteko, zenbakiak atzera eta aurrera bihurtzearekin amesten nuen

32 biteko bi zenbaki. Beste guztiak bezala ontziratu ditut

Baina ez, bi hauek lerrora gehitu behar dira lehenik BE bezala

Vadim Goncharov, [20.06.18 15:49] eta horregatik 404?

Vasily, [20.06.18 15:49] BAI!

Vadim Goncharov, [20.06.18 15:50] beraz, ez dut ulertzen zer "ez zuen aurkitu"

Vasily, [20.06.18 15:50] buruz

Ezin izan nuen halako deskonposiziorik aurkitu faktore lehenetan%

Akatsen berri ematea ere ez dugu kudeatu

Vasily, [20.06.18 20:18] Oh, MD5 ere badago. Dagoeneko hiru hash ezberdin

Gakoaren hatz-marka honela kalkulatzen da:

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

SHA1 eta sha2

Beraz, jar dezagun auth_key 2048 bit jaso ditugu Diffie-Hellman erabiliz. Zer da hurrengoa? Jarraian, gako honen beheko 1024 bitak ez direla inola ere erabiltzen ikusiko dugu... baina pentsa dezagun oraingoz. Urrats honetan, zerbitzariarekin partekatutako sekretu bat dugu. TLS saioaren analogo bat ezarri da, prozedura oso garestia dena. Baina zerbitzariak oraindik ez daki ezer garenari buruz! Oraindik ez, egia esan. baimena. Horiek. "saio-saio-pasahitza"-n pentsatu bazenuen, behin ICQ-n egin zenuen bezala, edo gutxienez "saio-hasi-gakoa", SSH-n bezala (adibidez, gitlab/github batzuetan). Anonimo bat jaso genuen. Zerbitzariak "telefono-zenbaki hauei beste DC batek ematen die zerbitzua" esaten badigu? Edo "zure telefono zenbakia debekatuta dago"? Egin dezakegun onena giltza mantentzea da baliagarria izango den eta ordurako ustelduko ez den itxaropenarekin.

Bide batez, erreserbekin β€œjaso” genuen. Adibidez, zerbitzariarekin fidatzen al gara? Eta faltsua bada? Egiaztapen kriptografikoak beharko lirateke:

Vasily, [21.06.18 17:53] Mugikorreko bezeroei eskaintzen diete 2 kbiteko zenbaki bat lehen mailakoa egiaztatzeko.

Baina ez dago batere argi, nafeijoa

Vasily, [21.06.18 18:02] Dokumentuak ez du esaten zer egin erraza ez bada.

Ez da esan. Ikus dezagun zer egiten duen Android bezero ofizialak kasu honetan? A hori da (eta bai, fitxategi osoa interesgarria da) - esan bezala, hemen utziko dut:

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

Ez, noski oraindik hor dago batzuk Zenbaki baten nagusitasunari buruzko probak daude, baina pertsonalki jada ez dut matematikaren ezagutza nahikorik.

Ados, giltza nagusia lortu dugu. Saioa hasteko, hau da. eskaerak bidali, enkriptatzea gehiago egin behar duzu, AES erabiliz.

Mezuaren gakoa mezuaren gorputzaren SHA128 erdiko 256 bit gisa definitzen da (saioa, mezuaren IDa, etab. barne), betegarrizko byteak barne, baimen-gakotik hartutako 32 bytez aurretik jarrita.

Vasily, [22.06.18 14:08] Batez bestekoa, puta, bits

Jaso auth_key. Denak. Horietatik haratago... dokumentuan ez dago argi. Anima zaitez iturburu irekiko kodea aztertzen.

Kontuan izan MTProto 2.0-k 12 eta 1024 byte arteko betegarritasuna behar duela, hala ere, ondoriozko mezuaren luzera 16 bytez zatigarria izateko baldintzapean.

Beraz, zenbat betegarri gehitu behar duzu?

Eta bai, akatsen kasuan 404 bat ere badago

Norbaitek dokumentazioaren diagrama eta testua arretaz aztertuz gero, hor MACrik ez dagoela ohartu zen. Eta AES hori beste inon erabiltzen ez den IGE modu jakin batean erabiltzen da. Haiek, noski, horri buruz idazten dute beren FAQ-en... Hemen, adibidez, mezuaren gakoa bera deszifratutako datuen SHA hash-a ere bada, osotasuna egiaztatzeko erabiltzen dena, eta bat ez datozenean, dokumentazioa arrazoiren batengatik. isilean jaramonik ez egitea gomendatzen du (baina segurtasunarekin zer gertatzen da, hausten badigute?).

Ni ez naiz kriptografoa, agian ez dago gaizki modu honetan kasu honetan ikuspuntu teorikotik. Baina argi eta garbi izenda dezaket arazo praktiko bat, Telegram Desktop adibide gisa erabiliz. Tokiko cachea (D877F783D5D3EF8C horiek guztiak) MTProtoko mezuen modu berean enkriptatzen du (kasu honetan soilik 1.0 bertsioa), hau da. lehenik mezuaren gakoa, gero datuak berak (eta nonbait handi nagusia alde batera utzita auth_key 256 byte, hori gabe msg_key alferrikakoa). Beraz, arazoa fitxategi handietan nabaritzen da. Alegia, datuen bi kopia gorde behar dituzu: enkriptatutako eta deszifratuta. Eta megabyte badaude, edo streaming bideoa, adibidez?... Testu zifratuaren ondoren MAC duten eskema klasikoek korrontea irakurtzeko aukera ematen dute, berehala transmitituz. Baina MTProto-rekin egin beharko duzu hasieran mezu osoa enkriptatu edo deszifratu, gero sarera edo diskora transferitu. Hori dela eta, Telegram Desktop-en azken bertsioetan cachean user_data Beste formatu bat ere erabiltzen da - AESrekin CTR moduan.

Vasily, [21.06.18 01:27] Oh, IGE zer den jakin nuen: IGE izan zen "autentifikazio-enkriptatzeko modua" egiteko lehen saiakera, jatorriz Kerberosentzat. Huts egin zuen saiakera bat izan zen (ez du osotasunaren babesik ematen), eta kendu egin behar izan zen. Hori izan zen funtzionatzen duen enkriptatze modu autentifikatzaile baten 20 urteko bilaketaren hasiera, duela gutxi OCB eta GCM moduetan amaitu zena.

Eta orain gurdiaren aldeko argudioak:

Telegramen atzean dagoen taldea, Nikolai Durov-ek zuzentzen duena, ACMko sei txapeldunek osatzen dute, erdiak matematikan doktoreak. Bi urte inguru behar izan zituzten MTProtoren egungo bertsioa zabaltzeko.

Hori barregarria da. Bi urte beheko mailan

Edo tls har dezakezu

Ados, demagun enkriptatzea eta beste Γ±abardura batzuk egin ditugula. Azkenean posible al da eskaerak TL-n seriatuta bidaltzea eta erantzunak deserializatu? Beraz, zer eta nola bidali behar duzu? Hemen, demagun, metodoa initConnection, agian hau da?

Vasily, [25.06.18 18:46] Konexioa hasieratzen du eta erabiltzailearen gailuan eta aplikazioan informazioa gordetzen du.

App_id, device_model, system_version, app_version eta lang_code onartzen ditu.

Eta zenbait kontsulta

Beti bezala dokumentazioa. Anima zaitez kode irekia aztertzen

InvokeWithLayer-ekin dena gutxi gorabehera argi bazegoen, zer dago gaizki hemen? Bihurtzen da, demagun badugula - bezeroak zerbitzariari buruz galdetu beharreko zerbait - bidali nahi genuen eskaera bat dago:

Vasily, [25.06.18 19:13] Kodearen arabera, lehen deia txalo honetan bilduta dago, eta txorakeria bera invokewithlayer-en bilduta dago.

Zergatik ezin izan initConnection dei bereizia izan, baina bilgarri bat izan behar du? Bai, ikusi denez, saio bakoitzaren hasieran aldiro egin behar da, eta ez behin, tekla nagusiarekin bezala. Baina! Ezin dio baimenik gabeko erabiltzaile batek deitu! Orain aplikagarria den fasera iritsi gara Hau dokumentazio orria - eta esaten digu...

API metodoen zati txiki bat baino ez dago erabilgarri baimenik gabeko erabiltzaileentzat:

  • auth.sendCode
  • auth.resendCode
  • kontua.getPassword
  • auth.checkPassword
  • auth.checkTelefonoa
  • auth.signUp
  • autentifikazioa.saioa
  • auth.importAuthorization
  • help.getConfig
  • lagundu.getNearestDc
  • help.getAppUpdate
  • help.getCdnConfig
  • langpack.getLangPack
  • langpack.getStrings
  • langpack.getDifference
  • langpack.getLanguages
  • langpack.getLanguage

Horietako lehenengoa, auth.sendCode, eta badago api_id eta api_hash bidaltzen ditugun lehen eskaera kuttun hori, eta horren ostean kode batekin SMS bat jasotzen dugu. Eta DC okerrean bagaude (herrialde honetako telefono-zenbakiak beste batek hornitzen ditu, adibidez), orduan errore bat jasoko dugu nahi den DC zenbakiarekin. DC zenbakiaren arabera zein IP helbidea konektatu behar duzun jakiteko, lagundu iezaguzu help.getConfig. Garai batean 5 sarrera baino ez zeuden, baina 2018ko gertaera ospetsuen ostean, kopurua nabarmen handitu da.

Orain gogoratu dezagun zerbitzarian modu anonimoan iritsi ginela fase honetara. Ez al da garestiegia IP helbide bat lortzea? Zergatik ez egin hau eta beste eragiketa batzuk MTProto-ren zifratu gabeko zatian? Objekzioa entzuten dut: "nola ziurtatu RKN ez dela helbide faltsuekin erantzungo duena?" Honi gogoratzen dugu, oro har, bezero ofizialak RSA gakoak txertatuta daude, hau da. besterik ezin duzu harpidetu informazio hori. Egia esan, bezeroek beste kanaletatik jasotzen dituzten blokeoak saihesteko informaziorako egiten ari da dagoeneko (logikoki, hori ezin da MTProto-n bertan egin; non konektatu ere jakin behar da).

ADOS. Bezeroaren baimenaren fase honetan, oraindik ez gaude baimenduta eta ez dugu gure aplikazioa erregistratu. Oraingoz zerbitzariak baimenik gabeko erabiltzaile baten eskura dauden metodoei zer erantzuten dien ikusi nahi dugu. Eta hemen…

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;

Eskeman, lehenengoa bigarrena da

tdesktop eskeman hirugarren balioa da

Bai, harrezkero, noski, dokumentazioa eguneratu egin da. Laster berriro garrantzirik gabekoa izan daitekeen arren. Nola jakin behar du garatzaile hasiberri batek? Agian zure eskaera erregistratzen baduzu, jakinaraziko dizute? Vasilik hau egin zuen, baina ai, ez zioten ezer bidali (berriro, bigarren zatian hitz egingo dugu honetaz).

...Ohartu zara dagoeneko nolabait APIra pasatu garela, hau da. hurrengo mailara, eta zerbait galdu duzu MTProto gaian? Ez da harritzekoa:

Vasily, [28.06.18 02:04] Mm, e2e-n algoritmo batzuk arakatzen ari dira

Mtproto-k bi domeinuetarako enkriptazio-algoritmoak eta gakoak definitzen ditu, baita bilgarri-egitura pixka bat ere

Baina etengabe nahasten dituzte pilaren maila desberdinak, beraz, ez dago beti argi non amaitu zen mtproto eta hurrengo maila hasi zen

Nola nahasten dira? Beno, hona hemen PFSrako aldi baterako gako bera, adibidez (bide batez, Telegram Desktopek ezin du egin). API eskaera baten bidez exekutatzen da auth.bindTempAuthKey, hau da. maila gorenetik. Baina, aldi berean, beheko mailan enkriptatzea oztopatzen du - horren ondoren, adibidez, berriro egin behar duzu initConnection etab., hau ez da besterik ez eskaera normala. Berezia dena da DC bakoitzeko aldi baterako gako BAT bakarrik izan dezakezula, eremua izan arren auth_key_id mezu bakoitzean gakoa gutxienez mezu guztietan aldatzeko aukera ematen du, eta zerbitzariak edozein unetan behin-behineko gakoa "ahazteko" eskubidea duela - dokumentazioak ez du esaten zer egin kasu honetan... beno, zergatik liteke. ez dituzu hainbat gako, etorkizuneko gatz multzo batekin bezala, eta?...

MTProto gaiari buruz aipatzeko moduko beste gauza batzuk daude.

Mezu-mezuak, msg_id, msg_seqno, berrespenak, ping-ak noranzko okerrean eta beste idiosinkrasia batzuk

Zergatik jakin behar duzu horiei buruz? Goiko maila batera "ihes" egiten dutelako, eta horien berri izan behar duzulako APIarekin lan egiten duzunean. Demagun ez zaigula msg_key interesatzen; beheko mailak dena deszifratu digu. Baina deszifratutako datuen barruan honako eremu hauek ditugu (datuen luzera ere, beraz, betegarria non dagoen dakigu, baina hori ez da garrantzitsua):

  • gatza - int64
  • session_id - int64
  • mezu_id β€” int64
  • seq_no - int32

Gogora dezagun DC osorako gatz bakarra dagoela. Zergatik jakin haren berri? Ez eskaera bat dagoelako bakarrik get_future_salts, zein tarte balioko duten esaten dizu, baina baita zure gatza "ustelduta" bada, orduan mezua (eskaera) galduko da besterik gabe. Zerbitzariak, jakina, gatz berriaren berri emango du jaulkiz new_session_created - baina zaharrarekin nolabait berriro bidali beharko duzu, adibidez. Eta arazo honek aplikazioen arkitekturari eragiten dio.

Zerbitzariak saioak guztiz uzteko eta modu honetan erantzuteko baimena du arrazoi askorengatik. Egia esan, zer da MTProto saio bat bezeroaren aldetik? Hauek bi zenbaki dira session_id ΠΈ seq_no saio honen barruan mezuak. Beno, eta azpian dagoen TCP konexioa, noski. Demagun gure bezeroak oraindik ez dakiela gauza asko egiten, deskonektatu, berriro konektatu zen. Hau azkar gertatu bada - saio zaharrak TCP konexio berrian jarraitu zuen, handitu seq_no aurrerago. Denbora luzea hartzen badu, zerbitzariak ezaba dezake, bere aldean ere ilara bat baitago, jakin dugunez.

Zer izan behar du seq_no? Oh, galdera delikatua da. Saiatu zintzotasunez ulertzen zer esan nahi den:

Edukiari lotutako mezua

Aitorpen esplizitua eskatzen duen mezua. Horien artean erabiltzailearen mezu guztiak eta zerbitzu asko daude, ia guztiak edukiontziak eta aitorpenak izan ezik.

Mezuen sekuentzia zenbakia (msg_seqno)

32 biteko zenbaki bat "edukiari lotutako" mezu kopuruaren bikoitza (onarpena eskatzen dutenak, eta bereziki edukiontziak ez direnak) igorleak mezu hau baino lehen sortu eta gero bat handitzen duena, uneko mezua bada. edukiarekin lotutako mezua. Edukiontzi bat beti sortzen da bere eduki osoaren ondoren; hortaz, bere sekuentzia-zenbakia bertan dauden mezuen sekuentzia-zenbakiak baino handiagoa edo berdina da.

Nolako zirkua da hau 1eko gehikuntzarekin, eta gero beste bat 2rekin?... Hasiera batean "ACKren bit esanguratsuena, gainerakoa zenbaki bat da" esan nahi zutela susmoa dut, baina emaitza ez da guztiz berdina - bereziki, ateratzen da, bidal daiteke hainbat berdinak dituzten baieztapenak seq_no! Nola? Bada, adibidez, zerbitzariak zerbait bidaltzen digu, bidaltzen digu, eta gu geu isilik geratzen gara, bere mezuak jaso direla baieztatzen duten zerbitzu-mezuekin soilik erantzuten. Kasu honetan, gure irteerako baieztapenek irteerako zenbaki bera izango dute. TCP ezagutzen baduzu eta hau nolabait basatia dirudiela uste baduzu, baina ez dirudi oso basatia, TCPn delako seq_no ez da aldatzen, baina baieztapena doa seq_no beste aldean, ziztu bizian asaldatzera. Berrespenak MTProto-n ematen dira EZ on seq_no, TCPn bezala, baina arabera msg_id !

Zer da hau msg_id, arlo horietako garrantzitsuena? Mezu-identifikatzaile bakarra, izenak dioen bezala. 64 biteko zenbaki gisa definitzen da, bit txikienek berriz "zerbitzaria-ez-zerbitzaria" magia dute, eta gainerakoa Unix-en denbora-zigilua da, zati zatikatua barne, 32 biteko ezkerrera desplazatuta. Horiek. denbora-zigilua berez (eta denbora gehiegi desberdintzen diren mezuak zerbitzariak baztertuko ditu). Hortik ateratzen da, oro har, bezeroarentzat globala den identifikatzaile bat dela. Hori ikusita - gogora dezagun session_id - Bermatuta gaude: Inolaz ere ezin da saio baterako zuzendutako mezu bat beste saio batera bidali. Hau da, jada badagoela ematen du hiru maila - saioa, saioaren zenbakia, mezuaren ID. Zergatik halako gehiegizko konplikazioa, misterio hau oso handia da.

Horrela, msg_id behar...

RPC: eskaerak, erantzunak, akatsak. Baieztapenak.

Konturatuko zinen bezala, diagraman ez dago "RPC eskaera bat egin" mota edo funtzio berezirik, nahiz eta erantzunak egon. Azken finean, edukiarekin lotutako mezuak ditugu! Hori da, edozein mezua eskaera bat izan daiteke! Edo ez izateko. Azken finean, bakoitzeko dago msg_id. Baina erantzunak daude:

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

Hor adierazten da zein mezuri erantzun zaion. Hori dela eta, APIaren goi mailan, zure eskaeraren zenbakia zein zen gogoratu beharko duzu - uste dut ez dagoela azaldu beharrik lana asinkronoa dela, eta hainbat eskaera egon daitezkeela aldi berean, zein erantzunak edozein ordenatan itzul daitezke? Printzipioz, honetatik eta langilerik ez bezalako errore-mezuetatik abiatuta, honen atzean dagoen arkitekturaren jarraipena egin daiteke: zurekin TCP konexioa mantentzen duen zerbitzaria front-end orekatzailea da, eskaerak backendetara bidaltzen ditu eta itzultzen ditu. message_id. Badirudi hemen dena argia, logikoa eta ona dela.

Bai?.. Eta pentsatzen baduzu? Azken finean, RPC erantzunak berak ere badu eremu bat msg_id! Zerbitzariari "ez duzu nire erantzuna erantzuten!" oihu egin behar al diogu? Eta bai, zer zegoen konfirmazioetan? Orriari buruz mezuei buruzko mezuak zer den esaten digu

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

eta alde bakoitzak egin behar du. Baina ez beti! RpcResult bat jaso baduzu, berak berrespen gisa balio du. Hau da, zerbitzariak zure eskaerari erantzun diezaioke MsgsAck-ekin, esate baterako, "Jaso dut". RpcResult-ek berehala erantzun dezake. Biak izan daitezke.

Eta bai, oraindik erantzuna erantzun behar duzu! Berrespena. Bestela, zerbitzariak entregatu ezinezkotzat joko du eta berriro bidaliko dizu. Berriro konektatu ondoren ere. Baina hemen, noski, denbora-mugaren arazoa sortzen da. Ikus ditzagun pixka bat geroago.

Bitartean, ikus ditzagun kontsultaren exekuzio akats posibleak.

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

Oh, norbaitek oihukatuko du, hona hemen formatu gizatiarragoa - lerro bat dago! Hartu zure denbora. Hemen akatsen zerrenda, baina, noski, ez osoa. Hortik ikasten dugu kodea dela antzeko zerbait HTTP erroreak (beno, noski, erantzunen semantika ez da errespetatzen, leku batzuetan ausaz banatzen dira kodeen artean), eta lerroak itxura du. CAPITAL_LETTERS_AND_NUMBERS. Adibidez, PHONE_NUMBER_OCCUPIED edo FILE_PART_Π₯_MISSING. Tira, hau da, oraindik ere lerro hau beharko duzu analizatu. Adibidez FLOOD_WAIT_3600 ordubete itxaron behar duzula esan nahi du, eta PHONE_MIGRATE_5, aurrizki hori duen telefono-zenbaki bat 5. DCn erregistratu behar dela. Hizkuntza mota bat dugu, ezta? Ez dugu kate bateko argumenturik behar, ohikoek nahiko dute, ados.

Berriz ere, hau ez dago zerbitzu-mezuen orrian, baina, proiektu honekin ohikoa den bezala, informazioa aurki daiteke beste dokumentazio orri batean. edo susmoa bota. Lehenik eta behin, begiratu, idazketa/geruza urratzea - RpcError habiaratu daiteke RpcResult. Zergatik ez kanpoan? Zer ez genuen kontuan hartu?... Horren arabera, non dago horren bermea RpcError baliteke EZ sartuta egotea RpcResult, baina zuzenean edo beste mota batean habiatuta egon?... Eta ezin bada, zergatik ez dago goi mailan, hau da. falta da req_msg_id ? ..

Baina jarrai dezagun zerbitzu mezuei buruz. Bezeroak zerbitzariak denbora luzez pentsatzen duela pentsa dezake eta eskaera zoragarri hau egin dezake:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Galdera honetarako hiru erantzun posible daude, berriro ere berrespen-mekanismoarekin gurutzatuta; zer izan behar duten ulertzen saiatzea (eta baieztapenik behar ez duten moten zerrenda orokorra zein den) irakurlearen esku uzten da etxeko lan gisa (oharra: informazioa Telegram Desktop iturburu kodea ez dago osatuta).

Droga-mendekotasuna: mezuen egoerak

Orokorrean, TL, MTProto eta Telegram-en leku askotan egoskorkeria sentsazioa uzten dute, baina adeitasunagatik, taktoagatik eta beste batzuengatik. Soft gaitasunak Poliki isilik geratu ginen, eta elkarrizketetako lizunkeriak zentsuratu. Hala ere, leku hauОorrialdearen zatirik handiena buruz da mezuei buruzko mezuak Harrigarria da niretzat ere, sareko protokoloekin lan egiten dudan denbora luzez eta makurtasun maila ezberdineko bizikletak ikusi ditudana.

Inozoki hasten da, baieztapenekin. Jarraian kontatzen digute

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;

Bada, MTProto-rekin lanean hasten diren guztiek aurre egin beharko diete; "zuzendu - birkonpilatu - abiarazi" zikloan, ediketetan gaizki ateratzea lortu duten zenbaki-akatsak edo gatza lortzea ohikoa da. Hala ere, bi puntu daude hemen:

  1. Horrek esan nahi du jatorrizko mezua galdu dela. Ilara batzuk sortu behar ditugu, geroago ikusiko dugu.
  2. Zeintzuk dira errore-zenbaki bitxi hauek? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... non daude beste zenbakiak, Tommy?

Dokumentazioak dio:

Asmoa da error_code balioak taldekatzea (error_code >> 4): adibidez, 0x40 β€” 0x4f kodeak edukiontzien deskonposizioan akatsei dagozkie.

baina, lehenik, beste norabidean aldatzea, eta bigarrenik, berdin dio, non daude gainerako kodeak? Egilearen buruan?... Hala ere, huskeriak dira hauek.

Menpekotasuna mezuen egoerari eta mezuen kopiei buruzko mezuetan hasten da:

  • Mezuen egoerari buruzko informazioa eskatzea
    Alderdi batek irteteko mezuen egoerari buruzko informaziorik jaso ez badu denbora batean, berariaz eska diezaioke beste alderdiari:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Mezuen egoerari buruzko informazio-mezua
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Hemen, info Sarrerako msg_ids zerrendako mezu bakoitzeko byte bat zehatz-mehatz daukan kate bat da:

    • 1 = ez da ezer ezagutzen mezuari buruz (msg_id baxuegia, baliteke beste alderdiak ahaztu izana)
    • 2 = mezua ez da jaso (msg_id gordetako identifikatzaileen barrutian sartzen da; hala ere, beste alderdiak ez du horrelako mezurik jaso)
    • 3 = mezua ez da jaso (msg_id altuegia; hala ere, beste alderdiak ez du oraindik jaso)
    • 4 = jasotako mezua (kontuan izan erantzun hau ordainagiria ere badela aldi berean)
    • +8 = dagoeneko onartutako mezua
    • +16 = onarpenik behar ez duen mezua
    • +32 = Prozesatzen ari den mezuan edo prozesatzen dagoeneko amaituta dagoen RPC kontsulta
    • +64 = edukiari lotutako erantzuna dagoeneko sortutako mezuari
    • +128 = beste batek badaki mezua dagoeneko jaso dela
      Erantzun honek ez du onarpenik behar. Dagokion msgs_state_req-aren aitorpena da, berez.
      Kontuan izan bat-batean beste alderdiak bidali zaion mezurik ez daukala, mezua berriro bidal daitekeela. Beste aldeak mezuaren bi kopia aldi berean jaso behar baditu ere, bikoiztua ez da aintzat hartuko. (Denbora gehiegi igaro bada, eta jatorrizko msg_id-ak balio ez badu, mezua msg_copy-n bildu behar da).
  • Mezuen egoeraren borondatezko komunikazioa
    Alderdi bakoitzak bere borondatez jakinarazi diezaioke beste alderdiari beste alderdiak igorritako mezuen egoera.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Mezu baten egoeraren borondatezko komunikazio hedatua
    ...
    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;
  • Mezuak berriro bidaltzeko eskaera esplizitua
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    Urruneko alderdiak berehala erantzuten du eskatutako mezuak berriro bidaliz [...]
  • Erantzunak berriro bidaltzeko eskaera esplizitua
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    Urruneko alderdiak berehala erantzuten du berriro bidaliz erantzunak eskatutako mezuetara […]
  • Mezuen kopiak
    Zenbait egoeratan, baliorik ez duen msg_id duen mezu zahar bat berriro bidali behar da. Ondoren, kopia-edukiontzi batean bilduko da:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Behin jasota, mezua bilgarria bertan egongo ez balitz bezala prozesatzen da. Hala ere, orig_message.msg_id mezua jaso dela ziur badaki, orduan mezu berria ez da prozesatzen (aldi berean, hori eta orig_message.msg_id onartzen dira). orig_message.msg_id balioak edukiontziaren msg_id baino txikiagoa izan behar du.

Isilik ere egon gaitezen zertaz msgs_state_info berriro ere amaitu gabeko TL-aren belarriak ateratzen ari dira (byte-bektore bat behar genuen, eta beheko bi bitetan enum bat zegoen, eta bi bit altuenean banderak zeuden). Kontua ezberdina da. Inork ulertzen al du zergatik den hau guztia praktikan? benetako bezero batean beharrezkoa?... Zailtasunarekin, baina onuraren bat imajina daiteke pertsona bat arazketan aritzen bada eta modu interaktiboan - galdetu zerbitzariari zer eta nola. Baina hemen eskaerak deskribatzen dira joan-etorria.

Horren ondorioz, alderdi bakoitzak mezuak enkriptatu eta bidali behar ditu, baizik eta bere buruari buruzko datuak, haiei emandako erantzunei buruzkoak, denbora ezezagun batean gorde behar dituela. Dokumentazioak ez du deskribatzen ezaugarri horien denborak edo aplikagarritasun praktikoa. inola ere ez. Harrigarriena da benetan bezero ofizialen kodean erabiltzen direla! Dirudienez, dokumentazio publikoan jasota ez zegoen zerbait kontatu zieten. Kodetik ulertu zergatik, jada ez da TL-ren kasuan bezain sinplea - ez da (nahiko) logikoki isolatutako zati bat, aplikazio-arkitekturari loturiko pieza bat baizik, hau da. denbora nabarmen gehiago beharko du aplikazioaren kodea ulertzeko.

Ping eta denborak. Ilarak.

Dena den, zerbitzariaren arkitekturari buruzko asmakizunak gogoratzen baditugu (eskaerak backend-en artean banatzea), gauza triste samarra dator - TCPn entrega-berme guztiak izan arren (datuak entregatzen dira edo hutsunearen berri emango zaizu, baina datuak arazoa gertatu baino lehen entregatuko dira), berrespenak MTProto bertan - bermerik ez. Zerbitzariak erraz galdu edo bota dezake zure mezua, eta ezin da ezer egin horri buruz, erabili makulu mota desberdinak.

Eta lehenik eta behin - mezu-ilarak. Beno, gauza batekin dena begi-bistakoa zen hasiera-hasieratik: baieztatu gabeko mezu bat gorde eta berriro bidali behar da. Eta zer ordu igarota? Eta bufoiak ezagutzen du. Agian menpekotasun-mezu horiek makuluekin arazo hau nolabait konpontzen dute, esate baterako, Telegram Desktop-en haiei dagozkien 4 ilara inguru daude (agian gehiago, esan bezala, horretarako bere kodean eta arkitekturan serioago sakondu behar duzu; aldi berean denbora, badakigu ezin dela lagin gisa hartu; MTProto eskemako mota kopuru jakin bat ez da bertan erabiltzen).

Zergatik gertatzen da hau? Seguruenik, zerbitzari-programatzaileek ezin izan zuten kluster barruan fidagarritasuna bermatu, ezta aurrealdeko orekatzailean buffering-a ere, eta arazo hau bezeroari transferitu zioten. Etsipenaz, Vasily aukera alternatibo bat ezartzen saiatu zen, bi ilara bakarrik, TCP-ren algoritmoak erabiliz - RTT zerbitzarirako neurtzea eta "leihoaren" tamaina (mezuetan) doitzea baieztatu gabeko eskaera kopuruaren arabera. Hau da, zerbitzariaren karga ebaluatzeko hain heuristika zakarra da gure eskaera zenbat murtxikatu ditzakeen aldi berean eta ez galdu.

Tira, hau da, ulertzen duzu, ezta? TCP berriro inplementatu behar baduzu TCPren gainean exekutatzen den protokolo baten gainean, horrek oso gaizki diseinatutako protokoloa adierazten du.

Bai, zergatik behar duzu ilara bat baino gehiago, eta zer esan nahi du horrek goi-mailako API batekin lan egiten duen pertsonarentzat, hala ere? Begira, eskaera bat egiten duzu, serializatu, baina askotan ezin duzu berehala bidali. Zergatik? Erantzuna izango baita msg_id, aldi baterakoa denaΠ°Etiketa bat naiz, zeinaren esleipena ahalik eta beranduen arte atzeratu behar den - zerbitzariak gure eta beraren arteko denbora-desegokitasunagatik baztertzen badu (noski, gure denbora orainalditik aldentzen duen makulu bat egin dezakegu. zerbitzariari zerbitzariaren erantzunetatik kalkulatutako delta bat gehituz - bezero ofizialek hori egiten dute, baina gordina eta zehazgabea da buffering-a dela eta). Hori dela eta, liburutegitik funtzio lokaleko dei batekin eskaera bat egiten duzunean, mezua fase hauetatik igarotzen da:

  1. Ilara batean dago eta enkriptatzeko zain dago.
  2. Izendatua msg_id eta mezua beste ilara batera joan zen - birbidaltze posiblea; bidali socketera.
  3. a) Zerbitzariak MsgsAck erantzun zuen - mezua entregatu zen, "beste ilaratik" ezabatzen dugu.
    b) Edo alderantziz, ez zitzaion zerbait gustatzen, badmsg erantzun zuen - "beste ilara batetik" birbidali
    c) Ez da ezer ezagutzen, mezua beste ilara batetik birbidali behar da, baina ez da ezagutzen zehazki noiz.
  4. Zerbitzariak erantzun zuen azkenean RpcResult - benetako erantzuna (edo akatsa) - ez bakarrik entregatu, baita prozesatu ere.

Agian, edukiontziak erabiltzeak arazoa partzialki konpondu lezake. Hau da mezu mordoa bakarrean biltzen direnean, eta zerbitzariak denei berrespen batekin erantzun die aldi berean, batean. msg_id. Baina pakete hori ere baztertuko du, zerbait gaizki ateraz gero, bere osotasunean.

Eta puntu honetan, teknikoak ez diren kontuak sartzen dira jokoan. Esperientziaz, makulu asko ikusi ditugu, eta horrez gain, aholku eta arkitektura txarren adibide gehiago ikusiko ditugu orain –halako baldintzetan, merezi al du fidatzeak eta horrelako erabakiak hartzeak–? Galdera erretorikoa da (noski ez).

Zertaz ari gara? "Mezuei buruzko droga-mezuen" gaiari buruz oraindik espekulatu dezakezu "ergela zara, ez duzu gure plan bikaina ulertu!" (Beraz, idatzi dokumentazioa lehenik, jende arruntak behar lukeen moduan, paketeen trukearen arrazoi eta adibideekin, gero hitz egingo dugu), orduan denborak/denbora-mugak galdera praktiko eta zehatz hutsa dira, hemen dena aspalditik ezagutzen da. Zer esaten digu dokumentazioak denbora-mugei buruz?

Zerbitzari batek normalean bezero baten mezu bat jaso izana aitortzen du (normalean, RPC kontsulta bat) RPC erantzuna erabiliz. Erantzun bat denbora luzez iristen bada, zerbitzari batek ordainagiria bidal dezake lehenik, eta pixka bat geroago, RPC erantzuna bera.

Bezero batek normalean zerbitzari batetik mezu bat jaso duela aitortzen du (normalean, RPC erantzuna) hurrengo RPC kontsultari aitorpena gehituz, beranduegi transmititzen ez bada (sortzen bada, demagun, 60-120 segundo jaso ondoren). zerbitzariaren mezu batena). Hala ere, denbora luzez zerbitzariari mezuak bidaltzeko arrazoirik ez badago edo zerbitzaritik aitortu gabeko mezu ugari badago (esaterako, 16 baino gehiago), bezeroak aitorpen autonomoa transmititzen du.

... Itzultzen dut: guk geuk ez dakigu zenbat eta nola behar dugun, beraz, demagun horrela izan dadila.

Eta pingei buruz:

Ping mezuak (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Erantzun bat konexio berera itzultzen da normalean:

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

Mezu hauek ez dute onarpenik behar. Pong bat ping bati erantzuteko soilik transmititzen da, ping-a bi aldeetatik hasi daitekeen bitartean.

Konexioaren itxiera geroratua + PING

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

Ping bezala funtzionatzen du. Horrez gain, hori jaso ondoren, zerbitzariak tenporizadore bat abiarazten du eta horrek uneko konexioa disconnect_delay segundo geroago itxiko du, aurreko tenporizadore guztiak automatikoki berrezartzen dituen mota bereko mezu berri bat jasotzen ez badu. Bezeroak ping hauek 60 segundoan behin bidaltzen baditu, adibidez, disconnect_delay 75 segundoren berdina ezar dezake.

Zoratuta zaude?! 60 segundotan, trena geltokira sartuko da, bidaiariak utzi eta hartuko ditu, eta berriro ere kontaktua galduko du tunelean. 120 segundotan, entzuten duzun bitartean, beste batera iritsiko da, eta ziurrenik konexioa hautsi egingo da. Beno, argi dago hankak nondik datozen - "Dei bat entzun dut, baina ez dakit non dagoen", badago Nagl-en algoritmoa eta TCP_NODELAY aukera, lan interaktiborako pentsatua. Baina, barkatu, eutsi bere balio lehenetsiari - 200 Millisegundoak Benetan antzeko zerbait irudikatu eta pakete pare batean gorde nahi baduzu, utzi 5 segundoz edo "Erabiltzailea idazten ari da..." mezuaren denbora-muga dagoena. Baina gehiago ez.

Eta azkenik, ping-ak. Hau da, TCP konexioaren bizitasuna egiaztatzea. Dibertigarria da, baina duela 10 bat urte gure fakultateko gelako mezulariari buruzko testu kritiko bat idatzi nuen; hango egileek bezeroaren zerbitzariari ping egin zioten, eta ez alderantziz. Baina 3. mailako ikasleak gauza bat dira, eta nazioarteko bulego bat beste bat, ezta?...

Lehenik eta behin, hezkuntza programa txiki bat. TCP konexioa, pakete-trukerik ezean, astez bizi daiteke. Hau ona eta txarra da, helburuaren arabera. Ona da zerbitzarirako SSH konexioa irekita bazenu, ordenagailutik altxatu, bideratzailea berrabiarazi, zure lekura itzuli zinen - zerbitzari honen bidezko saioa ez zen urratu (ez duzu ezer idatzi, ez zegoen paketerik) , komenigarria da. Txarra da zerbitzarian milaka bezero badaude, bakoitzak baliabideak hartzen dituela (kaixo, Postgres!), eta baliteke bezeroaren ostalaria duela denbora asko berrabiarazi izana, baina ez dugu horren berri izango.

Txat/IM sistemak bigarren kasuan sartzen dira beste arrazoi batengatik: lineako egoerak. Erabiltzailea "erortzen" bada, bere solaskideei horren berri eman behar diezu. Bestela, Jabber-en sortzaileek egin zuten (eta 20 urtez zuzendutako) akats batekin amaituko duzu - erabiltzailea deskonektatu da, baina mezuak idazten jarraitzen dute, sarean dagoela sinetsita (horietan ere guztiz galduta zeudenak). deskonexioa aurkitu baino minutu batzuk lehenago). Ez, TCP_KEEPALIVE aukerak, TCP tenporizadoreek nola funtzionatzen duten ulertzen ez duten jende askok ausaz sartzen duen (hamarka segundo bezalako balio basatiak ezarriz), ez du hemen lagunduko - OS kernelak ez ezik, ziurtatu behar duzu. erabiltzailearen makina bizirik dago, baina normal funtzionatzen du, erantzuteko gai, eta aplikazioa bera (uste duzu ezin dela izoztu? Ubuntu 18.04ko Telegram mahaigaina izoztu zitzaidan behin baino gehiagotan).

Horregatik ping egin behar duzu zerbitzaria bezeroa, eta ez alderantziz - bezeroak hau egiten badu, konexioa hausten bada, ping-a ez da emango, helburua ez da lortuko.

Zer ikusten dugu Telegramen? Justu kontrakoa da! Tira, hori da. Formalki, noski, bi aldeek elkarri ping dezakete. Praktikan, bezeroek makulua erabiltzen dute ping_delay_disconnect, zerbitzarian tenporizadorea ezartzen duena. Tira, barkatu, bezeroari ez dagokio ping gabe han zenbat denbora bizi nahi duen erabakitzea. Zerbitzariak, bere kargaren arabera, hobeto daki. Baina, noski, baliabideak axola ez bazaizkizu, orduan zure Pinotxo gaiztoa izango zara, eta makulu batek balioko du...

Nola diseinatu behar zen?

Uste dut goiko gertaerek argi adierazten dutela Telegram/VKontakte taldea ez dela oso konpetentea sare informatikoen garraioaren (eta beheko) mailan eta gai garrantzitsuetan duten kualifikazio baxuan.

Zergatik suertatu zen hain konplikatua, eta nola saiatu daitezke Telegram arkitektoak aurka egiten? TCP konexio-hausteetatik bizirik irauten duen saio bat egiten saiatu zirela, hau da, orain entregatu ez zena, geroago emango dugu. Ziurrenik UDP garraioa egiten ere saiatu ziren, baina zailtasunak aurkitu zituzten eta bertan behera utzi zuten (horregatik dago dokumentazioa hutsik - ez zegoen harrotzeko ezer). Baina sareek orokorrean eta TCP bereziki nola funtzionatzen duten, non fida zaitezkeen eta zuk zeuk egin behar duzun (eta nola) ulertzen ez delako eta hori kriptografiarekin konbinatzeko saiakera bat dela eta "bi txorirekin". harri bat”, hau da emaitza.

Nola zen beharrezkoa? Hori oinarri hartuta msg_id Ikuspegi kriptografikotik beharrezkoa den denbora-zigilua da errepikapenen erasoak saihesteko, akatsa da hari identifikatzaile esklusiboko funtzio bat eranstea. Hori dela eta, egungo arkitektura funtsean aldatu gabe (Eguneratzeen korrontea sortzen denean, hori goi-mailako API gaia da mezu-sail honetako beste zati baterako), honako hau egin beharko litzateke:

  1. Bezeroarekiko TCP konexioa duen zerbitzariak bere gain hartzen du erantzukizuna - socketetik irakurri badu, mesedez aitortu, prozesatu edo itzuli errore bat, ez da galdu. Orduan, berrespena ez da IDen bektore bat, "jasotako azken seq_no" besterik ez da - zenbaki bat besterik ez, TCPn bezala (bi zenbaki - zure seq eta baieztatutakoa). Beti saio barruan gaude, ezta?
  2. Errepikatzeko erasoak saihesteko denbora-zigilua eremu bereizi bihurtzen da, a la nonce. Egiaztatu egiten da, baina ez du beste ezertan eragiten. Nahikoa eta uint32 - Gure gatza gutxienez egun erdian behin aldatzen bada, 16 bit eslei diezazkiekegu egungo denbora osoko zati txikiko bitei, gainerakoa - segundo zati batean (orain bezala).
  3. Kendua msg_id inola ere - backendetan eskaerak bereiztearen ikuspegitik, lehenik, bezeroaren IDa dago, eta bigarrenik, saioaren IDa, kateatu. Horren arabera, gauza bakarra da nahikoa eskaeraren identifikatzaile gisa seq_no.

Hau ere ez da aukerarik arrakastatsuena; ausazko oso batek identifikatzaile gisa balio dezake; hori dagoeneko egiten da maila altuko APIan mezu bat bidaltzean, bide batez. Hobe litzateke arkitektura erlatibotik absoluturako erabat birsortzea, baina hau beste atal bateko gaia da, ez mezu honena.

APIa?

Ta-daam! Beraz, minez eta makuluz betetako bidetik borrokatu ostean, azkenean zerbitzariari edozein eskaera bidali eta erantzunak jaso ahal izan genituen, baita zerbitzariaren eguneraketak jaso ere (ez eskaera bati erantzuteko, baina bera bidaltzen digu, PUSH bezala, norbaitek argiago badu horrela).

Kontuz, orain Perlen adibide bakarra egongo da artikuluan! (sintaxia ezagutzen ez dutenentzat, bless-en lehen argumentua objektuaren datu-egitura da, bigarrena bere klasea):

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

Bai, ez da nahita spoiler bat - oraindik irakurri ez baduzu, aurrera egin!

Oh, wai~~... nolakoa da hau? Oso ezaguna den zerbait... agian hau da JSON-ko Web API tipiko baten datu-egitura, klaseak objektuei ere erantsita daudela izan ezik?...

Beraz, honela geratzen da... Zertan datza, kamaradak?... Hainbeste esfortzu - eta Web-programatzaileek atseden hartzeko gelditu ginen. hasi berritan?...Ez al litzateke JSON HTTPS baino gehiago sinpleagoa izango?! Zer lortu dugu trukean? Merezi al zuen ahaleginak?

Balora dezagun TL+MTProtok zer eman digun eta zein alternatiba posible diren. Beno, HTTP, eskaera-erantzun ereduan zentratzen dena, gaizki moldatzen da, baina gutxienez TLSaren gain?

Serializazio trinkoa. Datu-egitura hau ikusita, JSONen antzekoa, gogoan dut horren bertsio bitarrak daudela. Markatu dezagun MsgPack nahikoa hedagarri gisa, baina badago, adibidez, CBOR - bide batez, hemen deskribatzen den estandar bat. RFC 7049. Aipagarria da definitzen duelako etiketak, hedapen mekanismo gisa, eta artean dagoeneko normalizatuta eskuragarri:

  • 25 + 256 - errepikatutako lerroak lerro-zenbakiaren erreferentzia batekin ordezkatzea, konpresio metodo merkea hain zuzen.
  • 26 - Perl objektu seriatua klase-izena eta konstruktore-argumentuekin
  • 27 - Serializatutako hizkuntzatik independentea den objektu mota-izena eta eraikitzaile-argumentuekin

Beno, datu berdinak TL-n eta CBOR-en serializatzen saiatu nintzen kate eta objektuen paketea gaituta. Emaitza CBORren alde aldatzen hasi zen nonbait megabyte batetik:

cborlen=1039673 tl_len=1095092

Horrela, ondorio: Sinkronizazio hutsegitearen edo identifikatzaile ezezagunaren arazoaren menpe ez dauden formatu nabarmen sinpleagoak daude, eraginkortasun parekoarekin.

Konexio azkarra ezartzea. Horrek esan nahi du zero RTT berriro konektatu ondoren (gakoa dagoeneko behin sortu denean) - MTProto-ren lehen mezutik aplikagarria, baina erreserba batzuekin - gatz bera jo, saioa ez da usteltzen, etab. Zer eskaintzen digu TLS-k ordez? Gaiari buruzko aipua:

PFS TLSn erabiltzean, TLS saioko txartelak (RFC 5077) enkriptatutako saio bati berriro hasteko gakoak berriro negoziatu gabe eta zerbitzarian gakoen informazioa gorde gabe. Lehen konexioa ireki eta gakoak sortzean, zerbitzariak konexio-egoera enkriptatzen du eta bezeroari transmititzen dio (saio-txartel baten moduan). Horren arabera, konexioa berriro hasten denean, bezeroak saio-txartel bat bidaltzen du, saio-gakoa barne, zerbitzarira. Tiketa bera aldi baterako gako batekin (saio-txartelaren gakoa) enkriptatuta dago, zerbitzarian gordetzen dena eta SSL konponbide multzokatuetan prozesatzen duten frontend zerbitzari guztien artean banatu behar da.[10]. Horrela, saio-txartel bat sartzeak PFS urratu dezake aldi baterako zerbitzariaren gakoak arriskuan jartzen badira, adibidez, denbora luzez gordetzen direnean (OpenSSL, nginx, Apache-k programaren iraupen osoan gordetzen dituzte lehenespenez; gune ezagunek erabiltzen dute). giltza hainbat orduz, egun arte).

Hemen RTT ez da zero, gutxienez ClientHello eta ServerHello trukatu behar dituzu, ondoren bezeroak datuak bidali ditzake Amaituarekin batera. Baina hemen gogoratu behar dugu ez dugula Weba, ireki berri diren konexio mordoarekin, mezulari bat baizik, zeinaren konexioa sarritan iraupen luzekoa eta nahiko laburra den Web orrietarako eskaera - dena multiplexatuta dago. barnean. Hau da, nahiko onargarria da metroaren zati oso txarrarekin topo egiten ez bagenu.

Beste zerbait ahaztu al duzu? Idatzi iruzkinetan.

Jarraituko du!

Mezu sorta honen bigarren zatian ez ditugu kontu teknikoak, antolakuntza-gaiak aztertuko ditugu: planteamenduak, ideologia, interfazea, erabiltzaileekiko jarrera, etab. Hemen aurkeztutako informazio teknikoan oinarrituta, ordea.

Hirugarren zatian osagai teknikoa / garapen esperientzia aztertzen jarraituko da. Ikasiko duzu, bereziki:

  • pandemonioaren jarraipena TL mota ezberdinekin
  • kanal eta supertaldeei buruzko gauza ezezagunak
  • zergatik diren elkarrizketak zerrenda baino okerragoak
  • Mezuen helbideratze absolutua vs erlatiboari buruz
  • zein da argazkiaren eta irudiaren arteko aldea
  • emoji-ek testu etzanean nola oztopatzen duten

eta beste makulu batzuk! Egon adi!

Iturria: www.habr.com

Gehitu iruzkin berria