Telegram-ի արարողակարգային և կազմակերպչական մոտեցումների քննադատություն. Մաս 1, տեխնիկական. հաճախորդը զրոյից գրելու փորձ - TL, MT

Վերջերս Habré-ում սկսել են ավելի հաճախ հայտնվել հրապարակումներ այն մասին, թե որքան լավն է Telegram-ը, որքան փայլուն և փորձառու են Դուրով եղբայրները ցանցային համակարգեր կառուցելու գործում և այլն։ Միևնույն ժամանակ, շատ քչերն են իսկապես ընկղմվել տեխնիկական սարքի մեջ. առավելագույնը նրանք օգտագործում են բավականին պարզ (և MTProto-ից միանգամայն տարբեր) JSON-ի վրա հիմնված Bot API և սովորաբար պարզապես ընդունում են: հավատքի վրա բոլոր գովեստներն ու PR-ը, որոնք պտտվում են մեսենջերի շուրջ: Գրեթե մեկուկես տարի առաջ Էշելոն ՀԿ-ի իմ գործընկեր Վասիլիը (ցավոք, նրա հաշիվը Habré-ում սևագրի հետ ջնջվեց) սկսեց զրոյից գրել իր սեփական Telegram հաճախորդը Perl-ում, իսկ ավելի ուշ միացավ այս տողերի հեղինակը: Ինչու՞ Պերլ, ոմանք անմիջապես կհարցնեն: Որովհետև նման նախագծեր արդեն գոյություն ունեն այլ լեզուներով:Իրականում հարցը սա չէ, կարող է լինել ցանկացած այլ լեզու, որտեղ չկա: պատրաստի գրադարան, և համապատասխանաբար հեղինակը պետք է գնա մինչև վերջ զրոյից. Ավելին, ծածկագրությունը վստահության հարց է, բայց ստուգեք: Անվտանգությանն ուղղված արտադրանքի դեպքում դուք չեք կարող պարզապես ապավինել արտադրողի պատրաստի գրադարանին և կուրորեն վստահել դրան (սակայն սա երկրորդ մասի թեմա է): Այս պահին գրադարանը բավականին լավ է աշխատում «միջին» մակարդակում (թույլ է տալիս կատարել ցանկացած API հարցում):

Այնուամենայնիվ, այս գրառումների շարքում շատ գաղտնագրություն կամ մաթեմատիկա չի լինի: Բայց կլինեն շատ այլ տեխնիկական մանրամասներ և ճարտարապետական ​​հենակներ (նաև օգտակար նրանց համար, ովքեր զրոյից չեն գրի, բայց գրադարանը կօգտագործեն ցանկացած լեզվով): Այսպիսով, հիմնական նպատակն էր փորձել զրոյից իրականացնել հաճախորդը ըստ պաշտոնական փաստաթղթերի. Այսինքն, ենթադրենք, որ պաշտոնական հաճախորդների սկզբնական կոդը փակ է (կրկին, երկրորդ մասում մենք ավելի մանրամասն կանդրադառնանք այն փաստին, որ դա ճիշտ է. դա տեղի է ունենում այսպես), բայց, ինչպես հին ժամանակներում, օրինակ, կա RFC-ի նման ստանդարտ. հնարավո՞ր է հաճախորդ գրել միայն ճշգրտման համաձայն, «առանց նայելու» աղբյուրի կոդը, լինի դա պաշտոնական (Telegram Desktop, բջջային), թե՞ ոչ պաշտոնական հեռուստամարաթոն:

Բովանդակություն:

Փաստաթղթեր... գոյություն ունի, չէ՞: Արդյոք դա ճիշտ է?..

Այս հոդվածի համար գրառումների հատվածները սկսեցին հավաքվել անցյալ տարվա ամռանը: Այս ամբողջ ընթացքում պաշտոնական կայքում https://core.telegram.org Փաստաթղթերը եղել են 23-րդ շերտի դրությամբ, այսինքն. ինչ-որ տեղ խրված 2014 թվականին (հիշում եք, այն ժամանակ նույնիսկ ալիքներ չկային): Իհարկե, տեսականորեն սա պետք է մեզ թույլ տար 2014 թվականին այդ ժամանակ ֆունկցիոնալությամբ հաճախորդ իրականացնել։ Բայց նույնիսկ այս վիճակում փաստաթղթավորումը նախ թերի էր, երկրորդ՝ տեղ-տեղ հակասում էր ինքն իրեն։ Ընդամենը մեկ ամիս առաջ՝ 2019 թվականի սեպտեմբերին, դա տեղի ունեցավ պատահաբար Պարզվեց, որ կայքում կա փաստաթղթերի մեծ թարմացում՝ բոլորովին վերջերս Layer 105-ի համար, նշելով, որ այժմ ամեն ինչ պետք է նորից կարդալ: Իրոք, շատ հոդվածներ վերանայվեցին, բայց շատերը մնացին անփոփոխ։ Հետևաբար, փաստաթղթի վերաբերյալ ստորև ներկայացված քննադատությունը կարդալիս պետք է նկատի ունենալ, որ այս բաներից որոշներն այլևս տեղին չեն, բայց որոշները դեռևս բավականաչափ են: Ի վերջո, ժամանակակից աշխարհում 5 տարին ոչ միայն երկար ժամանակ է, այլ շատ շատ. Այդ ժամանակներից ի վեր (հատկապես եթե հաշվի չեք առնում այդ ժամանակից ի վեր անտեսված և վերածնված գեոչաթի կայքերը), սխեմայում API մեթոդների թիվը հարյուրից հասել է ավելի քան երկու հարյուր հիսունի:

Որտեղի՞ց սկսել որպես երիտասարդ հեղինակ:

Կարևոր չէ՝ դուք գրում եք զրոյից, թե օգտագործում եք, օրինակ, պատրաստի գրադարաններ, ինչպիսիք են Հեռուստամարաթոն Python-ի համար կամ Madeline PHP-ի համար, ամեն դեպքում նախ ձեզ պետք կգա գրանցեք ձեր դիմումը - ստանալ պարամետրեր api_id и api_hash (նրանք, ովքեր աշխատել են VKontakte API-ի հետ, անմիջապես հասկանում են), որով սերվերը նույնականացնում է հավելվածը: Սա պետք է դա արեք իրավական պատճառներով, բայց մենք ավելի շատ կխոսենք այն մասին, թե ինչու գրադարանի հեղինակները չեն կարող այն հրապարակել երկրորդ մասում: Դուք կարող եք գոհ լինել թեստի արժեքներից, թեև դրանք շատ սահմանափակ են. փաստն այն է, որ այժմ կարող եք գրանցվել միայն մեկը հավելվածը, այնպես որ մի շտապեք դրան:

Հիմա, տեխնիկական տեսանկյունից, մեզ պետք է հետաքրքրի, որ գրանցումից հետո Telegram-ից ստանանք ծանուցումներ փաստաթղթերի, արձանագրության և այլնի թարմացումների մասին։ Այսինքն՝ կարելի էր ենթադրել, որ նավահանգիստներով կայքը պարզապես լքվել է և շարունակվել է աշխատել հատուկ նրանց հետ, ովքեր սկսել են հաճախորդներ ստեղծել, քանի որ. ավելի հեշտ է: Բայց ոչ, նման բան չի նկատվել, տեղեկություն չի եկել։

Եվ եթե դուք գրում եք զրոյից, ապա ստացված պարամետրերի օգտագործումը իրականում դեռ շատ հեռու է: Չնայած նրան https://core.telegram.org/ և առաջին հերթին խոսում է դրանց մասին «Սկսել»-ում, ըստ էության, նախ պետք է իրականացնել MTProto արձանագրություն - բայց եթե հավատայիք դասավորությունը ըստ OSI մոդելի էջի վերջում արձանագրության ընդհանուր նկարագրության համար, ապա դա ամբողջովին ապարդյուն է:

Իրականում և՛ MTProto-ից առաջ, և՛ հետո, միանգամից մի քանի մակարդակով (ինչպես ասում են ՕՀ-ի միջուկում աշխատող արտասահմանյան ցանցերները՝ շերտի խախտում), մի մեծ, ցավոտ ու սարսափելի թեմա կխոչընդոտի...

Երկուական սերիականացում. TL (Type Language) և դրա սխեման, շերտերը և շատ այլ սարսափելի բառեր

Այս թեման, ըստ էության, Telegram-ի խնդիրների բանալին է։ Եվ շատ սարսափելի խոսքեր կլինեն, եթե փորձեք խորանալ դրա մեջ:

Այսպիսով, ահա դիագրամը. Եթե ​​այս բառը մտքովդ անցնի, ասա. JSON սխեման, ճիշտ ես մտածել։ Նպատակը նույնն է՝ ինչ-որ լեզու նկարագրելու փոխանցված տվյալների հնարավոր փաթեթը: Հենց այստեղ էլ ավարտվում են նմանությունները։ Եթե ​​էջից MTProto արձանագրություն, կամ պաշտոնական հաճախորդի աղբյուրի ծառից, մենք կփորձենք բացել ինչ-որ սխեման, կտեսնենք նման բան.

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;

Մարդը, ով առաջին անգամ է տեսնում դա, ինտուիտիվ կերպով կկարողանա ճանաչել գրվածի միայն մի մասը. լավ, դրանք, ըստ երևույթին, կառուցվածքներ են (չնայած որտեղ է անունը, ձախ կողմում, թե աջ կողմում), դրանցում կան դաշտեր, որից հետո երկու կետից հետո մի տեսակ է հաջորդում... հավանաբար. Այստեղ անկյունային փակագծերում հավանաբար կան C++-ի նման ձևանմուշներ (իրականում, ոչ իրականում). Իսկ ի՞նչ են նշանակում մնացած բոլոր նշանները՝ հարցականներ, բացականչական նշաններ, տոկոսներ, հեշ նշաններ (և ակնհայտորեն տարբեր բաներ են նշանակում տարբեր տեղերում), երբեմն ներկա և երբեմն ոչ, տասնվեցական թվեր, և ամենակարևորը, թե ինչպես կարելի է դրանից դուրս գալ։ ճիշտ է (որը չի մերժվի սերվերի կողմից) բայթ հոսք: Դուք պետք է կարդաք փաստաթղթերը (այո, մոտակա JSON տարբերակում կան սխեմայի հղումներ, բայց դա ավելի պարզ չի դարձնում).

Բացեք էջը Երկուական տվյալների սերիականացում և սուզվեք սնկերի և դիսկրետ մաթեմատիկայի կախարդական աշխարհ, որը նման է մատանային 4-րդ տարում: Այբուբեն, տեսակ, արժեք, կոմբինատոր, ֆունկցիոնալ կոմբինատոր, նորմալ ձև, կոմպոզիտային տեսակ, պոլիմորֆ տեսակ... և դա ընդամենը առաջին էջն է: Հաջորդը սպասում է ձեզ TL լեզու, որը, թեև այն արդեն պարունակում է չնչին խնդրանքի և պատասխանի օրինակ, բայց ամենևին պատասխան չի տալիս ավելի բնորոշ դեպքերին, ինչը նշանակում է, որ դուք ստիպված կլինեք անցնել ռուսերենից անգլերեն թարգմանված մաթեմատիկայի վերապատմում ևս ութ ներկառուցված: էջեր!

Ֆունկցիոնալ լեզուներին և ավտոմատ տիպի եզրակացությանը ծանոթ ընթերցողները, իհարկե, այս լեզվով նկարագրության լեզուն կտեսնեն, նույնիսկ օրինակից, շատ ավելի ծանոթ, և կարող են ասել, որ դա իրականում սկզբունքորեն վատ չէ: Սրա առարկություններն են.

  • Այո, նպատակ լավ է հնչում, բայց ավաղ, նա ձեռք չի բերվել
  • Ռուսական բուհերում կրթությունը տարբերվում է նույնիսկ ՏՏ մասնագիտությունների միջև. ոչ բոլորն են անցել համապատասխան դասընթաց
  • Վերջապես, ինչպես կտեսնենք, գործնականում այդպես է պարտադիր չէ, քանի որ օգտագործվում է նույնիսկ նկարագրված TL-ի միայն սահմանափակ ենթաբազմություն

Ինչպես ասվեց LeoNerd ալիքի վրա #perl FreeNode IRC ցանցում, ով փորձել է իրականացնել մի դարպաս Telegram-ից դեպի Matrix (մեջբերի թարգմանությունը հիշողության մեջ ճշգրիտ չէ).

Այնպիսի տպավորություն է, որ ինչ-որ մեկին առաջին անգամ են ծանոթացրել տպագրության տեսությունը, հուզվել և սկսել է փորձել խաղալ դրա հետ՝ իրոք չհետաքրքրվելով, թե արդյոք դա անհրաժեշտ է գործնականում:

Ինքներդ տեսեք, եթե մերկ տիպերի (ինտեր, երկար և այլն) անհրաժեշտությունը որպես տարրական բան հարցեր չի առաջացնում, վերջիվերջո դրանք պետք է իրականացվեն ձեռքով, օրինակ՝ փորձենք բխել դրանցից։ վեկտոր. Այսինքն, ըստ էության, զանգված, եթե ստացված իրերը կոչեք իրենց հատուկ անուններով։

Բայց առաջ

TL շարահյուսության ենթաբազմության կարճ նկարագրություն նրանց համար, ովքեր չեն կարդում պաշտոնական փաստաթղթերը

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;

Սահմանումը միշտ սկսվում է կոնստրուկտոր, որից հետո ընտրովի (գործնականում՝ միշտ) խորհրդանիշի միջոցով # պետք է CRC32 այս տեսակի նկարագրության նորմալացված տողից: Հաջորդը գալիս է դաշտերի նկարագրությունը, եթե դրանք կան, ապա տեսակը կարող է դատարկ լինել: Այս ամենը ավարտվում է հավասար նշանով, այն տեսակի անվանումը, որին պատկանում է այս կոնստրուկտորը, այսինքն՝ իրականում ենթատեսակը։ Հավասարի նշանի աջ կողմում գտնվող տղան է պոլիմորֆ - այսինքն, դրան կարող են համապատասխանել մի քանի կոնկրետ տեսակներ։

Եթե ​​սահմանումը տեղի է ունենում տողից հետո ---functions---, ապա շարահյուսությունը կմնա նույնը, բայց իմաստը կլինի տարբեր. , սա պարզապես կլինի նշանակված նշանակությունը), իսկ «պոլիմորֆ տիպը»՝ վերադարձված արդյունքի տեսակը։ Ճիշտ է, այն դեռևս կմնա պոլիմորֆ՝ հենց նոր նշված բաժնում ---types---, բայց այս կոնստրուկտորը «չի դիտարկվի»։ Կոչվող ֆունկցիաների տեսակների գերբեռնում իրենց արգումենտներով, այսինքն. Չգիտես ինչու, նույն անունով, բայց տարբեր ստորագրություններով մի քանի գործառույթներ, ինչպես C++-ում, նախատեսված չեն TL-ում:

Ինչու՞ «կոնստրուկտոր» և «պոլիմորֆ», եթե դա OOP չէ: Դե, փաստորեն, ինչ-որ մեկի համար ավելի հեշտ կլինի մտածել այս մասին OOP-ի տերմիններով. final մի շարք լեզուների տերմինաբանության մեջ։ Իրականում, իհարկե, միայն այստեղ նմանություն իրական ծանրաբեռնված կոնստրուկտորական մեթոդներով OO ծրագրավորման լեզուներով: Քանի որ այստեղ պարզապես տվյալների կառուցվածքներ կան, մեթոդներ չկան (չնայած գործառույթների և մեթոդների հետագա նկարագրությունը կարող է գլխում շփոթություն առաջացնել, որ դրանք կան, բայց դա այլ հարց է), դուք կարող եք պատկերացնել կոնստրուկտորը որպես արժեք: որը կառուցվում է մուտքագրեք բայթ հոսք կարդալիս:

Ինչպե՞ս է դա տեղի ունենում: Ապասերիալիզատորը, որը միշտ կարդում է 4 բայթ, տեսնում է արժեքը 0xcrc32 - և հասկանում է, թե ինչ կլինի հետո field1 տեսակի հետ int, այսինքն. կարդում է ուղիղ 4 բայթ, դրա վրա ծածկված դաշտը տիպի հետ PolymorType կարդալ. Տեսնում է 0x2crc32 և հասկանում է, որ հետագա երկու դաշտ կա, առաջինը long, ինչը նշանակում է, որ մենք կարդում ենք 8 բայթ: Եվ հետո նորից բարդ տեսակ, որը նույն կերպ ապասերիալացվում է։ Օրինակ, Type3 կարող է հայտարարվել միացումում, հենց որ համապատասխանաբար երկու կոնստրուկտորներ, ապա նրանք պետք է հանդիպեն որևէ մեկին 0x12abcd34, որից հետո պետք է կարդալ ևս 4 բայթ intԿամ 0x6789cdef, որից հետո ոչինչ չի լինի։ Ցանկացած այլ բան - դուք պետք է բացառություն գցեք: Ինչևէ, սրանից հետո մենք վերադառնում ենք 4 բայթ կարդալուն int դաշտերը field_c в constructorTwo և դրանով մենք ավարտում ենք մեր ընթերցումը PolymorType.

