Telegram хаттамасына және ұйымдастырушылық тәсілдеріне сын. 1-бөлім, техникалық: клиентті нөлден жазу тәжірибесі - TL, MT

Жақында Habré сайтында Telegram-дың қаншалықты жақсы екендігі, ағайынды Дуровтардың желілік жүйелерді құруда қаншалықты тамаша және тәжірибелі екендігі және т.б. туралы хабарламалар жиі шыға бастады. Сонымен қатар, өте аз адамдар техникалық құрылғыға шынымен еніп кетті - олар көбінде өте қарапайым (және MTProto-дан мүлдем өзгеше) JSON негізіндегі Bot API пайдаланады және әдетте жай ғана қабылдайды. сенім туралы мессенджердің айналасында айналатын барлық мақтаулар мен пиарлар. Бір жарым жыл бұрын менің NPO Эшелондағы әріптесім Василий (өкінішке орай, оның Хабредегі аккаунты жобамен бірге жойылды) Perl-де өзінің Telegram клиентін нөлден бастап жаза бастады, кейінірек осы жолдардың авторы қосылды. Неліктен Perl, кейбіреулер бірден сұрайды? Өйткені мұндай жобалар басқа тілдерде де бар.Негізі бұл жерде мәселе емес, қай жерде басқа тіл болуы мүмкін біткен кітапхана, және сәйкесінше автор барлық жолмен жүруі керек нөлден бастап. Оның үстіне, криптография - бұл сену, бірақ тексеру. Қауіпсіздікке бағытталған өніммен сіз жай ғана сатушының дайын кітапханасына сене алмайсыз және оған соқыр сене алмайсыз (бірақ бұл екінші бөлімде толығырақ тақырып). Қазіргі уақытта кітапхана «орташа» деңгейде жақсы жұмыс істейді (кез келген API сұрауларын жасауға мүмкіндік береді).

Дегенмен, бұл жазбалар сериясында криптография мен математика көп болмайды. Бірақ басқа да көптеген техникалық бөлшектер мен архитектуралық балдақтар болады (бұл нөлден жазбайтын, бірақ кітапхананы кез келген тілде пайдаланатындар үшін де пайдалы болады). Сонымен, негізгі мақсат клиентті нөлден бастап жүзеге асыруға тырысу болды ресми құжаттамаға сәйкес. Яғни, ресми клиенттердің бастапқы коды жабық делік (қайтадан, екінші бөлімде біз бұл шын мәнінде не деген тақырыпты толығырақ ашамыз. бұл болады солай), бірақ, ескі күндердегідей, мысалы, RFC сияқты стандарт бар - клиентті бастапқы кодқа «қадағаламай», тіпті ресми (Telegram Desktop, мобильді) спецификацияға сәйкес жазуға болады ма? ), тіпті бейресми телемарафон?

Мазмұны:

Құжаттама ... бар ма? Рас па?..

Осы мақалаға арналған жазбалардың фрагменттері өткен жазда жинала бастады. Осы уақыттың барлығы ресми сайтта https://core.telegram.org құжаттама 23-деңгей бойынша болды, яғни. 2014 жылы бір жерде тұрып қалды (есіңізде ме, ол кезде арналар әлі болған жоқ па?). Әрине, теориялық тұрғыдан бұл 2014 жылы сол уақытта функционалдығы бар клиентті енгізуге мүмкіндік беруі керек еді. Бірақ бұл күйдің өзінде құжаттама, біріншіден, толық емес, екіншіден, бір жерде өзіне-өзі қайшы келетін. Бір айдан сәл астам уақыт бұрын, 2019 жылдың қыркүйегінде болды кездейсоқ емес Сайтта толығымен жаңа 105-қабатқа арналған құжаттаманың үлкен жаңартуы бар екені анықталды, енді барлығын қайтадан оқу керек екендігі туралы ескертпе бар. Шынында да, көптеген мақалалар қайта қаралды, бірақ көпшілігі өзгеріссіз қалды. Сондықтан, төменде құжаттамаға қатысты сынды оқығанда, олардың кейбіреулері енді өзекті емес, бірақ кейбіреулері әлі де маңызды екенін есте ұстаған жөн. Өйткені, қазіргі әлемде 5 жыл өте көп емес, бірақ өте көптеген. Содан бері (әсіресе, содан бері лақтырылған және қайта тірілген геочаттарды есепке алмасаңыз), схемадағы API әдістерінің саны жүзден екі жүз елуден астамға дейін өсті!

Жас жазушы ретінде неден бастайсыз?

Сіз нөлден жазасыз ба немесе, мысалы, сияқты дайын кітапханаларды пайдалансаңыз, маңызды емес Python үшін телетон немесе PHP үшін Madeline, кез келген жағдайда алдымен сізге қажет болады өтінішіңізді тіркеңіз - параметрлерін алу 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 тілі, ол қазірдің өзінде тривиальды сұрау мен жауаптың мысалын қамтығанымен, әдеттегі жағдайларға мүлдем жауап бермейді, бұл сізге орыс тілінен ағылшын тіліне аударылған математиканы тағы сегіз кірістірілгенде қайталау арқылы өтуге тура келетінін білдіреді. беттер!

Функционалдық тілдермен және автоматты түрдегі қорытындымен таныс оқырмандар, әрине, бұл тілдегі сипаттамаларды көрді, тіпті мысалдан да әлдеқайда таныс және бұл әдетте жаман емес деп айта алады. Бұған қарсылықтар:

  • иә, цель жақсы естіледі, бірақ өкінішті қол жеткізілмеді
  • Ресей университеттеріндегі білім тіпті IT мамандықтары арасында да өзгереді - бәрі бірдей тиісті курсты оқи бермейді
  • Ақырында, біз көретініміздей, іс жүзінде солай қажет емес, өйткені сипатталған TL-нің шектеулі жиыны ғана пайдаланылады

Айтқандай ЛеоНерд арнада #perl FreeNode IRC желісінде Telegram-дан Matrix-қа арналған қақпаны іске асыруға тырысады (дәйексөздің аудармасы жадтан дәл емес):

Теру теориясымен алғаш рет танысқан адам қуанып, онымен ойнауға тырысқандай сезінеді, бұл іс жүзінде қажет болды ма деп ойламайды.

