Critica di u protocolu è l'approcciu urganisazione di Telegram. Parte 1, tecnicu: sperienza di scrive un cliente da zero - TL, MT

Ricertamenti, i posti nantu à quantu hè bonu Telegram, quantu brillanti è sperimentati i fratelli Durov sò in custruzzione di sistemi di rete, etc., anu cuminciatu à apparisce più spessu in Habré. À u listessu tempu, assai pochi persone si sò veramente immersi in u dispusitivu tecnicu - à u più, usanu un API Bot abbastanza simplice (è assai sfarente da MTProto) basatu in JSON, è di solitu accettanu solu. nantu à a fede tutte e lode è PR chì giranu intornu à u messenger. Quasi un annu è mezu fà, u mo cumpagnu di l'ONG Eshelon Vasily (sfurtunatamente, u so contu nantu à Habré hè statu sguassatu cù u prugettu) hà cuminciatu à scrive u so propiu cliente Telegram da zero in Perl, è più tardi l'autore di queste linee si unì. Perchè Perl, alcuni dumandaranu immediatamente? Perchè tali prughjetti esistenu digià in altre lingue.In fatti, ùn hè micca questu, puderia esse qualsiasi altra lingua induve ùn ci hè micca. biblioteca pronta, è dunque l'autore deve andà in tuttu da zeru. Inoltre, a criptografia hè una materia di fiducia, ma verificate. Cù un pruduttu destinatu à a sicurità, ùn pudete micca solu cunfidendu una biblioteca pronta da u fabricatore è cunfidendu ciecamente (in ogni modu, questu hè un tema per a seconda parte). À u mumentu, a biblioteca funziona abbastanza bè à u livellu "mediu" (permette di fà qualsiasi dumande API).

Tuttavia, ùn ci sarà micca assai criptografia o matematica in questa serie di posti. Ma ci saranu assai altri dettagli tecnichi è crutches architecturale (ancu utili per quelli chì ùn scriveranu micca da zero, ma aduprà a biblioteca in ogni lingua). Dunque, u scopu principale era di pruvà à implementà u cliente da zero secondu a documentazione ufficiale. Hè per quessa, supponemu chì u codice fonte di i clienti ufficiali hè chjusu (di novu, in a seconda parte, copremu in più in detail u tema di u fattu chì questu hè veru. succede cusì), ma, cum'è in i vechji tempi, per esempiu, ci hè un standard cum'è RFC - hè pussibule di scrive un cliente secondu a specificazione solu, "senza guardà" à u codice fonte, sia ufficiale (Telegram Desktop, mobile), o Telethon non ufficiale?

Table di cuntenutu:

A ducumentazione... esiste, nò ? Hè vera ?...

I frammenti di note per questu articulu cuminciaru à esse cullucatu l'estiu passatu. Tuttu stu tempu nantu à u situ ufficiali https://core.telegram.org A ducumentazione era da a Layer 23, i.e. stuck somewhere in 2014 (ricurdatevi, ùn ci era mancu canali allora ?). Di sicuru, in teoria, questu duverebbe permette di implementà un cliente cù funziunalità in quellu tempu in 2014. Ma ancu in questu statu, a ducumentazione era, prima, incompleta, è in segundu, in i lochi si cuntradite. Pocu più di un mese fà, in settembre 2019, era accidintali Hè stata scuperta chì ci era una grande aghjurnazione di a documentazione nantu à u situ, per u Layer 105 cumplettamente recente, cù una nota chì avà tuttu deve esse lettu novu. In verità, parechji articuli sò stati rivisiuti, ma assai sò stati invariati. Dunque, quandu leghje e critiche sottu à a ducumentazione, duvete tene in mente chì alcune di queste cose ùn sò più pertinenti, ma alcune sò sempre abbastanza. Dopu tuttu, 5 anni in u mondu mudernu ùn hè micca solu un longu tempu, ma assai una mansa di. Dapoi quelli tempi (soprattuttu s'ellu ùn pigliate micca in contu i siti geochat scartati è rinviviti da tandu), u numeru di metudi API in u schema hè cresciutu da un centu à più di dui centu cinquanta!

Da induve principià cum'è un ghjovanu autore?