Ի վերջո, եթե ձեզ բռնեն 0xdeadcrc համար constructorThree, հետո ամեն ինչ ավելի է բարդանում։ Մեր առաջին ոլորտն է bit_flags_of_what_really_present տեսակի հետ # - Իրականում, սա պարզապես տեսակի կեղծանուն է nat, նշանակում է «բնական թիվ»։ Այսինքն, ըստ էության, անստորագիր int-ը, ի դեպ, միակ դեպքն է, երբ իրական սխեմաներում առաջանում են անստորագիր թվեր։ Այսպիսով, հաջորդը հարցական նշանով կոնստրուկցիա է, ինչը նշանակում է, որ այս դաշտը կլինի հաղորդալարի վրա միայն այն դեպքում, եթե համապատասխան բիթը դրված է նշված դաշտում (մոտավորապես եռակի օպերատորի նման): Այսպիսով, ենթադրենք, որ այս բիթը սահմանված է, ինչը նշանակում է, որ հետագայում մենք պետք է կարդանք նման դաշտ Type, որը մեր օրինակում ունի 2 կոնստրուկտոր։ Մեկը դատարկ է (կազմված է միայն նույնացուցիչից), մյուսն ունի դաշտ ids տեսակի հետ ids:Vector<long>.

Դուք կարող եք մտածել, որ և՛ կաղապարները, և՛ ընդհանուր սարքերը դրական են կամ Java-ում: Բայց ոչ. Գրեթե. Սա միայնակ իրական սխեմաներում անկյունային փակագծերի օգտագործման դեպքում, և այն օգտագործվում է ՄԻԱՅՆ Վեկտորի համար: Բայթային հոսքում դրանք կլինեն 4 CRC32 բայթ բուն Vector տեսակի համար, միշտ նույնը, այնուհետև 4 բայթ՝ զանգվածի տարրերի քանակը, այնուհետև հենց այս տարրերը:

Սրան գումարենք այն փաստը, որ սերիականացումը միշտ տեղի է ունենում 4 բայթ բառերով, բոլոր տեսակները դրա բազմապատիկն են. նկարագրված են նաև ներկառուցված տեսակները: bytes и string երկարության ձեռքով սերիալիզացմամբ և 4-ով այս հավասարեցմամբ - լավ, թվում է, թե նորմալ է և նույնիսկ համեմատաբար արդյունավետ: Չնայած պնդում են, որ TL-ն արդյունավետ երկուական սերիալացում է, դժոխք նրանց հետ, գրեթե ցանկացած բանի, նույնիսկ բուլյան արժեքների և մեկ նիշերի տողերի ընդլայնմամբ մինչև 4 բայթ, JSON-ը դեռ շատ ավելի հաստ կլինի: Նայեք, նույնիսկ անհարկի դաշտերը կարելի է բաց թողնել բիթային դրոշակներով, ամեն ինչ բավականին լավ է, և նույնիսկ ընդարձակելի ապագայի համար, այնպես որ, ինչու՞ ավելի ուշ չավելացնել նոր ընտրովի դաշտեր կոնստրուկտորին:

Բայց ոչ, եթե կարդաք ոչ թե իմ հակիրճ նկարագրությունը, այլ ամբողջական փաստաթղթերը և մտածեք իրականացման մասին։ Նախ, կոնստրուկտորի CRC32-ը հաշվարկվում է սխեմայի տեքստի նկարագրության նորմալացված տողի համաձայն (հեռացնել լրացուցիչ բացատ և այլն) - այնպես որ, եթե նոր դաշտ ավելացվի, տիպի նկարագրության տողը կփոխվի, հետևաբար դրա CRC32 և , հետևաբար՝ սերիականացում։ Իսկ ի՞նչ կաներ հին հաճախորդը, եթե ստանա դաշտ, որտեղ դրված են նոր դրոշներ, և նա չգիտի, թե ինչ անել դրանց հետ հետո:

Երկրորդ՝ հիշենք CRC32, որն այստեղ հիմնականում օգտագործվում է որպես հեշ գործառույթներ եզակիորեն որոշել, թե որ տեսակն է (ապա)սերիալացվում: Այստեղ մենք կանգնած ենք բախումների խնդրի հետ, և ոչ, հավանականությունը 232-ից մեկը չէ, այլ շատ ավելի մեծ։ Ո՞վ հիշեց, որ CRC32-ը նախատեսված է հաղորդակցության ալիքում սխալները հայտնաբերելու (և շտկելու) համար և, համապատասխանաբար, բարելավում է այդ հատկությունները ի վնաս ուրիշների: Օրինակ, նրան չի հետաքրքրում բայթերի վերադասավորումը. եթե CRC32-ը հաշվում եք երկու տողից, ապա երկրորդում առաջին 4 բայթը փոխում եք հաջորդ 4 բայթի հետ, նույնը կլինի: Երբ մեր մուտքագրումը լատինական այբուբենից տեքստային տողեր են (և մի փոքր կետադրական նշաններ), և այդ անուններն առանձնապես պատահական չեն, նման վերադասավորման հավանականությունը մեծապես մեծանում է:

Ի դեպ, ո՞վ է ստուգել, ​​թե ինչ կա այնտեղ։ իրականում CRC32? Վաղ սկզբնաղբյուր կոդերից մեկը (նույնիսկ Ուոլթմենից առաջ) ուներ հեշ ֆունկցիա, որը յուրաքանչյուր նիշը բազմապատկում էր 239 թվով, այնքան սիրելի այս մարդկանց համար, հա հա:

Վերջապես, լավ, մենք հասկացանք, որ կոնստրուկտորները դաշտի տիպով Vector<int> и Vector<PolymorType> կունենա տարբեր CRC32: Ինչ վերաբերում է առցանց կատարմանը: Իսկ տեսական տեսանկյունից. արդյոք սա դառնում է տիպի մաս? Ենթադրենք, մենք փոխանցում ենք տասը հազար թվերի զանգված, լավ հետ Vector<int> ամեն ինչ պարզ է, երկարությունը և ևս 40000 բայթ։ Իսկ եթե սա Vector<Type2>, որը բաղկացած է միայն մեկ դաշտից int և այն մենակ է տիպի մեջ. պե՞տք է արդյոք կրկնել 10000xabcdef0 34 անգամ և հետո 4 բայթ: int, կամ լեզուն ի վիճակի է մեզ համար ԱՆԿԱԽԵԼ այն կոնստրուկտորից fixedVec իսկ 80000 բայթի փոխարեն նորից փոխանցի ընդամենը 40000?

Սա ամենևին էլ պարապ տեսական հարց չէ. պատկերացրեք, որ դուք ստանում եք խմբի օգտատերերի ցուցակ, որոնցից յուրաքանչյուրն ունի ID, անուն, ազգանուն, բջջային կապի միջոցով փոխանցված տվյալների քանակի տարբերությունը կարող է զգալի լինել: Մեզ գովազդվում է հենց Telegram-ի սերիալացման արդյունավետությունը:

Այսպիսով ...

Վեկտոր, որն այդպես էլ չթողարկվեց

Եթե ​​փորձեք զննել կոմբինատորների նկարագրության էջերը և այլն, ապա կտեսնեք, որ վեկտորը (և նույնիսկ մատրիցը) պաշտոնապես փորձում է դուրս գալ մի քանի թերթերի բազմակի միջով: Բայց վերջում մոռանում են, վերջնական քայլը բաց է թողնվում, ու ուղղակի տրվում է վեկտորի սահմանում, որը դեռ տիպի հետ կապված չէ։ Ինչ է պատահել? Լեզուներով ծրագրավորում, հատկապես ֆունկցիոնալները, բավականին բնորոշ է կառուցվածքը ռեկուրսիվ կերպով նկարագրելը. կոմպիլյատորն իր ծույլ գնահատմամբ կհասկանա և ամեն ինչ կանի ինքն իրեն։ Լեզվի մեջ տվյալների սերիականացում անհրաժեշտ է ԱՐԴՅՈՒՆԱՎԵՏՈՒԹՅՈՒՆԸ. բավական է պարզապես նկարագրել ցանկ, այսինքն. երկու տարրերի կառուցվածք. առաջինը տվյալների տարր է, երկրորդը նույն կառուցվածքն է կամ պոչի համար դատարկ տարածություն (փաթեթ (cons) Լիսպում): Բայց սա ակնհայտորեն կպահանջի յուրաքանչյուր տարրը ծախսում է լրացուցիչ 4 բայթ (CRC32 դեպքում՝ TL-ով) իր տեսակը նկարագրելու համար: Զանգվածը նույնպես կարելի է հեշտությամբ նկարագրել ֆիքսված չափս, բայց նախապես անհայտ երկարությամբ զանգվածի դեպքում մենք անջատվում ենք։

Հետևաբար, քանի որ TL-ը թույլ չի տալիս վեկտոր դուրս բերել, այն պետք է ավելացվեր կողքի վրա: Ի վերջո, փաստաթղթում ասվում է.