Қарапайым түрлердің (int, long және т. векторы. Яғни, шын мәнінде, массив, егер сіз нәтижелі заттарды өз аттарымен атасаңыз.

Бірақ бұрын

Қолданбайтындар үшін 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---, онда синтаксис өзгеріссіз қалады, бірақ мағынасы басқаша болады: конструктор RPC функциясының атауына айналады, өрістер параметрлерге айналады (жақсы, яғни төменде сипатталғандай дәл сол берілген құрылым қалады, бұл жай ғана берілген мағына болады) және "полиморфты түрі" - қайтарылған нәтиженің түрі. Рас, ол әлі де полиморфты болып қалады - бөлімде ғана анықталған ---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«натурал сан» дегенді білдіреді. Яғни, шын мәнінде, unsigned int - айтпақшы, нақты схемаларда таңбасыз сандар табылған жалғыз жағдай. Сонымен, келесіде сұрақ белгісі бар конструкция, бұл өріс дегенді білдіреді - ол сілтеме берілген өрісте сәйкес бит орнатылған болса ғана сымда болады (шамамен үштік оператор сияқты). Мәселен, бұл бит қосулы болды делік, содан кейін сияқты өрісті оқу керек Type, біздің мысалда 2 конструктор бар. Біреуі бос (тек идентификатордан тұрады), екіншісінде өріс бар ids түрімен ids:Vector<long>.

Үлгілер де, генериктер де жақсы немесе Java деп ойлайсыз. Бірақ жоқ. Шамамен. Бұл жалғыз нақты тізбектердегі бұрыштық жақшалар жағдайы және ол ТЕК Вектор үшін қолданылады. Байт ағынында бұл Vector түрінің өзі үшін 4 CRC32 байт болады, әрқашан бірдей, содан кейін 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 XNUMX байт. Ал егер бұл Vector<Type2>, ол тек бір өрістен тұрады int және бұл түрдегі жалғыз - бізге 10000xabcdef0 34 рет, содан кейін 4 байт қайталау керек пе? int, немесе тіл конструктордан біз үшін мұны КӨРСЕТЕ алады fixedVec және 80000 40000 байттың орнына тек XNUMX XNUMX-ды қайта жіберіңіз бе?

Бұл мүлдем бос теориялық сұрақ емес - сіз топ пайдаланушыларының тізімін аласыз елестетіңіз, олардың әрқайсысының идентификаторы, аты, тегі бар - ұялы байланыс арқылы тасымалданатын деректер көлемінің айырмашылығы айтарлықтай болуы мүмкін. Бұл бізге жарнамаланған Telegram сериализациясының тиімділігі.

Сонымен ...

Шығаруға келмейтін вектор

Егер сіз комбинаторлардың және шамамен сипаттама беттерін аралауға тырыссаңыз, вектордың (тіпті матрицаның) кортеждер арқылы бірнеше парақтарды ресми түрде шығаруға тырысатынын көресіз. Бірақ соңында олар соққыға ұшырайды, соңғы қадам өткізілмейді және вектордың анықтамасы жай ғана беріледі, ол да түрге байланысты емес. Мұнда не болды? Тілдерде бағдарламалау, әсіресе функционалды, құрылымды рекурсивті сипаттау өте тән - компилятор өзінің жалқау бағалауымен бәрін түсінеді және оны жасайды. Тілде деректерді сериялау бірақ ТИІМДІЛІК қажет: жай ғана сипаттау жеткілікті тізім, яғни. екі элементтің құрылымы - біріншісі деректер элементі, екіншісі - бірдей құрылымның өзі немесе құйрық үшін бос орын (бума) (cons) Лиспте). Бірақ бұл қажет болатыны анық әрқайсысының элемент түрін сипаттау үшін қосымша 4 байтты (TL жағдайында CRC32) жұмсайды. Массивті сипаттау оңай бекітілген өлшем, бірақ бұрын белгісіз ұзындықтағы массив жағдайында біз үземіз.

TL векторды шығаруға мүмкіндік бермейтіндіктен, оны бүйірге қосу керек болды. Ақырында құжаттама былай дейді:

Серияландыру әрқашан t түріндегі айнымалының арнайы мәніне тәуелді емес бірдей конструкторды пайдаланады (const 0x1cb5c415 = crc32 («вектор t:Түр # [ t ] = Вектор t»).

Қосымша параметрдің t мәні сериялауға қатыспайды, себебі ол нәтиже түрінен алынған (сериясыздандыруға дейін әрқашан белгілі).

Жақынырақ қараңыз: vector {t:Type} # [ t ] = Vector t - бірақ еш жерде жоқ анықтаманың өзі бірінші сан вектордың ұзындығына тең болуы керек деп айтпайды! Және ол ешқайдан ілеспейді. Бұл есте сақтау және өз қолыңызбен жүзеге асыру қажет берілген. Басқа жерде құжаттамада бұл түрдің жалған екендігі туралы нақты айтылған:

Vector t полиморфты псевдотипі «түр» болып табылады, оның мәні қораптағы немесе жалаңаш түрдегі кез келген t түрінің мәндерінің тізбегі болып табылады.

... бірақ оған назар аудармайды. Математиканы тереңдетуден шаршаған кезде (сізге университет курсынан таныс болуыңыз мүмкін) ұпай жинап, онымен іс жүзінде қалай жұмыс істеуге болатынын көруді шешкен кезде, сіздің ойыңызда әсер қалады: мұнда Serious Mathematics негізделген. , анық Cool People (екі математик - ACM жеңімпазы), және тек ешкім емес. Мақсат – шашу – орындалды.

Айтпақшы, сан туралы. Еске түсіру # бұл синоним nat, натурал сан:

Түрлі өрнектер бар (typeexpr) және сандық өрнектер (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 ВКонтакте желісінде Дуровтың үлесін сатумен белгілі оқиғаларға дейін дүниеге келген және (Күдіксіз), тіпті 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>;

Бұл int - Type жұптарының векторы ретінде хэшмап үлгі түрінің анықтамасы. C++ тілінде ол келесідей болады:

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

солай, alpha - кілт сөз! Бірақ тек C++ тілінде T жазуға болады, бірақ альфа, бета жазу керек... Бірақ 8 параметрден аспайды, фантазия тетада аяқталды. Бір кездері Санкт-Петербургте шамамен осындай диалогтар болған сияқты:

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

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

Бірақ бұл «жалпы» ТЖ-ны бірінші рет іске асыру туралы болды. Нақты Telegram клиенттеріндегі енгізулерді қарастыруға көшейік.

Василийдің сөзі:

Василий, [09.10.18 17:07] Ең бастысы, олар бір топ абстракцияны бұрап, сосын оларға болтты соғып, коджерге балдақ кигізгені үшін есек ыстық.
Нәтижесінде доктардан алдымен pilot.jpg
Содан кейін jekichan.webp кодынан

Әрине, алгоритмдер мен математикадан хабардар адамдардан олар Aho, Ullman оқығанын және ондаған жылдар бойы DSL компиляторларын жазуға арналған де-факто салалық стандартты құралдарымен таныс екенін күтуге болады, солай емес пе? ..

Авторы бойынша telegram-cli Виталий Валтман, TLO пішімінің оның (cli) шегінен тыс пайда болуынан түсінуге болады, команда мүшесі - енді TL талдауға арналған кітапхана бөлінген. бөлекоған қандай әсер қалды TL талдаушысы? ..

16.12 04:18 Василий: менің ойымша, біреу lex + yacc тілін меңгермеген
16.12 04:18 Василий: әйтпесе мен түсіндіре алмаймын
16.12 04:18 Василий: жақсы, немесе олар ВК-дағы жолдар саны үшін төленген
16.12 04:19 Василий: басқалардың 3k+ жолы<censored> талдаушының орнына

Мүмкін ерекшелік? Қалай көрейік жасайды бұл РЕСМИ клиент — Telegram жұмыс үстелі:

    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] және бұл жерде сіз мұндай ТЖ қажет пе туралы ойлануыңыз керек.
егер мен балама енгізулермен араласқым келсе, жол үзілімдерін кірістіруді бастайтын едім, талдаушылардың жартысы көп жолды анықтамаларда үзіледі
tdesktop, алайда

Бір лайнерлер туралы ойды есте сақтаңыз, біз оған сәл кейінірек ораламыз.

Жарайды, telegram-cli бейресми, Telegram жұмыс үстелі ресми, ал басқалары ше? Кім біледі?.. Android клиенттік кодында схемалық талдаушы мүлде болған жоқ (бұл ашық бастапқы код туралы сұрақтар тудырады, бірақ бұл екінші бөлікке арналған), бірақ кодтың бірнеше күлкілі бөліктері болды, бірақ олар туралы төменгі бөлім.

Тәжірибеде сериялау тағы қандай сұрақтарды тудырады? Мысалы, олар, әрине, бит өрістерімен және шартты өрістермен бұрмаланды:

Василий: flags.0? true
өрістің бар екенін және жалау орнатылған болса, шын екенін білдіреді

Василий: flags.1? int
өрістің бар екенін және сериядан шығару қажет екенін білдіреді

Василий: Әй, күйбе, не істеп жатырсың!
Василий: Құжаттың бір жерінде true нөлдік ұзындықтың жалаң түрі деп жазылған, бірақ олардың құжаттарынан бірдеңе жинау мүмкін емес.
Василий: Ашық іске асыруда да мұндай нәрсе жоқ, бірақ балдақтар мен тіректер көп

Телемарафон ше? 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] және жұмыс үстелінде?

Василий, [22.06.18 19:23] Бірақ реттеушілердегі олардың TL талдаушысы оны жемейтін шығар

// parsed manually

TL - әдемі абстракция, оны ешкім толық жүзеге асырмайды

Ал олардың сұлба нұсқасында % жоқ

Бірақ мұнда құжаттама өзіне қайшы келеді, сондықтан xs

Бұл грамматикадан табылды, олар семантиканы сипаттауды ұмытып кетуі мүмкін

Жарайды, сіз 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 КБ бөлік_өлшеміне біркелкі бөлінуі керек)