Ùn importa micca s'ellu scrivite da zero o utilizate, per esempiu, biblioteche pronti cum'è Telethon per Python o Madeline per PHP, in ogni casu, vi tuccherà prima registrate a vostra applicazione - ottene paràmetri api_id и api_hash (quelli chì anu travagliatu cù l'API VKontakte capiscenu immediatamente) da quale u servitore identificà l'applicazione. Questu duverà fà per ragioni legali, ma parleremu più di perchè l'autori di a biblioteca ùn ponu micca pubblicà in a seconda parte. Pudete esse cuntentu cù i valori di teste, anche si sò assai limitati - u fattu hè chì avà pudete registrà solu unu l'app, per quessa, ùn vi precipite micca.

Avà, da un puntu di vista tecnicu, duvemu esse interessatu in u fattu chì dopu a registrazione duvemu riceve notificazioni da Telegram nantu à l'aghjurnamenti à a documentazione, u protocolu, etc. Vale à dì, unu puderia suppone chì u situ cù i docks era solu abbandunatu è cuntinuò à travaglià specificamente cù quelli chì cuminciaru à fà clienti, perchè hè più faciule. Ma nò, nunda di cusì hè statu osservatu, nisuna infurmazione hè ghjunta.

È s'è vo scrivite da zero, allora l'usu di i paràmetri ottenuti hè ancu assai luntanu. Eppuru https://core.telegram.org/ è parla di elli in Getting Started prima di tuttu, in fatti, prima vi tuccherà à implementà Protokollu MTProto - ma si crede layout secondu u mudellu OSI à a fine di a pagina per una descrizzione generale di u protokollu, allora hè cumplettamente in vain.

In fatti, sia prima è dopu MTProto, à parechji livelli à una volta (cum'è i networkers stranieri chì travaglianu in u kernel OS dicenu, violazione di strati), un tema grande, duluroso è terribili si metterà in u modu...

Serializazione binaria: TL (Type Language) è u so schema, è strati, è parechje altre parolle spaventose

Stu tema, in fattu, hè a chjave per i prublemi di Telegram. E ci saranu assai parolle terribili si pruvate à sfondate.

Allora, quì hè u schema. Se sta parolla vi vene in mente, dite, Schema JSON, Avete pensatu bè. U scopu hè u listessu: qualchì lingua per discrìviri un inseme pussibuli di dati trasmessi. Questu hè induve finiscinu e similitudini. Se da a pagina Protokollu MTProto, o da l'arburu fonte di u cliente ufficiale, avemu da pruvà à apre qualchì schema, vedemu qualcosa cum'è:

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;

Una persona chì vede questu per a prima volta serà intuitivamente capace di ricunnosce solu una parte di ciò chì hè scrittu - bè, queste sò apparentemente strutture (ancu se induve hè u nome, à manca o à diritta?), ci sò campi in elli, dopu chì un tipu seguita dopu à un colon... prubabilmente. Quì in parentesi angulari ci sò probabilmente mudelli cum'è in C++ (in fattu, micca propiu). È ciò chì significanu tutti l'altri simboli, segni d'interrogazione, esclamazioni, percentuali, segni di hash (è ovviamente significanu cose diverse in diversi lochi), qualchì volta prisenti è à volte micca, numeri esadecimali - è più impurtante, cumu si ghjunghje da questu. u dirittu (chì ùn serà micca rifiutatu da u servitore) flussu di byte? Avete da leghje a documentazione (iè, ci sò ligami à u schema in a versione JSON vicinu - ma questu ùn rende micca più chjaru).

Aprite a pagina Serializazione di dati binari è immerse in u mondu magicu di funghi è matematica discreta, qualcosa simili à matan in u 4u annu. Alfabetu, tipu, valore, cumminatore, cumminatore funziunale, forma nurmale, tipu cumpostu, tipu polimorficu... è questu hè solu a prima pagina ! Dopu vi aspetta Lingua TL, chì, ancu s'ellu cuntene digià un esempiu di una dumanda triviale è risposta, ùn furnisce micca una risposta à tutti i casi più tipici, chì significa chì vi tuccherà à sguillà à traversu una retalling di matematica traduttu da u russu à l'inglese nantu à un altru ottu incrustatu. pagine!

I lettori familiarizati cù e lingue funziunali è l'inferenza di u tipu automaticu, di sicuru, vede a lingua di descrizzione in questa lingua, ancu da l'esempiu, cum'è assai più familiare, è ponu dì chì questu hè veramente micca male in principiu. L'obiezioni à questu sò:

  • u scopu sona bè, ma ahimè, ella micca ottenutu
  • L'educazione in l'università russe varieghja ancu trà e specialità IT - micca tutti anu pigliatu u cursu currispundente
  • Infine, cum'è avemu da vede, in pratica hè micca necessariu, postu chì solu un subset limitatu di ancu u TL chì hè statu descrittu hè utilizatu

Cumu hà dettu Leonerd nant'à u canali #perl in a reta FreeNode IRC, chì hà pruvatu à implementà una porta da Telegram à Matrix (a traduzzione di a citazione hè imprecisa da a memoria):

Sembra chì qualcunu hè statu introduttu à a teoria di u tipu per a prima volta, s'hè entusiasmu, è hà cuminciatu à pruvà à ghjucà cun ella, ùn importa micca veramente s'ellu era necessariu in pratica.

Vede per sè stessu, se a necessità di bare-types (int, long, etc.) cum'è qualcosa elementare ùn suscite micca dumande - in fine, anu da esse implementatu manualmente - per esempiu, femu un tentativu di derivà da elli. vettore. Hè, in fatti, array, se chjamate e cose resultanti cù i so nomi propiu.

Ma prima

Una breve descrizzione di un subset di sintassi TL per quelli chì ùn leghjenu micca a documentazione ufficiale

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;

A definizione principia sempre costruttore, dopu à quale optionally (in pratica - sempre) attraversu u simbulu # duverebbe CRC32 da a stringa di descrizzione nurmalizata di stu tipu. Dopu vene una descrizzione di i campi; se esistenu, u tipu pò esse viotu. Tuttu chistu finisci cù un signu uguali, u nome di u tipu à quale stu custruttore - vale à dì, in fattu, u subtipu - appartene. U tippu à a diritta di u segnu uguali hè polimorficu - vale à dì, parechji tipi specifichi ponu currisponde à questu.

Se a definizione si trova dopu à a linea ---functions---, tandu a sintassi restarà a listessa, ma u significatu serà diversu: u custruttore diventerà u nome di a funzione RPC, i campi diventeranu parametri (bene, vale à dì, ferma esattamente a listessa struttura data, cum'è descritta quì sottu. , questu serà solu u significatu assignatu), è u "tipu polimorficu" - u tipu di u risultatu vultatu. True, ferma sempre polimorficu - solu definitu in a sezione ---types---, ma stu custruttore "ùn serà micca cunsideratu". Overloading i tipi di funzioni chjamati da i so argumenti, i.e. Per una certa ragione, parechje funzioni cù u stessu nome, ma diverse firme, cum'è in C++, ùn sò micca previste in u TL.

Perchè "costruttore" è "polimorficu" s'ellu ùn hè micca OOP? Ebbè, in fattu, serà più faciule per qualchissia per pensà à questu in termini OOP - un tipu polimorficu cum'è una classa astratta, è i custruttori sò i so classi discendenti diretti, è final in a terminologia di parechje lingue. In fatti, sicuru, quì solu sumiglianza cù veri metudi di costruttore sovraccarico in linguaggi di prugrammazione OO. Siccomu quì sò solu strutture di dati, ùn ci sò micca metudi (ancu se a descrizzione di e funzioni è i metudi in più hè abbastanza capace di creà cunfusione in u capu chì esistenu, ma hè una materia diversa) - pudete pensà à un constructore cum'è un valore da chì hè in costruzione tipu quandu leghje un flussu di byte.

Cumu succede questu? U deserializatore, chì sempre leghje 4 bytes, vede u valore 0xcrc32 - è capisce ciò chì succede dopu field1 cù tipu int, i.e. leghje esattamente 4 bytes, nantu à questu u campu sopra cù u tipu PolymorType leghje. Vede 0x2crc32 è capisce chì ci sò dui campi in più, prima long, chì significa chì leghjemu 8 bytes. E poi di novu un tipu cumplessu, chì hè deserializatu in u listessu modu. Per esempiu, Type3 Puderanu esse dichjaratu in u circuitu appena dui custruttori, rispettivamente, allora si deve scuntrà sia 0x12abcd34, dopu à quale avete bisognu di leghje 4 bytes più int, o 0x6789cdef, dopu à quale ùn ci sarà nunda. Qualchese altru - avete bisognu di scaccià una eccezzioni. In ogni casu, dopu à questu avemu torna à leghje 4 bytes int campi field_c в constructorTwo è cun questu avemu finitu di leghje u nostru PolymorType.

Infine, s'è vo avete pigliatu 0xdeadcrc di constructorThree, allura tuttu diventa più cumplicatu. U nostru primu campu hè bit_flags_of_what_really_present cù tipu # - in fattu, questu hè solu un alias per u tipu nat, chì significa "numaru naturali". Hè, in fattu, unsigned int hè, per via, l'unicu casu quandu i numeri unsigned si trovanu in circuiti reali. Allora, dopu hè una custruzzione cù un puntu d'interrogazione, chì significa chì questu campu - serà presente nantu à u filu solu se u bit currispundente hè stallatu in u campu riferitu (circa cum'è un operatore ternariu). Allora, supponemu chì questu bit hè statu stabilitu, chì significa chì più avemu bisognu di leghje un campu cum'è Type, chì in u nostru esempiu hà 2 custruttori. Unu hè viotu (custituitu solu di l'identificatore), l'altru hà un campu ids cù tipu ids:Vector<long>.

Puderete pensà chì i mudelli è i generici sò in i pros o Java. Ma nò. Quasi. Questu u solu in casu di usu di parentesi angulari in circuiti reali, è hè aduprata SOLAMENTE per Vector. In un flussu di byte, questi seranu 4 byte CRC32 per u tipu Vector stessu, sempre u stessu, dopu 4 bytes - u numeru di elementi di array, è dopu questi elementi stessi.

Aghjunghjite à questu u fattu chì a serializazione si trova sempre in parolle di 4 bytes, tutti i tipi sò multiplici - i tipi integrati sò ancu descritti. bytes и string cù a serializazione manuale di a lunghezza è questu allinamentu da 4 - bè, pare chì sona normale è ancu relativamente efficace? Ancu s'ellu si dice chì TL hè una serializazione binaria efficace, à l'infernu cun elli, cù l'espansione di quasi qualcosa, ancu i valori booleani è e stringhe di un caratteru à 4 bytes, JSON serà sempre assai più grossu? Fighjate, ancu i campi innecessarii ponu esse saltati cù bandieri di bit, tuttu hè abbastanza bonu, è ancu estensibile per u futuru, allora perchè ùn aghjunghjenu novi campi opzionali à u custruttore dopu?

Ma no, s'è vo leghje micca a mo breve descrizzione, ma a documentazione completa, è pensate à l'implementazione. Prima, u CRC32 di u custruttore hè calculatu secondu a linea nurmalizata di a descrizzione di testu di u schema (sguassate spazii bianchi extra, etc.) - dunque se un novu campu hè aghjuntu, a linea di descrizzione di tipu cambierà, è dunque u so CRC32 è , in cunseguenza, serializazione. È chì faria u vechju clientu s'ellu hà ricevutu un campu cù novi bandieri, è ùn sà micca chì fà cun elli dopu?...

Siconda, ricurdemu CRC32, chì hè usatu quì essenzialmente cum'è funzioni hash per determinà in modu unicu chì tipu hè esse (de) serializatu. Quì avemu affruntatu u prublema di colissioni - è no, a probabilità ùn hè micca unu in 232, ma assai più grande. Quale hè ricurdatu chì CRC32 hè pensatu per detectà (è curregge) errori in u canali di cumunicazione, è per quessa migliurà queste proprietà in detrimentu di l'altri? Per esempiu, ùn importa micca di rearrangeing bytes: se calculate CRC32 da duie linee, in u sicondu scambià i primi 4 bytes cù i prossimi 4 bytes - serà u listessu. Quandu u nostru input hè strings di testu da l'alfabetu latinu (è un pocu puntuazione), è questi nomi ùn sò micca particularmente aleatorii, a probabilità di un tali rearrangiamentu aumenta assai.

Per via, quale hà verificatu ciò chì ci era ? veramente CRC32? Unu di i primi codici fonte (ancu prima di Waltman) hà avutu una funzione hash chì multiplicava ogni caratteru per u numeru 239, cusì amatu da queste persone, ha ha!

Infine, va bè, avemu capitu chì i custruttori cù un tipu di campu Vector<int> и Vector<PolymorType> averà diverse CRC32. E prestazioni in linea? È da un puntu di vista teoricu, questu diventa parte di u tipu? Diciamu chì passemu un array di decimila numeri, bè cù Vector<int> tuttu hè chjaru, a lunghezza è altri 40000 XNUMX bytes. E se questu Vector<Type2>, chì si compone di un solu campu int è hè solu in u tipu - avemu bisognu di ripetiri 10000xabcdef0 34 4 volte è dopu XNUMX bytes int, o a lingua hè capaci di INDEPEND per noi da u custruttore fixedVec è invece di 80000 40000 bytes, trasfiriri di novu solu XNUMX XNUMX?

Questa ùn hè micca una quistione teorica inattiva - imaginate chì riceve una lista di l'utilizatori di u gruppu, chì ognunu hà un id, nome, cognome - a diferenza in a quantità di dati trasferiti nantu à una cunnessione mobile pò esse significativa. Hè precisamente l'efficacità di a serializazione di Telegram chì ci hè annunziatu.

Allora…

Vector, chì ùn hè mai statu liberatu

Se pruvate di passà per e pagine di descrizzione di combinatori è cusì, vi vede chì un vettore (è ancu una matrice) prova formalmente à esse uscita per tuple di parechji fogli. Ma à a fine si scurdanu, u passu finali hè saltatu, è una definizione di un vettore hè simplicemente datu, chì ùn hè micca ancora ligata à un tipu. Chì ci hè? In lingue prugrammazione, soprattuttu funziunali, hè abbastanza tipicu per discrìviri a struttura recursivamente - u compilatore cù a so valutazione pigra capisce è fà tuttu. In lingua serializazione di dati ciò chì ci vole hè EFFICIENZA : basta à discrivà solu a lista, i.e. struttura di dui elementi - u primu hè un elementu di dati, u sicondu hè a stessa struttura stessa o un spaziu viotu per a cuda (pacchettu (cons) in Lisp). Ma questu serà ovviamente bisognu di ognunu L'elementu spende 4 bytes supplementari (CRC32 in u casu in TL) per discrive u so tipu. Un array pò ancu esse facilmente discrittu taglia fissa, ma in u casu di un array di lunghezza scunnisciuta in anticipu, sguassate.

Dunque, postu chì TL ùn permette micca di produzzione di un vettore, hà da esse aghjuntu à u latu. In fine, a documentazione dice:

A serializazione usa sempre u listessu custruttore "vector" (const 0x1cb5c415 = crc32 ("vector t:Type # [t] = Vector t") chì ùn hè micca dipendente da u valore specificu di a variabile di tipu t.

U valore di u paràmetru facultativu t ùn hè micca implicatu in a serializazione postu chì hè derivatu da u tipu di risultatu (sempre cunnisciutu prima di a deserializazione).

Fighjate più attente: vector {t:Type} # [ t ] = Vector t - ma nunda Sta definizione stessu ùn dice micca chì u primu numeru deve esse uguali à a lunghezza di u vettore ! È ùn vene da nudda parte. Questu hè un datu chì deve esse guardatu in mente è implementatu cù e vostre mani. In altrò, a documentazione ancu onestamente ammenta chì u tipu ùn hè micca reale:

U pseudotipu polimorficu Vector t hè un "tipu" chì u so valore hè una sequenza di valori di ogni tipu t, in scatula o nuda.

... ma ùn si concentra micca nantu à questu. Quandu tù, stancu di vading through the stretching di matematica (forse ancu cunnisciuta da voi da un cursu universitariu), decide di rinunzià è veramente guardà cumu travaglià cun ella in pratica, l'impressione lasciata in a vostra testa hè chì questu hè Seriu. A matematica in u core, hè stata chjaramente inventata da Cool People (dui matematichi - vincitore ACM), è micca solu qualcunu. L'obiettivu - di mostrà - hè statu rializatu.

Per via, circa u numeru. Lasciamu ricurdà chì # hè un sinonimu nat, numeru naturali:

Ci sò espressioni di tipu (tipu-espr) è espressioni numeriche (nat-espr). Tuttavia, sò definiti in u listessu modu.

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

ma in a grammatica sò discritti in listessa manera, i.e. Sta diffarenza deve esse ricurdata è mette in implementazione manualmente.

Iè, i tipi di mudelli (vector<int>, vector<User>) anu un identificatore cumuni (#1cb5c415), i.e. se sapete chì a chjama hè annunziata cum'è

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

tandu ùn site più aspittendu solu un vettore, ma un vettore di utilizatori. Più precisamente, duverebbe aspetta - in u codice veru, ogni elementu, s'ellu ùn hè micca un tipu nudu, avarà un custruttore, è in una bona manera in implementazione, ci vole à verificà - ma avemu statu mandatu esattamente in ogni elementu di stu vettore. stu tipu? E s'ellu era un tipu di PHP, in quale un array pò cuntene tipi diffirenti in elementi diffirenti?

À questu puntu avete principiatu à pensà - hè un tali TL necessariu? Forse per u carrettu saria pussibile aduprà un serializadoru umanu, u listessu protobuf chì esiste digià tandu ? Hè stata a teoria, fighjemu a pratica.

Implementazioni TL esistenti in codice

TL hè natu in a prufundità di VKontakte ancu prima di i famosi avvenimenti cù a vendita di a parte di Durov è (sicuru), ancu prima chì u sviluppu di Telegram hà iniziatu. È in open source codice fonte di a prima implementazione pudete truvà assai crutches divertenti. È a lingua stessa hè stata implementata quì più cumpletamente chè avà in Telegram. Per esempiu, i hashes ùn sò micca usati in tuttu in u schema (chì significheghja un pseudotipu integratu (cum'è un vettore) cù cumpurtamentu deviant). Or

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

ma cunsideremu, per esse cumpletu, di traccia, per dì cusì, l'evuluzione di u Gigante di u Pensu.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

O questu bellu:

    static const char *reserved_words_polymorhic[] = {

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

      };

Stu fragmentu hè nantu à mudelli cum'è:

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

Questa hè a definizione di un tipu di mudellu di hashmap cum'è un vettore di int - Type pairs. In C ++ parerebbe qualcosa cusì:

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

dunque, alpha - keyword! Ma solu in C ++ pudete scrive T, ma duvete scrive alfa, beta ... Ma micca più di 8 paràmetri, hè quì chì a fantasia finisci. Pare chì una volta in San Petruburgu si facianu parechji dialoghi cusì :

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

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

Ma questu era nantu à a prima implementazione publicata di TL "in generale". Passemu à cunsiderà implementazioni in i clienti Telegram stessi.

Parola à Vasily:

Vasily, [09.10.18 17:07] A maiò parte di tuttu, u culo hè caldu perchè anu creatu una mansa di astrazioni, è poi martellatu un bolt nantu à elli, è copre u generatore di codice cù crutches.
In u risultatu, prima da dock pilot.jpg
Allora da u codice dzhekichan.webp

Di sicuru, da e persone familiarizati cù l'algoritmi è a matematica, pudemu aspittà chì anu lettu Aho, Ullmann, è sò familiarizati cù l'arnesi chì sò diventati de facto standard in l'industria annantu à i decennii per scrive i so compilatori DSL, nò?

By telegramma-cli hè Vitaly Valtman, cum'è si pò capisce da l'occurrence di u formatu TLO fora di i so cunfini (cli), un membru di a squadra - avà una biblioteca per l'analisi TL hè stata attribuita. separatamente, chì hè l'impressione di ella parser TL? ...

16.12 04:18 Vasily: Pensu chì qualchissia ùn hà micca maestru di lex+yacc
16.12 04:18 Vasily: Ùn possu micca spiegà altrimenti
16.12 04:18 Vasily: bè, o sò stati pagati per u numeru di linii in VK
16.12 04:19 Vasily: 3k+ linee ecc.<censored> invece di un parser

Forse un'eccezzioni? Videmu cumu face Questu hè u cliente UFFICIALE - 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+ linee in Python, un paru di espressioni regulari + casi speciali cum'è un vettore, chì, sicuru, hè dichjaratu in u schema cum'è duverebbe esse secondu a sintassi TL, ma si basavanu in questa sintassi per analizà ... Ci hè a quistione, perchè era tuttu un miraculu?иHè più strattu s'ellu nimu hà da analizà secondu a documentazione in ogni modu ?!

A propositu... Ricurdativi chì avemu parlatu di u cuntrollu CRC32 ? Dunque, in u generatore di codice Telegram Desktop ci hè una lista di eccezzioni per quelli tipi in quale u CRC32 calculatu. ùn currisponde micca cù quellu indicatu in u schema !

Vasily, [18.12/22 49:XNUMX] è quì pensu se un tali TL hè necessariu
s'ellu vulia scherzà cù implementazioni alternative, aghju cuminciatu à inserisce interruzioni di linea, a mità di i parsers si romperanu in definizioni multi-linea.
tdesktop, però, ancu

Ricurdativi di u puntu nantu à una linea, ci vulteremu un pocu dopu.

Va bè, telegram-cli ùn hè micca ufficiale, Telegram Desktop hè ufficiale, ma chì ne di l'altri? Quale sà? .. In u codice di u cliente di Android ùn ci era micca un parser di schema à tutti (chì suscita dumande nantu à a fonte aperta, ma questu hè per a seconda parte), ma ci era parechje altre pezzi di còdici divertenti, ma più nantu à elli in u sottosezzione sottu.

Chì altre dumande suscita a serializazione in pratica? Per esempiu, anu fattu assai cose, sicuru, cù campi di bit è campi cundiziunali:

Vassili: flags.0? true
significa chì u campu hè prisente è uguali à veru se a bandiera hè stallata

Vassili: flags.1? int
significa chì u campu hè presente è deve esse deserialized

Vasily : Ass, ùn ti preoccupi di ciò chì faci !
Vasily: Ci hè una menzione in qualchì locu in u documentu chì u veru hè un tipu nudu di lunghezza zero, ma hè impussibile di assemblà qualcosa da u so documentu.
Vasily: In l'implementazioni open source ùn hè micca u casu ancu, ma ci sò una mansa di crutches è supporti.

E Telethon ? In vista di u tema di MTProto, un esempiu - in a documentazione ci sò tali pezzi, ma u segnu % hè discrittu solu cum'è "currispondente à un tipu nudu datu", i.e. in l'esempii quì sottu ci hè un errore o qualcosa micca documentatu:

Vasily, [22.06.18 18:38] In un locu:

msg_container#73f1f8dc messages:vector message = MessageContainer;

In un altru:

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

È questi sò dui grandi diffirenzii, in a vita reale vene un tipu di vettore nudu

Ùn aghju micca vistu una definizione vettoriale nuda è ùn aghju micca scontru unu

L'analisi hè scritta a manu in telethon

In u so diagramma, a definizione hè cummentata msg_container

In novu, a quistione resta circa %. Ùn hè micca descrittu.

Vadim Goncharov, [22.06.18 19:22] è in tdesktop?

Vasily, [22.06.18 19:23] Ma u so parser TL nantu à i mutori regulari, probabilmente ùn manghja micca ancu questu.

// parsed manually

TL hè una bella astrazione, nimu l'implementa cumpletamente

È % ùn hè micca in a so versione di u schema

Ma quì a ducumentazione si cuntradisce, cusì idk

Hè stata truvata in a grammatica, puderianu solu esse scurdate di discrìviri a semantica

Avete vistu u documentu nantu à TL, ùn pudete micca capisce senza mezu litru

"Bè, diciamu", un altru lettore diciarà, "criticate qualcosa, dunque mostrami cumu si deve esse fattu".

Vasily risponde: "In quantu à u parser, mi piace e cose cum'è

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

in qualchì manera piace megliu cà

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

o

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

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

questu hè u lexer TUTTO:

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

quelli. u più simplice hè di mette in modu moderatu ".

In generale, per via di u risultatu, l'analizzatore è u generatore di codice per u subset di TL attualmente utilizatu si mette in circa 100 linee di grammatica è ~ 300 linee di u generatore (contendu tutti printcodice generatu 's), cumpresi i tippi di informazioni per l'introspezione in ogni classe. Ogni tipu polimorficu si trasforma in una classa di basa astratta viota, è i custruttori ereditanu da ellu è anu metudi per a serializazione è a deserializazione.

Mancanza di tipi in a lingua di tipu

A digitazione forte hè una bona cosa, nò? No, questu ùn hè micca un holivar (ancu se preferite lingue dinamiche), ma un postulatu in u quadru di TL. Basatu nantu à questu, a lingua deve furnisce ogni tipu di cuntrolli per noi. Ebbè, va bè, forse micca ellu stessu, ma l'implementazione, ma deve almenu discrive. È chì tipu di opportunità vulemu ?

Prima di tuttu, limitazioni. Quì vedemu in a documentazione per a carica di i schedari:

U cuntenutu binariu di u schedariu hè allora divisu in parti. Tutte e parti deve avè a stessa dimensione ( part_size ) è e seguenti cundizioni devenu esse cumplette:

  • part_size % 1024 = 0 (divisibile per 1 KB)
  • 524288 % part_size = 0 (512 KB deve esse divisibule uniformemente per part_size)

L'ultima parte ùn deve micca suddisfà queste cundizioni, basta chì a so dimensione hè menu di part_size.

Ogni parte deve avè un numeru di sequenza, file_part, cù un valore chì varieghja da 0 à 2,999.

Dopu chì u schedariu hè stata partizionata, avete bisognu di sceglie un metudu per salvà in u servitore. Aduprà upload.saveBigFilePart in casu chì a dimensione completa di u schedariu hè più di 10 MB è upload.saveFilePart per i schedarii più chjuchi.
[…] unu di i seguenti errori di input di dati pò esse tornatu:

  • FILE_PARTS_INVALID - U numeru di parti invalidu. U valore ùn hè micca trà 1..3000

Ci hè qualcosa di questu in u diagramma? Hè in qualchì modu esprimibile cù TL? Innò. Ma scusate, ancu Turbo Pascal di missiavu hà sappiutu discrive i tipi specificati intervalli. È sapia una cosa più, avà più cunnisciutu cum'è enum - un tipu custituitu da una enumerazione di un numeru fissu (picculu) di valori. In lingue cum'è C - numericu, nutate chì finu à avà avemu parlatu solu di tipi numari. Ma ci sò ancu array, strings... per esempiu, saria bellu di discrive chì sta stringa pò cuntene solu un numeru di telefunu, nò ?

Nisunu di questu hè in u TL. Ma ci hè, per esempiu, in JSON Schema. È s'ellu qualcunu altru puderia argumentà nantu à a divisibilità di 512 KB, chì questu hè sempre da esse verificatu in u codice, allora assicuratevi chì u cliente solu ùn pudia micca mandate un numeru fora di u range 1..3000 (è l'errore currispundente ùn puderia micca esse ghjuntu) saria statu pussibule, nò ?...

Per via, circa l'errori è i valori di ritornu. Ancu quelli chì anu travagliatu cù TL sfocanu i so ochji - ùn ci hè micca subitu subitu ognunu una funzione in TL pò veramente vultà micca solu u tipu di ritornu descrittu, ma ancu un errore. Ma questu ùn pò micca esse deduciutu in ogni modu cù u TL stessu. Di sicuru, hè digià chjaru è ùn ci hè bisognu di nunda in a pratica (ancu in fattu, RPC pò esse fattu in modi diffirenti, avemu da vultà à questu più tardi) - ma chì ne di a Purità di i cuncetti di Matematica di Tipi Astratti da u mondu celestiale ?.. Aghju pigliatu u tiru - cusì currisponde.

È infine, chì ne di a leghjibilità? Ebbè, quì, in generale, mi piacerebbe description l'avete ghjustu in u schema (in u schema JSON, di novu, hè), ma s'ellu hè digià struitu cù questu, allora chì ne di u latu praticu - almenu trivially fighjendu diffs durante l'aghjurnamenti? Vede per voi stessu à esempi veri:

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

o

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

Dipende da tutti, ma GitHub, per esempiu, ricusa di mette in risaltu i cambiamenti in tali linee lunghe. U ghjocu "truvà 10 differenze", è ciò chì u cervellu vede immediatamente hè chì l'iniziu è a fine in i dui esempi sò listessi, avete bisognu di leghje tediously in un locu in u mezu ... In my opinion, questu ùn hè micca solu in teoria, ma puramente visualmente brutta è sciaccata.

Per via, nantu à a purità di a teoria. Perchè avemu bisognu di campi di bit? Ùn pare micca ch'elli odore male da u puntu di vista di a teoria di u tipu ? A spiegazione pò esse vistu in versioni precedenti di u schema. In prima, sì, hè cusì, per ogni starnutu hè creatu un novu tipu. Sti rudimenti esistenu sempre in questa forma, per esempiu:

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;

Ma avà imaginate, se avete 5 campi opzionali in a vostra struttura, allora avete bisognu di 32 tipi per tutte l'opzioni pussibuli. Esplosione cumminatoria. Cusì, a purità di cristallu di a teoria TL s'hè lampatu una volta di più contr'à u culo di ferru di a dura realità di a serializazione.

Inoltre, in certi lochi sti picciotti stessi violanu a so propria tipologia. Per esempiu, in MTProto (capitulu prossimu) a risposta pò esse cumpressa da Gzip, tuttu hè bè - salvu chì i strati è u circuitu sò violati. Una volta, ùn era micca RpcResult stessu chì hè stata cugliera, ma u so cuntenutu. Ebbè, perchè fà questu? .. Aghju avutu à cutà in una crutch per chì a cumpressione travaglia in ogni locu.

O un altru esempiu, avemu scupertu una volta un errore - hè statu mandatu InputPeerUser invece di InputUser. O viceversa. Ma hà travagliatu! Questu hè, u servitore ùn hà micca cura di u tipu. Cumu pò esse questu? A risposta pò esse datu à noi da frammenti di codice da telegram-cli:

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

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

In altre parolle, questu hè induve a serializazione hè fatta MANUALMENTE, codice micca generatu! Forsi u servitore hè implementatu in una manera simile ? .. In principiu, questu travaglià se fattu una volta, ma cumu pò esse supportatu dopu durante l'aghjurnamenti? Hè per quessa chì u schema hè statu inventatu? È quì andemu à a quistione dopu.

Versioning. Strati

Perchè e versioni schemichi sò chjamati strati ponu esse speculati solu nantu à a storia di schemi publicati. Apparentemente, in prima l'autori pensanu chì e cose basi puderanu esse fattu cù u schema senza cambiatu, è solu induve necessariu, per richieste specifiche, indicanu chì sò stati fatti cù una versione diversa. In principiu, ancu una bona idea - è u novu serà, per esse, "mistu", strattu nantu à u vechju. Ma vedemu cumu hè stata fatta. True, ùn aghju micca pussutu fighjà da u principiu - hè divertente, ma u diagramma di a capa di basa ùn esiste micca solu. Layers hà cuminciatu cù 2. A documentazione ci parla di una funzione TL speciale:

Se un cliente supporta Layer 2, allora u constructore seguente deve esse usatu:

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

In pratica, questu significa chì prima di ogni chjama API, un int cù u valore 0x289dd1f6 deve esse aghjuntu prima di u numeru di mètudu.

Sona normale. Ma chì successe dopu ? Allora apparsu

invokeWithLayer3#b7475268 query:!X = X;

Allora chì hè dopu? Cume pudete invintà,

invokeWithLayer4#dea0d430 query:!X = X;

Divertente? Innò, hè troppu prestu per ride, pensate à u fattu ognunu una dumanda da una altra capa deve esse impannillata in un tipu cusì speziale - si sò tutti diffirenti per voi, cumu si pò distingue? È aghjunghje solu 4 bytes in fronte hè un metudu abbastanza efficace. Allora,

invokeWithLayer5#417a57ae query:!X = X;

Ma hè ovvi chì dopu un pocu tempu questu diventerà un tipu di baccanalia. È a suluzione hè ghjunta:

Actualizazione: Partendu da u Layer 9, i metudi d'aiutu invokeWithLayerN pò esse usatu solu inseme initConnection

Eura! Dopu à 9 versioni, avemu finalmente ghjuntu à ciò chì hè statu fattu in i protokolli Internet in l'anni 80 - accunsentendu a versione una volta à u principiu di a cunnessione!

Allora chì hè dopu?...

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

Ma avà pudete sempre ride. Solu dopu à un altru 9 strati, un custruttore universale cù un numeru di versione hè statu infine aghjuntu, chì deve esse chjamatu solu una volta à l'iniziu di a cunnessione, è u significatu di i strati pareva sparitu, avà hè solu una versione cundizionale, cum'è in ogni locu. Prublemu risoltu.

Esattamente?...

Vasily, [16.07.18 14:01] Ancu u venneri aghju pensatu:
U teleserver manda avvenimenti senza una dumanda. E dumande devenu esse imballate in InvokeWithLayer. U servitore ùn impacchetta l'aghjurnamenti; ùn ci hè micca una struttura per imballà risposte è aghjurnamenti.

Quelli. u cliente ùn pò micca specificà a strata in quale ellu vole aghjurnamenti

Vadim Goncharov, [16.07.18 14:02] InvokeWithLayer ùn hè micca una crutch in principiu?

Vasily, [16.07.18 14:02] Questu hè l'unicu modu

Vadim Goncharov, [16.07.18 14:02] chì essenzialmente duveria significà d'accordu nantu à a strata à u principiu di a sessione

Per via, seguita chì u downgrade di u cliente ùn hè micca furnitu

L'aghjurnamenti, i.e. tipu Updates in u schema, questu hè ciò chì u servitore manda à u cliente micca in risposta à una dumanda API, ma indipindentamente quandu si faci un avvenimentu. Questu hè un tema cumplessu chì serà discututu in un altru postu, ma per avà hè impurtante sapè chì u servitore salva l'aghjurnamenti ancu quandu u cliente hè offline.

Cusì, si ricusate di imballà di ognunu pacchettu per indicà a so versione, questu logicamente porta à i seguenti prublemi pussibuli:

  • u servitore manda l'aghjurnamenti à u cliente ancu prima chì u cliente hà infurmatu a versione chì sustene
  • chì deve fà dopu avè aghjurnatu u cliente?
  • chì guaranziachì l'opinione di u servitore nantu à u numeru di strati ùn cambierà micca durante u prucessu?

Pensate chì questu hè una speculazione puramente teorica, è in pratica ùn pò micca succede, perchè u servitore hè scrittu currettamente (almenu, hè pruvatu bè)? Ha! Ùn importa micca cumu hè!

Questu hè esattamente ciò chì avemu avutu in l'aostu. U 14 d'aostu, ci sò stati missaghji chì qualcosa era aghjurnatu nantu à i servitori di Telegram ... è dopu in i logs:

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.

è dopu parechji megabytes di stack traces (bene, à u stessu tempu u logging hè stata fissata). Dopu tuttu, se qualcosa ùn hè micca ricunnisciutu in u vostru TL, hè binariu per firma, più in a linea TUTTI va, a decodificazione diventerà impussibile. Chì duvete fà in una tale situazione?

Ebbè, a prima cosa chì vene in mente à qualcunu hè di disconnect è pruvà torna. Ùn hà micca aiutu. Avemu google CRC32 - questi sò diventati ogetti da u schema 73, ancu s'è avemu travagliatu nantu à 82. Fighjemu cù cura à i logs - ci sò identificatori da dui schemi diffirenti!

Forse u prublema hè solu in u nostru cliente non ufficiale? No, lancemu Telegram Desktop 1.2.17 (versione furnita in una quantità di distribuzioni Linux), scrive à u logu di eccezzioni: MTP Unexpected type id #b5223b0f leghje in MTPMessageMedia...

Critica di u protocolu è l'approcciu urganisazione di Telegram. Parte 1, tecnicu: sperienza di scrive un cliente da zero - TL, MT

Google hà dimustratu chì un prublema simili era digià accadutu à unu di i clienti non-ufficiali, ma dopu i numeri di versione è, per quessa, l'assunzioni eranu sfarenti ...

Allora chì duvemu fà ? Vasily è aghju spartutu: hà pruvatu à aghjurnà u circuitu à 91, decisu d'aspittà uni pochi di ghjorni è pruvà à 73. I dui metudi anu travagliatu, ma postu chì sò empirichi, ùn ci hè micca una cunniscenza di quantu versioni sopra o falà avete bisognu. per saltà, o quantu duvete aspittà.

In seguitu, aghju pussutu ripruduce a situazione: lanciamu u cliente, spegnemu, ricompile u circuitu à un altru stratu, riavvia, catturà u prublema di novu, vultà à u precedente - oops, senza quantità di cambiamentu di circuitu è ​​riavvia u cliente per un pochi minuti aiutanu. Riceverete una mistura di strutture di dati da diverse strati.

Spiegazione ? Comu pudete guessà da parechji sintomi indiretti, u servitore hè custituitu da parechji prucessi di diversi tipi nantu à diverse macchine. Hè assai prubabile, u servitore chì hè rispunsevuli di "buffering" mette in a fila ciò chì i so superiori l'anu datu, è l'anu datu in u schema chì era in u locu à u mumentu di a generazione. È finu à sta fila "rotten", ùn si pudia fà nunda.

Forse... ma questu hè una crutch terribili ?!.. Innò, prima di pensà à l'idee pazze, fighjemu u codice di i clienti ufficiali. In a versione Android ùn truvamu micca un parser TL, ma truvamu un schedariu grossu (GitHub ricusa di toccu) cù (de)serializazione. Eccu i frammenti di codice:

public static class TL_message_layer68 extends TL_message {
    public static int constructor = 0xc09be45f;
//...
//еще пачка подобных
//...
    public static class TL_message_layer47 extends TL_message {
        public static int constructor = 0xc992e15c;
        public static Message TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) {
            Message result = null;
            switch (constructor) {
                case 0x1d86f70e:
                    result = new TL_messageService_old2();
                    break;
                case 0xa7ab1991:
                    result = new TL_message_old3();
                    break;
                case 0xc3060325:
                    result = new TL_message_old4();
                    break;
                case 0x555555fa:
                    result = new TL_message_secret();
                    break;
                case 0x555555f9:
                    result = new TL_message_secret_layer72();
                    break;
                case 0x90dddc11:
                    result = new TL_message_layer72();
                    break;
                case 0xc09be45f:
                    result = new TL_message_layer68();
                    break;
                case 0xc992e15c:
                    result = new TL_message_layer47();
                    break;
                case 0x5ba66c13:
                    result = new TL_message_old7();
                    break;
                case 0xc06b9607:
                    result = new TL_messageService_layer48();
                    break;
                case 0x83e5de54:
                    result = new TL_messageEmpty();
                    break;
                case 0x2bebfa86:
                    result = new TL_message_old6();
                    break;
                case 0x44f9b43d:
                    result = new TL_message_layer104();
                    break;
                case 0x1c9b1027:
                    result = new TL_message_layer104_2();
                    break;
                case 0xa367e716:
                    result = new TL_messageForwarded_old2(); //custom
                    break;
                case 0x5f46804:
                    result = new TL_messageForwarded_old(); //custom
                    break;
                case 0x567699b3:
                    result = new TL_message_old2(); //custom
                    break;
                case 0x9f8d60bb:
                    result = new TL_messageService_old(); //custom
                    break;
                case 0x22eb6aba:
                    result = new TL_message_old(); //custom
                    break;
                case 0x555555F8:
                    result = new TL_message_secret_old(); //custom
                    break;
                case 0x9789dac4:
                    result = new TL_message_layer104_3();
                    break;

o

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

Hmm... pare salvaticu. Ma, prubabilmente, questu hè u codice generatu, allora va bè? .. Ma certamente sustene tutte e versioni! True, ùn hè micca chjaru perchè tuttu hè mischju inseme, chats secreti, è ogni tipu di _old7 in qualchì manera ùn pare micca generazioni di machini... Tuttavia, a maiò parte di tuttu hè stata sbattuta

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Ragazzi, ùn pudete ancu decide ciò chì hè in una strata ?! Ebbè, va bè, dicemu chì "dui" sò stati liberati cù un errore, bè, succede, ma TRE ? .. Subitu, u listessu rake di novu? Chì tipu di pornografia hè questu, scusate?...

In u codice fonte di Telegram Desktop, per via, una cosa simili succede - se hè cusì, parechji impegni in una fila à u schema ùn cambianu micca u so numeru di strata, ma riparà qualcosa. In cundizioni induve ùn ci hè micca una fonte ufficiale di dati per u schema, induve pò esse acquistatu, salvu u codice fonte di u cliente ufficiale? E s'è vo pigliate da quì, ùn pudete micca esse sicuru chì u schema hè cumplettamente currettu finu à pruvà tutti i metudi.

Cumu pò ancu esse pruvatu? Spergu chì i fan di unità, funziunali è altre teste sparteranu in i cumenti.

Va bè, fighjemu un altru pezzu di codice:

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;

Stu cummentariu "creatu manualmente" suggerisce chì solu una parte di stu schedariu hè stata scritta manualmente (pudete imaginà tutta l'incubo di mantenimentu?), è u restu hè stata generata da a macchina. In ogni casu, ci hè una altra quistione - chì e fonti sò dispunibili micca cumplettamente (à la GPL blobs in u kernel Linux), ma questu hè digià un tema per a seconda parte.

Ma abbastanza. Passemu à u protokollu nantu à quale tutta sta serializazione corre.

MT Proto

Allora, apremu descrizzione generale и descrizzione dettagliata di u protocolu è a prima cosa chì sbattemu hè a terminologia. È cù una bundanza di tuttu. In generale, questu pare esse una funzione proprietaria di Telegram - chjamà e cose in modu diversu in diversi posti, o cose diverse cù una sola parolla, o vice versa (per esempiu, in una API d'altu livellu, se vede un pacchettu di sticker, ùn hè micca ciò chì pensate).

Per esempiu, "messaghju" è "sessione" significanu qualcosa di sfarente quì chì in l'interfaccia di u cliente di Telegram. Ebbè, tuttu hè chjaru cù u missaghju, puderia esse interpretatu in termini OOP, o simpricimenti chjamatu a parolla "pacchettu" - questu hè un livellu bassu di trasportu, ùn ci sò micca listessi missaghji cum'è in l'interfaccia, ci sò parechji missaghji di serviziu. . Ma a sessione... ma prima cosa prima.

Stratu di trasportu

A prima cosa hè u trasportu. Ci diceranu circa 5 opzioni:

  • TCP
  • Websocket
  • Websocket nantu à HTTPS
  • HTTP
  • HTTPS

Vasily, [15.06.18 15:04] Ci hè ancu u trasportu UDP, ma ùn hè micca documentatu

È TCP in trè varianti

U primu hè simile à UDP sopra TCP, ogni pacchettu include un numeru di sequenza è crc
Perchè a lettura di documenti nantu à un carrettu hè cusì dolorosa?

Ebbè, quì hè avà TCP digià in 4 varianti:

  • Abridu
  • Intermediu
  • Intermedi imbottitu
  • Full

Ebbè, ok, Padded intermediate per MTProxy, questu hè statu aghjuntu dopu à causa di avvenimenti famosi. Ma perchè duie versioni più (trè in totale) quandu pudete fà cun una? Tutti i quattru sò essenzialmente diffirenti solu in quantu à stabilisce a durata è a carica utile di u MTProto principale, chì serà discututu in più:

  • in Abred hè 1 o 4 bytes, ma micca 0xef, allora u corpu
  • in Intermediate questu hè 4 bytes di lunghezza è un campu, è a prima volta chì u cliente deve mandà 0xeeeeeeee per indicà chì hè Intermediate
  • in Full u più addictive, da u puntu di vista di un networker: lunghezza, numeru di sequenza, è NON U ONE chì hè principalmente MTProto, corpu, CRC32. Iè, tuttu questu hè sopra à TCP. Chì ci furnisce un trasportu affidabile in forma di un flussu di byte sequenziale; ùn ci hè micca bisognu di sequenze, in particulare checksums. D'accordu, avà qualcunu s'oppone à mè chì TCP hà un checksum di 16-bit, cusì a corruzzione di dati succede. Grande, ma avemu veramente un protokollu criptograficu cù hashes più longu di 16 bytes, tutti questi errori - è ancu di più - seranu catturati da una discrepanza SHA à un livellu più altu. Ùn ci hè micca puntu in CRC32 sopra à questu.

Fighjemu l'Abreviatu, in quale un byte di lunghezza hè pussibule, cù Intermediate, chì ghjustificà "In casu chì l'allineamentu di dati 4-byte hè necessariu", chì hè abbastanza stupidu. Chì, si crede chì i programatori di Telegram sò cusì incompetenti chì ùn ponu micca leghje dati da un socket in un buffer allinatu? Avete sempre à fà questu, perchè a lettura pò rinvià ogni numeru di byte (è ci sò ancu servitori proxy, per esempiu...). O d'altra banda, perchè bluccà Abridged s'ellu avemu sempre un padding pesante in cima à 16 byte - salvà 3 byte иногда ?

Ci hè l'impressione chì Nikolai Durov veramente piace à reinventà e roti, cumpresi i protokolli di a rete, senza alcuna necessità pratica reale.

Altre opzioni di trasportu, incl. Web è MTProxy, ùn cunsideremu micca avà, forse in un altru postu, se ci hè una dumanda. Riguardu à questu stessu MTProxy, ricurdemu solu avà chì pocu dopu a so liberazione in 2018, i fornituri anu amparatu rapidamente à bluccà, destinatu à bloccu di bypassda dimensione di u pacchettu! È ancu u fattu chì u servitore MTProxy scrittu (di novu da Waltman) in C era eccessivamente ligatu à e specificità Linux, ancu s'ellu ùn era micca necessariu (Phil Kulin cunfirmà), è chì un servitore simili sia in Go o Node.js avissi. fit in menu di centu linee.

Ma ne tireremu cunclusioni nantu à l'alfabetizazione tecnica di sti persone à a fine di a rùbbrica, dopu avè cunsideratu altre tematiche. Per avà, andemu à l'OSI layer 5, sessione - nantu à quale anu postu a sessione MTProto.

Chjavi, missaghji, sessioni, Diffie-Hellman

L'anu pusatu quì micca sanu currettamente ... Una sessione ùn hè micca a listessa sessione chì hè visibile in l'interfaccia sottu Sessioni Active. Ma in ordine.

Critica di u protocolu è l'approcciu urganisazione di Telegram. Parte 1, tecnicu: sperienza di scrive un cliente da zero - TL, MT

Allora avemu ricevutu una stringa di byte di lunghezza cunnisciuta da a capa di trasportu. Questu hè o un missaghju criptatu o un testu chjaru - se simu sempre in u stadiu di l'accordu chjave è u facemu veramente. Qualessu di u gruppu di cuncetti chjamati "chjavi" parlemu? Chjaremu stu prublema per a squadra di Telegram stessu (scusate per avè traduttu a mo propria documentazione da l'inglese cù un cervellu stancu à 4 ore di sera, era più faciule per lascià alcune frasi cum'è sò):

Ci sò duie entità chjamate sessione - unu in l'UI di i clienti ufficiali sottu "sessioni currenti", induve ogni sessione currisponde à un dispositivu / OS sanu.
Sicondu - sessione MTProto, chì hà u numeru di sequenza di u messagiu (in un sensu bassu livellu) in questu, è quale pò durà trà e diverse cunnessione TCP. Diversi sessioni MTProto ponu esse installate à u stessu tempu, per esempiu, per accelerà u scaricamentu di u schedariu.

Trà questi dui u dischettu ci hè un cuncettu autorisazioni. In u casu degeneratu, pudemu dì chì sessione UI hè u listessu cum'è autorisazioni, ma sfortunatamente, tuttu hè cumplicatu. Fighjemu:

  • L'utilizatore nantu à u novu dispositivu genera prima auth_key è ligami à contu, per esempiu via SMS - hè per quessa autorisazioni
  • Hè accadutu in u primu sessione MTProto, chì hà session_id in sè stessu.
  • À questu passu, a cumminazzioni autorisazioni и session_id puderia esse chjamatu esempiu - sta parolla appare in a documentazione è u codice di certi clienti
  • Allora, u cliente pò apre parechji sessioni MTProto sottu u listessu auth_key - à u listessu DC.
  • Allora, un ghjornu, u cliente hà bisognu di dumandà u schedariu da un altru DC - è per questu DC un novu serà generatu auth_key !
  • Per informà u sistema chì ùn hè micca un novu utilizatore chì hè registratu, ma u listessu autorisazioni (sessione UI), u cliente usa chjama API auth.exportAuthorization in casa DC auth.importAuthorization in u novu DC.
  • Tuttu hè listessu, parechji ponu esse aperti sessioni MTProto (ognunu cù u so propiu session_id) à sta nova DC, sottu a so auth_key.
  • Infine, u cliente pò vulerà Perfect Forward Secrecy. Ogni auth_key era Permanente chjave - per DC - è u cliente pò chjamà auth.bindTempAuthKey per usu momentu auth_key - è dinò, solu unu temp_auth_key per DC, cumunu à tutti sessioni MTProto à questu DC.

custata, chì u sali (è i sali futuri) hè ancu unu auth_key quelli. spartutu trà tutti sessioni MTProto à u listessu DC.

Cosa significa "trà e diverse cunnessione TCP"? Allora questu significa qualcosa cum'è cookie d'autorizazione in un situ web - persiste (survive) parechje cunnessione TCP à un servitore determinatu, ma un ghjornu anda male. Solu à u cuntrariu di HTTP, in i missaghji MTProto in una sessione sò numerati è cunfirmati in sequenza; se entranu in u tunelu, a cunnessione hè stata rotta - dopu avè stabilitu una nova cunnessione, u servitore hà amabilmente mandà tuttu in questa sessione chì ùn hà micca furnitu in a precedente. cunnessione TCP.

Tuttavia, l'infurmazioni sopra sò riassunte dopu parechji mesi di investigazione. Intantu, implementemu u nostru cliente da zero? - Turnemu à u principiu.

Allora generà auth_key nantu Versioni Diffie-Hellman da Telegram. Pruvemu di capisce a ducumentazione...

Vasily, [19.06.18 20:05] data_with_hash := SHA1 (data) + data + (qualsiasi byte aleatoriu); tali chì a lunghezza uguale à 255 bytes;
dati_criptati := RSA (data_with_hash, server_public_key); un numeru longu di 255 byte (big endian) hè elevatu à u putere necessariu nantu à u modulu necessariu, è u risultatu hè guardatu cum'è un numeru di 256 byte.

Hanu qualchì droga DH

Ùn pare micca u DH di una persona sana
Ùn ci hè micca duie chjave pubbliche in dx

Ebbè, à a fine, questu hè stata risolta, ma un residuu restava - a prova di u travagliu hè fatta da u cliente chì hà sappiutu fà u numeru. Tipu di prutezzione contra attacchi DoS. È a chjave RSA hè aduprata solu una volta in una direzzione, essenzialmente per a criptografia new_nonce. Ma mentre sta operazione apparentemente simplice hà da successu, chì avete da affruntà?

Vasily, [20.06.18/00/26 XNUMX:XNUMX] Ùn aghju micca ancu ghjuntu à a dumanda appid

Aghju mandatu sta dumanda à DH

È, in u dock di trasportu, dice chì pò risponde cù 4 bytes di un codice d'errore. Eccu tuttu

Ebbè, m'hà dettu -404, allora chì ?

Allora l'aghju dettu: "Catch your bullshit encrypted with a server key with an fingerprint like this, I want DH", è hà rispostu cù un stupidu 404.

Chì pensate di sta risposta di u servitore? Chì fà ? Ùn ci hè nimu à dumandà (ma più nantu à questu in a seconda parte).

Quì tuttu l'interessu hè fattu nantu à u dock

Ùn aghju nunda à fà, aghju solu sunniatu di cunvertisce i numeri avanti è avanti

Dui numeri di 32 bit. L'aghju imballatu cum'è tutti l'altri

Ma no, questi dui devenu esse aghjuntu à a linea prima cum'è BE

Vadim Goncharov, [20.06.18 15:49] è per via di questu 404?

Vasily, [20.06.18 15:49] SI !

Vadim Goncharov, [20.06.18 15:50] cusì ùn capiscu micca ciò chì pò "ùn hà micca truvatu"

Vasily, [20.06.18 15:50] appressu

Ùn aghju micca pussutu truvà una tale descomposizione in fattori primi %)

Ùn avemu mancu gestitu rapportu d'errore

Vasily, [20.06.18 20:18] Oh, ci hè ancu MD5. Dighjà trè hashes differenti

L'impronta digitale chjave hè calculata cusì:

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

SHA1 è sha2

Allora mettemu auth_key avemu ricevutu 2048 bits in size use Diffie-Hellman. Chì ci hè dopu ? Dopu scopre chì i 1024 bits più bassi di sta chjave ùn sò micca usati in ogni modu ... ma pensemu à questu per avà. À questu passu, avemu un sicretu spartutu cù u servitore. Un analogu di a sessione TLS hè statu stabilitu, chì hè un prucessu assai caru. Ma u servitore ùn sà ancu nunda di quale simu! Non ancora, in realtà. autorizazione. Quelli. se pensate in termini di "login-password", cum'è avete fattu una volta in ICQ, o almenu "login-key", cum'è in SSH (per esempiu, in qualchi gitlab / github). Avemu ricevutu un anonimu. E se u servitore ci dice "sti numeri di telefunu sò serviti da un altru DC"? O ancu "u vostru numeru di telefunu hè pruibitu"? U megliu chì pudemu fà hè di mantene a chjave in a speranza chì serà utile è ùn andarà micca rottu da allora.

In modu, avemu "ricivutu" cù riservazioni. Per esempiu, avemu fiducia in u servitore? E s'ellu hè falsu ? I cuntrolli criptografici seranu necessarii:

Vasily, [21.06.18 17:53] Offrenu à i clienti mobili per verificà un numeru di 2kbit per a primalità%)

Ma ùn hè micca chjaru in tuttu, nafeijoa

Vasily, [21.06.18 18:02] U documentu ùn dice micca ciò chì deve fà s'ellu risulta micca simplice

Ùn hè micca dettu. Videmu ciò chì face u cliente ufficiale Android in questu casu? A hè ciò chì (è iè, tuttu u schedariu hè interessante) - cum'è dicenu, lasceraghju quì quì:

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

Innò, di sicuru hè sempre quì qualchi Ci sò testi per a primalità di un numeru, ma personalmente ùn aghju più cunniscenze di matematica.

Va bè, avemu a chjave maestra. Per login, i.e. mandate richieste, avete bisognu di eseguisce più criptografia, usendu AES.

A chjave di u messagiu hè definitu cum'è i 128 bits media di u SHA256 di u corpu di u messagiu (cumpresa a sessione, l'ID di messagiu, etc.), cumprese i bytes di padding, prepended by 32 bytes pigliati da a chjave d'autorizazione.

Vasily, [22.06.18 14:08] Media, cagna, bits

Ricevutu auth_key. Tuttu. Al di là di elli... ùn hè micca chjaru da u documentu. Sentite liberu di studià u codice open source.

Nota chì MTProto 2.0 richiede da 12 à 1024 bytes di padding, sempre sottumessu à a cundizione chì a lunghezza di u messagiu risultatu sia divisibile da 16 bytes.

Allora quantu padding duvete aghjunghje?

È sì, ci hè ancu un 404 in casu d'errore

Se qualchissia hà studiatu currettamente u diagramma è u testu di a documentazione, anu nutatu chì ùn ci hè micca MAC. È chì AES hè utilizatu in un certu modu IGE chì ùn hè micca usatu in altrò. Iddi, sicuru, scrivenu nantu à questu in i so FAQ ... Quì, cum'è, a chjave di u missaghju stessu hè ancu u SHA hash di i dati decriptati, utilizatu per verificà l'integrità - è in casu d'una discordanza, a documentazione per una certa ragione. ricumanda di ignurà in silenziu (ma chì ne di a sicurità, è s'ellu ci rompenu ?).

Ùn sò micca un criptografu, forse ùn ci hè nunda di male in questu modu in questu casu da un puntu di vista teoricu. Ma possu chjaramente chjamà un prublema pratica, utilizendu Telegram Desktop cum'è un esempiu. Cifra u cache lucale (tutti questi D877F783D5D3EF8C) in u listessu modu cum'è i missaghji in MTProto (solu in questu casu versione 1.0), i.e. prima a chjave di u missaghju, dopu i dati stessi (è in qualchì parte da u principale grande auth_key 256 bytes, senza quale msg_key inutile). Cusì, u prublema diventa notevuli nantu à i schedarii grossi. Vale à dì, avete bisognu di mantene duie copie di e dati - criptate è decrypted. È s'ellu ci sò megabytes, o streaming video, per esempiu? .. Schemi classici cù MAC dopu à u ciphertext permettenu di leghje u flussu, trasmette immediatamente. Ma cù MTProto vi tuccherà a prima criptate o decifrate u missaghju tutale, solu dopu trasfiriu à a reta o à u discu. Dunque, in l'ultime versioni di Telegram Desktop in a cache in user_data Un altru furmatu hè ancu usatu - cù AES in modu CTR.

Vasily, [21.06.18 01:27] Oh, aghju scupertu ciò chì hè IGE: IGE hè statu u primu tentativu di un "modu di crittografia di autenticazione", urigginariamente per Kerberos. Hè statu un tentativu fallutu (ùn furnisce micca a prutezzione di l'integrità), è hà da esse eliminatu. Hè stata l'iniziu di una ricerca di 20 anni per un modu di crittografia autenticante chì funziona, chì recentemente culminatu in modi cum'è OCB è GCM.

È avà l'argumenti da u latu di u carrettu:

A squadra daretu à Telegram, guidata da Nikolai Durov, hè custituita da sei campioni ACM, a mità di elli Ph.Ds in matematica. Ci hà pigliatu circa dui anni per sparghje a versione attuale di MTProto.

Hè divertente. Dui anni à u livellu più bassu

O pudete solu piglià tls

Va bè, dicemu chì avemu fattu a criptografia è altre sfumature. Hè infine pussibule di mandà richieste serializate in TL è deserializà e risposte? Allora chì è cumu duvete mandà? Eccu, dicemu, u metudu initConnection, forse questu hè ?

Vasily, [25.06.18 18:46] Inizializza a cunnessione è salvà l'infurmazioni nantu à u dispusitivu è l'applicazione di l'utilizatori.

Accepta app_id, device_model, system_version, app_version è lang_code.

È qualchì dumanda

Documentazione cum'è sempre. Sentite liberu di studià a fonte aperta

Se tuttu era apprussimatamente chjaru cù invokeWithLayer, allora chì hè sbagliatu quì? Risulta, dicemu chì avemu - u cliente avia digià qualcosa da dumandà à u servitore - ci hè una dumanda chì vulemu mandà:

Vasily, [25.06.18 19:13] A ghjudicà da u codice, a prima chjamata hè impannillata in questa merda, è a merda stessa hè impannillata in invokewithlayer.

Perchè initConnection ùn puderia micca esse una chjama separata, ma deve esse un wrapper? Iè, cum'è hè risultatu, deve esse fattu ogni volta à u principiu di ogni sessione, è micca una volta, cum'è cù a chjave principale. Ma! Ùn pò micca esse chjamatu da un utilizatore micca autorizatu! Avà avemu ghjuntu à u stadiu induve hè applicabile Questu pagina di documentazione - è ci dice chì...

Solu una piccula parte di i metudi API sò dispunibuli per l'utilizatori micca autorizati:

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

U primu di elli, auth.sendCode, è ci hè quella prima dumanda apprezzata in quale mandemu api_id è api_hash, è dopu ricevemu un SMS cù un codice. E s'è no simu in u DC sbagliatu (i numeri di telefuni in stu paese sò servuti da un altru, per esempiu), allora riceveremu un errore cù u numeru di u DC desideratu. Per sapè quale indirizzu IP per u numeru DC avete bisognu di cunnette, aiutateci help.getConfig. À un tempu ci era solu 5 entrate, ma dopu à l'avvenimenti famosi di 2018, u numeru hè aumentatu significativamente.

Avà ricurdatemu chì avemu ghjuntu à questu stadiu in u servitore anonimamente. Ùn hè micca troppu caru per avè solu un indirizzu IP? Perchè ùn fate micca questu, è altre operazioni, in a parte non criptata di MTProto? Sentu l'obiezione: "cumu pudemu assicurà chì ùn hè micca RKN chì risponde cù falsi indirizzi?" À questu avemu ricurdatu chì, in generale, i clienti ufficiali I chjavi RSA sò incrustati, i.e. pudete solu segnu sta infurmazione. In realtà, questu hè digià fattu per l'infurmazioni nantu à sguassate u bloccu chì i clienti ricevenu attraversu altri canali (logicamente, questu ùn pò micca esse fattu in MTProto stessu; avete ancu bisognu di sapè induve cunnette).

OK. In questa fase di l'autorizazione di u cliente, ùn simu ancu autorizati è ùn avemu micca registratu a nostra applicazione. Vulemu solu vede per avà ciò chì u servitore risponde à i metudi dispunibuli per un utilizatore micca autorizatu. È quì…

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;

In u schema, u primu vene secondu

In u schema tdesktop u terzu valore hè

Iè, da tandu, sicuru, a ducumentazione hè stata aghjurnata. Ancu s'ellu pò prestu torna irrilevante. Cumu deve sapè un sviluppatore principiante? Forse si registra a vostra applicazione, vi informaranu? Vasily hà fattu questu, ma sfortunatamente, ùn anu micca mandatu nunda (di novu, parlemu di questu in a seconda parte).

...Avete nutatu chì avemu digià in qualchì manera spustatu à l'API, i.e. à u prossimu livellu, è missu qualcosa in u tema MTProto? Nisuna sorpresa:

Vasily, [28.06.18 02:04] Mm, stanu frugando in certi algoritmi nantu à e2e

Mtproto definisce l'algoritmi di criptografia è e chjave per i dui duminii, è ancu un pocu di una struttura di wrapper

Ma mischianu constantemente diversi livelli di a pila, cusì ùn hè micca sempre chjaru induve mtproto finisci è u prossimu livellu cuminciò.

Cumu si mischianu? Ebbè, quì hè a stessa chjave temporale per PFS, per esempiu (per via, Telegram Desktop ùn pò micca fà). Hè eseguitu da una dumanda API auth.bindTempAuthKey, i.e. da u livellu più altu. Ma à u stessu tempu interferiscenu cù a criptografia à u livellu più bassu - dopu, per esempiu, avete bisognu di fà di novu. initConnection etc., questu ùn hè micca solu dumanda normale. Ciò chì hè ancu speciale hè chì pudete avè solu UNA chjave temporale per DC, ancu s'ellu u campu auth_key_id in ogni missaghju permette di cambià a chjave almenu ogni missaghju, è chì u servitore hà u dirittu di "scurdà" di a chjave temporale in ogni mumentu - a ducumentazione ùn dice micca ciò chì deve fà in questu casu ... bè, perchè puderia micca Ùn avete parechje chjave, cum'è cù un inseme di sali futuri, è ?

Ci hè parechje altre cose chì vale a pena nutà nantu à u tema MTProto.

Messaggi di messagiu, msg_id, msg_seqno, cunferma, ping in a direzzione sbagliata è altre idiosincrasie

Perchè avete bisognu di sapè di elli? Perchè "fughjenu" à un livellu più altu, è avete bisognu di esse cuscenti quandu travaglianu cù l'API. Assumimu chì ùn ci interessa micca msg_key; u livellu più bassu hà decifratu tuttu per noi. Ma in i dati decriptati avemu i seguenti campi (ancu a durata di e dati, cusì sapemu induve hè u padding, ma questu ùn hè micca impurtante):

  • sali - int64
  • session_id - int64
  • message_id - int64
  • seq_no - int32

Ricurdemu chì ci hè solu un sali per tuttu u DC. Perchè sapè di ella? Micca solu perchè ci hè una dumanda get_future_salts, chì vi dice chì intervalli seranu validi, ma ancu perchè se u vostru sali hè "rotten", allura u missaghju (a dumanda) serà simplicemente persu. U servitore, di sicuru, rappurtarà u novu sali da emissione new_session_created - ma cù u vechju vi tuccherà à rinvià in una certa manera, per esempiu. È questu prublema afecta l'architettura di l'applicazione.

U servitore hè permessu di abbandunà e sessioni in tuttu è risponde in questu modu per parechje motivi. In verità, chì hè una sessione MTProto da u cliente? Quessi sò dui numeri session_id и seq_no missaghji in sta sessione. Ebbè, è a cunnessione TCP sottostante, sicuru. Dicemu chì u nostru cliente ùn sà ancu cumu fà parechje cose, hà disconnected and reconnected. S'ellu hè accadutu rapidamente - a vechja sessione cuntinuò in a nova cunnessione TCP, cresce seq_no in più. S'ellu pigghia assai tempu, u servitore puderia sguassà, perchè da u so latu hè ancu una fila, cum'è avemu scupertu.

Chì deve esse seq_no? Oh, hè una quistione complicata. Pruvate à capisce onestamente ciò chì vulia dì:

Missaghju legatu à u cuntenutu

Un missaghju chì richiede una ricunniscenza esplicita. Questi includenu tutti l'utilizatori è parechji missaghji di serviziu, quasi tutti cù l'eccezzioni di cuntenituri è ricunniscenza.

Numero di Sequenza di Missaghju (msg_seqno)

Un numeru di 32 bit uguale à duie volte u numeru di missaghji "liate à u cuntenutu" (quelli chì necessitanu ricunniscenza, è in particulare quelli chì ùn sò micca cuntenituri) creati da u mittente prima di stu missaghju è successivamente aumentatu da unu se u messagiu attuale hè un missaghju legatu à u cuntenutu. Un cuntinuu hè sempre generatu dopu à tuttu u so cuntenutu; dunque, u so numeru di sequenza hè più grande o uguale à i numeri di sequenza di i missaghji cuntenuti in questu.

Chì tipu di circu hè questu cù un incrementu da 1, è dopu un altru da 2?... Sospettate chì inizialmente significavanu "u pocu significativu per ACK, u restu hè un numeru", ma u risultatu ùn hè micca u listessu - in particulare, esce, pò esse mandatu parechji cunferma chì hà u listessu seq_no! Cumu? Ebbè, per esempiu, u servitore ci manda qualcosa, u manda, è noi stessi stemu in silenziu, solu rispundenu cù messagi di serviziu chì cunfirmanu a ricezione di i so missaghji. In questu casu, i nostri cunfirmazioni in uscita avarà u listessu numeru in uscita. Sè vo site familiarizatu cù TCP è pensate chì questu sona in modu salvaticu, ma pare micca assai salvaticu, perchè in TCP seq_no ùn cambia micca, ma a cunferma va à seq_no da l'altra parte, mi affreteraraghju à sfurzàvi. A cunferma hè furnita in MTProto Micca nantu seq_no, cum'è in TCP, ma da msg_id !

Chì ghjè stu msg_id, u più impurtante di sti campi ? Un identificatore di missaghju unicu, cum'è u nome suggerisce. Hè definitu cum'è un numeru di 64-bit, i più bassu di quale anu torna a magia "servitore-non-server", è u restu hè un timestamp Unix, cumpresa a parte fraccionaria, spustata 32 bits à manca. Quelli. timestamp per se (è i missaghji cù i tempi chì differenu troppu seranu rifiutati da u servitore). Da questu risulta chì in generale questu hè un identificatore chì hè globale per u cliente. In vista di questu - ricurdemu session_id - simu garantiti: In nessuna circustanza un missaghju destinatu à una sessione pò esse mandatu in una sessione diversa. Vale à dì, si trova chì ci hè digià trè livellu - sessione, numeru di sessione, id missaghju. Perchè tali overcomplication, stu misteru hè assai grande.

Cusì, msg_id necessariu per...

RPC: dumande, risposte, errori. Cunfirmazioni.

Comu pudete avè nutatu, ùn ci hè micca un tipu o funzione speciale "fà una dumanda RPC" in ogni locu in u diagramma, ancu s'ellu ci sò risposte. Dopu tuttu, avemu i missaghji di cuntenutu! Hè, qualsiasi u missaghju puderia esse una dumanda! O micca esse. Malgradu tuttu, di ognunumsg_id. Ma ci sò risposte:

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

Questu hè induve hè indicatu quale missaghju hè una risposta. Dunque, à u livellu più altu di l'API, vi tuccherà à ricurdà ciò chì u numeru di a vostra dumanda era - Pensu chì ùn ci hè bisognu di spiegà chì u travagliu hè asincronu, è ci ponu esse parechje dumande in prugressu à u stessu tempu, e risposte à quale pò esse tornatu in ogni ordine? In principiu, da questu è i missaghji d'errore cum'è nimu travagliadori, l'architettura daretu à questu pò esse tracciata: u servitore chì mantene una cunnessione TCP cun voi hè un equilibratore front-end, trasmette e richieste à i backends è li raccoglie via via. message_id. Sembra chì tuttu quì hè chjaru, logicu è bonu.

Iè ?.. È s'ellu ci pensa ? Dopu tuttu, a risposta RPC stessu hà ancu un campu msg_id! Avemu bisognu di gridà à u servitore "ùn rispondi micca à a mo risposta!"? È iè, chì ci era di cunferma? À propositu di a pagina missaghji nantu à i missaghji ci dice ciò chì hè

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

è deve esse fattu da ogni parte. Ma micca sempre! Se avete ricevutu un RpcResult, ellu stessu serve cum'è cunferma. Questu hè, u servitore pò risponde à a vostra dumanda cù MsgsAck - cum'è "L'aghju ricevutu". RpcResult pò risponde immediatamente. Puderia esse tramindui.

È iè, avete sempre à risponde à a risposta ! Cunfirmazione. Altrimenti, u servitore u cunsidererà micca livabile è u rinvià à voi di novu. Ancu dopu a ricunniscenza. Ma quì, di sicuru, u prublema di i timeouts sorge. Fighjemu un pocu dopu.

Intantu, fighjemu i pussibuli errori di esecuzione di e dumande.

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

Oh, qualcunu esclamerà, quì hè un furmatu più umanu - ci hè una linea! Piglia u to tempu. Quì lista di errori, ma di sicuru micca cumpletu. Da ellu avemu amparatu chì u codice hè qualcosa cum'è Errori HTTP (bene, sicuru, a semantica di e risposte ùn hè micca rispettata, in certi lochi sò distribuiti aleatoriamente trà i codici), è a linea s'assumiglia CAPITAL_LETTERS_AND_NUMBERS. Per esempiu, PHONE_NUMBER_OCCUPIED o FILE_PART_Х_MISSING. Ebbè, questu hè, avete sempre bisognu di sta linea parse. Per esempiu, FLOOD_WAIT_3600 significherà chì avete da aspittà una ora, è PHONE_MIGRATE_5, chì un numeru di telefunu cù stu prefissu deve esse registratu in u 5u DC. Avemu un tipu di lingua, nò? Ùn avemu micca bisognu di un argumentu da una stringa, quelli regulari farà, va bè.

In novu, questu ùn hè micca nantu à a pagina di messagi di serviziu, ma, cum'è di solitu cù stu prughjettu, l'infurmazione pò esse truvata in una altra pagina di documentazione. O scaccià i suspetti. Prima, fighjate, scrive / violazione di strati - RpcError pò esse nidificatu in RpcResult. Perchè micca fora? Chì ùn avemu micca pigliatu in contu ?.. Per quessa, induve hè a guaranzia chì RpcError Ùn pò micca esse incrustati in RpcResult, ma esse direttamente o nidificatu in un altru tipu ? .. È s'ellu ùn pò micca, perchè ùn hè micca à u livellu più altu, i.e. manca req_msg_id ? ...

Ma cuntinuemu nantu à i missaghji di serviziu. U cliente pò pensà chì u servitore pensa per un bellu pezzu è fà sta dumanda maravigliosa:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Ci sò trè risposti pussibuli à sta quistione, chì si intreccianu di novu cù u mecanismu di cunferma; pruvà à capisce ciò chì deve esse (è ciò chì a lista generale di tipi chì ùn anu micca bisognu di cunferma) hè lasciatu à u lettore cum'è travagliu di casa (nota: l'infurmazioni in u codice fonte di Telegram Desktop ùn hè micca cumpletu).

Addiction à a droga: statuti di missaghju

In generale, parechji posti in TL, MTProto è Telegram in generale lascianu un sensu di stubbornness, ma per educazione, tact è altri. e cumpetenze intelligenti Avemu educatu mantenemu silenziu annantu à questu, è censuramu l'obscenità in i dialoghi. Tuttavia, stu locuОa maiò parte di a pagina hè circa missaghji nantu à i missaghji Hè chocante ancu per mè, chì hà travagliatu cù protokolli di rete per un bellu pezzu è hà vistu biciclette di varii gradi di crookedness.

Si principia innocuously, cù cunferma. Dopu ci parlanu

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;

Ebbè, tutti quelli chì cumincianu à travaglià cù MTProto anu da trattà cun elli; in u ciculu "currettatu - ricompilatu - lanciatu", ottene errori numerichi o sali chì hà sappiutu per andà male durante l'edizioni hè una cosa cumuna. Tuttavia, ci sò dui punti quì:

  1. Questu significa chì u missaghju originale hè persu. Avemu bisognu di creà qualchi fila, avemu da guardà più tardi.
  2. Chì sò questi strani numeri di errore? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... induve sò l'altri numeri, Tommy ?

A documentazione dice:

L'intenzione hè chì i valori di error_code sò raggruppati (error_code >> 4): per esempiu, i codici 0x40 - 0x4f currispondenu à errori in a descomposizione di u containeru.

ma, prima, un cambiamentu in l'altru direzzione, è secondu, ùn importa micca, induve sò l'altri codici? In u capu di l'autore ?.. Tuttavia, questi sò trifles.

L'addiction principia in i missaghji nantu à u statutu di i missaghji è e copie di i missaghji:

  • Request for Message Status Information
    Se una parte ùn hà micca ricevutu infurmazione nantu à u statutu di i so messagi in uscita per un tempu, pò dumandà esplicitamente da l'altra parte:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Messaghju Informativu riguardanti Status of Messages
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Eccu, info hè una stringa chì cuntene esattamente un byte di statutu di messagiu per ogni missaghju da a lista di msg_ids entranti:

    • 1 = nunda ùn hè cunnisciutu di u messagiu (msg_id troppu bassu, l'altra parte pò esse scurdatu)
    • 2 = missaghju micca ricevutu (msg_id entra in a gamma di identificatori almacenati; però, l'altra parte ùn hà certamente micca ricevutu un missaghju cusì)
    • 3 = missaghju micca ricevutu (msg_id troppu altu; però, l'altra parte ùn l'avete certamente micca ricevutu)
    • 4 = missaghju ricevutu (nota chì sta risposta hè ancu à u stessu tempu una ricunniscenza di ricevuta)
    • +8 = messagiu digià ricunnisciutu
    • +16 = missaghju chì ùn hà micca bisognu di ricunniscenza
    • +32 = dumanda RPC cuntenuta in u missaghju chì hè trattatu o trasfurmatu digià cumpletu
    • +64 = risposta à u cuntenutu à u messagiu digià generatu
    • +128 = l'altra parte sà per un fattu chì u messagiu hè digià ricevutu
      Sta risposta ùn hà micca bisognu di ricunniscenza. Hè un ricunniscenza di u msgs_state_req pertinente, in sè stessu.
      Nota chì s'ellu si trova subitu chì l'altra parte ùn hà micca un missaghju chì pare chì hè statu mandatu à ellu, u missaghju pò esse simpricimenti rinviatu. Ancu s'è l'altra parte deve riceve duie copie di u messagiu à u stessu tempu, u duplicatu serà ignoratu. (Se troppu tempu hè passatu, è u msg_id uriginale ùn hè più validu, u missaghju deve esse impannillatu in msg_copy).
  • Comunicazione Voluntaria di Status of Messages
    Ogni parte pò vuluntariamente informà l'altra parte di u statutu di i missaghji trasmessi da l'altra parte.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Comunicazione Voluntaria Estesa di Status di Un Messaghju
    ...
    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;
  • Richiesta Esplicita di Rienvià Missaghji
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    A parte remota risponde immediatamente rinviendu i missaghji richiesti [...]
  • Richiesta esplicita per rinvià e risposte
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    U partitu remota risponde subitu per rinvià risposte à i missaghji richiesti […]
  • Copie di messagiu
    In certi casi, un vechju missaghju cù un msg_id chì ùn hè più validu deve esse rimandatu. Allora, hè impannillatu in un containeru di copia:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Una volta ricivutu, u missaghju hè trattatu cum'è s'ellu ùn ci era micca u wrapper. In ogni casu, s'ellu hè cunnisciutu per certu chì u missaghju orig_message.msg_id hè statu ricevutu, allura u novu missaghju ùn hè micca trattatu (mentre à u stessu tempu, è orig_message.msg_id sò ricunnisciuti). U valore di orig_message.msg_id deve esse più bassu di u msg_id di u containeru.

Restemu ancu zitti nantu à ciò chì msgs_state_info dinò l'arechje di u TL infinitu sò spuntate (avemu bisognu di un vettore di bytes, è in i dui bits inferjuri ci era un enum, è in i dui bits più altu ci era bandieri). U puntu hè diversu. Qualchissia capisce perchè tuttu questu hè in pratica? in un veru cliente necessariu? .. Cù difficultà, ma unu pò imaginà qualchì benefiziu se una persona hè impegnata in debugging, è in modu interattivu - dumandate à u servitore ciò chì è cumu. Ma quì e dumande sò descritte andata e ritorno.

Ne segue chì ogni partitu ùn deve micca solu criptu è mandà missaghji, ma ancu almacenà dati nantu à elli stessi, nantu à e risposte à elli, per un tempu scunnisciutu. A documentazione ùn descrive nè i timings nè l'applicabilità pratica di queste caratteristiche. senza modu. Ciò chì hè più maravigghiusu hè chì sò veramente usati in u codice di i clienti ufficiali! Apparentemente, anu dettu qualcosa chì ùn era micca inclusu in a documentazione publica. Capisce da u codice perchè, ùn hè più simplice quant'è in u casu di TL - ùn hè micca una parte (relativamente) logicamente isolata, ma un pezzu ligatu à l'architettura di l'applicazione, i.e. averà bisognu di più tempu per capisce u codice di l'applicazione.

Pings è timings. Coda.

Da tuttu, s'ellu ci ricurdate di l'ipotesi nantu à l'architettura di u servitore (distribuzione di e dumande à traversu backends), seguita una cosa piuttostu triste - malgradu tutte e garanzie di consegna in TCP (o i dati sò mandati, o sarete infurmatu nantu à a pausa, ma i dati saranu consegnati finu à chì u prublema si verifica), chì cunferma in MTProto stessu - senza garanzie. U servitore pò facilmente perde o scaccià u vostru missaghju, è nunda ùn pò esse fattu, basta aduprà diverse tippi di crutches.

È prima di tuttu - fila di messagi. Ebbè, cù una cosa tuttu era ovvi da u principiu - un missaghju micca cunfirmatu deve esse guardatu è risentitu. È dopu chì tempu ? È u buffonu u cunnosce. Forsi quelli messagi di serviziu addicted in qualchì modu risolve stu prublema cù crutches, per dì, in Telegram Desktop ci sò circa 4 fili chì currispondenu à elli (forse di più, cum'è digià dettu, per questu avete bisognu à sfondà in u so codice è l'architettura più seriu; à u listessu tempu). tempu, avemu Sapemu chì ùn pò esse pigliatu cum'è una mostra, un certu nùmeru di tipi da u schema MTProto ùn sò micca usatu in lu).

Perchè questu succede? Probabilmente, i programatori di u servitore ùn anu pussutu assicurà affidabilità in u cluster, o ancu buffering in u balancer frontale, è trasfirìu stu prublema à u cliente. Per disperazione, Vasily hà pruvatu à implementà una opzione alternativa, cù solu duie fila, utilizendu algoritmi da TCP - misurazione di l'RTT à u servitore è aghjustendu a dimensione di a "finestra" (in i missaghji) secondu u numeru di richieste micca cunfirmate. Vale à dì, un heuristicu cusì grossu per valutà a carica di u servitore hè quante di e nostre richieste pò masticà à u stessu tempu è micca perde.

Ebbè, hè, avete capitu, nò ? Sè avete da implementà TCP di novu nantu à un protokollu chì corre nantu à TCP, questu indica un protocolu assai pocu cuncepitu.

Oh, sì, perchè avete bisognu di più di una fila, è chì significa questu per una persona chì travaglia cù una API d'altu livellu in ogni modu? Fighjate, fate una dumanda, serializzala, ma spessu ùn pudete micca mandà immediatamente. Perchè? Perchè a risposta serà msg_id, chì hè tempuraleаSò una etichetta, l'assignazione di quale hè megliu posposta finu à u più tardu pussibule - in casu chì u servitore u rifiuta per una mancanza di tempu trà noi è ellu (di sicuru, pudemu fà una crutch chì cambia u nostru tempu da u presente. à u servitore aghjunghjendu un delta calculatu da e risposte di u servitore - i clienti ufficiali facenu questu, ma hè crudu è imprecisu per via di buffering). Dunque, quandu fate una dumanda cù una chjama di funzione lucale da a biblioteca, u messagiu passa per e seguenti tappe:

  1. Si trova in una fila è aspetta a criptografia.
  2. Appuntatu msg_id è u missaghju si n'andò in un'altra fila - invio pussibule; mandate à u socket.
  3. a) U servitore hà rispostu MsgsAck - u missaghju hè statu mandatu, sguassemu da a "altra fila".
    b) O vice versa, ùn li piacia micca qualcosa, hà rispostu badmsg - rinviate da "una altra fila"
    c) Nunda hè cunnisciutu, u missaghju deve esse rimandatu da un'altra fila - ma ùn hè micca cunnisciutu esattamente quandu.
  4. U servitore hà finalmente rispostu RpcResult - a risposta attuale (o errore) - micca solu furnitu, ma ancu processatu.

Probabilmente, l'usu di cuntenituri puderia risolve in parte u prublema. Questu hè quandu una mansa di messagi sò imballati in unu, è u servitore hà rispostu cun una cunferma à tutti in una volta, in unu. msg_id. Ma ellu ricuserà ancu stu pacchettu, se qualcosa andava male, in tuttu.

È à questu puntu entranu in ghjocu considerazioni non tecniche. Da l'esperienza, avemu vistu assai crutches, è in più, avà vedemu più esempi di mali cunsiglii è architettura - in tali cundizioni, vale a pena di fiducia è di piglià tali decisioni? A quistione hè retorica (di sicuru micca).

Di chì parlemu ? Se nantu à u tema di "missaghji di droga nantu à i missaghji" pudete ancu speculate cù l'obiezioni cum'è "sì stupidu, ùn avete micca capitu u nostru pianu brillanti!" (Dunque scrivite a ducumentazione prima, cum'è a ghjente normale deve, cù raghjone è esempi di scambiu di pacchetti, allora parlemu), allora i timings / timeouts sò una quistione puramente pratica è specifica, tuttu quì hè cunnisciutu per un bellu pezzu. Chì ci dice a documentazione nantu à i timeout?

Un servitore generalmente ricunnosce a ricezione di un messagiu da un cliente (normalmente, una dumanda RPC) utilizendu una risposta RPC. Se una risposta hè longa, un servitore pò mandà prima una ricunniscenza di ricevuta, è un pocu dopu, a risposta RPC stessu.

Un cliente generalmente ricunnosce a ricezione di un missaghju da un servitore (di solitu, una risposta RPC) aghjunghjendu un ricunniscenza à a prossima dumanda RPC si ùn hè micca trasmessa troppu tardi (se hè generatu, per dì, 60-120 seconde dopu à a ricezione). di un missaghju da u servitore). In ogni casu, se per un longu periodu di tempu ùn ci hè nisuna ragione per mandà messagi à u servitore o s'ellu ci hè un gran numaru di missaghji micca ricunnisciuti da u servitore (per dì, più di 16), u cliente trasmette un ricunniscenza stand-alone.

... I traduce : noi stessi ùn sapemu quantu è cumu ne avemu bisognu, allora supponemu chì sia cusì.

È nantu à i pings:

Missaghji Ping (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Una risposta hè generalmente tornata à a stessa cunnessione:

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

Questi missaghji ùn anu micca bisognu di ricunniscenza. Un pong hè trasmessu solu in risposta à un ping mentre un ping pò esse iniziatu da ogni parte.

Chiusura di cunnessione differita + PING

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

Funziona cum'è ping. Inoltre, dopu chì questu hè ricevutu, u servitore principia un timer chì chjuderà a cunnessione attuale disconnect_delay seconde dopu à menu chì ùn riceve un novu missaghju di u listessu tipu chì resetta automaticamente tutti i timers precedenti. Se u cliente manda sti ping una volta ogni 60 seconde, per esempiu, pò stabilisce disconnect_delay uguale à 75 seconde.

sì pazza ?! In 60 seconde, u trenu entrerà in a stazione, abbandunà è piglia i passageri, è torna à perde u cuntattu in u tunnel. In 120 seconde, mentre l'avete intesu, ghjunghjerà à un altru, è a cunnessione più prubabilmente si rompe. Ebbè, hè chjaru induve venenu e gammi - "Aghju intesu un sonu, ma ùn sanu micca induve hè", ci hè l'algoritmu di Nagl è l'opzione TCP_NODELAY, destinata à u travagliu interattivu. Ma, scusate, mantene u so valore predeterminatu - 200 Milliseconde Sè vo vulete veramente rapprisintà qualcosa simili è salvà nantu à un coppiu pussibuli di pacchetti, poi mettite fora per 5 seconde, o qualunque sia u timeout di u messagiu "User typing..." hè avà. Ma micca più.

È infine, pings. Questu hè, cuntrollà a vivacità di a cunnessione TCP. Hè divertente, ma circa 10 anni fà aghju scrittu un testu criticu nantu à u messenger di u dormitoriu di a nostra facultà - l'autori anu ancu pinged u servitore da u cliente, è micca vice versa. Ma i studienti di 3u annu sò una cosa, è un uffiziu internaziunale hè un altru, nò?

Prima, un pocu prugramma educativu. Una cunnessione TCP, in l'absenza di scambiu di pacchetti, pò campà per settimane. Questu hè bonu è male, secondu u scopu. Hè bonu se avete avutu una cunnessione SSH aperta à u servitore, avete alzatu da l'urdinatore, riavviate u router, tornatu à u vostru locu - a sessione attraversu stu servitore ùn hè micca strappata (ùn avete micca scrittu nunda, ùn ci era micca pacchetti) , hè cunvene. Hè male s'ellu ci sò millaie di clienti nantu à u servitore, ognunu piglia risorse (ciao, Postgres!), È l'ospitu di u cliente pò esse riavviatu assai tempu fà - ma ùn sapemu micca.

I sistemi di chat / IM cadenu in u sicondu casu per una ragione addiziale - stati in linea. Se l'utilizatore "cadde", avete bisognu di informà i so interlocutori nantu à questu. Altrimenti, vi finiscinu cù un sbagliu chì i creatori di Jabber hà fattu (è currettu per 20 anni) - l'utilizatore hà disconnected, ma cuntinueghjanu à scrive missaghji per ellu, crede chì ellu hè in linea (chì eranu ancu persu completamente in questi). pochi minuti prima chì a disconnessione hè stata scuperta). Innò, l'opzione TCP_KEEPALIVE, chì parechje persone chì ùn capiscenu micca cumu travaglianu i timers TCP, mettenu in modu aleatoriu (impostendu valori salvatichi cum'è decine di seconde), ùn aiuterà micca quì - avete bisognu di assicurà chì micca solu u kernel OS. di a macchina di l'utilizatori hè vivu, ma ancu funziunendu nurmalmente, in capacità di risponde, è l'appiecazione stessu (pensate chì ùn pò micca freeze? Telegram Desktop nantu à Ubuntu 18.04 si congelava per mè più di una volta).

Hè per quessa chì avete da ping servitore cliente, è micca vice versa - se u cliente faci questu, se a cunnessione hè rotta, u ping ùn serà micca mandatu, u scopu ùn serà micca rializatu.

Chì vedemu nantu à Telegram? Hè esattamente u cuntrariu ! Ebbè, questu hè. Formalmente, sicuru, i dui lati ponu ping l'un l'altru. In pratica, i clienti utilizanu una crutch ping_delay_disconnect, chì stabilisce u timer nantu à u servitore. Ebbè, scusate, ùn tocca à u cliente di decide quantu tempu ellu voli campà quì senza ping. U servitore, basatu nantu à a so carica, sà megliu. Ma, sicuru, s'ellu ùn vi importa micca e risorse, allora sarete u vostru propiu Pinocchiu male, è una crutch farà ...

Cumu deve esse statu cuncepitu?

Credu chì i fatti sopra indicanu chjaramente chì a squadra di Telegram / VKontakte ùn hè micca assai cumpetente in u campu di u trasportu (è più bassu) di u livellu di e rete di l'informatica è a so bassa qualificazione in materia pertinenti.

Perchè hè diventatu cusì cumplicatu, è cumu l'architetti di Telegram ponu pruvà à ughjettà? U fattu chì anu pruvatu à fà una sessione chì sopravvive à e rotture di a cunnessione TCP, vale à dì, ciò chì ùn hè micca statu mandatu avà, daremu più tardi. Probabilmente anu ancu pruvatu à fà un trasportu UDP, ma anu scontru difficultà è l'abbandunonu (hè per quessa chì a ducumentazione hè viota - ùn ci era nunda di vantà). Ma a causa di una mancanza di capiscitura di cumu e rete in generale è TCP in particulare u travagliu, induve pudete cunfidassi nantu à questu, è induve avete bisognu di fà ellu stessu (è cumu), è un tentativu di cumminà questu cù a criptografia "dui acelli cù una petra ", questu hè u risultatu.

Cumu era necessariu? Basatu nantu à u fattu chì msg_id hè un timestamp necessariu da un puntu di vista criptograficu per prevene attacchi di replay, hè un sbagliu per attaccà una funzione d'identificatore unicu. Dunque, senza cambià fundamentalmente l'architettura attuale (quandu u flussu di l'aghjurnamenti hè generatu, questu hè un tema API d'altu livellu per un'altra parte di sta serie di posti), ci vole à:

  1. U servitore chì tene a cunnessione TCP à u cliente assume a rispunsabilità - s'ellu hà lettu da u socket, per piacè ricunnosce, processà o rinvià un errore, senza pèrdite. Allora a cunferma ùn hè micca un vettore di ids, ma solu "l'ultimu seq_no ricevutu" - solu un numeru, cum'è in TCP (dui numeri - u vostru seq è u cunfirmatu). Semu sempre in a sessione, ùn hè micca?
  2. U timestamp per prevene l'attacchi di replay diventa un campu separatu, à la nonce. Hè verificatu, ma ùn affetta nunda altru. Basta è uint32 - se u nostru sali cambia almenu ogni mità di ghjornu, pudemu attribuisce 16 bits à i bits low-order di una parte intera di u tempu attuale, u restu - à una parte fraccionaria di un secondu (cum'è avà).
  3. Sguassatu msg_id in tuttu - da u puntu di vista di distingue e dumande nantu à i backends, ci hè, prima, l'ID di u cliente, è in segundu, l'ID di sessione, concatenate. Dunque, solu una cosa hè abbastanza cum'è identificatore di dumanda seq_no.

Questa ùn hè ancu l'opzione più riescita; un aleatoriu cumpletu puderia serve cum'è identificatore - questu hè digià fattu in l'API d'altu livellu quandu mandate un missaghju, per via. Saria megliu per rinfurzà cumplettamente l'architettura da parente à assolutu, ma questu hè un tema per un'altra parte, micca questu post.

API ?

Ta-daam ! Allora, dopu avè luttatu per una strada piena di dulore è crutches, avemu infine capace di mandà ogni dumanda à u servitore è riceve ogni risposta à elli, è ancu riceve l'aghjurnamenti da u servitore (micca in risposta à una dumanda, ma ellu stessu). ci manda, cum'è PUSH, se qualcunu hè più chjaru cusì).

Attenzione, avà ci sarà l'unicu esempiu in Perl in l'articulu! (per quelli chì ùn sò micca familiarizati cù a sintassi, u primu argumentu di benedizzione hè a struttura di dati di l'ughjettu, u sicondu hè a so classe):

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

Iè, micca un spoiler apposta - se ùn l'avete ancu lettu, vai avanti è fate!

Oh, wai~~... chì pare questu? Qualcosa assai familiare... forsi questu hè a struttura di dati di una API Web tipica in JSON, salvu chì e classi sò ancu attaccati à l'uggetti?

Allora questu hè cusì chì hè ... Di chì hè tuttu, camaradi ? .. Tantu sforzu - è avemu firmatu à riposu induve i programatori Web appena principiatu?..JSON solu per HTTPS ùn saria più simplice ?! Chì avemu avutu in scambiu ? U sforzu valeva a pena ?

Evaluemu ciò chì TL + MTProto ci hà datu è quali alternative sò pussibuli. Ebbè, HTTP, chì si focalizeghja nantu à u mudellu di dumanda-risposta, hè un male fit, ma almenu qualcosa in cima di TLS?

Serializazione compatta. Videndu sta struttura di dati, simili à JSON, mi ricordu chì ci sò versioni binari di questu. Marquemu MsgPack cum'è insufficientemente estensibile, ma ci hè, per esempiu, CBOR - per via, un standard descrittu in RFC 7049. Hè notu per u fattu chì definisce tags, cum'è un mecanismu di espansione, è trà digià standardizatu dispunibile:

  • 25 + 256 - rimpiazzà e linee ripetute cù una riferenza à u numeru di linea, cusì un metudu di compressione economicu
  • 26 - uggettu Perl seriale cù u nome di a classa è l'argumenti di u custruttore
  • 27 - ughjettu serializatu indipendente da a lingua cù u nome di u tipu è l'argumenti di u custruttore

Ebbè, aghju pruvatu à serializà i stessi dati in TL è in CBOR cù stringa è imballaggio d'oggetti attivati. U risultatu hà cuminciatu à varià in favore di CBOR da un megabyte:

cborlen=1039673 tl_len=1095092

Cusì, cunclusione: Ci sò formati sustanziali più simplici chì ùn sò micca sottumessi à u prublema di fallimentu di sincronizazione o identificatore scunnisciutu, cù efficienza paragunabili.

Stabilimentu di cunnessione veloce. Questu significa zero RTT dopu a ricunniscenza (quandu a chjave hè stata generata una volta) - applicabile da u primu missaghju MTProto, ma cù qualchì riservazione - culpisce u listessu salitu, a sessione ùn hè micca putrefa, etc. Chì ci offre invece TLS? Citazione nantu à u tema:

Quandu si usa PFS in TLS, i biglietti di sessione TLS (RFC 5077) per ripiglià una sessione criptata senza rinegozià e chjave è senza guardà l'infurmazioni chjave in u servitore. Quandu apre a prima cunnessione è creanu chjave, u servitore cripta u statu di cunnessione è u trasmette à u cliente (in a forma di un bigliettu di sessione). Dunque, quandu a cunnessione hè ripresa, u cliente manda un bigliettu di sessione, cumprese a chjave di sessione, torna à u servitore. U bigliettu stessu hè criptatu cù una chjave temporale (chjave di u bigliettu di sessione), chì hè guardatu in u servitore è deve esse distribuitu trà tutti i servitori di frontend chì processanu SSL in suluzioni clustered.[10]. Cusì, l'intruduzioni di un bigliettu di sessione pò violà PFS se i chjavi di u servitore tempurane sò cumprumessi, per esempiu, quandu sò stati guardati per un bellu pezzu (OpenSSL, nginx, Apache li almacenanu per automaticamente per tutta a durata di u prugramma; i siti populari utilizanu). a chjave per parechje ore, finu à ghjorni).

Quì u RTT ùn hè micca zero, avete bisognu di scambià almenu ClientHello è ServerHello, dopu chì u cliente pò mandà dati cù Finished. Ma quì duvemu ricurdà chì ùn avemu micca u Web, cù u so munzeddu di cunnessione di novu apertu, ma un messageru, a cunnessione di quale hè spessu una è più o menu longa, dumande relativamente brevi à e pagine Web - tuttu hè multiplexed. internu. Questu hè, hè abbastanza accettabile s'ellu ùn avemu micca scontru in una seccione di metro veramente pessima.

Avete scurdatu qualcosa d'altru? Scrivite in i cumenti.

À seguità!

In a seconda parte di sta serie di posti avemu da cunsiderà micca tecnichi, ma tematiche urganisazione - avvicinamenti, ideologia, interfaccia, attitudine versu l'utilizatori, etc. Basatu, però, nantu à l'infurmazione tecnica chì hè stata presentata quì.

A terza parte hà da cuntinuà à analizà u cumpunente tecnicu / sperienza di sviluppu. Amparerete, in particulare:

  • cuntinuazione di u pandemoniu cù a varietà di tipi di TL
  • cose scunnisciute nantu à i canali è i supergruppi
  • perchè i dialoghi sò peggiu di a lista
  • circa l'indirizzu di messagiu assolutu versus relative
  • chì hè a diffarenza trà foto è imagine
  • cumu l'emoji interferiscenu cù u testu corsu

e altre stampelle ! Restate à sente !

Source: www.habr.com

Add a comment