Սերիալիզացիան միշտ օգտագործում է նույն կոնստրուկտոր «վեկտորը» (const 0x1cb5c415 = crc32 («վեկտոր t:Type # [ t ] = Vector t»), որը կախված չէ t տեսակի փոփոխականի հատուկ արժեքից:

t կամընտիր պարամետրի արժեքը չի մասնակցում սերիականացմանը, քանի որ այն բխում է արդյունքի տեսակից (միշտ հայտնի է մինչև ապասերիալացում):

Ավելի ուշադիր նայեք. vector {t:Type} # [ t ] = Vector t -Բայց ոչ մի տեղ Այս սահմանումն ինքնին չի ասում, որ առաջին թիվը պետք է հավասար լինի վեկտորի երկարությանը: Եվ դա ոչ մի տեղից չի գալիս: Սա տրված է, որը պետք է հիշել և իրականացնել ձեր ձեռքերով: Մեկ այլ տեղ, փաստաթղթում նույնիսկ անկեղծորեն նշվում է, որ տեսակը իրական չէ.

Վեկտոր t պոլիմորֆ կեղծտիպը «տեսակ» է, որի արժեքը ցանկացած t տիպի արժեքների հաջորդականություն է՝ տուփով կամ բաց:

... բայց չի կենտրոնանում դրա վրա: Երբ դուք, հոգնած լինելով մաթեմատիկայի (գուցե նույնիսկ ձեզ հայտնի համալսարանական դասընթացից), որոշում եք հանձնվել և իրականում նայել, թե ինչպես աշխատել դրա հետ գործնականում, ձեր գլխում այնպիսի տպավորություն է թողնվում, որ սա լուրջ է: Հիմնականում մաթեմատիկան, այն ակնհայտորեն հորինել է Cool People-ը (երկու մաթեմատիկոս՝ ACM-ի հաղթող), և ոչ թե որևէ մեկը: Նպատակը՝ ցոյց տալը, հասած է։

Ի դեպ, թվի մասին. Հիշեցնենք, որ # դա հոմանիշ է nat, բնական թիվ:

Կան տիպային արտահայտություններ (տեսակ-էքսպր) և թվային արտահայտություններ (nat-expr) Այնուամենայնիվ, դրանք սահմանվում են նույն կերպ.

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

բայց քերականության մեջ դրանք նկարագրված են նույն կերպ, այսինքն. Այս տարբերությունը կրկին պետք է հիշել և ձեռքով կյանքի կոչել։

Դե, այո, ձևանմուշների տեսակները (vector<int>, vector<User>) ունեն ընդհանուր նույնացուցիչ (#1cb5c415), այսինքն. եթե գիտեք, որ զանգը հայտարարված է որպես

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

ապա դուք այլևս սպասում եք ոչ թե պարզապես վեկտորի, այլ օգտագործողների վեկտորի: Ավելի ճիշտ՝ պետք սպասեք - իրական կոդում յուրաքանչյուր տարր, եթե ոչ մերկ տիպ, կունենա կոնստրուկտոր, և լավ իմաստով իրականացման ժամանակ անհրաժեշտ կլինի ստուգել, ​​բայց մենք ուղարկվել ենք հենց այս վեկտորի յուրաքանչյուր տարրում: այդ տեսակը? Իսկ եթե դա լիներ ինչ-որ PHP, որտեղ զանգվածը կարող է պարունակել տարբեր տեսակներ տարբեր տարրերով:

Այս պահին դուք սկսում եք մտածել, արդյոք անհրաժեշտ է նման TL: Միգուցե սայլի համար հնարավոր լիներ օգտագործել մարդկային սերիալիզատոր, նույն պրոտոբուֆը, որն այն ժամանակ արդեն կար: Դա էր տեսությունը, եկեք պրակտիկային նայենք:

Գոյություն ունեցող TL իրականացումներ կոդում

TL-ն ծնվել է VKontakte-ի խորքերում նույնիսկ Դուրովի բաժնեմասի վաճառքով հայտնի իրադարձություններից առաջ և (անշուշտ), նույնիսկ նախքան Telegram-ի զարգացումը սկսելը։ Եվ բաց կոդով առաջին ներդրման սկզբնական կոդը դուք կարող եք գտնել շատ զվարճալի հենակներ: Եվ լեզուն ինքնին այնտեղ ներդրվել է ավելի լիարժեք, քան այժմ Telegram-ում։ Օրինակ, սխեմայում հեշերը ընդհանրապես չեն օգտագործվում (նկատի ունի ներկառուցված կեղծ տիպ (ինչպես վեկտոր) շեղված վարքով): Կամ

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

բայց եկեք դիտարկենք, հանուն ամբողջականության, հետևենք, այսպես ասած, Մտքի Հսկայի էվոլյուցիան:

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

Կամ այս գեղեցիկը.

    static const char *reserved_words_polymorhic[] = {

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

      };

Այս հատվածը նման կաղապարների մասին է.

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

Սա hashmap կաղապարի տիպի սահմանումն է որպես int - Type զույգերի վեկտոր: C++-ում այսպիսի տեսք կունենար.

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

այսպես, alpha - հիմնաբառ! Բայց միայն C++-ով կարելի է գրել T, բայց պետք է գրել ալֆա, բետա... Բայց ոչ ավելի, քան 8 պարամետր, այստեղ ավարտվում է ֆանտազիան։ Կարծես մի ժամանակ Սանկտ Պետերբուրգում այսպիսի երկխոսություններ են տեղի ունեցել.

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

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

Բայց սա «ընդհանուր առմամբ» TL-ի առաջին հրապարակված իրականացման մասին էր։ Եկեք անցնենք Telegram-ի հաճախորդներում իրականացումները դիտարկելուն:

Խոսք Վասիլիին.

Վասիլի, [09.10.18 17:07] Ամենից շատ էշը տաք է, որովհետև նրանք ստեղծեցին մի փունջ աբստրակցիաներ, իսկ հետո մի պտուտակ խփեցին դրանց վրա և ծածկեցին կոդերի գեներատորը հենակներով:
Արդյունքում, նախ dock pilot.jpg-ից
Այնուհետև dzhekichan.webp ծածկագրից

Իհարկե, ալգորիթմներին և մաթեմատիկային ծանոթ մարդկանցից մենք կարող ենք ակնկալել, որ նրանք կարդացել են Aho, Ullmann և ծանոթ են այն գործիքներին, որոնք տասնամյակների ընթացքում արդյունաբերության մեջ դե ֆակտո ստանդարտ են դարձել իրենց DSL կոմպիլյատորները գրելու համար, այնպես չէ՞:

Հեղինակի կողմից telegram-cli Վիտալի Վալթմանն է, ինչպես կարելի է հասկանալ TLO ձևաչափի հայտնվելուց նրա (cli) սահմաններից դուրս, թիմի անդամ. այժմ TL վերլուծության գրադարան է հատկացվել: առանձին, ինչ տպավորություն է թողնում նրա մասին TL վերլուծիչ.....

16.12 04:18 Վասիլի. Կարծում եմ, ինչ-որ մեկը չի տիրապետել lex+yacc-ին
16.12 04:18 Վասիլի. Այլ կերպ չեմ կարող բացատրել
16.12 04:18 Վասիլի. լավ, կամ նրանք վճարվել են VK-ում տողերի քանակի համար
16.12 04:19 Վասիլի. 3k+ տող և այլն:<censored> վերլուծիչի փոխարեն

Միգուցե բացառությո՞ւն: Տեսնենք, թե ինչպես դեեթ Սա ՊԱՇՏՈՆԱԿԱՆ հաճախորդն է՝ Telegram Desktop:

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

Python-ում 1100+ տող, մի քանի կանոնավոր արտահայտություն + վեկտորի նման հատուկ դեպքեր, որոնք, իհարկե, սխեմայում հայտարարված են այնպես, ինչպես պետք է լինի ըստ TL շարահյուսության, բայց նրանք հիմնվել են այս շարահյուսության վրա՝ այն վերլուծելու համար... Հարց է առաջանում՝ ինչո՞ւ էր այդ ամենը հրաշք։иԱյն ավելի շերտավոր է, եթե ոչ ոք չի պատրաստվում այն ​​վերլուծել ըստ փաստաթղթերի:

Ի դեպ... Հիշո՞ւմ եք, որ մենք խոսեցինք CRC32 ստուգման մասին: Այսպիսով, Telegram Desktop կոդի գեներատորում կա բացառությունների ցանկ այն տեսակների համար, որոնցում հաշվարկված CRC32-ը չի համապատասխանում գծապատկերում նշվածի հետ:

Վասիլի, [18.12/22 49:XNUMX] և այստեղ ես կմտածեի, թե արդյոք նման TL անհրաժեշտ է.
եթե ես ուզենայի խառնվել այլընտրանքային իրականացումներին, ես կսկսեի ներդնել տողերի ընդմիջումներ, վերլուծողների կեսը կկոտրվի բազմակողմ սահմանումների վրա
tdesktop, սակայն, նույնպես

Հիշեք միակողմանի կետը, մենք կանդրադառնանք մի փոքր ուշ:

Լավ, telegram-cli-ն ոչ պաշտոնական է, Telegram Desktop-ը պաշտոնական է, իսկ մյուսների մասին ի՞նչ կասեք: Ո՞վ գիտի... Android-ի հաճախորդի կոդում ընդհանրապես չկար սխեմաների վերլուծիչ (ինչը հարցեր է առաջացնում բաց կոդով, բայց սա երկրորդ մասի համար է), բայց կային մի քանի այլ զվարճալի կոդեր, բայց ավելին դրանց մասին՝ ստորև բերված ենթաբաժինը:

Ի՞նչ այլ հարցեր է առաջացնում սերիալացումը գործնականում: Օրինակ, նրանք շատ բան արեցին, իհարկե, բիտ դաշտերով և պայմանական դաշտերով.

Վասիլի. flags.0? true
նշանակում է, որ դաշտը ներկա է և հավասար է ճշմարիտ, եթե դրոշը դրված է

Վասիլի. flags.1? int
նշանակում է, որ դաշտն առկա է և պետք է ապասերիալիզացվի

Վասիլի. Էշ, մի անհանգստացիր, թե ինչ ես անում:
Վասիլի. Փաստաթղթում ինչ-որ տեղ նշվում է, որ ճիշտը զրոյական երկարության տեսակ է, բայց անհնար է որևէ բան հավաքել նրանց փաստաթղթից:
Վասիլի. Բաց կոդով իրականացումներում դա նույնպես այդպես չէ, բայց կան մի քանի հենակներ և հենարաններ:

Ինչ վերաբերում է Հեռուստամարաթոնին: Առաջ նայելով MTProto-ի թեմային, օրինակ՝ փաստաթղթերում կան այդպիսի կտորներ, բայց նշանը. % այն նկարագրվում է միայն որպես «տվյալ մերկ տիպին համապատասխան», այսինքն. Ստորև բերված օրինակներում կա կամ սխալ կա, կամ ինչ-որ բան չփաստաթղթավորված.

Վասիլի, [22.06.18 18:38] Մեկ տեղում.

msg_container#73f1f8dc messages:vector message = MessageContainer;

Այլ կերպ՝

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

Եվ սրանք երկու մեծ տարբերություններ են, իրական կյանքում ինչ-որ մերկ վեկտոր է գալիս

Ես չեմ տեսել բաց վեկտորի սահմանում և չեմ հանդիպել

Հեռուստամարաթոնում վերլուծությունը գրվում է ձեռքով

Նրա դիագրամում մեկնաբանվում է սահմանումը msg_container

Կրկին հարցը մնում է տոկոսի մասին։ Այն նկարագրված չէ։

Վադիմ Գոնչարով, [22.06.18 19:22] իսկ tdesktop-ում.

Վասիլի, [22.06.18 19:23] Բայց սովորական շարժիչների նրանց TL վերլուծիչը, ամենայն հավանականությամբ, սա էլ չի ուտի.

// parsed manually

TL-ը գեղեցիկ աբստրակցիա է, ոչ ոք այն ամբողջությամբ չի իրականացնում

Իսկ %–ը սխեմայի իրենց տարբերակում չկա

Բայց այստեղ փաստաթղթավորումը հակասում է ինքն իրեն, այնպես որ idk

Այն հայտնաբերվել է քերականության մեջ, նրանք կարող էին պարզապես մոռանալ նկարագրել իմաստաբանությունը

Դուք փաստաթուղթը տեսել եք TL-ով, առանց կես լիտրի չեք կարող հասկանալ

«Դե, ասենք,- կասի մեկ այլ ընթերցող,- դու քննադատում ես ինչ-որ բան, ուրեմն ցույց տուր ինձ, թե ինչպես պետք է դա անել»:

Վասիլին պատասխանում է. «Ինչ վերաբերում է վերլուծողին, ես սիրում եմ նման բաներ

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

ինչ-որ կերպ ավելի լավ է, քան

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

կամ

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

սա ՈՂՋ լեքսերն է.

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

դրանք. ավելի պարզ, մեղմ ասած»:

Ընդհանուր առմամբ, արդյունքում, TL-ի փաստացի օգտագործված ենթաբազմության վերլուծիչը և կոդերի գեներատորը տեղավորվում են մոտավորապես 100 քերականական տողերի և գեներատորի ~300 տողերի մեջ (հաշվելով բոլորը print-ի ստեղծած ծածկագիրը), ներառյալ տիպի տեղեկատվական բլիթները՝ յուրաքանչյուր դասում ներդնելու համար: Յուրաքանչյուր պոլիմորֆ տեսակ վերածվում է դատարկ վերացական բազային դասի, և կոնստրուկտորները ժառանգում են դրանից և ունեն սերիալացման և ապասերիալացման մեթոդներ։

Տիպի լեզվում տիպերի բացակայություն

Ուժեղ մեքենագրելը լավ բան է, չէ՞: Չէ, սա հոլիվար չէ (չնայած ես նախընտրում եմ դինամիկ լեզուներ), այլ պոստուլատ՝ TL-ի շրջանակներում։ Դրա հիման վրա լեզուն մեզ համար պետք է ապահովի բոլոր տեսակի ստուգումներ։ Լավ, լավ, գուցե ոչ ինքը, այլ իրականացումը, բայց գոնե պետք է նկարագրի դրանք։ Իսկ ինչպիսի՞ հնարավորություններ ենք մենք ուզում։

Առաջին հերթին՝ սահմանափակումներ. Այստեղ մենք տեսնում ենք ֆայլերի վերբեռնման փաստաթղթերում.

Ֆայլի երկուական բովանդակությունն այնուհետև բաժանվում է մասերի: Բոլոր մասերը պետք է ունենան նույն չափը ( մասի չափ ) և պետք է պահպանվեն հետևյալ պայմանները.

  • part_size % 1024 = 0 (բաժանվում է 1 ԿԲ-ի)
  • 524288 % part_size = 0 (512 ԿԲ պետք է հավասարապես բաժանվի մասի չափով)

Վերջին մասը պարտադիր չէ, որ բավարարի այս պայմանները, պայմանով, որ դրա չափը փոքր է part_size-ից:

Յուրաքանչյուր մաս պետք է ունենա հերթական համարը, file_part0-ից մինչև 2,999 արժեքով:

Ֆայլը բաժանվելուց հետո դուք պետք է ընտրեք այն սերվերում պահելու եղանակ: Օգտագործեք upload.saveBigFilePart այն դեպքում, երբ ֆայլի ամբողջական չափը 10 ՄԲ-ից ավելի է և upload.saveFilePart ավելի փոքր ֆայլերի համար:
[…] կարող է վերադարձվել հետևյալ տվյալների մուտքագրման սխալներից մեկը.

  • FILE_PARTS_INVALID — Անվավեր քանակությամբ մասեր: Արժեքը միջև չէ 1..3000

Սրանից որևէ մեկը կա՞ դիագրամում: Արդյո՞ք սա ինչ-որ կերպ արտահայտելի է TL-ի միջոցով: Ոչ Բայց կներեք ինձ, նույնիսկ պապիկի Turbo Pascal-ը կարողացավ նկարագրել նշված տեսակները միջակայքերը. Եվ նա գիտեր ևս մեկ բան, որն այժմ ավելի հայտնի է որպես enum - մի տեսակ, որը բաղկացած է ֆիքսված (փոքր) թվով արժեքների թվարկումից: C - թվային լեզուներով, նշեք, որ մինչ այժմ մենք խոսել ենք միայն տեսակների մասին թվեր. Բայց կան նաև զանգվածներ, տողեր... օրինակ, լավ կլինի նկարագրել, որ այս տողը կարող է միայն հեռախոսահամար պարունակել, չէ՞։

Սրանցից ոչ մեկը TL-ում չկա: Բայց կա, օրինակ, JSON Schema-ում: Եվ եթե մեկ ուրիշը կարող է վիճել 512 ԿԲ-ի բաժանելիության մասին, որ դա դեռ պետք է ստուգվի կոդով, ապա համոզվեք, որ հաճախորդը պարզապես Ես չէի կարող ուղարկել շարքից դուրս 1..3000 (իսկ համապատասխան սխալը չէր կարող առաջանալ) հնարավոր կլիներ, չէ՞։

Ի դեպ, սխալների և վերադարձվող արժեքների մասին։ Նույնիսկ նրանք, ովքեր աշխատել են TL-ով, պղտորում են իրենց աչքերը, դա մեզ անմիջապես չհասկացավ ամեն մեկը TL ֆունկցիան իրականում կարող է վերադարձնել ոչ միայն նկարագրված վերադարձի տեսակը, այլև սխալ: Բայց դա ոչ մի կերպ չի կարելի եզրակացնել՝ օգտագործելով TL-ն: Իհարկե, դա արդեն պարզ է, և գործնականում ոչ մի բանի կարիք չկա (չնայած իրականում RPC-ն կարող է իրականացվել տարբեր ձևերով, մենք կանդրադառնանք դրան ավելի ուշ), բայց ինչ վերաբերում է վերացական տեսակների մաթեմատիկայի հասկացությունների մաքրությանը: երկնային աշխարհի՞ց... Ես վերցրեցի քաշքշիկը, ուրեմն համընկնի դրան:

Եվ վերջապես, ի՞նչ կասեք ընթեռնելիության մասին։ Դե, այնտեղ, ընդհանուր առմամբ, ես կցանկանայի նկարագրություն դա ճիշտ է սխեմայում (JSON սխեմայում, էլի, այդպես է), բայց եթե դուք արդեն լարվել եք դրա հետ, ապա ի՞նչ կասեք գործնական կողմի մասին. թարմացումների ժամանակ տարբերություններին նայելը առնվազն չնչին է: Ինքներդ տեսեք իրական օրինակներ:

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

կամ

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

Դա կախված է բոլորից, բայց GitHub-ը, օրինակ, հրաժարվում է ընդգծել փոփոխությունները նման երկար գծերի ներսում: «Գտիր 10 տարբերություն» խաղը, և ուղեղը անմիջապես տեսնում է, որ երկու օրինակների սկիզբն ու ավարտը նույնն են, պետք է հոգնեցուցիչ կերպով կարդալ ինչ-որ տեղ մեջտեղում... Իմ կարծիքով, սա միայն տեսականորեն չէ, բայց զուտ տեսողական կեղտոտ և անփույթ.

Ի դեպ, տեսության մաքրության մասին. Ինչու՞ մեզ պետք են բիտ դաշտեր: Չի՞ թվում, որ նրանք հոտը վա՞տ է տիպերի տեսության տեսակետից։ Բացատրությունը կարելի է տեսնել գծապատկերի ավելի վաղ տարբերակներում: Սկզբում, այո, այդպես էր, ամեն փռշտոցի համար նոր տեսակ էր ստեղծվում։ Այս սկզբնական տարրերը դեռ գոյություն ունեն այս ձևով, օրինակ.

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;

Բայց հիմա պատկերացրեք, եթե ձեր կառուցվածքում ունեք 5 ընտրովի դաշտ, ապա ձեզ անհրաժեշտ կլինի 32 տեսակ բոլոր հնարավոր տարբերակների համար: Կոմբինատոր պայթյուն. Այսպիսով, TL տեսության բյուրեղյա մաքրությունը ևս մեկ անգամ փշրվեց սերիականացման դաժան իրականության չուգուն էշի դեմ:

Բացի այդ, որոշ տեղերում այդ տղաներն իրենք են խախտում իրենց տիպաբանությունը։ Օրինակ, MTProto-ում (հաջորդ գլուխ) պատասխանը կարող է սեղմվել Gzip-ով, ամեն ինչ կարգին է, բացառությամբ, որ շերտերն ու միացումը խախտված են: Կրկին, ոչ թե RpcResult-ն է հնձվել, այլ դրա բովանդակությունը: Լավ, ինչու՞ դա անել... Ես ստիպված էի կտրել հենակը, որպեսզի սեղմումը ամեն տեղ աշխատի։

Կամ մեկ այլ օրինակ, մենք մի անգամ սխալ ենք հայտնաբերել՝ այն ուղարկվել է InputPeerUser փոխարենը InputUser. Կամ հակառակը։ Բայց դա աշխատեց: Այսինքն՝ սերվերը թքած ունի տեսակի վրա։ Ինչպե՞ս կարող է սա լինել: Պատասխանը մեզ կարող է տրվել 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);

Այսինքն՝ այստեղ սերիականացում է կատարվում ՁԵՌՔՈՎ, չի ստեղծվել կոդը: Միգուցե սերվերը նման կերպ է ներդրված... Սկզբունքորեն սա կաշխատի, եթե մեկ անգամ արվի, բայց ինչպե՞ս կարելի է այն հետագայում սպասարկել թարմացումների ժամանակ: Արդյո՞ք սրա համար է հորինվել սխեման: Եվ ահա մենք անցնում ենք հաջորդ հարցին.

Տարբերակում. Շերտեր

Թե ինչու են սխեմատիկ տարբերակները կոչվում շերտեր, կարելի է ենթադրել միայն հրապարակված սխեմաների պատմության հիման վրա: Ըստ երևույթին, սկզբում հեղինակները կարծում էին, որ հիմնական բաները կարելի է անել՝ օգտագործելով անփոփոխ սխեմա, և միայն այն դեպքում, երբ անհրաժեշտ է, կոնկրետ խնդրանքների համար նշել, որ դրանք արվում են այլ տարբերակի միջոցով: Սկզբունքորեն, նույնիսկ լավ գաղափար է, և նորը կլինի, կարծես, «խառը», շերտավորված հինի վրա: Բայց տեսնենք, թե ինչպես է դա արվել։ Ճիշտ է, ես չկարողացա հենց սկզբից նայել դրան, դա ծիծաղելի է, բայց բազային շերտի դիագրամը պարզապես գոյություն չունի: Շերտերը սկսվել են 2-ով: Փաստաթղթերը մեզ ասում են TL հատուկ հատկանիշի մասին.

Եթե ​​հաճախորդն աջակցում է 2-րդ շերտին, ապա պետք է օգտագործվի հետևյալ կոնստրուկտորը.

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

Գործնականում դա նշանակում է, որ API-ի յուրաքանչյուր զանգից առաջ արժեքով int է 0x289dd1f6 պետք է ավելացվի մեթոդի համարից առաջ:

Նորմալ է հնչում։ Բայց ի՞նչ եղավ հետո։ Հետո հայտնվեց

invokeWithLayer3#b7475268 query:!X = X;

Այսպիսով, ինչ է հաջորդը: Ինչպես կարող եք կռահել,

invokeWithLayer4#dea0d430 query:!X = X;

Զվարճալի՞ Չէ, դեռ վաղ է ծիծաղելու համար, մտածիր այդ մասին յուրաքանչյուրը Մեկ այլ շերտի խնդրանքը պետք է փաթաթվի նման հատուկ տեսակի մեջ. եթե դրանք բոլորը տարբեր են ձեզ համար, այլ կերպ ինչպե՞ս կարող եք տարբերակել դրանք: Իսկ առջևում ընդամենը 4 բայթ ավելացնելը բավականին արդյունավետ մեթոդ է: Այսպիսով,

invokeWithLayer5#417a57ae query:!X = X;

Բայց ակնհայտ է, որ որոշ ժամանակ անց սա դառնալու է ինչ-որ բախանալիա։ Եվ լուծումը եկավ.

Թարմացում. սկսած 9-րդ շերտից, օգնական մեթոդներից invokeWithLayerN կարելի է օգտագործել միայն հետ միասին initConnection

Ուռա՜ 9 տարբերակներից հետո մենք վերջապես հասանք նրան, ինչ արվում էր ինտերնետ արձանագրություններում դեռ 80-ականներին՝ միացման սկզբում մեկ անգամ համաձայնեցնելով տարբերակը:

Այսպիսով, ինչ է հաջորդը..

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

Բայց հիմա դուք դեռ կարող եք ծիծաղել: Եվս 9 շերտից հետո միայն վերջապես ավելացվեց տարբերակի համարով ունիվերսալ կոնստրուկտոր, որը պետք է զանգել միայն մեկ անգամ կապի սկզբում, իսկ շերտերի իմաստը կարծես անհետացել էր, հիմա դա ուղղակի պայմանական տարբերակ է, ինչպես. ամենուր ուրիշ տեղ: Խնդիրը լուծված է.

Ճիշտ?..

Վասիլի, [16.07.18 14:01] Նույնիսկ ուրբաթ օրը ես մտածում էի.
Հեռուստասերվերը իրադարձություններ է ուղարկում առանց հարցման: Հարցումները պետք է փաթաթված լինեն InvokeWithLayer-ում: Սերվերը չի փաթաթում թարմացումները, պատասխանների և թարմացումների փաթեթավորման կառուցվածք չկա:

Նրանք. հաճախորդը չի կարող նշել այն շերտը, որտեղ նա ցանկանում է թարմացումներ

Վադիմ Գոնչարով, [16.07.18 14:02] մի՞թե InvokeWithLayer-ը սկզբունքորեն հենակ չէ:

Վասիլի, [16.07.18 14:02] Սա միակ ճանապարհն է

Վադիմ Գոնչարով, [16.07.18 14:02], ինչը, ըստ էության, պետք է նշանակի նիստի սկզբում շերտի շուրջ համաձայնություն.

Ի դեպ, հետևում է, որ հաճախորդի իջեցում չի տրամադրվում

Թարմացումները, այսինքն. տիպ Updates սխեմայում սա այն է, ինչ սերվերը ուղարկում է հաճախորդին ոչ թե ի պատասխան API հարցման, այլ ինքնուրույն, երբ տեղի է ունենում իրադարձություն: Սա բարդ թեմա է, որը կքննարկվի մեկ այլ գրառման մեջ, բայց առայժմ կարևոր է իմանալ, որ սերվերը պահպանում է Թարմացումները նույնիսկ այն ժամանակ, երբ հաճախորդը անցանց է:

Այսպիսով, եթե դուք հրաժարվում եք փաթաթել յուրաքանչյուր փաթեթ՝ իր տարբերակը նշելու համար, դա տրամաբանորեն հանգեցնում է հետևյալ հնարավոր խնդիրների.

  • սերվերը թարմացումներ է ուղարկում հաճախորդին նույնիսկ նախքան հաճախորդը կտեղեկացնի, թե որ տարբերակն է աջակցում
  • ինչ պետք է անեմ հաճախորդին թարմացնելուց հետո:
  • ով երաշխիքներոր սերվերի կարծիքը շերտի համարի մասին գործընթացի ընթացքում չի՞ փոխվի։

Ի՞նչ եք կարծում, սա զուտ տեսական շահարկում է, և գործնականում դա չի կարող լինել, քանի որ սերվերը ճիշտ է գրված (համենայնդեպս, այն լավ փորձարկված է): Հա՜ Անկախ նրանից, թե ինչպես է դա!

Սա հենց այն է, ինչին մենք բախվեցինք օգոստոսին: Օգոստոսի 14-ին հաղորդագրություններ եղան, որ ինչ-որ բան թարմացվում է Telegram-ի սերվերներում... իսկ հետո գրանցամատյաններում.

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.

և այնուհետև մի քանի մեգաբայթ stack հետքեր (դե, միևնույն ժամանակ հատումները ֆիքսվել են): Ի վերջո, եթե ինչ-որ բան չի ճանաչվում ձեր TL-ում, այն ստորագրությամբ երկուական է, ավելի ուշ ԲՈԼՈՐ գնում է, ապակոդավորումն անհնարին կդառնա։ Ի՞նչ պետք է անեք նման իրավիճակում:

Դե, առաջին բանը, որ գալիս է որևէ մեկի մտքին, դա անջատելն է և նորից փորձել: Չօգնեց. Մենք google-ում ենք CRC32 - պարզվեց, որ դրանք օբյեկտներ են 73-րդ սխեմայից, չնայած մենք աշխատել ենք 82-ի վրա: Մենք ուշադիր նայում ենք տեղեկամատյաններին. կան նույնացուցիչներ երկու տարբեր սխեմաներից:

Գուցե խնդիրը զուտ մեր ոչ պաշտոնական հաճախորդի՞ մեջ է։ Ոչ, մենք գործարկում ենք Telegram Desktop 1.2.17-ը (տարբերակը տրամադրվում է Linux-ի մի շարք բաշխումներում), այն գրում է Բացառության մատյանում.

Telegram-ի արարողակարգային և կազմակերպչական մոտեցումների քննադատություն. Մաս 1, տեխնիկական. հաճախորդը զրոյից գրելու փորձ - TL, MT

Google-ը ցույց է տվել, որ նմանատիպ խնդիր արդեն եղել է ոչ պաշտոնական հաճախորդներից մեկի հետ, բայց հետո տարբերակների համարները և, համապատասխանաբար, ենթադրությունները տարբեր են եղել...

Ուրեմն ի՞նչ պետք է անենք։ Ես ու Վասիլին բաժանվեցինք. նա փորձեց թարմացնել շղթան 91-ի, ես որոշեցի սպասել մի քանի օր և փորձել 73-ը: Երկու մեթոդներն էլ աշխատեցին, բայց քանի որ դրանք էմպիրիկ են, հասկանալի չէ, թե քանի տարբերակ է անհրաժեշտ վեր կամ վար: ցատկել, կամ որքան ժամանակ է պետք սպասել:

Հետագայում ես կարողացա վերարտադրել իրավիճակը. մենք գործարկում ենք հաճախորդը, անջատում ենք այն, նորից կոմպիլացնում ենք սխեման մեկ այլ շերտի վրա, վերագործարկում ենք, նորից բռնում խնդիրը, վերադառնում ենք նախկինին. օպ, առանց միացումների միացման և հաճախորդը վերագործարկվում է մի քանի րոպե կօգնի: Դուք կստանաք տվյալների կառուցվածքների խառնուրդ տարբեր շերտերից:

Բացատրություն? Ինչպես կարող եք կռահել տարբեր անուղղակի ախտանիշներից, սերվերը բաղկացած է տարբեր տեսակի բազմաթիվ գործընթացներից տարբեր մեքենաների վրա: Ամենայն հավանականությամբ, սերվերը, որը պատասխանատու է «բուֆերացման» համար, հերթի մեջ է դրել այն, ինչ տվել են իր վերադասները, և նրանք տվել են այն սխեմայով, որը գործում էր գեներացման պահին: Եվ քանի դեռ այս հերթը չի «փտել», ոչինչ անել հնարավոր չէր։

Միգուցե... բայց սա սարսափելի հենակ է?!... Ոչ, նախքան խելահեղ գաղափարների մասին մտածելը, եկեք նայենք պաշտոնական հաճախորդների ծածկագրին։ Android-ի տարբերակում մենք չենք գտնում որևէ TL վերլուծիչ, բայց մենք գտնում ենք հսկայական ֆայլ (GitHub-ը հրաժարվում է դիպչել դրան) (ապ) սերիականացումով: Ահա կոդի հատվածները.

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;

կամ

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

Հմմ... վայրենի տեսք ունի: Բայց, հավանաբար, սա գեներացվել է կոդ, ապա լա՞վ: Բայց այն, իհարկե, աջակցում է բոլոր տարբերակները: Ճիշտ է, անհասկանալի է, թե ինչու են ամեն ինչ խառնվում իրար, գաղտնի զրույցները և ամեն տեսակ _old7 ինչ-որ կերպ կարծես մեքենաների սերունդ չես... Այնուամենայնիվ, ամենից շատ ես հիացած էի

TL_message_layer104
TL_message_layer104_2
TL_message_layer104_3

Տղերք, չե՞ք կարող որոշել, թե ինչ կա մեկ շերտի ներսում: Լավ, լավ, ասենք «երկու»-ը սխալմամբ ազատ են արձակվել, լավ, պատահում է, բայց ԵՐԵՔ?.. Միանգամից, նորից նույն փոցխը: Սա ի՞նչ պոռնոգրաֆիա է, կներեք...

Telegram Desktop-ի սկզբնաղբյուրում, ի դեպ, նման բան է պատահում. եթե այդպես է, ապա սխեմային մի քանի անընդմեջ պարտավորություններ չեն փոխում դրա շերտի համարը, այլ ինչ-որ բան շտկում: Այն պայմաններում, երբ չկա սխեմայի համար տվյալների պաշտոնական աղբյուր, որտեղի՞ց կարելի է դրանք ձեռք բերել, բացառությամբ պաշտոնական հաճախորդի սկզբնաղբյուրի: Եվ եթե այն վերցնեք այնտեղից, ապա չեք կարող վստահ լինել, որ սխեման լիովին ճիշտ է, քանի դեռ չեք փորձարկել բոլոր մեթոդները:

Ինչպե՞ս կարելի է դա նույնիսկ փորձարկել: Հուսով եմ, որ միավորի, ֆունկցիոնալ և այլ թեստերի երկրպագուները կկիսվեն մեկնաբանություններում:

Լավ, եկեք նայենք կոդի մեկ այլ մասի.

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;

Այս «ձեռքով ստեղծված» մեկնաբանությունը հուշում է, որ այս ֆայլի միայն մի մասն է գրվել ձեռքով (պատկերացնու՞մ եք սպասարկման ամբողջ մղձավանջը), իսկ մնացածը ստեղծվել է մեքենայի միջոցով: Սակայն, հետո մեկ այլ հարց է ծագում՝ որ աղբյուրները հասանելի են ոչ ամբողջությամբ (a la GPL blobs in Linux kernel-ում), բայց սա արդեն երկրորդ մասի թեմա է։

Բայց բավական է։ Եկեք անցնենք արձանագրությանը, որի վերևում աշխատում է այս ամբողջ սերիականացումը:

MT Պրոտո

Այսպիսով, եկեք բացենք ընդհանուր նկարագրությունը и արձանագրության մանրամասն նկարագրությունը և առաջին բանը, որին մենք սայթաքում ենք, տերմինաբանությունն է: Եվ ամեն ինչի առատությամբ: Ընդհանուր առմամբ, սա կարծես թե Telegram-ի սեփականատիրական հատկությունն է՝ տարբեր վայրերում իրերը տարբեր կերպ կոչել, կամ մեկ բառով տարբեր բաներ, կամ հակառակը (օրինակ՝ բարձր մակարդակի API-ում, եթե տեսնեք կպչուն փաթեթ, դա այդպես չէ։ ինչ եք մտածել):

Օրինակ, «հաղորդագրությունը» և «նիստը» այստեղ այլ բան են նշանակում, քան սովորական Telegram հաճախորդի միջերեսում: Դե, հաղորդագրության հետ ամեն ինչ պարզ է, այն կարելի է մեկնաբանել OOP տերմիններով, կամ պարզապես անվանել «փաթեթ» բառը. սա ցածր, տրանսպորտային մակարդակ է, նույն հաղորդագրությունները չկան, ինչ միջերեսում, կան բազմաթիվ սպասարկման հաղորդագրություններ: . Բայց նիստը... բայց առաջին հերթին:

տրանսպորտային շերտ

Առաջինը տրանսպորտն է։ Նրանք մեզ կպատմեն 5 տարբերակի մասին.

  • TCP
  • Ոստայնագրիչ
  • Websocket HTTPS-ով
  • HTTP
  • HTTPS

Վասիլի, [15.06.18 15:04] Կա նաև UDP տրանսպորտ, բայց փաստաթղթավորված չէ.

Եվ TCP երեք տարբերակով

Առաջինը նման է UDP-ին TCP-ի միջոցով, յուրաքանչյուր փաթեթ ներառում է հաջորդական համար և crc
Ինչու է սայլի վրա փաստաթղթեր կարդալն այդքան ցավոտ:

Դե, հիմա կա TCP արդեն 4 տարբերակով:

  • համառոտ
  • Միջանկյալ
  • Լիցքավորված միջանկյալ
  • Լրիվ

Դե, լավ, MTProxy-ի համար padded intermediate, սա հետագայում ավելացվեց հայտնի իրադարձությունների պատճառով: Բայց ինչու՞ ևս երկու տարբերակ (ընդհանուր երեքը), երբ կարող էիր հաղթահարել մեկով: Բոլոր չորսն էլ էապես տարբերվում են միայն հիմնական MTProto-ի երկարությունը և բեռնվածությունը սահմանելու հարցում, ինչը կքննարկվի հետագա.

  • Համառոտում դա 1 կամ 4 բայթ է, բայց ոչ 0xef, հետո մարմինը
  • Intermediate-ում սա 4 բայթ երկարություն է և դաշտ, և առաջին անգամ հաճախորդը պետք է ուղարկի 0xeeeeeeee նշելու, որ այն Միջանկյալ է
  • Ամբողջովին ամենակախություն է առաջացնում ցանցագործի տեսանկյունից՝ երկարությունը, հաջորդականության համարը և ՈՉ ՄԵԿԸ, որը հիմնականում MTProto է, մարմին, CRC32: Այո, այս ամենը TCP-ի վերևում է: Ինչն ապահովում է մեզ հուսալի փոխադրում հաջորդական բայթային հոսքի տեսքով, ոչ մի հաջորդականություն պետք չէ, հատկապես ստուգիչ գումարներ: Լավ, հիմա ինչ-որ մեկը կառարկի ինձ, որ TCP-ն ունի 16-բիթանոց checksum, ուստի տվյալների կոռուպցիան տեղի է ունենում: Հիանալի է, բայց մենք իրականում ունենք 16 բայթից երկար հեշերով գաղտնագրային արձանագրություն, այս բոլոր սխալները և նույնիսկ ավելին կբռնվեն ավելի բարձր մակարդակի SHA-ի անհամապատասխանության պատճառով: CRC32-ում այս ամենի վրա ոչ մի կետ չկա:

Համեմատենք «Abridged»-ը, որի դեպքում հնարավոր է մեկ բայթ երկարություն, «Intermediate»-ի հետ, որը հիմնավորում է «Այն դեպքում, երբ անհրաժեշտ է տվյալների հավասարեցում», ինչը բավականին անհեթեթություն է: Ի՞նչ է, ենթադրվում է, որ Telegram-ի ծրագրավորողներն այնքան անգործունակ են, որ չեն կարող տվյալներ կարդալ վարդակից հավասարեցված բուֆերի մեջ: Դուք դեռ պետք է դա անեք, քանի որ կարդալը կարող է ձեզ վերադարձնել ցանկացած քանակությամբ բայթ (և կան նաև պրոքսի սերվերներ, օրինակ...): Կամ, մյուս կողմից, ինչու՞ արգելափակել «Abridged»-ը, եթե մենք դեռևս կունենանք 4 բայթի վրա մեծ լիցք. խնայեք 16 բայթ: երբեմն ?

Տպավորություն է ստեղծվում, որ Նիկոլայ Դուրովը իսկապես սիրում է նորովի հայտնագործել անիվները, ներառյալ ցանցային արձանագրությունները, առանց իրական գործնական անհրաժեշտության:

Տրանսպորտի այլ տարբերակներ, ներառյալ. Վեբ և MTProxy, հիմա չենք դիտարկի, գուցե մեկ այլ գրառման մեջ, եթե խնդրանք լինի։ Այս նույն MTProxy-ի մասին, եկեք միայն հիմա հիշենք, որ 2018-ին թողարկվելուց անմիջապես հետո պրովայդերները արագ սովորեցին արգելափակել այն, որը նախատեսված էր շրջանցման արգելափակումՄիջոցով փաթեթի չափը! Եվ նաև այն փաստը, որ C-ով գրված MTProxy սերվերը (կրկին Waltman-ի կողմից) չափազանց կապված էր Linux-ի առանձնահատկությունների հետ, թեև դա ընդհանրապես պարտադիր չէր (Ֆիլ Կուլինը կհաստատի), և որ նմանատիպ սերվերը կա՛մ Go-ում, կա՛մ Node.js-ում: տեղավորվում է հարյուրից պակաս տողերում:

Բայց այս մարդկանց տեխնիկական գրագիտության մասին եզրակացություններ կանենք բաժնի վերջում՝ այլ հարցեր դիտարկելուց հետո։ Առայժմ անցնենք OSI շերտի 5-ին, նիստ, որի վրա տեղադրեցին MTProto նիստը:

Բանալիներ, հաղորդագրություններ, նիստեր, Diffie-Hellman

Նրանք այն տեղադրեցին այնտեղ ոչ բոլորովին ճիշտ... Սեսիան այն նույն նիստը չէ, որը տեսանելի է ինտերֆեյսում Active sessions-ի ներքո: Բայց կարգով.

Telegram-ի արարողակարգային և կազմակերպչական մոտեցումների քննադատություն. Մաս 1, տեխնիկական. հաճախորդը զրոյից գրելու փորձ - TL, MT

Այսպիսով, մենք ստացանք տրանսպորտային շերտից հայտնի երկարությամբ բայթ տող: Սա կա՛մ կոդավորված հաղորդագրություն է, կա՛մ պարզ տեքստ, եթե մենք դեռ գտնվում ենք հիմնական համաձայնագրի փուլում և իրականում դա անում ենք: «Բանալին» կոչվող հասկացություններից ո՞րի մասին է խոսքը: Եկեք պարզաբանենք այս հարցը հենց Telegram-ի թիմի համար (ներողություն եմ խնդրում առավոտյան ժամը 4-ին հոգնած ուղեղով իմ սեփական փաստաթղթերը անգլերենից թարգմանելու համար, ավելի հեշտ էր որոշ արտահայտություններ թողնել այնպես, ինչպես կան).

Կան երկու սուբյեկտներ, որոնք կոչվում են Նիստ - մեկը պաշտոնական հաճախորդների միջերեսում «ընթացիկ նիստերի» ներքո, որտեղ յուրաքանչյուր նստաշրջան համապատասխանում է մի ամբողջ սարքի / ՕՀ-ին:
Երկրորդ - MTProto նիստ, որն իր մեջ ունի հաղորդագրության հաջորդական համարը (ցածր մակարդակի իմաստով), և որը կարող է տևել տարբեր TCP կապերի միջև: Մի քանի MTProto նիստեր կարող են տեղադրվել միաժամանակ, օրինակ՝ ֆայլերի ներբեռնումն արագացնելու համար:

Այս երկուսի միջև նիստերը հայեցակարգ կա լիազորություն. Այլասերվածի դեպքում կարելի է ասել UI նիստ նույնն է, ինչ լիազորություն, բայց ավաղ, ամեն ինչ բարդ է։ Եկեք նայենք.

  • Նոր սարքի օգտագործողը նախ գեներացնում է auth_key և կապում է այն հաշվի հետ, օրինակ՝ SMS-ի միջոցով, ահա թե ինչու լիազորություն
  • Դա տեղի ունեցավ առաջինի ներսում MTProto նիստ, որն ունի session_id քո ներսում:
  • Այս քայլում համադրությունը լիազորություն и session_id կարելի էր անվանել օրինակ - այս բառը հայտնվում է որոշ հաճախորդների փաստաթղթերում և ծածկագրում
  • Այնուհետև հաճախորդը կարող է բացել մոտ MTProto նիստեր նույնի տակ auth_key - նույն DC-ին:
  • Այնուհետև մի օր հաճախորդը պետք է պահանջի ֆայլը մեկ այլ DC - և այս DC-ի համար կստեղծվի նորը auth_key !
  • Համակարգին տեղեկացնել, որ ոչ թե նոր օգտատեր է գրանցվում, այլ նույնը լիազորություն (UI նիստ), հաճախորդը օգտագործում է API զանգեր auth.exportAuthorization տանը DC auth.importAuthorization նոր DC-ում:
  • Ամեն ինչ նույնն է, մի քանիսը կարող են բաց լինել MTProto նիստեր (յուրաքանչյուրն իր հետ session_id) այս նոր DC-ին, տակ նրա auth_key.
  • Վերջապես, հաճախորդը կարող է ցանկանալ Perfect Forward Secrecy: Ամեն auth_key էր մշտական բանալին - մեկ DC - և հաճախորդը կարող է զանգահարել auth.bindTempAuthKey օգտագործման համար ժամանակավոր auth_key - և նորից՝ միայն մեկը temp_auth_key մեկ DC-ի համար, ընդհանուր բոլորի համար MTProto նիստեր այս DC-ին:

նկատել, որ աղ (և ապագա աղերը) նույնպես մեկն է auth_key դրանք. կիսվում է բոլորի միջև MTProto նիստեր նույն DC-ին:

Ի՞նչ է նշանակում «տարբեր TCP կապերի միջև»: Այսպիսով, սա նշանակում է նման մի բան Թույլտվության թխուկը վեբկայքում. այն պահպանում է (պահպանում է) բազմաթիվ TCP կապեր տվյալ սերվերի հետ, բայց մի օր այն վատանում է: Միայն ի տարբերություն HTTP-ի, MTProto-ում սեսիայի ընթացքում հաղորդագրությունները հաջորդաբար համարակալվում և հաստատվում են, եթե դրանք մտել են թունել, կապը խզվել է. TCP կապ.

Այնուամենայնիվ, վերը նշված տեղեկատվությունը ամփոփվում է երկար ամիսների հետաքննությունից հետո: Միևնույն ժամանակ, մենք մեր հաճախորդին զրոյի՞ց ենք իրականացնում: - Վերադառնանք սկզբին։

Այսպիսով, եկեք գեներացնենք auth_key մասին Diffie-Hellman-ի տարբերակները Telegram-ից. Փորձենք հասկանալ փաստաթղթերը...

Վասիլի, [19.06.18 20:05] data_with_hash := SHA1(տվյալներ) + տվյալներ + (ցանկացած պատահական բայթ); այնպես, որ երկարությունը հավասար է 255 բայթ;
կոդավորված_տվյալներ := RSA (տվյալներ_հեշ, սերվերի_հանրային_բանալին); 255 բայթ երկարությամբ թիվը (մեծ էնդիան) բարձրացվում է մինչև անհրաժեշտ հզորությունը անհրաժեշտ մոդուլի վրա, և արդյունքը պահվում է որպես 256 բայթ թիվ:

Նրանք ունեն որոշ դոպ ԴՀ

Առողջ մարդու DH-ի տեսք չունի
dx-ում երկու հանրային բանալի չկա

Դե, ի վերջո, սա կարգավորվեց, բայց մնացորդ մնաց՝ հաճախորդի կողմից աշխատանքի ապացույց է արվում, որ նա կարողացել է գործակցել թիվը: Պաշտպանության տեսակը DoS հարձակումներից: Իսկ RSA բանալին օգտագործվում է միայն մեկ անգամ մեկ ուղղությամբ, հիմնականում գաղտնագրման համար new_nonce. Բայց մինչ այս պարզ թվացող օպերացիան հաջողության կհասնի, ինչի՞ հետ պետք է բախվեք:

Վասիլի, [20.06.18/00/26 XNUMX:XNUMX] Ես դեռ չեմ հասել հավելվածի խնդրանքին

Ես այս հարցումն ուղարկել եմ DH-ին

Իսկ տրանսպորտային դոկում գրված է, որ այն կարող է պատասխանել 4 բայթ սխալի կոդով։ Այսքանը

Դե, նա ինձ ասաց -404, իսկ ինչ:

Ուստի ես ասացի նրան. «Բռնիր քո հիմարությունը, որը ծածկագրված է սերվերի ստեղնով, այսպիսի մատնահետքով, ես ուզում եմ DH», և նա պատասխանեց հիմար 404-ով:

Ի՞նչ կմտածեք այս սերվերի պատասխանի մասին: Ինչ անել? Հարցնող չկա (բայց դրա մասին ավելի շատ՝ երկրորդ մասում)։

Այստեղ ամբողջ հետաքրքրությունը կատարվում է նավամատույցի վրա

Ուրիշ անելիք չունեմ, ուղղակի երազում էի թվերը հետ ու առաջ փոխակերպել

Երկու 32 բիթանոց համարներ. Ես փաթեթավորեցի դրանք բոլորի նման

Բայց ոչ, այս երկուսը նախ պետք է ավելացնել տողին որպես BE

Վադիմ Գոնչարով, [20.06.18 15:49] իսկ սրա պատճառով 404.

Վասիլի, [20.06.18 15:49] ԱՅՈ։

Վադիմ Գոնչարով, [20.06.18 15:50] այնպես որ ես չեմ հասկանում, թե ինչ կարող է նա «չգտել».

Վասիլի, [20.06.18 15:50] մոտավորապես

Ես չկարողացա նման տարրալուծում գտնել պարզ գործոնների%)

Մենք նույնիսկ չենք կառավարել սխալների մասին հաղորդումը

Վասիլի, [20.06.18 20:18] Օ, կա նաև MD5: Արդեն երեք տարբեր հեշեր

Բանալին մատնահետքը հաշվարկվում է հետևյալ կերպ.

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

SHA1 և sha2

Այսպիսով, եկեք այն դնենք auth_key մենք ստացանք 2048 բիթ չափի, օգտագործելով Diffie-Hellman-ը: Ի՞նչ է հաջորդը: Հաջորդիվ մենք հայտնաբերում ենք, որ այս ստեղնի ստորին 1024 բիթերը ոչ մի կերպ չեն օգտագործվում... բայց եկեք այս մասին մտածենք առայժմ: Այս քայլում մենք ունենք ընդհանուր գաղտնիք սերվերի հետ: Ստեղծվել է TLS նիստի անալոգը, որը շատ թանկ ընթացակարգ է։ Բայց սերվերը դեռ ոչինչ չգիտի, թե ով ենք մենք: Դեռ ոչ, իրականում: թույլտվություն. Նրանք. եթե մտածում էիք «մուտքի գաղտնաբառի» առումով, ինչպես ժամանակին արել էիք ICQ-ում, կամ գոնե «login-key», ինչպես SSH-ում (օրինակ՝ ինչ-որ gitlab/github-ում): Մենք ստացանք անանուն մեկը: Ի՞նչ անել, եթե սերվերը մեզ ասի, որ «այս հեռախոսահամարները սպասարկվում են մեկ այլ DC-ի կողմից»: Կամ նույնիսկ «ձեր հեռախոսահամարն արգելված է»: Լավագույնը, որ մենք կարող ենք անել, բանալին պահելն է՝ հուսալով, որ այն օգտակար կլինի և մինչ այդ չի փչանա:

Ի դեպ, վերապահումով ենք «ընդունել»։ Օրինակ, մենք վստահո՞ւմ ենք սերվերին: Իսկ եթե դա կեղծ է: Կրիպտոգրաֆիկ ստուգումներ են անհրաժեշտ.

Վասիլի, [21.06.18 17:53] Բջջային հաճախորդներին առաջարկում են ստուգել 2 կբիթանոց համարը նախնականության համար%)