Соңғы бөліктің өлшемі бөлік_өлшемінен аз болған жағдайда, бұл шарттарды қанағаттандыруы міндетті емес.

Әрбір бөліктің реттік нөмірі болуы керек, файл_бөлігі, мәні 0 мен 2,999 аралығында.

Файлды бөлгеннен кейін оны серверде сақтау әдісін таңдау керек. пайдалану upload.saveBigFilePart файлдың толық өлшемі 10 МБ-тан асатын жағдайда және upload.saveFilePart кішірек файлдар үшін.
[…] келесі деректерді енгізу қателерінің бірі қайтарылуы мүмкін:

  • FILE_PARTS_INVALID - бөліктердің саны жарамсыз. Мән арасында емес 1..3000

Олардың кез келгені схемада бар ма? Бұл TL арқылы қандай да бір түрде білдіруге бола ма? Жоқ. Кешіріңіз, тіпті ескірген Турбо Паскаль берген түрлерін сипаттай алды. диапазондар. Және ол қазір жақсы белгілі тағы бір нәрсені жасай алады enum - мәндердің тіркелген (аз) санының санауынан тұратын тип. С сияқты тілдерде - сандық, ескеріңіз, біз осы уақытқа дейін тек түрлері туралы айттық. сандар. Бірақ сонымен қатар массивтер, жолдар бар ... мысалы, бұл жолда тек телефон нөмірі болуы мүмкін екенін сипаттау жақсы болар еді, солай емес пе?

Мұның ешқайсысы TL-де жоқ. Бірақ, мысалы, JSON схемасында бар. Егер басқа біреу 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.

содан кейін бірнеше мегабайт стек ізі (жақсы, сонымен бірге журналға тіркеу бекітілді). Ақыр соңында, егер сіздің TL-де бірдеңе танылмаса - бұл қолтаңбалар бойынша екілік, одан әрі ағында БАРЛЫҚ барса, декодтау мүмкін емес болады. Мұндай жағдайда не істеу керек?

Кез келген адамның ойына бірінші келетін нәрсе - ажыратып, қайталап көру. Көмек бермеді. Біз CRC32-ді google-де іздедік - бұл 73-схемадағы нысандар болып шықты, бірақ біз 82-схемада жұмыс істедік. Біз журналдарды мұқият қараймыз - екі түрлі схеманың идентификаторлары бар!

Мүмкін мәселе тек біздің бейресми клиентте болуы мүмкін бе? Жоқ, біз Telegram Desktop 1.2.17 бағдарламасын іске қосамыз (бірқатар Linux дистрибутивтерімен қамтамасыз етілген нұсқа), ол Ерекшелік журналына жазады: MTP күтпеген түр идентификаторы #b5223b0f MTPMessageMedia-да оқылады…

Telegram хаттамасына және ұйымдастырушылық тәсілдеріне сын. 1-бөлім, техникалық: клиентті нөлден жазу тәжірибесі - TL, MT

Google осыған ұқсас мәселе бейресми клиенттердің бірінде болғанын көрсетті, бірақ содан кейін нұсқа нөмірлері және сәйкесінше болжамдар басқаша болды ...

Сонымен не істеу керек? Василий екеуміз бөлінді: ол схеманы 91-ге жаңартуға тырысты, мен бірнеше күн күтіп, 73-ке тырысуды шештім. Екі әдіс де жұмыс істеді, бірақ олар эмпирикалық болғандықтан, қанша нұсқаны көтеру керек екенін түсіну жоқ. немесе төмен, не қанша уақыт күту керек.

Кейінірек мен жағдайды жаңғырта алдым: біз клиентті іске қосамыз, оны өшіреміз, схеманы басқа қабатқа қайта құрастырамыз, қайта іске қосамыз, мәселені қайтадан шешеміз, алдыңғыға ораламыз - ау, схеманы ауыстыру және клиентті бірнеше рет қайта іске қосу жоқ. минут көмектеседі. Сіз әртүрлі деңгейлерден деректер құрылымдарының қоспасын аласыз.

Түсіндіру? Әртүрлі жанама белгілерден болжауға болатындай, сервер әртүрлі машиналардағы көптеген әртүрлі процестерден тұрады. Сірә, «буферлеуге» жауапты серверлердің бірі кезекке жоғары тұрғандардың бергенін қояды және олар оны генерация кезіндегі схемада берді. Бұл кезек «шірігенге» дейін бұл туралы ештеңе істеу мүмкін болмады.