Բայց ամենևին էլ պարզ չէ, նաֆեյխոա

Վասիլի, [21.06.18 18:02] Փաստաթղթում նշված չէ, թե ինչ անել, եթե պարզվի, որ պարզ չէ.

Չասված. Տեսնենք, թե այս դեպքում ինչ է անում պաշտոնական Android հաճախորդը: Ա ահա թե ինչ (և այո, ամբողջ ֆայլը հետաքրքիր է) - ինչպես ասում են, ես ուղղակի սա կթողնեմ այստեղ.

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

Ոչ, իհարկե, դեռ կա մի քանի Կան թվերի պարզունակության թեստեր, բայց անձամբ ես մաթեմատիկայից արդեն բավարար գիտելիքներ չունեմ։

Լավ, մենք ստացել ենք հիմնական բանալին: Մուտք գործելու համար, այսինքն. ուղարկել հարցումներ, դուք պետք է կատարեք հետագա գաղտնագրում, օգտագործելով AES:

Հաղորդագրության ստեղնը սահմանվում է որպես հաղորդագրության մարմնի SHA128-ի 256 միջին բիթ (ներառյալ նստաշրջանը, հաղորդագրության ID-ն և այլն), ներառյալ լիցքավորման բայթերը, որոնք վերցված են 32 բայթով, որոնք վերցված են լիազորման բանալիից:

Վասիլի, [22.06.18 14:08] Միջին, բիծ, բիթ.

Ստացել է auth_key. Բոլորը. Նրանցից այն կողմ… փաստաթղթից պարզ չէ: Ազատորեն ուսումնասիրեք բաց կոդով կոդը:

Նկատի ունեցեք, որ MTProto 2.0-ը պահանջում է 12-ից մինչև 1024 բայթ լիցք, դեռևս պայմանով, որ ստացված հաղորդագրության երկարությունը բաժանվի 16 բայթի:

Այսպիսով, որքա՞ն լիցք պետք է ավելացնեք:

Եվ այո, կա նաև 404 սխալի դեպքում

Եթե ​​որևէ մեկը ուշադիր ուսումնասիրել է փաստաթղթերի դիագրամը և տեքստը, ապա նկատել է, որ այնտեղ MAC չկա: Եվ այդ AES-ն օգտագործվում է որոշակի IGE ռեժիմում, որն այլ տեղ չի օգտագործվում: Նրանք, իհարկե, գրում են այս մասին իրենց ՀՏՀ-ում... Այստեղ, օրինակ, հաղորդագրության բանալին ինքնին նաև վերծանված տվյալների SHA հեշն է, որն օգտագործվում է ամբողջականությունը ստուգելու համար, իսկ անհամապատասխանության դեպքում՝ ինչ-ինչ պատճառներով փաստաթղթերը: խորհուրդ է տալիս լուռ անտեսել դրանք (բայց ինչ վերաբերում է անվտանգությանը, իսկ եթե նրանք կոտրեն մեզ):

Ես կրիպտոգրաֆիկ չեմ, երևի տեսական տեսակետից այս ռեժիմում վատ բան չկա այս դեպքում։ Բայց ես կարող եմ հստակ նշել գործնական խնդիր՝ որպես օրինակ օգտագործելով Telegram Desktop-ը։ Այն գաղտնագրում է տեղական քեշը (այս բոլոր D877F783D5D3EF8C) նույն կերպ, ինչպես հաղորդագրությունները MTProto-ում (միայն այս դեպքում 1.0 տարբերակ), այսինքն. սկզբում հաղորդագրության ստեղնը, այնուհետև տվյալները (և ինչ-որ տեղ մի կողմ հիմնական մեծը auth_key 256 բայթ, առանց որի msg_key անօգուտ): Այսպիսով, խնդիրը նկատելի է դառնում մեծ ֆայլերի վրա: Մասնավորապես, դուք պետք է պահեք տվյալների երկու օրինակ՝ կոդավորված և ապակոդավորված: Իսկ եթե կան մեգաբայթեր, կամ, օրինակ, սթրիմինգ վիդեո... Դասական սխեմաները MAC-ով ծածկագրված տեքստից հետո թույլ են տալիս կարդալ այն հոսքային՝ անմիջապես փոխանցելով: Բայց MTProto-ի հետ դուք ստիպված կլինեք սկզբում գաղտնագրել կամ վերծանել ամբողջ հաղորդագրությունը, միայն դրանից հետո փոխանցել այն ցանցին կամ սկավառակին: Հետևաբար, Telegram Desktop-ի վերջին տարբերակներում քեշում user_data Օգտագործվում է նաև մեկ այլ ձևաչափ՝ AES-ով CTR ռեժիմում։

Վասիլի, [21.06.18 01:27] Օ՜, ես պարզեցի, թե ինչ է IGE-ն. IGE-ն առաջին փորձն էր «կոդավորման նույնականացման ռեժիմի», ի սկզբանե Kerberos-ի համար: Դա անհաջող փորձ էր (այն չի ապահովում ամբողջականության պաշտպանություն), և պետք է հեռացվեր: Դա 20-ամյա գաղտնագրման գործող ռեժիմի փնտրտուքի սկիզբն էր, որը վերջերս ավարտվեց այնպիսի ռեժիմներով, ինչպիսիք են OCB-ն և GCM-ը:

Եվ հիմա փաստարկները սայլի կողմից.

Telegram-ի ետևում գտնվող թիմը, որը գլխավորում է Նիկոլայ Դուրովը, բաղկացած է ACM-ի վեց չեմպիոններից, որոնց կեսը մաթեմատիկայի դոկտորներ են: Նրանց մոտ երկու տարի պահանջվեց MTProto-ի ընթացիկ տարբերակը թողարկելու համար:

Դա ծիծաղելի է: Երկու տարի ցածր մակարդակում

Կամ դուք կարող եք պարզապես վերցնել tls

Լավ, ասենք, որ մենք կատարել ենք գաղտնագրումը և այլ նրբերանգներ: Վերջապես հնարավո՞ր է ուղարկել TL-ով սերիականացված հարցումներ և ապասերիալացնել պատասխանները: Այսպիսով, ինչ և ինչպես պետք է ուղարկեք: Ահա, ասենք, մեթոդը initConnection, միգուցե սա է՞

Վասիլի, [25.06.18 18:46] Նախաձեռնում է կապը և պահպանում է տեղեկատվությունը օգտատիրոջ սարքի և հավելվածի վրա:

Այն ընդունում է app_id, device_model, system_version, app_version և lang_code:

Եվ մի քանի հարցում

Փաստաթղթեր, ինչպես միշտ: Ազատորեն ուսումնասիրեք բաց կոդով

Եթե ​​ամեն ինչ մոտավորապես պարզ էր invokeWithLayer-ի հետ, ապա ի՞նչն է սխալ այստեղ: Ստացվում է, ենթադրենք, մենք ունենք - հաճախորդն արդեն ուներ սերվերին հարցնելու բան - կա հարցում, որը մենք ուզում էինք ուղարկել.

Վասիլի, [25.06.18 19:13] Դատելով ծածկագրից, առաջին զանգը փաթաթված է այս խեղկատակության մեջ, իսկ խենթն ինքնին փաթաթված է invokewithlayer-ով:

Ինչու՞ initConnection-ը չէր կարող լինել առանձին զանգ, բայց պետք է լինի փաթաթված: Այո, ինչպես պարզվեց, դա պետք է արվի ամեն անգամ յուրաքանչյուր նիստի սկզբում, և ոչ թե մեկ անգամ, ինչպես հիմնական բանալիով: Բայց! Այն չի կարող կանչվել չարտոնված օգտվողի կողմից: Այժմ մենք հասել ենք այն փուլին, որտեղ այն կիրառելի է այս մեկը փաստաթղթերի էջ - և այն մեզ ասում է, որ...

API-ի մեթոդների միայն մի փոքր մասը հասանելի է չարտոնված օգտվողներին.

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

Նրանցից ամենաառաջինը, auth.sendCode, և կա այդ նվիրական առաջին հարցումը, որով ուղարկում ենք api_id և api_hash, որից հետո ստանում ենք SMS կոդով։ Իսկ եթե մենք սխալ DC-ում ենք (այս երկրի հեռախոսահամարները, օրինակ, սպասարկում է մեկ ուրիշը), ապա մենք սխալ կստանանք ցանկալի DC-ի համարով: Պարզելու համար, թե DC համարով որ IP հասցեին պետք է միանաք, օգնեք մեզ help.getConfig. Ժամանակին ընդամենը 5 գրառում կար, սակայն 2018 թվականի հայտնի իրադարձություններից հետո թիվը զգալիորեն ավելացել է։