Әйтпесе... бірақ бұл сұмдық балдақ па?!.. Жоқ, ақымақ ойлар туралы ойланбас бұрын, ресми клиенттердің кодына назар аударайық. Android нұсқасында біз ешқандай TL талдаушысын таппаймыз, бірақ (de) сериялауы бар үлкен файлды (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;

Мұндағы «қолмен жасалған» түсініктемесі бұл файлдың бір бөлігі ғана қолмен жазылғанын білдіреді (сіз техникалық қызмет көрсету қорқынышын елестете аласыз ба?), ал қалған бөлігі машинада жасалған. Алайда, содан кейін тағы бір сұрақ туындайды - бұл дереккөздер бар толық емес (Linux ядросындағы GPL астында la blobs), бірақ бұл екінші бөлімнің тақырыбы.

Бірақ жеткілікті. Осы сериализацияның бәрі қуып жететін хаттамаға көшейік.

MT Proto

Ендеше ашайық Жалпы сипаттама и хаттаманың толық сипаттамасы ал ең бірінші сүрінетініміз – терминология. Және бәрінің көптігімен. Жалпы, бұл Telegram-тың сауда белгісі сияқты - әртүрлі орындардағы заттарды әртүрлі тәсілдермен немесе әртүрлі заттарды бір сөзбен шақыру немесе керісінше (мысалы, жоғары деңгейлі API-де стикерлер бумасын көрсеңіз - бұл Сіз ойлағандай емес).

Мысалы, «хабарлама» (хабарлама) және «сеанс» (сеанс) - бұл жерде олар Telegram клиентінің әдеттегі интерфейсінен басқа нәрсені білдіреді. Хабарламада бәрі түсінікті, оны OOP тұрғысынан түсіндіруге болады немесе жай ғана «пакет» сөзі деп атауға болады - бұл төмен, тасымалдау деңгейі, интерфейстегідей хабарламалар жоқ, көп нәрсе бар қызмет көрсететіндердің. Бірақ сессия ... бірақ бірінші нәрсе.

Тасымалдау қабаты

Біріншісі - көлік. Бізге 5 нұсқа туралы айтылады:

  • TCP
  • Вебсокет
  • HTTPS арқылы веб-сокет
  • HTTP
  • HTTPS

Василий, [15.06.18 15:04] Сондай-ақ UDP көлігі бар, бірақ ол құжатталмаған

Және TCP үш нұсқада

Біріншісі TCP арқылы UDP-ге ұқсас, әрбір пакетте реттік нөмір және crc бар
Арбадағы доктарды оқу неге соншалықты ауыр?

Жарайды қазір TCP қазірдің өзінде 4 нұсқада:

  • Қысқартылған
  • аралық
  • толтырылған аралық
  • толық

Жарайды, MTProxy үшін Padded аралық, бұл белгілі оқиғаларға байланысты кейінірек қосылды. Бірақ неге тағы екі нұсқа (барлығы үш), біреуін жасауға болады? Төртеуі де негізгі MTProto-ның ұзындығы мен пайдалы жүктемесін орнату жолында ғана ерекшеленеді, олар әрі қарай талқыланады:

  • Қысқартылғанда ол 1 немесе 4 байт, бірақ 0xef емес
  • Intermediate-де бұл ұзындығы 4 байт және өріс және клиент бірінші рет жіберуі керек 0xeeeeeeee аралық екенін көрсету үшін
  • Толық, сетевойдың көзқарасы бойынша ең тәуелді: ұзындығы, реттік нөмірі және негізінен MTProto, дене, CRC32 болып табылатын БІР ЕМЕС. Иә, мұның бәрі TCP арқылы. Бұл бізге байттардың сериялық ағыны түріндегі сенімді тасымалдауды қамтамасыз етеді, ешқандай реттілік қажет емес, әсіресе бақылау сомасы. Жарайды, енді маған TCP-де 16-биттік бақылау сомасы бар екеніне қарсы болады, сондықтан деректердің бүлінуі орын алады. Керемет, егер бізде хэштері 16 байттан асатын криптографиялық протокол бар болса, бұл қателердің барлығы, тіпті одан да көп - SHA сәйкессіздігі жоғары деңгейде ұсталады. Бұған қатысты CRC32-де ешқандай мәселе жоқ.

Ұзындығы бір байт мүмкін болатын Қысқартылғанды ​​«4 байт деректерді теңестіру қажет болған жағдайда» ақтайтын аралық деңгеймен салыстырайық, бұл өте бос сөз. Не, Telegram бағдарламашылары соншалықты ебедейсіз, олар розеткадан деректерді тураланған буферге оқи алмайды деп саналады? Мұны әлі де істеу керек, себебі оқу сізге кез келген байт санын қайтара алады (сонымен қатар прокси серверлер бар, мысалы ...). Немесе, екінші жағынан, егер бізде әлі де 16 байттан жоғары толтырғыштар болса, қысқартылғанмен неге алаңдау керек - 3 байтты үнемдеңіз кейде ?

Николай Дуров велосипедтерді, соның ішінде желілік протоколдарды нақты практикалық қажетсіз ойлап табуды жақсы көреді деген әсер қалдырады.

Басқа көлік опциялары, соның ішінде. Web және MTProxy, егер сұраныс болса, біз қазір қарастырмаймыз, мүмкін басқа постта. Біз 2018 жылы шығарылғаннан кейін көп ұзамай провайдерлер оны бұғаттауды тез үйренген дәл осы MTProxy туралы еске саламыз. блокты айналып өтутуралы пакет өлшемі! Сондай-ақ, C тілінде жазылған MTProxy сервері (қайтадан Уолтман) Linux ерекшеліктерімен қажетсіз байланыстырылған, бірақ ол мүлде қажет болмаса да (Фил Кулин растайды) және Go немесе Node.js сайтында ұқсас сервер болуы. жүз жолдан аз болады.

Бірақ біз бұл адамдардың техникалық сауаттылығы туралы басқа мәселелерді қарастырғаннан кейін бөлімнің соңында қорытынды жасаймыз. Әзірге олар MTProto сеансын орналастырған 5-ші OSI деңгейіне, сессияға көшейік.

Кілттер, хабарламалар, сеанстар, Диффи-Хеллман

Олар оны мүлде дұрыс қойған жоқ... Сеанс - белсенді сеанстар астындағы интерфейсте көрінетін сеанс емес. Бірақ ретімен.

Telegram хаттамасына және ұйымдастырушылық тәсілдеріне сын. 1-бөлім, техникалық: клиентті нөлден жазу тәжірибесі - TL, MT

Мұнда біз транспорттық қабаттан белгілі ұзындықтағы байт жолын алдық. Бұл шифрланған хабарлама немесе ашық мәтін - егер біз әлі де негізгі келіссөздер сатысында болсақ және оны шынымен жасап жатсақ. Біз «кілт» деп аталатын ұғымдардың қайсысы туралы айтып отырмыз? Telegram командасының өзі үшін бұл мәселені түсіндіріп көрейік (мен өз құжаттамамды ағылшын тілінен немесе таңғы 4-те шаршаған миға аударғаным үшін кешірім сұраймын, кейбір фразаларды сол күйінде қалдыру оңайырақ болды):

деп аталатын екі нысан бар сессия - әрбір сеанс тұтас құрылғыға/ОЖ-ға сәйкес келетін «ағымдағы сеанстар» астындағы ресми клиенттердің пайдаланушы интерфейсіндегі біреуі.
Екінші MTProto сеансы, оның ішінде хабарлама реттік нөмірі (төмен деңгейлі мағынада) бар және қайсысы әртүрлі TCP қосылымдары арасында созылуы мүмкін. Бірнеше MTProto сеанстарын бір уақытта орнатуға болады, мысалы, файлдарды жүктеуді жылдамдату үшін.

Осы екеуінің арасында сессиялар ұғым болып табылады авторизациялау. Деградация жағдайында бұл туралы айтуға болады UI сеансы сияқты авторизациялауБірақ, өкінішке орай, бұл күрделі. Біз қараймыз:

  • Жаңа құрылғыдағы пайдаланушы алдымен жасайды аутентификациялық_кілт және оны есепке байланыстырады, мысалы, SMS арқылы - сондықтан авторизациялау
  • Бұл біріншісінің ішінде болды MTProto сеансы, бар session_id ішінде.
  • Бұл қадамда комбинация авторизациялау и session_id шақыруға болады данасы - бұл сөз кейбір клиенттердің құжаттамасында және кодында кездеседі
  • Содан кейін клиент аша алады бірнеше MTProto сеанстары бірдей астында аутентификациялық_кілт - сол тұрақты токқа.
  • Содан кейін бір күні клиент файлды сұрауы керек басқа DC - және бұл тұрақты ток үшін жаңасы жасалады аутентификациялық_кілт !
  • Жүйеге бұл тіркелетін жаңа пайдаланушы емес, дәл солай екенін айту авторизациялау (UI сеансы), клиент API қоңырауларын пайдаланады auth.exportAuthorization үйдегі DC auth.importAuthorization жаңа DC-де.
  • Бәрібір, бірнеше ашық болуы мүмкін MTProto сеанстары (әрқайсысы өз session_id) осы жаңа DC, астында оның аутентификациялық_кілт.
  • Ақырында, клиент мінсіз форвард құпиясын қалауы мүмкін. Әр аутентификациялық_кілт болды Тұрақты кілт - тұрақты токқа - және клиент қоңырау шала алады auth.bindTempAuthKey пайдалану үшін уақытша аутентификациялық_кілт - және тағы бір рет temp_auth_key тұрақты ток үшін, барлығына ортақ MTProto сеанстары осы DC.

Айта кетейік тұз (және болашақ тұздар) да бір аутентификациялық_кілт анау. барлығына ортақ MTProto сеанстары бірдей тұрақты токқа.

«Әртүрлі TCP қосылымдары» нені білдіреді? Бұл дегенді білдіреді сияқты нәрсе веб-сайттағы авторизация куки - ол осы серверге көптеген TCP қосылымдарын сақтайды (тірі қалады), бірақ бір күні ол нашарлайды. Тек HTTP-тен айырмашылығы, MTProto-да сеанс ішінде хабарламалар ретімен нөмірленеді және расталады, олар туннельге кірді, байланыс үзілді - жаңа қосылымды орнатқаннан кейін сервер осы сеанста жеткізбегеннің барлығын мейірімділікпен жібереді. алдыңғы TCP қосылымы.

Дегенмен, жоғарыдағы ақпарат көп айға созылған сот процестерінен кейінгі сығымдау. Осы уақытта біз өз клиентімізді нөлден бастап іске асырамыз ба? - басына оралайық.

Сондықтан біз жасаймыз auth_key туралы Telegram-дан Diffie-Hellman нұсқалары. Құжаттаманы түсінуге тырысайық ...

Василий, [19.06.18 20:05] data_with_hash := SHA1(деректер) + деректер + (кез келген кездейсоқ байттар); ұзындығы 255 байтқа тең болатындай;
шифрланған_деректер := RSA(хэшпен_деректер, сервер_қоғамдық_кілт); 255 байт ұзын сан (үлкен индиан) қажетті модуль бойынша қажетті қуатқа көтеріледі және нәтиже 256 байт сан ретінде сақталады.

Олар DH есірткісін алды

Дені сау адамның DH-ге ұқсамайды
dx-де екі ашық кілт жоқ

Ақырында, біз оны анықтадық, бірақ шөгінді қалды - жұмыстың дәлелі клиенттің санды көбейте алатынын көрсетеді. DoS шабуылдарынан қорғау түрі. Ал RSA кілті бір бағытта, негізінен шифрлау үшін ғана пайдаланылады new_nonce. Бірақ бұл қарапайым болып көрінетін операция сәтті болғанымен, сіз немен бетпе-бет келуіңіз керек?

Василий, [20.06.18 00:26] Мен қосымша сұрауға әлі жеткен жоқпын

Мен 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 бит өлшемін алдық. Ары қарай не? Содан кейін біз бұл кілттің төменгі 1024 биттері ешқандай жолмен пайдаланылмайтынын білеміз ... бірақ қазір бұл туралы ойланайық. Бұл қадамда бізде сервермен ортақ құпия бар. TLS сеансының аналогы орнатылды, бұл өте қымбат процедура. Бірақ сервер біздің кім екенімізді әлі білмейді! Әлі емес, шын мәнінде рұқсат. Анау. егер сіз бұрын ICQ-де болғандай «логин-пароль» немесе кем дегенде 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 арқылы шифрлауды жалғастыру қажет.

Хабарлама кілті авторизация кілтінен алынған 128 байтпен алдын ала жазылған толтыру байттарын қоса алғанда, хабарлама корпусының SHA256 ортаңғы 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 жұмыс үстелінің соңғы нұсқаларында кэште user_data басқа пішім бұрыннан қолданылған - CTR режимінде AES бар.

Василий, [21.06.18 01:27] О, мен IGE не екенін білдім: IGE бастапқыда Kerberos үшін "аутентификациялау шифрлау режимінің" бірінші әрекеті болды. Бұл сәтсіз әрекет болды (ол тұтастықты қорғауды қамтамасыз етпейді) және оны жоюға тура келді. Бұл жұмыс істейтін аутентификациялық шифрлау режиміне арналған 20 жылдық ізденістің басы болды, ол жақында OCB және GCM сияқты режимдерде аяқталды.

Ал енді арба жағынан дәлелдер:

Николай Дуров басқаратын Telegram командасының құрамында алты ACM чемпионы бар, олардың жартысы математика ғылымдарының кандидаттары. MTProto-ның қазіргі нұсқасын шығару үшін оларға екі жылдай уақыт қажет болды.

Не қызық. Төменгі деңгейге екі жыл

Немесе біз тек TL ала аламыз

Жарайды, шифрлауды және басқа да нюанстарды жасадық делік. Ақырында TL-серияланған сұрауларды жіберіп, жауаптарды сериядан шығаруға болады ма? Сонымен не жіберу керек және қалай? Міне, әдіс initConnectionмүмкін бұл?

Василий, [25.06.18 18:46] Қосылымды инициализациялайды және пайдаланушының құрылғысы мен қолданбасындағы ақпаратты сақтайды.

Ол қолданба_идентификаторын, құрылғы_моделін, жүйе_нұсқасын, қолданба_нұсқасын және тіл_кодын қабылдайды.

Және кейбір сұраулар

Әдеттегідей құжаттама. Ашық дереккөзді зерделеуден тартынбаңыз

Егер бәрі 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 нөмірімен қате аламыз. Тұрақты ток нөмірі арқылы қандай IP мекенжайына қосылу керектігін білу үшін бізге көмектеседі help.getConfig. Бір кездері бар болғаны 5 жазба болса, 2018 жылғы белгілі оқиғалардан кейін олардың саны айтарлықтай өсті.

Енді біз осы кезеңде анонимді серверде болғанымызды еске түсірейік. Тек IP мекенжайын алу тым қымбат емес пе? Неліктен мұны және басқа әрекеттерді MTProto шифрланбаған бөлігінде жасамасқа? Қарсылықты естимін: «Қалайша жалған мекенжайлармен жауап беретін РКН емес екеніне көз жеткізуге болады?». Бұл үшін, шын мәнінде, ресми клиенттерде екенін еске саламыз ендірілген 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 екі домен үшін де шифрлау алгоритмдері мен кілттерін, сондай-ақ қаптама құрылымының біразын анықтайды.

Бірақ олар әртүрлі стек деңгейлерін үнемі араластырып отырады, сондықтан mtproto қай жерде аяқталып, келесі деңгей қашан басталғаны әрқашан анық емес.

Олар қалай араласады? Міне, мысалы, PFS үшін бірдей уақытша кілт (айтпақшы, Telegram Desktop оны қалай жасау керектігін білмейді). Ол API сұрауы арқылы орындалады auth.bindTempAuthKey, яғни. жоғарғы деңгейден. Бірақ сонымен бірге ол төменгі деңгейде шифрлауға кедергі келтіреді - одан кейін, мысалы, оны қайтадан жасау керек. initConnection т.б., бұл емес жай ғана қалыпты сұраныс. Бөлек, ол сонымен қатар өріске қарамастан тұрақты токта тек БІР уақытша кілтке ие болатындығын жеткізеді 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 өсімі бар бұл қандай цирк? .. Мен бастапқы мағынасы «ACK үшін төмен бит, қалғаны - сан» деп күдіктенемін, бірақ нәтиже дұрыс емес - атап айтқанда, жіберуге болады екен бірнеше бірдей растаулар seq_no! Қалай? Мысалы, сервер бізге бірдеңе жібереді, жібереді, ал біз өзіміз үндемейміз, біз тек оның хабарламаларын қабылдау туралы қызметті растау хабарламаларымен жауап береміз. Бұл жағдайда біздің шығыс растауларымыз бірдей шығыс нөмірге ие болады. Егер сіз TCP-мен таныс болсаңыз және бұл ақылға сыймайтын сияқты деп ойласаңыз, бірақ бұл өте жабайы емес сияқты, өйткені TCP-де seq_no өзгермейді және растау келесіге өтеді seq_no екінші жағы – сосын ренжітуге асығамын. MTProto-ға растаулар келіп жатыр ЕМЕС туралы seq_no, TCP-дегідей, бірақ msg_id !

Бұл дегеніміз не? msg_id, осы өрістердің ең маңыздысы? Хабардың бірегей идентификаторы, аты айтып тұрғандай. Ол 64-биттік сан ретінде анықталады, оның ең аз маңызды биттері қайтадан сервер-сервер емес сиқырына ие, ал қалғандары Unix уақыт белгісі, оның ішінде бөлшек бөлігін солға жылжытқан 32 бит. Анау. уақыт белгісі (және уақыты тым әртүрлі хабарларды сервер қабылдамайды). Бұдан, жалпы алғанда, бұл клиент үшін ғаламдық идентификатор екені белгілі болды. Әзірге - есте сақта session_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_X_MISSING. Яғни, сіз әлі де осы сызыққа баруыңыз керек талдау. Мысалы, FLOOD_WAIT_3600 бір сағат күту керек дегенді білдіреді және PHONE_MIGRATE_5осы префиксі бар телефон нөмірі 5-ші DC-де тіркелуі керек. Бізде типтік тіл бар, солай ма? Бізге жолдан аргумент қажет емес, тұрақты өрнектер жасайды, cho.

Тағы да, бұл қызметтік хабарламалар бетінде емес, бірақ бұл жобада әдеттегідей ақпаратты табуға болады басқа құжаттама бетінде. Немесе күдік туғызу. Біріншіден, теру/қабаттарды бұзу - 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... қалған сандар қайда, Томми?

Құжаттамада былай делінген:

Қате_коды мәндері топтастырылған (қате_коды >> 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 жұмыс үстелінде оларға сәйкес келетін 4-ке жуық кезек бар (мүмкін, жоғарыда айтылғандай, бұл үшін оның коды мен архитектурасына мұқият қарау керек; сонымен бірге уақыт, біз оны үлгі ретінде қабылдауға болмайтынын білеміз, MTProto схемасындағы белгілі бір типтер онда пайдаланылмайды).

Неліктен бұл болып жатыр? Мүмкін, серверлік бағдарламашылар кластердегі сенімділікті қамтамасыз ете алмады немесе, ең болмағанда, алдыңғы теңгерімде буферлеуді қамтамасыз ете алмады және бұл мәселені клиентке ауыстырды. Василий шарасыздықтан TCP алгоритмдерін қолданып, тек екі кезекпен балама нұсқаны енгізуге тырысты - серверге RTT өлшеу және қабылданбаған сұраулар санына байланысты «терезе» өлшемін (хабарламаларда) реттеу. Яғни, сервер жүктемесін бағалауға арналған осындай өрескел эвристикалық - ол біздің қанша сұрауымызды бір уақытта шайнап, жоғалтпайды.

Яғни, сіз түсінесіз, солай ма? TCP арқылы жұмыс істейтін протоколдың үстіне қайтадан TCP енгізу қажет болса, бұл өте нашар жобаланған протоколды көрсетеді.

Иә, неліктен бірнеше кезек қажет және жалпы, бұл жоғары деңгейлі API-мен жұмыс істейтін адам үшін нені білдіреді? Қараңыз, сіз сұраныс жасайсыз, оны сериялайсыз, бірақ оны бірден жіберу мүмкін емес. Неліктен? Өйткені жауап болады msg_id, бұл уақытшааМен жапсырмамын, оны тағайындауды мүмкіндігінше кешіктірген дұрыс - кенеттен сервер біздің және оның арасындағы уақыт сәйкессіздігіне байланысты оны қабылдамайды (әрине, біз уақытымызды қазіргі уақыттан ауыстыратын балдақ жасай аламыз. сервер жауаптарынан есептелген дельтаны қосу арқылы сервер уақытына - ресми клиенттер мұны жасайды, бірақ буферлеуге байланысты бұл әдіс өрескел және дәл емес). Сонымен, кітапханадан жергілікті функция шақыруымен сұрау жасағанда, хабарлама келесі кезеңдерден өтеді:

  1. Бір кезекте жатыр және шифрлауды күтуде.
  2. Тағайындалған msg_id және хабарлама басқа кезекке өтті - қайта жіберу мүмкіндігі; розеткаға жіберіңіз.
  3. а) Сервер MsgsAck деп жауап берді – хабарлама жеткізілді, біз оны «басқа кезектен» жоямыз.
    б) Немесе керісінше, оған бірдеңе ұнамады, ол badmsg деп жауап берді - біз «басқа кезекте» қайта жібереміз
    в) Ештеңе белгісіз, хабарламаны басқа кезектен қайта жіберу керек – бірақ нақты қашан екені белгісіз.
  4. Ақырында сервер жауап берді RpcResult - нақты жауап (немесе қате) - жеткізіліп қана қоймай, сонымен бірге өңделеді.