Այժմ հիշենք, որ մենք այս փուլին հասել ենք սերվերի վրա անանուն: Չափազանց թանկ չէ՞ պարզապես IP հասցե ստանալը: Ինչու չանել դա և այլ գործողություններ MTProto-ի չգաղտնագրված մասում: Ես լսում եմ առարկությունը. «ինչպե՞ս կարող ենք համոզվել, որ RKN-ն չէ, որ պատասխանի կեղծ հասցեներով»: Դրա համար մենք հիշում ենք, որ, ընդհանուր առմամբ, պաշտոնական հաճախորդներ RSA ստեղները ներկառուցված են, այսինքն. կարող եք պարզապես ստորագրելու համար այս տեղեկությունը։ Փաստորեն, սա արդեն արվում է արգելափակումը շրջանցելու մասին տեղեկատվության համար, որը հաճախորդները ստանում են այլ ալիքներով (տրամաբանական է, որ դա հնարավոր չէ անել հենց MTProto-ում, դուք նաև պետք է իմանաք, թե որտեղ պետք է միանալ):

ԼԱՎ. Հաճախորդի լիազորման այս փուլում մենք դեռ լիազորված չենք և չենք գրանցել մեր դիմումը: Մենք պարզապես ուզում ենք տեսնել, թե ինչ է սերվերը արձագանքում չարտոնված օգտվողին հասանելի մեթոդներին: Եվ ահա…

Վասիլի, [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;

Սխեմայում առաջինը երկրորդն է

tdesktop սխեմայում երրորդ արժեքն է

Այո, դրանից հետո, իհարկե, փաստաթղթերը թարմացվել են։ Թեեւ շուտով այն կարող է կրկին անկապ դառնալ։ Ինչպե՞ս պետք է իմանա սկսնակ ծրագրավորողը: Միգուցե դիմումդ գրանցելու դեպքում քեզ տեղյակ պահե՞ն։ Վասիլին դա արեց, բայց ավաղ, նրանք նրան ոչինչ չուղարկեցին (կրկին, մենք այս մասին կխոսենք երկրորդ մասում):

...Դուք նկատել եք, որ մենք արդեն ինչ-որ կերպ տեղափոխվել ենք API, այսինքն. դեպի հաջորդ մակարդակ, և ինչ-որ բան բաց թողե՞լ եք MTProto թեմայում: Ոչ մի անակնկալ.

Վասիլի, [28.06.18 02:04] Մմ, նրանք քրքրում են e2e-ի որոշ ալգորիթմներ

Mtproto-ն սահմանում է կոդավորման ալգորիթմներ և բանալիներ երկու տիրույթների համար, ինչպես նաև մի փոքր փաթաթման կառուցվածք

Բայց նրանք անընդհատ խառնում են stack-ի տարբեր մակարդակները, այնպես որ միշտ չէ, որ պարզ է, թե որտեղ է ավարտվել mtproto-ն և սկսվել հաջորդ մակարդակը:

Ինչպե՞ս են դրանք խառնվում: Դե, ահա, օրինակ, PFS-ի համար նույն ժամանակավոր բանալին (ի դեպ, Telegram Desktop-ը չի կարող դա անել): Այն իրականացվում է API հարցումով auth.bindTempAuthKey, այսինքն. վերին մակարդակից։ Բայց միևնույն ժամանակ դա խանգարում է գաղտնագրմանը ստորին մակարդակում. դրանից հետո, օրինակ, դուք պետք է դա նորից անեք initConnection և այլն, սա չէ ուղղակի նորմալ խնդրանք. Հատկանշական է նաև այն, որ դուք կարող եք ունենալ միայն ՄԵԿ ժամանակավոր բանալի յուրաքանչյուր DC-ում, թեև դաշտում auth_key_id յուրաքանչյուր հաղորդագրության մեջ թույլ է տալիս փոխել բանալին առնվազն յուրաքանչյուր հաղորդագրություն, և որ սերվերն իրավունք ունի ցանկացած պահի «մոռանալ» ժամանակավոր բանալին. փաստաթղթերը չեն ասում, թե ինչ անել այս դեպքում… Չունե՞ք մի քանի բանալի, ինչպես ապագա աղերի հավաքածուն է, և...

Կան մի քանի այլ բաներ, որոնք արժե ուշադրություն դարձնել MTProto թեմայի վերաբերյալ:

Հաղորդագրությունների հաղորդագրություններ, msg_id, msg_seqno, հաստատումներ, սխալ ուղղությամբ պինգեր և այլ յուրահատկություններ

Ինչու՞ պետք է իմանաք դրանց մասին: Քանի որ դրանք «արտահոսում» են ավելի բարձր մակարդակ, և դուք պետք է տեղյակ լինեք դրանց մասին API-ի հետ աշխատելիս: Ենթադրենք, մեզ չի հետաքրքրում msg_key-ը, ցածր մակարդակը մեզ համար վերծանել է ամեն ինչ: Բայց ապակոդավորված տվյալների ներսում մենք ունենք հետևյալ դաշտերը (նաև տվյալների երկարությունը, այնպես որ մենք գիտենք, թե որտեղ է լցոնումը, բայց դա կարևոր չէ).

  • աղ - int64
  • session_id - int64
  • message_id — int64
  • seq_no - int32

Հիշեցնենք, որ ամբողջ DC-ի համար կա միայն մեկ աղ։ Ինչու՞ իմանալ նրա մասին: Ոչ միայն այն պատճառով, որ կա խնդրանք get_future_salts, որը ցույց է տալիս, թե որ միջակայքերը կլինեն վավեր, բայց նաև այն պատճառով, որ եթե ձեր աղը «փտած» է, ապա հաղորդագրությունը (խնդրանքը) պարզապես կկորչի: Սերվերը, իհարկե, կհաղորդի նոր աղի մասին՝ թողարկելով new_session_created - բայց հնի հետ դուք ստիպված կլինեք ինչ-որ կերպ նորից ուղարկել, օրինակ: Եվ այս խնդիրն ազդում է հավելվածի ճարտարապետության վրա:

Սերվերին թույլատրվում է ընդհանրապես թողնել նիստերը և պատասխանել այս կերպ բազմաթիվ պատճառներով: Իրականում, ի՞նչ է MTProto նիստը հաճախորդի կողմից: Սրանք երկու թիվ են session_id и seq_no հաղորդագրություններ այս նիստի ընթացքում: Դե, և, իհարկե, հիմքում ընկած TCP կապը: Ենթադրենք, մեր հաճախորդը դեռ չգիտի, թե ինչպես անել շատ բաներ, նա անջատվեց և նորից միացավ: Եթե ​​դա տեղի ունեցավ արագ, ապա հին նիստը շարունակվեց նոր TCP կապում, ավելացրեք seq_no հետագա. Եթե ​​երկար ժամանակ պահանջվի, սերվերը կարող է ջնջել այն, քանի որ իր կողմից այն նույնպես հերթ է, ինչպես պարզեցինք։

Ինչ պետք է լինի seq_no? Օ, դա բարդ հարց է: Փորձեք անկեղծորեն հասկանալ, թե ինչ նկատի ուներ.

Բովանդակության հետ կապված հաղորդագրություն

Հաղորդագրություն, որը պահանջում է հստակ հաստատում: Դրանք ներառում են բոլոր օգտատերերի և բազմաթիվ ծառայությունների հաղորդագրությունները, գործնականում բոլորը, բացառությամբ բեռնարկղերի և հաստատումների:

Հաղորդագրության հաջորդականության համարը (msg_seqno)

32-բիթանոց թիվ, որը հավասար է «բովանդակության հետ կապված» հաղորդագրությունների կրկնակի թվին (նրանք, որոնք պահանջում են հաստատում, և, մասնավորապես, նրանք, որոնք բեռնարկղեր չեն), որը ստեղծվել է ուղարկողի կողմից այս հաղորդագրությունից առաջ և այնուհետև ավելացվել է մեկով, եթե ընթացիկ հաղորդագրությունը բովանդակության հետ կապված հաղորդագրություն. Կոնտեյները միշտ ստեղծվում է իր ամբողջ պարունակությունից հետո. հետևաբար, դրա հաջորդական համարը մեծ է կամ հավասար է դրանում պարունակվող հաղորդագրությունների հաջորդական թվերին:

Ինչպիսի՞ կրկես է սա 1-ով ավելացումով, իսկ հետո ևս մեկը 2-ով: Ես կասկածում եմ, որ սկզբում նրանք նկատի ունեին «ԱՔԿ-ի համար ամենաքիչ կարևորը, մնացածը թիվ է», բայց արդյունքը բոլորովին նույնը չէ. մասնավորապես, դուրս է գալիս, կարող է ուղարկվել մոտ հաստատումներ, որոնք ունեն նույնը seq_no! Ինչպե՞ս: Դե, օրինակ, սերվերը մեզ ինչ-որ բան է ուղարկում, ուղարկում, իսկ մենք ինքներս լռում ենք՝ պատասխանելով միայն իր հաղորդագրությունների ստացումը հաստատող ծառայողական հաղորդագրություններով։ Այս դեպքում մեր ելքային հաստատումները կունենան նույն ելքային համարը: Եթե ​​դուք ծանոթ եք TCP-ին և կարծում եք, որ սա ինչ-որ կերպ վայրի է հնչում, բայց թվում է, թե այնքան էլ վայրի չէ, քանի որ TCP-ում. seq_no չի փոխվում, բայց հաստատումը գնում է seq_no մյուս կողմից, ես կշտապեմ ձեզ վրդովեցնել։ Հաստատումները տրամադրվում են MTProto-ում ՉԷ մասին seq_no, ինչպես TCP-ում, բայց ըստ msg_id !

Ինչ է սա msg_id, այս ոլորտներից ամենակարեւորը. Հաղորդագրության եզակի նույնացուցիչ, ինչպես հուշում է անունը: Այն սահմանվում է որպես 64-բիթանոց թիվ, որի ամենացածր բիթերը կրկին ունեն «server-not-server» մոգությունը, իսկ մնացածը Unix-ի ժամանակային դրոշմ է, ներառյալ կոտորակային մասը, տեղափոխված 32 բիթ դեպի ձախ: Նրանք. ժամանակի դրոշմակն ինքնին (և չափից շատ տարբեր ժամանակներով հաղորդագրությունները կմերժվեն սերվերի կողմից): Այստեղից պարզվում է, որ ընդհանուր առմամբ սա հաճախորդի համար գլոբալ նույնացուցիչ է։ Հաշվի առնելով դա՝ հիշենք session_id - մենք երաշխավորված ենք. Ոչ մի դեպքում չի կարող մեկ նիստի համար նախատեսված հաղորդագրություն ուղարկվել մեկ այլ նիստի. Այսինքն՝ ստացվում է, որ արդեն կա երեք մակարդակ - նիստ, նիստի համար, հաղորդագրության id: Ինչու՞ նման գերբարդություն, այս առեղծվածը շատ մեծ է։

Այնպես որ, msg_id անհրաժեշտ է...

RPC. հարցումներ, պատասխաններ, սխալներ: Հաստատումներ.

Ինչպես նկատեցիք, դիագրամում որևէ «կատարել RPC հարցում» հատուկ տեսակ կամ գործառույթ չկա, թեև կան պատասխաններ: Ի վերջո, մենք ունենք բովանդակության հետ կապված հաղորդագրություններ: Այն է, ցանկացած հաղորդագրությունը կարող է լինել խնդրանք: Կամ չլինել։ Ամենից հետո, յուրաքանչյուր կա msg_id. Բայց կան պատասխաններ.

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

Այստեղ նշվում է, թե որ հաղորդագրությանն է սա պատասխան: Հետևաբար, API-ի վերին մակարդակում դուք ստիպված կլինեք հիշել, թե որն էր ձեր հարցման համարը. Կարծում եմ, կարիք չկա բացատրելու, որ աշխատանքը ասինխրոն է, և կարող են լինել միաժամանակ մի քանի հարցումներ ընթացքի մեջ, որոնց պատասխանները կարող են վերադարձվել ցանկացած հերթականությամբ: Սկզբունքորեն, այս և սխալի հաղորդագրություններից, ինչպիսիք են ոչ աշխատողները, կարելի է հետևել դրա հիմքում ընկած ճարտարապետությանը. սերվերը, որը պահպանում է TCP կապը ձեզ հետ, հանդիսանում է ֆրոնտային հավասարակշռող, այն ուղարկում է հարցումները դեպի հետնամասեր և հետ է հավաքում դրանք: message_id. Թվում է, թե այստեղ ամեն ինչ պարզ է, տրամաբանական և լավ։

Այո՞... Իսկ եթե մտածեք դրա մասին. Ի վերջո, RPC արձագանքն ինքնին նույնպես դաշտ ունի msg_id! Արդյո՞ք մենք պետք է բղավենք սերվերի վրա «դու չես պատասխանում իմ պատասխանին»: Եվ այո, ի՞նչ կար հաստատումների մասին։ Էջի մասին հաղորդագրությունների մասին հաղորդագրություններ ասում է մեզ, թե ինչ է

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

և դա պետք է արվի յուրաքանչյուր կողմից: Բայց ոչ միշտ։ Եթե ​​դուք ստացել եք RpcResult, այն ինքնին ծառայում է որպես հաստատում: Այսինքն, սերվերը կարող է պատասխանել ձեր խնդրանքին MsgsAck-ով, օրինակ՝ «Ես ստացել եմ այն»: RpcResult-ը կարող է անմիջապես արձագանքել: Դա կարող է լինել երկուսն էլ:

Եվ այո, դուք դեռ պետք է պատասխանեք պատասխանին: Հաստատում. Հակառակ դեպքում, սերվերը կհամարի այն չառաքվող և նորից կուղարկի ձեզ: Նույնիսկ վերամիացումից հետո: Բայց այստեղ, իհարկե, առաջանում է թայմաութների խնդիր։ Եկեք նրանց նայենք մի փոքր ուշ:

Միևնույն ժամանակ, եկեք դիտարկենք հարցումների կատարման հնարավոր սխալները:

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

Ա՜յ, ինչ-որ մեկը կբացականչի, ահա ավելի մարդասիրական ձևաչափ՝ տող կա։ Մի շտապեք. Այստեղ սխալների ցուցակ, բայց իհարկե ոչ ամբողջական։ Դրանից մենք իմանում ենք, որ կոդը նման մի բան HTTP սխալներ (դե, իհարկե, պատասխանների իմաստաբանությունը չի հարգվում, որոշ տեղերում դրանք պատահականորեն բաշխվում են կոդերի միջև), և գիծը նման է. CAPITAL_LETTERS_AND_NUMBERS. Օրինակ՝ PHONE_NUMBER_OCCUPIED կամ FILE_PART_Х_MISSING: Դե, այսինքն, ձեզ դեռ պետք կգա այս տողը վերլուծել. Օրինակ, FLOOD_WAIT_3600 կնշանակի, որ պետք է մեկ ժամ սպասել, և PHONE_MIGRATE_5, որ այս նախածանցով հեռախոսահամարը պետք է գրանցված լինի 5-րդ ԱՀ-ում։ Մենք ունենք տիպային լեզու, չէ՞: Մեզ լարային փաստարկ պետք չէ, սովորականները կանեն, լավ:

Կրկին, սա ծառայության հաղորդագրությունների էջում չէ, բայց, ինչպես արդեն սովորական է այս նախագծի հետ, տեղեկատվությունը կարելի է գտնել փաստաթղթերի մեկ այլ էջում. Կամ կասկածներ գցել. Նախ, նայեք, մուտքագրման/շերտի խախտում. RpcError կարող է բնադրվել RpcResult. Ինչու ոչ դրսում: Ի՞նչը մենք հաշվի չենք առել... Ըստ այդմ, որտե՞ղ է երաշխիքը, որ RpcError ՉԻ կարող ներկառուցված լինել RpcResult, բայց ուղղակիորեն կամ բնադրված լինի մեկ այլ տիպի մեջ: Իսկ եթե չի կարող, ինչու այն վերին մակարդակում չէ, այսինքն. այն բացակայում է req_msg_id .....

Բայց եկեք շարունակենք ծառայության հաղորդագրությունների մասին: Հաճախորդը կարող է մտածել, որ սերվերը երկար է մտածում և կատարել այս հրաշալի խնդրանքը.

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

Այս հարցի երեք հնարավոր պատասխաններ կան, որոնք կրկին հատվում են հաստատման մեխանիզմի հետ. փորձելով հասկանալ, թե որոնք պետք է լինեն (և հաստատում չպահանջող տեսակների ընդհանուր ցանկը) թողնված է ընթերցողին որպես տնային աշխատանք (նշում. Telegram Desktop-ի սկզբնական կոդը ամբողջական չէ):

Թմրամոլություն. հաղորդագրությունների կարգավիճակներ

Ընդհանրապես, շատ տեղեր TL-ում, MTProto-ում և ընդհանրապես Telegram-ում թողնում են համառության զգացում, բայց քաղաքավարությունից, տակտից և այլն: փափուկ հմտություններ Մենք քաղաքավարի կերպով լռեցինք այդ մասին և գրաքննեցինք երկխոսություններում տեղ գտած անպարկեշտությունները։ Այնուամենայնիվ, այս տեղըОէջի մեծ մասը վերաբերում է հաղորդագրությունների մասին հաղորդագրություններ Դա ցնցող է նույնիսկ ինձ համար, ով երկար ժամանակ աշխատում է ցանցային արձանագրությունների հետ և տեսել եմ տարբեր աստիճանի ծուռ հեծանիվներ:

Սկսվում է անվնաս, հաստատումներով։ Հաջորդը նրանք մեզ պատմում են

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;

Դե, բոլոր նրանք, ովքեր սկսում են աշխատել MTProto-ի հետ, ստիպված կլինեն զբաղվել դրանց հետ. «ուղղված - վերակոմպիլացված - գործարկված» ցիկլում թվային սխալներ կամ աղեր ստանալը, որոնք կարողացել են վատանալ խմբագրումների ժամանակ, սովորական բան է: Այնուամենայնիվ, այստեղ կա երկու կետ.

  1. Սա նշանակում է, որ սկզբնական հաղորդագրությունը կորել է: Մենք պետք է որոշ հերթեր ստեղծենք, մենք ավելի ուշ կանդրադառնանք դրան:
  2. Որո՞նք են այս տարօրինակ սխալ թվերը: 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... որտե՞ղ են մնացած թվերը, Թոմմի։

Փաստաթղթում ասվում է.

Նպատակն այն է, որ error_code արժեքները խմբավորվեն (error_code >> 4). Օրինակ՝ 0x40 — 0x4f կոդերը համապատասխանում են տարաների տարրալուծման սխալներին:

բայց, նախ, տեղաշարժ այլ ուղղությամբ, և երկրորդ, կարևոր չէ, որտե՞ղ են մնացած ծածկագրերը: Հեղինակի գլխո՞ւմ... Այնուամենայնիվ, սրանք մանրուքներ են։

Կախվածությունը սկսվում է հաղորդագրությունների կարգավիճակների և հաղորդագրությունների պատճենների մասին հաղորդագրություններից.

  • Հաղորդագրության կարգավիճակի տեղեկատվության հարցում
    Եթե ​​կողմերից որևէ մեկը որոշ ժամանակ չի ստացել տեղեկատվություն իր ելքային հաղորդագրությունների կարգավիճակի մասին, նա կարող է բացահայտորեն պահանջել այն մյուս կողմից.
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • Տեղեկատվական հաղորդագրություն հաղորդագրությունների կարգավիճակի վերաբերյալ
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    Այստեղ, info տող է, որը պարունակում է ուղիղ մեկ բայթ հաղորդագրության կարգավիճակ մուտքային msg_ids ցուցակից յուրաքանչյուր հաղորդագրության համար.

    • 1 = ոչինչ հայտնի չէ հաղորդագրության մասին (msg_id-ը չափազանց ցածր է, մյուս կողմը կարող է մոռացել այն)
    • 2 = հաղորդագրություն չի ստացվել (msg_id-ը գտնվում է պահպանված նույնացուցիչների տիրույթում, սակայն մյուս կողմը, իհարկե, նման հաղորդագրություն չի ստացել)
    • 3 = հաղորդագրությունը չի ստացվել (msg_id-ը չափազանց բարձր է, սակայն մյուս կողմը, իհարկե, դեռ չի ստացել այն)
    • 4 = ստացված հաղորդագրություն (նկատի ունեցեք, որ այս պատասխանը միևնույն ժամանակ նաև անդորրագիր է)
    • +8 = հաղորդագրությունն արդեն հաստատված է
    • +16 = հաղորդագրություն, որը չի պահանջում հաստատում
    • +32 = RPC հարցումը պարունակվում է մշակվող կամ մշակվող հաղորդագրությունում արդեն ավարտված
    • +64 = բովանդակության հետ կապված պատասխան արդեն իսկ ստեղծված հաղորդագրությանը
    • +128 = մյուս կողմը հաստատ գիտի, որ հաղորդագրությունն արդեն ստացվել է
      Այս պատասխանը ճանաչում չի պահանջում: Դա հաստատում է համապատասխան msgs_state_req, ինքնին:
      Նկատի ունեցեք, որ եթե հանկարծ պարզվի, որ մյուս կողմը չունի հաղորդագրություն, որը կարծես իրեն ուղարկվել է, հաղորդագրությունը կարող է պարզապես նորից ուղարկվել: Նույնիսկ եթե մյուս կողմը պետք է ստանա հաղորդագրության երկու օրինակ միաժամանակ, կրկնօրինակը անտեսվելու է: (Եթե չափազանց շատ ժամանակ է անցել, և բնօրինակ msg_id-ն այլևս վավեր չէ, հաղորդագրությունը պետք է փաթաթվի msg_copy-ով):
  • Հաղորդագրությունների կարգավիճակի կամավոր հաղորդում
    Կողմերից յուրաքանչյուրը կարող է կամավոր տեղեկացնել մյուս կողմին մյուս կողմի կողմից փոխանցված հաղորդագրությունների կարգավիճակի մասին:
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • Մեկ հաղորդագրության կարգավիճակի ընդլայնված կամավոր հաղորդակցություն
    ...
    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;
  • Հաղորդագրությունները կրկին ուղարկելու հստակ խնդրանք
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    Հեռավոր կողմն անմիջապես արձագանքում է՝ նորից ուղարկելով պահանջվող հաղորդագրությունները […]
  • Պատասխանները նորից ուղարկելու հստակ խնդրանք
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    Հեռավոր կողմն անմիջապես արձագանքում է՝ նորից ուղարկելով Թեմա պահանջվող հաղորդագրություններին […]
  • Հաղորդագրության պատճենները
    Որոշ իրավիճակներում հին հաղորդագրությունը msg_id-ով, որն այլևս վավեր չէ, պետք է նորից ուղարկվի: Այնուհետև այն փաթաթվում է պատճենահանման տարայի մեջ.
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    Ստանալուց հետո հաղորդագրությունը մշակվում է այնպես, կարծես փաթաթանն այնտեղ չէ: Այնուամենայնիվ, եթե հաստատապես հայտնի է, որ orig_message.msg_id հաղորդագրությունը ստացվել է, ապա նոր հաղորդագրությունը չի մշակվում (միևնույն ժամանակ, այն և orig_message.msg_id-ը հաստատվում են): orig_message.msg_id-ի արժեքը պետք է ցածր լինի կոնտեյների msg_id-ից:

Ինչի մասին նույնիսկ լռենք msgs_state_info դարձյալ անավարտ TL-ի ականջները դուրս են ցցվել (մեզ բայթերի վեկտոր էր պետք, իսկ ստորին երկու բիթում enum էր, իսկ երկու բարձր բիթում՝ դրոշակներ): Բանն այլ է. Ինչ-որ մեկը հասկանու՞մ է, թե ինչու է այս ամենը գործնականում։ իրական հաճախորդի մեջ անհրաժեշտ է?.. Դժվարությամբ, բայց կարելի է պատկերացնել ինչ-որ օգուտ, եթե մարդը զբաղվի վրիպազերծմամբ, իսկ ինտերակտիվ ռեժիմով՝ հարցրու սերվերին՝ ինչ և ինչպես: Բայց այստեղ խնդրանքները նկարագրված են Շրջագայություն.

Սրանից հետևում է, որ յուրաքանչյուր կողմ պետք է ոչ միայն գաղտնագրի և ուղարկի հաղորդագրություններ, այլև անհայտ ժամանակի ընթացքում պահպանի իր մասին տվյալներ, նրանց պատասխանների մասին: Փաստաթղթերը չեն նկարագրում այս հատկանիշների ոչ ժամկետները, ոչ էլ գործնական կիրառելիությունը: ոչ մի դեպքում. Ամենազարմանալին այն է, որ դրանք իրականում օգտագործվում են պաշտոնական հաճախորդների կոդում: Ըստ երևույթին, նրանց ասել են մի բան, որը ներառված չի եղել հանրային փաստաթղթում։ Հասկացեք ծածկագրից ինչու, այլևս այնքան պարզ չէ, որքան TL-ի դեպքում. այն ոչ թե (համեմատաբար) տրամաբանորեն մեկուսացված մաս է, այլ կիրառական ճարտարապետության հետ կապված մի կտոր, այսինքն. զգալիորեն ավելի շատ ժամանակ կպահանջվի հավելվածի կոդը հասկանալու համար:

Պինգեր և ժամկետներ. Հերթեր.

Ամեն ինչից, եթե հիշենք սերվերի ճարտարապետության մասին ենթադրությունները (հարցումների բաշխումը հետին հատվածներում), հետևում է բավականին տխուր բան. չնայած TCP-ում առաքման բոլոր երաշխիքներին (կամ տվյալները առաքվում են, կամ դուք կտեղեկանաք բացը, բայց տվյալները կհանձնվեն մինչև խնդրի առաջանալը), այդ հաստատումները հենց MTProto-ում. ոչ մի երաշխիք. Սերվերը կարող է հեշտությամբ կորցնել կամ դուրս նետել ձեր հաղորդագրությունը, և ոչինչ չի կարելի անել դրա դեմ, պարզապես օգտագործեք տարբեր տեսակի հենակներ:

Եվ առաջին հերթին՝ հաղորդագրությունների հերթեր։ Դե, մի բանում ամեն ինչ ակնհայտ էր հենց սկզբից՝ չհաստատված հաղորդագրությունը պետք է պահել և նորից ուղարկել։ Իսկ քանի՞ ժամից հետո։ Իսկ ծաղրածուն ճանաչում է նրան։ Միգուցե այդ կախվածություն ունեցող ծառայության հաղորդագրությունները ինչ-որ կերպ լուծում են այս խնդիրը հենակներով, ասենք, Telegram Desktop-ում դրանց համապատասխանող մոտ 4 հերթ կա (գուցե ավելի շատ, ինչպես արդեն նշվեց, դրա համար պետք է ավելի լրջորեն խորանալ դրա կոդի և ճարտարապետության մեջ. ժամանակ, մենք գիտենք, որ այն չի կարող ընդունվել որպես նմուշ, MTProto սխեմայից որոշակի քանակությամբ տեսակներ չեն օգտագործվում դրանում):

Ինչու է դա տեղի ունենում: Հավանաբար, սերվերի ծրագրավորողները չեն կարողացել ապահովել հուսալիություն կլաստերի ներսում, կամ նույնիսկ բուֆերացնել առջևի բալանսավորիչի վրա, և այս խնդիրը փոխանցել են հաճախորդին: Հուսահատությունից Վասիլին փորձեց իրականացնել այլընտրանքային տարբերակ՝ ընդամենը երկու հերթով՝ օգտագործելով TCP-ի ալգորիթմները՝ չափելով RTT-ը սերվերին և կարգավորելով «պատուհանի» չափը (հաղորդագրություններում)՝ կախված չհաստատված հարցումների քանակից: Այսինքն՝ սերվերի ծանրաբեռնվածությունը գնահատելու նման կոպիտ էվրիստիկա է այն, թե մեր հարցումներից քանիսը կարող է միաժամանակ ծամել և չկորցնել։

Դե, այսինքն, հասկանում եք, չէ՞: Եթե ​​դուք պետք է նորից ներդնեք TCP-ն TCP-ով աշխատող արձանագրության վերևում, սա ցույց է տալիս շատ վատ մշակված արձանագրություն:

Oh, այո, ինչու է ձեզ անհրաժեշտ մեկից ավելի հերթ, և ինչ է դա նշանակում, այնուամենայնիվ, բարձր մակարդակի API-ով աշխատող մարդու համար: Տեսեք, դուք հարցում եք անում, սերիականացնում, բայց հաճախ չեք կարողանում անմիջապես ուղարկել։ Ինչո՞ւ։ Որովհետև պատասխանը կլինի msg_id, որը ժամանակավոր էаԵս պիտակ եմ, որի նշանակումը լավագույնս հետաձգվում է որքան հնարավոր է ուշ, եթե սերվերը մերժի այն մեր և նրա միջև ժամանակի անհամապատասխանության պատճառով (իհարկե, մենք կարող ենք հենակ պատրաստել, որը փոխում է մեր ժամանակը ներկայից: սերվերին՝ ավելացնելով սերվերի պատասխաններից հաշվարկված դելտա. պաշտոնական հաճախորդները դա անում են, բայց դա կոպիտ է և ոչ ճշգրիտ՝ բուֆերացման պատճառով): Հետևաբար, երբ գրադարանից տեղական ֆունկցիայի կանչով հարցում եք կատարում, հաղորդագրությունն անցնում է հետևյալ փուլերով.

  1. Այն ընկած է մեկ հերթում և սպասում է կոդավորման:
  2. Նշանակվել է msg_id և հաղորդագրությունը գնաց մեկ այլ հերթ՝ հնարավոր վերահասցեավորում; ուղարկել վարդակից:
  3. ա) Սերվերը պատասխանել է MsgsAck - հաղորդագրությունը առաքվել է, մենք այն ջնջում ենք «այլ հերթից»:
    բ) Կամ հակառակը, նրան ինչ-որ բան դուր չի եկել, նա պատասխանել է badmsg - նորից ուղարկել «այլ հերթից»
    գ) Ոչինչ հայտնի չէ, հաղորդագրությունը պետք է նորից ուղարկվի մեկ այլ հերթից, բայց հստակ հայտնի չէ, թե երբ:
  4. Սերվերը վերջապես արձագանքեց RpcResult - իրական պատասխանը (կամ սխալը) - ոչ միայն առաքվել է, այլև մշակվել:

Գուցե, տարաների օգտագործումը կարող էր մասամբ լուծել խնդիրը։ Սա այն դեպքում, երբ հաղորդագրությունների մի խումբ փաթեթավորվում է մեկի մեջ, և սերվերը պատասխանել է դրանց բոլորին միանգամից հաստատմամբ, մեկում: msg_id. Բայց նա նաև կմերժի այս փաթեթը, եթե ինչ-որ բան սխալ լինի, ամբողջությամբ:

Եվ այս պահին ուժի մեջ են մտնում ոչ տեխնիկական նկատառումները: Փորձից մենք տեսել ենք բազմաթիվ հենակներ, և բացի այդ, հիմա մենք կտեսնենք վատ խորհուրդների և ճարտարապետության ավելի շատ օրինակներ՝ նման պայմաններում արժե՞ վստահել և նման որոշումներ կայացնել։ Հարցը հռետորական է (իհարկե ոչ):

Ինչի՞ մասին ենք խոսում։ Եթե ​​«Թմրամիջոցների մասին հաղորդագրությունների մասին» թեմայի շուրջ դուք դեռ կարող եք շահարկել առարկություններով, ինչպիսիք են «դու հիմար ես, չես հասկացել մեր փայլուն ծրագիրը»: (այդպես, նախ գրեք փաստաթղթերը, ինչպես պետք է նորմալ մարդիկ, հիմնավորումներով և փաթեթների փոխանակման օրինակներով, հետո կխոսենք), հետո ժամկետները/ժամկետները զուտ գործնական և կոնկրետ հարց են, այստեղ ամեն ինչ վաղուց հայտնի է: Ի՞նչ է մեզ պատմում փաստաթղթերը ժամանակի ընդհատումների մասին:

Սերվերը սովորաբար ընդունում է հաղորդագրության ստացումը հաճախորդից (սովորաբար, RPC հարցում)՝ օգտագործելով RPC պատասխանը: Եթե ​​պատասխանը երկար ժամանակ է գալիս, սերվերը կարող է սկզբում ուղարկել անդորրագրի հաստատում, իսկ որոշ ժամանակ անց՝ ինքը՝ RPC պատասխանը:

Հաճախորդը սովորաբար հաստատում է սերվերից հաղորդագրության ստացումը (սովորաբար, RPC պատասխան)՝ ավելացնելով հաստատում հաջորդ RPC հարցմանը, եթե այն շատ ուշ չի փոխանցվել (եթե այն ստեղծվել է, ասենք, ստացումից 60-120 վայրկյան հետո): սերվերից ստացված հաղորդագրություն): Այնուամենայնիվ, եթե երկար ժամանակ սերվերին հաղորդագրություններ ուղարկելու պատճառ չկա, կամ եթե սերվերից կա մեծ թվով չճանաչված հաղորդագրություններ (ասենք, 16-ից ավելի), հաճախորդը փոխանցում է առանձին հաստատում:

... Ես թարգմանում եմ. մենք ինքներս չգիտենք, թե որքան և ինչպես է դա մեզ անհրաժեշտ, ուստի ենթադրենք, որ թող լինի այսպես.

Իսկ պինգերի մասին.

Ping հաղորդագրություններ (PING/PONG)

ping#7abe77ec ping_id:long = Pong;

Պատասխանը սովորաբար վերադարձվում է նույն կապին.

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

Այս հաղորդագրությունները հաստատումներ չեն պահանջում: Պոնգը փոխանցվում է միայն ի պատասխան պինգի, մինչդեռ պինգը կարող է նախաձեռնվել ցանկացած կողմից:

Հետաձգված կապի փակում + PING

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