Мүмкін, контейнерлерді пайдалану мәселені ішінара шешуі мүмкін. Бұл хабарлар жиыны біреуге жиналғанда және сервер барлығына бірден растаумен жауап берді, біреуімен msg_id. Бірақ ол бірдеңе дұрыс болмаса, бұл пакеттен де бас тартады.

Осы кезде техникалық емес ойлар іске қосылады. Тәжірибеден біз көптеген балдақтарды көрдік, сонымен қатар, енді біз жаман кеңестер мен архитектураның мысалдарын көреміз - мұндай жағдайларда сенуге және осындай шешімдер қабылдауға тұрарлық па? Сұрақ риторикалық (әрине жоқ).

Біз не туралы айтып отырмыз? «Хабарлар туралы нашақорлық хабарламалар» тақырыбы бойынша сіз әлі де «сен ақымақсыз, біздің тамаша идеямызды түсінбедіңіз!» сияқты қарсылықтармен болжауға болады. (сондықтан алдымен құжаттаманы қарапайым адамдар қажет болса, негіздеу және пакет алмасу мысалдарымен жазыңыз, содан кейін сөйлесеміз), содан кейін таймингтер/тайм-ауттар таза практикалық және нақты мәселе, мұнда бәрі бұрыннан белгілі. Бірақ құжаттама бізге күту уақыты туралы не айтады?