Աշխատում է պինգի պես: Բացի այդ, այն ստանալուց հետո սերվերը գործարկում է ժմչփ, որը կփակի ընթացիկ կապը disconnect_delay վայրկյաններ անց, եթե այն չստանա նույն տեսակի նոր հաղորդագրություն, որն ավտոմատ կերպով վերակայում է նախորդ բոլոր ժամանակաչափերը: Եթե ​​հաճախորդն ուղարկում է այս պինգերը 60 վայրկյանը մեկ անգամ, օրինակ, այն կարող է սահմանել disconnect_delay-ը հավասար 75 վայրկյանի:

Դու խենթ ես?! 60 վայրկյանից գնացքը կմտնի կայարան, կիջնի ու կվերցնի ուղևորներին և նորից կկորցնի կապը թունելում։ 120 վայրկյանից, մինչ դուք լսեք այն, այն կհասնի մեկ ուրիշին, և կապը, ամենայն հավանականությամբ, կխզվի: Դե, պարզ է, թե որտեղից են գալիս ոտքերը. «Ես զանգ եմ լսել, բայց չգիտեմ որտեղ է», կա Nagl-ի ալգորիթմը և TCP_NODELAY տարբերակը, որը նախատեսված է ինտերակտիվ աշխատանքի համար: Բայց, կներեք ինձ, պահեք դրա լռելյայն արժեքը՝ 200 Միլլիվայրկյան Եթե ​​դուք իսկապես ցանկանում եք նման բան պատկերել և պահպանել հնարավոր մի քանի փաթեթների վրա, ապա հետաձգեք այն 5 վայրկյանով, կամ ինչ էլ որ լինի «Օգտվողը գրում է...» հաղորդագրության ժամկետը հիմա: Բայց ոչ ավելին։

Եվ վերջապես, պինգ. Այսինքն՝ ստուգելով TCP կապի ակտիվությունը։ Ծիծաղելի է, բայց մոտ 10 տարի առաջ ես քննադատական ​​տեքստ գրեցի մեր ֆակուլտետի հանրակացարանի մեսենջերի մասին. այնտեղի հեղինակները նաև սերվերը պինգ են արել հաճախորդից, և ոչ հակառակը: Բայց 3-րդ կուրսի ուսանողները մի բան են, իսկ միջազգային գրասենյակը՝ մեկ այլ բան, չէ՞։

Նախ՝ մի փոքր կրթական ծրագիր. TCP կապը, փաթեթների փոխանակման բացակայության դեպքում, կարող է ապրել շաբաթներով: Սա և՛ լավ է, և՛ վատ՝ կախված նպատակից: Լավ է, որ սերվերի հետ բաց SSH կապ ունեիք, վեր կացեք համակարգչից, վերագործարկեիք երթուղիչը, վերադարձաք ձեր տեղը - այս սերվերի միջոցով նիստը պատռված չէր (դուք ոչինչ չեք մուտքագրել, փաթեթներ չեն եղել) , հարմար է։ Վատ է, եթե սերվերում հազարավոր հաճախորդներ կան, որոնցից յուրաքանչյուրը ռեսուրսներ է վերցնում (բարև, Postgres!), և հաճախորդի հոսթինգը կարող է վաղուց վերագործարկվել, բայց մենք դրա մասին չենք իմանա:

Chat/IM համակարգերը ընկնում են երկրորդ դեպքում մեկ լրացուցիչ պատճառով՝ առցանց կարգավիճակներ: Եթե ​​օգտատերը «ընկավ», դուք պետք է այդ մասին տեղեկացնեք իր զրուցակիցներին: Հակառակ դեպքում, դուք կհայտնվեք մի սխալի հետ, որը թույլ են տվել Jabber-ի ստեղծողները (և ուղղել են 20 տարի)՝ օգտատերը անջատել է կապը, բայց նրանք շարունակում են հաղորդագրություններ գրել նրան՝ հավատալով, որ նա առցանց է (որոնք նույնպես ամբողջությամբ կորել են դրանց մեջ։ Անջատումը հայտնաբերելուց մի քանի րոպե առաջ): Ոչ, TCP_KEEPALIVE տարբերակը, որը շատ մարդիկ, ովքեր չեն հասկանում, թե ինչպես են աշխատում TCP ժամանակաչափերը, պատահականորեն նետում են (տասնյակ վայրկյանների նման վայրի արժեքներ դնելով), այստեղ չի օգնի, դուք պետք է համոզվեք, որ ոչ միայն OS միջուկը օգտատիրոջ մեքենան կենդանի է, բայց նաև աշխատում է նորմալ, ի վիճակի է արձագանքել, և հավելվածն ինքը (կարծում եք, որ այն չի՞ կարող սառչել: Telegram Desktop-ը Ubuntu 18.04-ում սառեցվել է ինձ համար մեկից ավելի անգամ):

Դրա համար պետք է պինգ անել սերվեր հաճախորդը, և ոչ հակառակը. եթե հաճախորդը դա անի, եթե կապը խզվի, ապա պինգը չի ստացվի, նպատակը չի իրականացվի:

Ի՞նչ ենք մենք տեսնում Telegram-ում: Ճիշտ հակառակն է։ Դե, դա է. Ֆորմալ առումով, իհարկե, երկու կողմերն էլ կարող են պինգ անել միմյանց: Գործնականում հաճախորդները օգտագործում են հենակ ping_delay_disconnect, որը սահմանում է ժամանակաչափը սերվերի վրա: Դե, կներեք ինձ, հաճախորդը չէ, որ պետք է որոշի, թե որքան ժամանակ է նա ցանկանում ապրել այնտեղ առանց պինգի: Սերվերը, ելնելով իր ծանրաբեռնվածությունից, ավելի լավ գիտի. Բայց, իհարկե, եթե դեմ չես ռեսուրսներին, ապա դու կլինես քո չար Պինոքիոն, և հենակը կանի…

Ինչպե՞ս պետք է այն նախագծվեր։

Կարծում եմ, որ վերը նշված փաստերը հստակ ցույց են տալիս, որ Telegram/VKontakte թիմը այնքան էլ իրավասու չէ համակարգչային ցանցերի տրանսպորտային (և ավելի ցածր) մակարդակի և համապատասխան հարցերում նրանց ցածր որակավորումների ոլորտում:

Ինչու՞ պարզվեց, որ այն այդքան բարդ է, և ինչպես կարող են Telegram-ի ճարտարապետները փորձել առարկել: Այն փաստը, որ նրանք փորձել են TCP կապը պահպանող նիստ անել, ընդհատվում է, այսինքն՝ այն, ինչ հիմա չի առաքվել, մենք ավելի ուշ կառաքենք: Նրանք հավանաբար փորձել են նաև UDP տրանսպորտ պատրաստել, բայց դժվարությունների են հանդիպել և լքել այն (այդ պատճառով էլ փաստաթղթերը դատարկ են. պարծենալու բան չկար): Բայց չհասկանալու պատճառով, թե ինչպես են աշխատում ցանցերն ընդհանրապես և TCP-ն մասնավորապես, որտեղ կարող եք ապավինել դրա վրա, և որտեղ դուք պետք է դա անեք ինքներդ (և ինչպես), և փորձեք դա համատեղել գաղտնագրության հետ «երկու թռչուններ մեկ քար», սա արդյունքն է։

Ինչպե՞ս էր դա անհրաժեշտ։ Ելնելով այն հանգամանքից, որ msg_id Կրիպտոգրաֆիկ տեսանկյունից անհրաժեշտ ժամանակի դրոշմակնիք է՝ կրկնվող հարձակումները կանխելու համար, սխալ է դրան կցել եզակի նույնացուցիչ ֆունկցիա։ Հետևաբար, առանց ընթացիկ ճարտարապետությունը հիմնովին փոխելու (երբ ստեղծվում է Թարմացումների հոսքը, դա բարձր մակարդակի API թեմա է այս գրառումների շարքի մեկ այլ մասի համար), պետք է.

  1. Հաճախորդի հետ TCP կապը պահող սերվերն իր վրա է վերցնում պատասխանատվությունը. եթե այն կարդացել է վարդակից, խնդրում ենք ընդունել, մշակել կամ վերադարձնել սխալը, ոչ մի կորուստ: Այնուհետև հաստատումը ID-ների վեկտոր չէ, այլ պարզապես «վերջին ստացված seq_no»-ն՝ ընդամենը թիվ, ինչպես TCP-ում (երկու համար՝ ձեր հաջորդականությունը և հաստատվածը): Մենք միշտ նիստի շրջանակներում ենք, այնպես չէ՞:
  2. Կրկնվող հարձակումները կանխելու ժամանակային դրոշմը դառնում է առանձին դաշտ՝ a la nonce: Ստուգված է, բայց այլ բանի վրա չի ազդում։ Բավական է և uint32 - եթե մեր աղը փոխվում է առնվազն օրը մեկ կեսը, մենք կարող ենք 16 բիթ հատկացնել ընթացիկ ժամանակի մի ամբողջ մասի ցածր կարգի բիթերին, մնացածը՝ վայրկյանի կոտորակային մասի (ինչպես հիմա):
  3. Հեռացված msg_id ընդհանրապես - հետնամասերի հարցումները տարբերակելու տեսանկյունից կա, առաջին հերթին, հաճախորդի id-ն, և երկրորդը, նիստի id-ն, միացնել դրանք: Համապատասխանաբար, միայն մեկ բան բավարար է որպես հարցումների նույնացուցիչ seq_no.

Սա նաև ամենահաջող տարբերակը չէ, ամբողջական պատահականությունը կարող է ծառայել որպես նույնացուցիչ, ի դեպ, սա արդեն արված է բարձր մակարդակի API-ում հաղորդագրություն ուղարկելիս: Ավելի լավ կլիներ ճարտարապետությունն ամբողջությամբ վերափոխել հարաբերականից բացարձակի, բայց սա այլ մասի թեմա է, ոչ թե այս գրառման։

API?

Թա-դամ! Այսպիսով, պայքարելով ցավով և հենակներով լի ուղու միջով, մենք վերջապես կարողացանք ցանկացած հարցում ուղարկել սերվեր և ստանալ դրանց պատասխանները, ինչպես նաև ստանալ թարմացումներ սերվերից (ոչ թե ի պատասխան հարցման, այլ հենց ինքը ուղարկում է մեզ, ինչպես PUSH-ը, եթե որևէ մեկը այդպես ավելի պարզ է):

Ուշադրություն, հիմա հոդվածում կլինի միակ օրինակը Perl-ում։ (նրանց համար, ովքեր ծանոթ չեն շարահյուսությանը, bless-ի առաջին արգումենտը օբյեկտի տվյալների կառուցվածքն է, երկրորդը՝ նրա դասը):

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

Այո, միտումնավոր սփոյլեր չէ, եթե դեռ չեք կարդացել, շարունակեք և արեք դա:

Օ՜, վայ~~... ինչ տեսք ունի սա: Ինչ-որ շատ ծանոթ բան... գուցե սա JSON-ում տիպիկ վեբ API-ի տվյալների կառուցվածքն է, բացի այն, որ դասերը նույնպես կցված են օբյեկտներին:

Ահա թե ինչպես է ստացվում... Ինչի՞ մասին է խոսքը, ընկերնե՛ր... Այսքան ջանք, և մենք կանգ առանք հանգստանալու այնտեղ, որտեղ վեբ ծրագրավորողները նոր է սկսվում?...Մի՞թե միայն JSON-ը HTTPS-ով ավելի պարզ չի՞ լինի: Ի՞նչ ստացանք դրա դիմաց։ Արդյո՞ք ջանքերն արժեր դրան:

Եկեք գնահատենք, թե ինչ է տվել մեզ TL+MTProto-ն և ինչ այլընտրանքներ են հնարավոր։ Դե, HTTP-ն, որը կենտրոնանում է հարցում-պատասխան մոդելի վրա, վատ տեղավորվում է, բայց գոնե ինչ-որ բան TLS-ի վերևում:

Կոմպակտ սերիականացում: Տեսնելով այս տվյալների կառուցվածքը, որը նման է JSON-ին, ես հիշում եմ, որ կան դրա երկուական տարբերակները: Եկեք նշենք MsgPack-ը որպես անբավարար ընդարձակելի, բայց կա, օրինակ, CBOR - ի դեպ, ստանդարտ նկարագրված է. RFC 7049. Հատկանշական է նրանով, որ սահմանում է պիտակներ, որպես ընդլայնման մեխանիզմ, և միջ արդեն ստանդարտացված հասանելի:

  • 25 + 256 - կրկնվող տողերի փոխարինում գծի համարին հղումով, նման էժան սեղմման մեթոդ
  • 26 - սերիականացված Perl օբյեկտ դասի անվան և կոնստրուկտորի արգումենտներով
  • 27 - սերիականացված լեզվից անկախ օբյեկտ՝ տիպի անվան և կոնստրուկտորի արգումենտներով

Դե, ես փորձեցի սերիականացնել նույն տվյալները TL-ում և CBOR-ում՝ միացված տողերի և օբյեկտների փաթեթավորման դեպքում: Արդյունքը սկսեց տարբերվել հօգուտ CBOR-ի՝ ինչ-որ մեգաբայթից.

cborlen=1039673 tl_len=1095092

Այնպես որ, եզրակացությունԿան էապես ավելի պարզ ձևաչափեր, որոնք ենթակա չեն համաժամացման ձախողման կամ անհայտ նույնացուցիչի խնդրին, համեմատելի արդյունավետությամբ:

Արագ կապի հաստատում. Սա նշանակում է զրոյական RTT վերամիացումից հետո (երբ բանալին արդեն ստեղծվել է մեկ անգամ) - կիրառելի է MTProto-ի առաջին հաղորդագրությունից, բայց որոշ վերապահումներով - հարվածեք նույն աղին, նիստը փտած չէ և այլն: Ի՞նչ է մեզ առաջարկում TLS-ի փոխարեն: Մեջբերում թեմայի վերաբերյալ.

TLS-ում PFS-ն օգտագործելիս, TLS նստաշրջանի տոմսերը (RFC 5077) վերսկսել գաղտնագրված նիստը՝ առանց բանալիների վերաբանակցման և առանց հիմնական տեղեկատվությունը սերվերում պահելու: Առաջին կապը բացելիս և բանալիներ ստեղծելիս սերվերը գաղտնագրում է կապի վիճակը և այն փոխանցում հաճախորդին (սեսիայի տոմսի տեսքով): Համապատասխանաբար, երբ կապը վերսկսվում է, հաճախորդը սեսիայի տոմսը, ներառյալ նիստի բանալին, հետ է ուղարկում սերվեր: Տոմսը ինքնին գաղտնագրված է ժամանակավոր բանալիով (սեսիայի տոմսի բանալի), որը պահվում է սերվերում և պետք է բաշխվի բոլոր առջևի սերվերների միջև, որոնք մշակում են SSL-ը կլաստերային լուծումներով։[10] Այսպիսով, նստաշրջանի տոմսի ներդրումը կարող է խախտել PFS-ն, եթե ժամանակավոր սերվերի ստեղները վտանգված են, օրինակ, երբ դրանք պահվում են երկար ժամանակ (OpenSSL, nginx, Apache-ն դրանք լռելյայն պահում են ծրագրի ողջ տևողության համար. հայտնի կայքերն օգտագործում են բանալին մի քանի ժամով, մինչև օր):

Այստեղ RTT-ը զրոյական չէ, պետք է փոխանակել առնվազն ClientHello-ն և ServerHello-ն, որից հետո հաճախորդը կարող է ուղարկել տվյալներ Finished-ի հետ միասին: Բայց այստեղ պետք է հիշել, որ մենք չունենք համացանց՝ իր նորաբաց կապերի փունջով, այլ սուրհանդակ, որի միացումը հաճախ մեկ և քիչ թե շատ երկարատև, համեմատաբար կարճ խնդրանքներ է վեբ էջերին. ամեն ինչ մուլտիպլեքսացված է։ ներքին. Այսինքն՝ միանգամայն ընդունելի է, եթե մենք չհանդիպենք մետրոյի իսկապես վատ հատվածի։

Ուրիշ բան մոռացե՞լ ես։ Գրեք մեկնաբանություններում։

Շարունակելի!

Այս գրառումների շարքի երկրորդ մասում կդիտարկենք ոչ թե տեխնիկական, այլ կազմակերպչական հարցեր՝ մոտեցումներ, գաղափարախոսություն, ինտերֆեյս, օգտատերերի նկատմամբ վերաբերմունք և այլն։ Հիմնվելով, սակայն, այստեղ ներկայացված տեխնիկական տեղեկատվության վրա։

Երրորդ մասը կշարունակի վերլուծել տեխնիկական բաղադրիչը / զարգացման փորձը: Դուք կսովորեք, մասնավորապես.

  • պանդեմոնիի շարունակությունը TL տեսակների բազմազանությամբ
  • անհայտ բաներ ալիքների և սուպերխմբերի մասին
  • ինչու են երկխոսություններն ավելի վատ, քան ցուցակը
  • բացարձակ և հարաբերական հաղորդագրության հասցեավորման մասին
  • որն է տարբերությունը լուսանկարի և պատկերի միջև
  • ինչպես են էմոջիները խանգարում շեղ տեքստին

և այլ հենակներ! Մնացեք մեզ հետ!

Source: www.habr.com

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