Сервер әдетте RPC жауабын пайдаланып клиенттен (әдетте, RPC сұрауы) хабарламаны алғанын растайды. Жауап ұзақ уақыт келе жатса, сервер алдымен түбіртек растауын, ал сәл кейінірек RPC жауабының өзін жіберуі мүмкін.

Клиент әдетте серверден хабарлама алғанын (әдетте, RPC жауабы) келесі RPC сұрауына растауды қосу арқылы растайды, егер ол тым кеш жіберілмесе (ол жасалған болса, мысалы, алынғаннан кейін 60-120 секундтан кейін). серверден келген хабар). Дегенмен, ұзақ уақыт бойы серверге хабарлама жіберуге ешқандай себеп болмаса немесе серверден қабылданбаған хабарламалардың көп саны болса (мысалы, 16-дан жоғары), клиент жеке растауды жібереді.

...Аудармамын: оның қаншалықты, қаншалықты қажет екенін өзіміз де білмейміз, жарайды, солай болсын деп есептейік.

Ал пингтер туралы:

Пинг хабарлары (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;

Пинг сияқты жұмыс істейді. Бұған қоса, бұл қабылданғаннан кейін сервер барлық алдыңғы таймерлерді автоматты түрде қалпына келтіретін бірдей түрдегі жаңа хабарды қабылдамайынша, ағымдағы қосылымды ажырату_кешіктіру секундтан кейін жабатын таймерді бастайды. Клиент бұл пингтерді 60 секунд сайын бір рет жіберсе, мысалы, ажырату_кідірісін 75 секундқа тең орнатуы мүмкін.

Ақылың жоқ па?! 60 секундтан кейін пойыз вокзалға кіріп, жолаушыларды түсіріп алып, туннельде қайтадан байланысын үзеді. 120 секундтан кейін, сіз сыртта болған кезде, ол басқасына келеді және байланыс үзілуі мүмкін. Аяқтардың қайдан өсетіні анық - «Мен қоңырау естідім, бірақ оның қайда екенін білмеймін», Нагле алгоритмі және интерактивті жұмысқа арналған TCP_NODELAY опциясы бар. Бірақ, кешіріңіз, оның әдепкі мәнін кешіктіріңіз - 200 ұлттықсекунд. Егер сіз шынымен ұқсас нәрсені бейнелеп, мүмкін болатын пакеттер жұбын үнемдегіңіз келсе, оны кем дегенде 5 секундқа қалдырыңыз немесе «Пайдаланушы теріп жатыр ...» хабарының күту уақыты қандай болса да. Бірақ енді жоқ.

Соңында, пингтер. Яғни, TCP қосылымының жандылығын тексеру. Бұл күлкілі, бірақ мен шамамен 10 жыл бұрын факультетіміздің жатақханасының хабаршысы туралы сыни мәтін жазғанмын - ол жерде авторлар серверге клиенттен пинг жіберді, керісінше емес. Бірақ үшінші курс студенттері бір нәрсе, ал халықаралық кеңсе басқа нәрсе, солай емес пе? ..

Біріншіден, шағын білім беру бағдарламасы. TCP қосылымы пакеттік алмасу болмаған жағдайда апта бойы өмір сүре алады. Бұл мақсатқа байланысты жақсы да, жаман да. Егер сізде серверге SSH қосылымы ашық болса, сіз компьютерден тұрдыңыз, маршрутизаторды қайта жүктедіңіз, өз орныңызға оралдыңыз - бұл сервер арқылы сеанс үзілмеді (ештеңе терген жоқ, пакеттер жоқ), қолайлы. Серверде мыңдаған клиенттер болса, олардың әрқайсысы ресурстарды алады (сәлем, Postgres!) және клиент хосты әлдеқашан қайта жүктелген болуы мүмкін, бірақ біз бұл туралы білмейміз.

Чат/IM жүйелері екінші жағдайға басқа, қосымша себеппен жатады - желілік күйлер. Егер пайдаланушы «құлап кетсе», бұл туралы әңгімелесушілеріне хабарлау қажет. Әйтпесе, Jabber жасаушылардың қатесі болады (және 20 жыл бойы түзетілді) - пайдаланушы ажыратылды, бірақ олар оның желіде екеніне сеніп, оған хабарламалар жазуды жалғастырады (олар да бірнеше минут бұрын жоғалып кеткен). үзіліс анықталды). Жоқ, TCP таймерлерінің қалай жұмыс істейтінін түсінбейтін көптеген адамдар кез келген жерде пайда болатын (ондаған секунд сияқты жабайы мәндерді орнату арқылы) TCP_KEEPALIVE опциясы бұл жерде көмектеспейді - тек ОЖ ядросы ғана емес екеніне көз жеткізу керек. пайдаланушының машинасы тірі, сонымен қатар қалыпты жұмыс істейді, жауап беруге қабілетті және қолданбаның өзі (сіз ол қатып қалмайды деп ойлайсыз ба? Ubuntu 18.04 жүйесіндегі Telegram жұмыс үстелі мен үшін бірнеше рет бұзылды).

Сондықтан пинг жасау керек сервер клиент және керісінше емес - егер клиент мұны істесе, байланыс үзілгенде, пинг жеткізілмейді, мақсатқа қол жеткізілмейді.

Ал Telegram-да не көреміз? Барлығы керісінше! Ал, яғни. формальды түрде, әрине, екі жақ бір-біріне пинг жасай алады. Іс жүзінде клиенттер балдақты пайдаланады ping_delay_disconnect, ол сервердегі таймерді іске қосады. Кешіріңіз, ол жерде қанша уақыт пингсіз тұрғысы келетінін шешу клиенттің ісі емес. Сервер өзінің жүктемесіне қарай жақсырақ біледі. Бірақ, әрине, егер сіз ресурстарды аямасаңыз, онда зұлым Пиноккио өздері, ал балдақ түседі ...

Ол қалай жобалануы керек еді?

Жоғарыда келтірілген фактілер Telegram / ВКонтакте командасының компьютерлік желілердің көліктік (және төменгі) деңгейі саласындағы өте жоғары емес құзыреттілігін және олардың тиісті мәселелердегі төмен біліктілігін айқын көрсетеді деп ойлаймын.

Неліктен бұл соншалықты күрделі болды және Telegram сәулетшілері қалай қарсылық білдіруге тырысады? Олардың TCP қосылымының үзілуінен аман қалатын сеанс жасауға тырысқаны, яғни біз қазір жеткізбегенімізді кейінірек жеткіземіз. Олар қиыншылыққа тап болып, оны тастап кетсе де, UDP тасымалдауын жасауға тырысқан шығар (сондықтан құжаттама бос - мақтанатын ештеңе жоқ). Бірақ жалпы желілердің және атап айтқанда TCP қалай жұмыс істейтінін, оған сенуге болатынын және мұны өзіңіз қай жерде (және қалай) жасау керектігін түсінбеуіңізге байланысты және оны криптографиямен біріктіруге тырысу «екіден бір кадр». бір тасты құстар» - осындай мәйіт шықты.

Бұл қалай болуы керек еді? Осыған негізделген msg_id қайталау шабуылдарының алдын алу үшін криптографиялық қажетті уақыт белгісі болып табылады, бұл оған бірегей идентификатор функциясын тіркеу қатесі. Сондықтан, ағымдағы архитектураны түбегейлі өзгертпестен (Жаңартулар ағыны қалыптасқан кезде, бұл осы жазбалар сериясының басқа бөлігі үшін жоғары деңгейлі API тақырыбы болып табылады), мынаны орындау керек:

  1. Клиентке TCP қосылымын ұстайтын сервер жауапкершілікті өз мойнына алады - егер сіз розеткадан шығарсаңыз, растасаңыз, қатені өңдеңіз немесе қайтарсаңыз, шығын жоқ. Сонда растау идентификаторлардың векторы емес, жай ғана «соңғы алынған сек_no» - TCP-дегідей жай ғана сан (екі сан - өз реті және расталған). Біз үнемі сессиядамыз, солай емес пе?
  2. Қайталау шабуылдарына жол бермеу үшін уақыт белгісі бөлек өріске айналады. Тексерілді, бірақ басқа ештеңе әсер етпейді. Жеткілікті және uint32 - егер біздің тұзымыз кем дегенде жарты күн сайын өзгеретін болса, біз 16 битті ағымдағы уақыттың бүтін бөлігінің төменгі биттеріне, қалғанын - секундтың бөлшек бөлігіне (қазіргідей) бөле аламыз.
  3. Қайтарылды msg_id мүлдем - серверлерде сұрауларды ажырату тұрғысынан, біріншіден, клиент идентификаторы, екіншіден, сеанс идентификаторы бар және оларды біріктіреді. Сәйкесінше, сұрау идентификаторы ретінде тек біреуі жеткілікті seq_no.

Сондай-ақ ең жақсы нұсқа емес, толық кездейсоқ идентификатор ретінде қызмет ете алады - айтпақшы, бұл хабарды жіберу кезінде жоғары деңгейлі API-де жасалған. Архитектураны салыстырмалыдан абсолюттіге дейін қайта жасау жақсы болар еді, бірақ бұл бұл постқа емес, басқа бөлікке арналған тақырып.

API?

Та-дам! Осылайша, ауырсыну мен балдаққа толы жолмен жүріп, біз серверге кез келген сұрауларды жіберіп, оларға кез келген жауаптар аламыз, сонымен қатар серверден жаңартуларды ала алдық (сұранысқа жауап ретінде емес, бірақ ол бізге өзін жібереді, мысалы, PUSH, егер біреу соншалықты анық болса).

Назар аударыңыз, енді мақалада жалғыз Perl мысалы болады! (синтаксиспен таныс емес адамдар үшін бірінші бата беретін аргумент нысанның деректер құрылымы, екіншісі - оның класы):

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 ішіндегі әдеттегі Web API деректер құрылымы, мүмкін сыныптар нысандарға тіркелген болуы мүмкін бе? ..

Міне, солай болды... Бұл не, жолдастар?.. Осыншама күш - ал біз веб-бағдарламашылар қайда демалуға тоқтадық. жаңадан бастап?.. HTTPS арқылы JSON оңайырақ емес пе?! Ал оның орнына не алдық? Бұл күш-жігерге тұрарлық болды ма?

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, TLS сессия билеттерінде PFS пайдаланған кезде (RFC 5077) кілттерді қайта келіспей және негізгі ақпаратты серверде сақтамай шифрланған сеансты жалғастыру. Бірінші қосылымды ашу және кілттерді генерациялау кезінде сервер қосылым күйін шифрлайды және оны клиентке жібереді (сеанс билеті түрінде). Тиісінше, қосылым қалпына келтірілгенде, клиент серверге сеанс кілті бар сеанс билетін жібереді. Билеттің өзі уақытша кілтпен (сеанс билетінің кілті) шифрланған, ол серверде сақталады және кластерлік шешімдерде SSL өңдейтін барлық фронтенд серверлеріне таратылуы керек.[10]. Осылайша, сеанс билетін енгізу, егер уақытша сервер кілттері бұзылса, мысалы, олар ұзақ уақыт сақталса (OpenSSL, nginx, Apache әдепкі бойынша оларды бағдарлама жұмыс істеп тұрған бүкіл уақыт бойы сақтайды; танымал сайттар) PFS бұзуы мүмкін. кілтті бірнеше сағат, күндерге дейін пайдаланыңыз).

Мұнда RTT нөлге тең емес, кем дегенде ClientHello және ServerHello алмасу керек, содан кейін Finished-пен бірге клиент деректерді жібере алады. Бірақ бұл жерде бізде жаңадан ашылған қосылымдары бар Интернет жоқ, бірақ байланысы жиі бір және бірнеше немесе аз ұзақ мерзімді, веб-беттерге салыстырмалы түрде қысқа сұраулар болатын хабаршы бар екенін есте ұстаған жөн. ішінде мультиплексирленген. Яғни, егер біз өте нашар метро учаскесіне тап болмасақ, бұл әбден қолайлы.

Тағы бір нәрсені ұмыттыңыз ба? Түсініктемелерде жазыңыз.

Жалғасы бар!

Осы жазбалар топтамасының екінші бөлімінде біз техникалық емес, ұйымдастырушылық мәселелерді қарастырамыз - тәсілдер, идеология, интерфейс, пайдаланушыларға деген көзқарас және т.б. Дегенмен, мұнда ұсынылған техникалық ақпаратқа негізделген.

Үшінші бөлімде техникалық құрамдас/әзірлеу тәжірибесін талдау жалғасады. Сіз әсіресе үйренесіз:

  • әртүрлі TL-түрлерімен пандемонияның жалғасуы
  • арналар мен супертоптар туралы белгісіз нәрселер
  • диалогтардан гөрі тізімнен нашар
  • абсолютті және салыстырмалы хабарлама адресациясы туралы
  • фото мен суреттің айырмашылығы неде
  • эмодзи курсивпен жазылған мәтінге қалай кедергі келтіреді

және басқа да балдақтар! Бізбен бірге қалыңыз!

Ақпарат көзі: www.habr.com

пікір қалдыру