Telegram-ийн протокол, зохион байгуулалтын арга барилыг шүүмжлэх. 1-р хэсэг, техникийн: үйлчлүүлэгчийг эхнээс нь бичих туршлага - TL, MT

Сүүлийн үед Telegram хэр сайн вэ, ах дүү Дуров нар сүлжээний систем бүтээхдээ хэр мундаг, туршлагатай гэх мэт нийтлэлүүд Habré дээр илүү олон гарч эхэлсэн. Үүний зэрэгцээ, маш цөөхөн хүн техникийн төхөөрөмжид үнэхээр шингэсэн байдаг - дээд тал нь тэд JSON дээр суурилсан нэлээн энгийн (мөн MTProto-оос тэс өөр) Bot API ашигладаг бөгөөд ихэвчлэн зүгээр л хүлээн зөвшөөрдөг. итгэл дээр мессенжерийн эргэн тойронд эргэлддэг бүх магтаал, PR. Бараг жил хагасын өмнө миний Эшелон ТББ-ын хамтран зүтгэгч Василий (харамсалтай нь Хабрегийн талаархи данс нь ноорогтой хамт устгагдсан) Perl дээр өөрийн Telegram үйлчлүүлэгчээ эхнээс нь бичиж эхэлсэн бөгөөд дараа нь эдгээр мөрийн зохиогч нэгдсэн. Яагаад Перл, зарим нь тэр даруй асуух болно? Яагаад гэвэл ийм төслүүд бусад хэл дээр аль хэдийн байдаг.Үнэндээ бол гол нь энэ биш, хаана ч байхгүй өөр хэл байж болно. бэлэн номын сан, үүний дагуу зохиогч бүх замыг туулах ёстой эхнээс нь. Түүнээс гадна криптограф бол итгэлцлийн асуудал боловч баталгаажуулах. Аюулгүй байдлыг хангахад чиглэсэн бүтээгдэхүүний хувьд та үйлдвэрлэгчийн бэлэн номын санд найдаж, сохроор итгэж болохгүй (гэхдээ энэ нь хоёрдугаар хэсгийн сэдэв юм). Одоогийн байдлаар номын сан нь "дундаж" түвшинд маш сайн ажилладаг (API хүсэлт гаргах боломжийг танд олгоно).

Гэсэн хэдий ч, энэ цуврал нийтлэлд криптограф эсвэл математик тийм ч их биш байх болно. Гэхдээ бусад олон техникийн нарийн ширийн зүйлс, архитектурын таягнууд байх болно (эхнээс нь бичихгүй, номын санг ямар ч хэлээр ашиглах хүмүүст хэрэгтэй). Тиймээс үйлчлүүлэгчийг эхнээс нь хэрэгжүүлэхийг хичээх нь гол зорилго байв албан ёсны баримт бичгийн дагуу. Өөрөөр хэлбэл, албан ёсны үйлчлүүлэгчдийн эх код хаалттай байна гэж бодъё (дахин, хоёрдугаар хэсэгт бид энэ үнэн гэсэн сэдвийг илүү дэлгэрэнгүй авч үзэх болно. Энэ нь тохиолддог тийм), гэхдээ хуучин үеийнх шиг, жишээ нь, RFC гэх мэт стандарт байдаг - албан ёсны эсэхээс үл хамааран эх кодыг "харалгүйгээр" зөвхөн техникийн үзүүлэлтийн дагуу үйлчлүүлэгч бичих боломжтой юу (Telegram Desktop, гар утас), эсвэл албан бус Telethon?

Агуулгын хүснэгт:

Баримт бичиг ... энэ нь байдаг, тийм үү? Энэ үнэн үү?..

Энэ нийтлэлийн тэмдэглэлийн хэсгүүдийг өнгөрсөн зун цуглуулж эхэлсэн. Энэ бүх хугацаанд албан ёсны вэбсайт дээр https://core.telegram.org Баримт бичиг нь 23-р давхаргад байсан, i.e. 2014 онд хаа нэгтээ гацсан (тэр үед суваг байхгүй байсныг санаж байна уу?). Мэдээжийн хэрэг, онолын хувьд энэ нь 2014 онд тухайн үеийн функциональ үйлчлүүлэгчийг хэрэгжүүлэх боломжийг бидэнд олгосон байх ёстой. Гэхдээ энэ байдалд ч гэсэн бичиг баримт нь нэгдүгээрт, дутуу байсан, хоёрдугаарт, зарим газар хоорондоо зөрчилддөг байв. Сар гаруйн өмнө буюу 2019 оны есдүгээр сард ийм байсан тохиолдлоор Сайт дээр шинэчлэгдсэн 105-р давхаргад зориулсан баримт бичгийн томоохон шинэчлэлт хийгдсэн бөгөөд одоо бүх зүйлийг дахин унших шаардлагатай гэсэн тэмдэглэлтэй байсан. Үнэхээр олон нийтлэл шинэчлэгдсэн боловч олонх нь өөрчлөгдөөгүй хэвээр байв. Тиймээс, баримт бичгийн талаархи доорх шүүмжлэлийг уншихдаа эдгээр зүйлсийн зарим нь хамааралгүй болсон ч зарим нь хангалттай хэвээр байгааг санах хэрэгтэй. Эцсийн эцэст, орчин үеийн ертөнцөд 5 жил бол зүгээр л урт хугацаа биш, гэхдээ маш их маш их. Тэр цагаас хойш (ялангуяа тэр цагаас хойш хаягдсан, дахин сэргэсэн геочат сайтуудыг тооцохгүй бол) схем дэх API аргын тоо зуугаас хоёр зуун тавин болж өссөн байна!

Залуу зохиолчийн хувьд хаанаас эхлэх вэ?

Та эхнээс нь бичиж байгаа эсэх, жишээлбэл, бэлэн номын санг ашиглах нь хамаагүй Python-д зориулсан телетон буюу PHP-д зориулсан Madeline, ямар ч тохиолдолд та эхлээд хэрэгтэй болно өргөдлөө бүртгүүлнэ үү - параметрүүдийг авах api_id и api_hash (VKontakte API-тай ажиллаж байсан хүмүүс шууд ойлгодог) сервер нь програмыг таних болно. Энэ байх ёстой Үүнийг хууль ёсны шалтгаанаар хийх боловч номын сангийн зохиогчид яагаад үүнийг хоёр дахь хэсэгт нийтэлж болохгүй талаар илүү дэлгэрэнгүй ярих болно. Та тестийн утгуудад сэтгэл хангалуун байж магадгүй, гэхдээ тэдгээр нь маш хязгаарлагдмал байдаг - баримт бол одоо та бүртгүүлж болно ганцхан апп, тиймээс түүн рүү яарах хэрэггүй.

Одоо бид техникийн үүднээс авч үзвэл бүртгүүлсний дараа бид Telegram-аас баримт бичиг, протокол гэх мэт шинэчлэлтүүдийн талаар мэдэгдэл хүлээн авах ёстойг сонирхож байна. Өөрөөр хэлбэл, усан онгоцны зогсоол бүхий сайтыг зүгээр л орхиж, үйлчлүүлэгч хийж эхэлсэн хүмүүстэй тусгайлан үргэлжлүүлэн ажиллаж байсан гэж таамаглаж болно. энэ нь илүү хялбар. Гэтэл үгүй ​​ээ, тийм зүйл ажиглагдаагүй, мэдээлэл ирээгүй.

Хэрэв та эхнээс нь бичих юм бол олж авсан параметрүүдийг ашиглах нь үнэндээ хол хэвээр байна. Хэдийгээр https://core.telegram.org/ Эхлэхийн тулд эхлээд тэдний тухай ярьдаг, үнэндээ та эхлээд хэрэгжүүлэх хэрэгтэй болно MTProto протокол - гэхдээ та итгэсэн бол OSI загварын дагуу зохион байгуулалт Протоколын ерөнхий тайлбарыг хуудасны төгсгөлд оруулбал энэ нь дэмий хоосон болно.

Үнэн хэрэгтээ MTProto-ийн өмнө болон дараа нь хэд хэдэн түвшинд нэг дор (OS-ийн цөмд ажилладаг гадаадын сүлжээчдийн хэлснээр, давхаргын зөрчил) том, зовлонтой, аймшигтай сэдэв саад болно ...

Хоёртын цуваа: TL (Type Language) ба түүний схем, давхаргууд болон бусад олон аймшигтай үгс

Энэ сэдэв нь үнэндээ Telegram-ийн асуудлын түлхүүр юм. Хэрэв та үүнийг гүнзгийрүүлэх гэж оролдвол маш олон аймшигтай үгс гарах болно.

Тэгэхээр энд диаграмм байна. Хэрэв энэ үг таны санаанд орвол: JSON схем, Та зөв бодсон. Зорилго нь адилхан: дамжуулагдсан мэдээллийн боломжит багцыг тайлбарлах зарим хэл. Үүнтэй ижил төстэй байдал дуусна. Хэрэв хуудаснаас MTProto протокол, эсвэл албан ёсны үйлчлүүлэгчийн эх модноос бид зарим схемийг нээхийг оролдох болно, бид дараах зүйлийг харах болно:

int ? = Int;
long ? = Long;
double ? = Double;
string ? = String;

vector#1cb5c415 {t:Type} # [ t ] = Vector t;

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

rpc_answer_unknown#5e2ad36e = RpcDropAnswer;
rpc_answer_dropped_running#cd78e586 = RpcDropAnswer;
rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;

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

---functions---

set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer;

ping#7abe77ec ping_id:long = Pong;
ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;

invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;

account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User;
account.sendChangePhoneCode#8e57deb flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode;

Үүнийг анх удаа харж байгаа хүн зөн совингоор бичсэн зүйлийн зөвхөн хэсгийг л таних боломжтой болно - эдгээр нь бүтэц юм шиг байна (хэдийгээр нэр нь хаана байна, зүүн талд эсвэл баруун талд байна уу?), тэдгээрийн дотор талбарууд байдаг, үүний дараа бүдүүн гэдэсний дараа төрөл гарч ирдэг... магадгүй. Энд өнцөг хаалтанд C++ шиг загварууд байгаа байх (үнэндээ, тийм биш). Бусад бүх тэмдэгтүүд юу гэсэн үг вэ, асуултын тэмдэг, анхаарлын тэмдэг, хувь хэмжээ, хэш тэмдэг (мөн өөр өөр газар өөр өөр зүйлийг илэрхийлдэг нь ойлгомжтой), заримдаа одоо байгаа, заримдаа үгүй, арван зургаатын тоо, хамгийн чухал нь үүнээс хэрхэн яаж авах вэ? зөвийг (энэ нь серверээс татгалзахгүй) байт урсгал? Та баримт бичгийг унших хэрэгтэй (тиймээ, ойролцоох JSON хувилбарт схемийн холбоосууд байгаа - гэхдээ энэ нь илүү тодорхой болгодоггүй).

Хуудсыг нээнэ үү Хоёртын өгөгдлийн цуваа мөн 4 дэх жилдээ матантай төстэй мөөг, салангид математикийн ид шидийн ертөнцөд шумбаарай. Цагаан толгой, төрөл, утга, нэгтгэгч, функциональ хослол, хэвийн хэлбэр, нийлмэл төрөл, полиморф төрөл... энэ бүхэн зөвхөн эхний хуудас! Дараагийнх нь таныг хүлээж байна TL хэл, энэ нь хэдийгээр өчүүхэн хүсэлт, хариултын жишээг агуулсан боловч ердийн тохиолдлуудад огт хариулт өгөхгүй байгаа тул та орос хэлнээс англи хэл рүү хөрвүүлсэн математикийг өөр найман суулгасан дээр давтан унших хэрэгтэй болно гэсэн үг юм. хуудаснууд!

Функциональ хэл, автомат төрлийн дүгнэлтийг мэддэг уншигчид мэдээжийн хэрэг, энэ хэл дээрх тайлбарын хэлийг жишээнээс нь ч илүү танил мэт харж, энэ нь зарчмын хувьд тийм ч муу биш гэж хэлж болно. Үүнийг эсэргүүцэж байгаа зүйлүүд нь:

  • тийм ээ, цель сайхан сонсогдож байна, гэхдээ харамсалтай нь тэр хүрч чадаагүй
  • ОХУ-ын их дээд сургуулиудын боловсрол мэдээллийн технологийн мэргэжлээр харилцан адилгүй байдаг - хүн бүр зохих курст хамрагдаагүй байна
  • Эцэст нь, бидний харж байгаачлан практик дээр ийм байна шаардлагагүй, учир нь тайлбарласан TL-ийн зөвхөн хязгаарлагдмал дэд багцыг ашигладаг

Гэж хэлсэнчлэн ЛеоНерд суваг дээр #perl FreeNode IRC сүлжээнд Telegram-аас Матриц руу хаалгыг хэрэгжүүлэх гэж оролдсон (ишлэлийн орчуулга санах ойноос буруу байна):

Хэн нэгэн хүн анх удаа бичгийн онолтой танилцаж, сэтгэл нь хөдөлж, практикт хэрэгтэй эсэхэд нь огтхон ч санаа зовдоггүй, түүгээр тоглох гэж оролдсон юм шиг санагддаг.

Хэрэв нүцгэн төрлийн (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---, гэхдээ энэ бүтээгчийг "харгалзаж үзэхгүй". Дуудагдсан функцүүдийн төрлийг аргументаар нь хэт ачаалах, i.e. Зарим шалтгааны улмаас C++ хэл дээрх шиг ижил нэртэй боловч өөр гарын үсэг бүхий хэд хэдэн функцийг TL-д заагаагүй болно.

Хэрэв энэ нь OOP биш бол яагаад "бүтээгч" ба "полиморф" гэж? Үнэн хэрэгтээ хэн нэгэн үүнийг OOP нэр томъёогоор бодох нь илүү хялбар байх болно - хийсвэр анги болох полиморф төрөл, бүтээгчид нь түүний шууд удам анги, мөн. final хэд хэдэн хэлний нэр томъёонд. Үнэндээ, мэдээжийн хэрэг, зөвхөн энд ижил төстэй байдал OO програмчлалын хэл дээрх бодит хэт ачаалалтай байгуулагчийн аргуудтай. Энд зөвхөн өгөгдлийн бүтэц байгаа тул ямар ч арга байхгүй (хэдийгээр функц, аргуудын тайлбар нь тэдгээр нь байдаг гэсэн толгойд төөрөгдөл үүсгэх чадвартай боловч энэ нь өөр асуудал юм) - та бүтээгчийг дараах утгаас төсөөлж болно. аль баригдаж байна байт урсгалыг унших үед бичнэ үү.

Энэ нь яаж болдог вэ? Үргэлж 4 байт уншдаг deserializer нь утгыг хардаг 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-д байдаг гэж та бодож магадгүй. Гэхдээ үгүй. Бараг л. Энэ цорын ганц Бодит хэлхээнд өнцгийн хаалт ашиглах тохиолдол бөгөөд үүнийг ЗӨВХӨН Векторт ашигладаг. Байтын урсгалд эдгээр нь Вектор хэлбэрийн хувьд 4 CRC32 байт байх ба үргэлж ижил, дараа нь 4 байт - массивын элементийн тоо, дараа нь эдгээр элементүүд өөрсдөө байх болно.

Үүн дээр цуваачлал үргэлж 4 байт үгээр явагддаг, бүх төрлүүд нь үржвэрүүд байдаг гэдгийг нэмээд суулгасан төрлүүдийг мөн тайлбарласан болно. bytes и string уртыг гараар цувуулж, 4-ээр тохируулснаар энэ нь хэвийн, бүр харьцангуй үр дүнтэй сонсогдож байна уу? TL нь үр дүнтэй хоёртын цуваа гэж мэдэгдэж байгаа ч бараг бүх зүйлийг, тэр ч байтугай Boolean утгууд болон нэг тэмдэгтийн мөрүүдийг 4 байт хүртэл өргөжүүлснээр JSON илүү зузаан хэвээр байх уу? Хараач, шаардлагагүй талбаруудыг ч гэсэн бит дарцагаар алгасаж болно, бүх зүйл маш сайн, тэр ч байтугай ирээдүйд өргөтгөх боломжтой, тэгвэл яагаад бүтээгчид дараа нь нэмэлт талбар нэмж болохгүй гэж?

Гэхдээ үгүй, хэрэв та миний товч тайлбарыг биш, харин бүрэн баримт бичгийг уншиж, хэрэгжилтийн талаар бодож үзээрэй. Нэгдүгээрт, бүтээгчийн CRC32-ыг схемийн текстийн тайлбарын нормчлогдсон мөрийн дагуу тооцдог (нэмэлт хоосон зайг арилгах гэх мэт) - хэрэв шинэ талбар нэмбэл төрлийг тайлбарлах мөр өөрчлөгдөнө, улмаар түүний CRC32 ба , улмаар, цуваа болгох. Хуучин үйлчлүүлэгч шинэ тугнууд бүхий талбайг хүлээж аваад дараа нь тэдэнтэй юу хийхээ мэдэхгүй байвал яах вэ? ..

Хоёрдугаарт, санацгаая CRC32, энэ нь энд үндсэндээ хэрэглэгддэг хэш функцууд ямар төрлийг цуваажуулж байгааг онцгойлон тодорхойлох. Энд бид мөргөлдөөний асуудалтай тулгарсан - үгүй, магадлал нь 232-д нэг биш, гэхдээ илүү их юм. CRC32 нь харилцаа холбооны суваг дахь алдааг илрүүлэх (болон залруулах) зорилготой бөгөөд үүний дагуу эдгээр шинж чанарыг бусдад хохирол учруулахаар сайжруулдаг гэдгийг хэн санаж байна вэ? Жишээлбэл, байтыг дахин зохион байгуулах нь хамаагүй: хэрэв та CRC32-ийг хоёр мөрөөс тооцоолвол хоёр дахь удаагаа эхний 4 байтыг дараагийн 4 байтаар солино - энэ нь ижил байх болно. Бидний оруулах зүйл бол латин цагаан толгойн үсгийн текстийн мөрүүд (мөн бага зэрэг цэг таслал) бөгөөд эдгээр нэр нь санамсаргүй биш байвал ийм өөрчлөлт хийх магадлал эрс нэмэгддэг.

Дашрамд хэлэхэд, тэнд юу байгааг хэн шалгасан бэ? Үнэхээр CRC32? Эртний эх кодын нэг нь (Уолтманы өмнө ч байсан) тэмдэгт бүрийг 239 тоогоор үржүүлдэг хэш функцтэй байсан тул эдгээр хүмүүсийн дуртай байсан, ха ха!

Эцэст нь, бид талбарын төрөлтэй бүтээгчид гэдгийг ойлгосон Vector<int> и Vector<PolymorType> өөр CRC32 байх болно. Онлайн гүйцэтгэлийн талаар юу хэлэх вэ? Мөн онолын үүднээс авч үзвэл, энэ төрлийн нэг хэсэг болж байна уу? Бид арван мянган тооны массивыг дамжууллаа гэж бодъё Vector<int> бүх зүйл тодорхой, урт, өөр 40000 байт. Хэрэв энэ бол яах вэ Vector<Type2>, энэ нь зөвхөн нэг талбараас бүрддэг int ба энэ нь дангаараа байгаа - бид 10000xabcdef0-ийг 34 удаа давтаж, дараа нь 4 байт байх шаардлагатай юу? int, эсвэл хэл нь үүнийг бүтээгчээс БИЕДЭЭРЛЭХ боломжтой fixedVec мөн 80000 байт биш харин 40000-ыг дахин шилжүүлэх үү?

Энэ бол хоосон онолын асуулт биш юм - та тус бүр нь ID, овог нэр, овогтой групп хэрэглэгчдийн жагсаалтыг хүлээн авч байна гэж төсөөлөөд үз дээ - гар утасны холболтоор дамжуулсан өгөгдлийн хэмжээ ихээхэн ялгаатай байж болно. Энэ нь бидэнд сурталчилсан Telegram цувралын үр нөлөө юм.

Тиймээс ...

Хэзээ ч гараагүй вектор

Хэрэв та комбинаторуудын тайлбарын хуудсуудыг гүйлгэж үзэхийг оролдвол вектор (тэр ч байтугай матриц) нь албан ёсоор хэд хэдэн хуудасны багцаар гарахыг оролдож байгааг харах болно. Гэвч эцэст нь тэд мартаж, эцсийн шатыг алгасаж, векторын тодорхойлолтыг зүгээр л өгсөн бөгөөд энэ нь төрөлд хараахан холбогдоогүй байна. Юу болсон бэ? Хэл дээр програмчлал, ялангуяа функциональ, бүтцийг рекурсив байдлаар дүрслэх нь нэлээд ердийн зүйл юм - залхуу үнэлгээ бүхий хөрвүүлэгч өөрөө бүх зүйлийг ойлгож, хийх болно. Хэлээр өгөгдлийг цуваа болгох хэрэгтэй зүйл бол ҮР АШИГТАЙ: зүгээр л тайлбарлахад л хангалттай жагсаалт, өөрөөр хэлбэл хоёр элементийн бүтэц - эхнийх нь өгөгдлийн элемент, хоёр дахь нь ижил бүтэц эсвэл сүүлний хоосон зай (багц) (cons) Lisp-д). Гэхдээ үүнийг шаардах нь ойлгомжтой тус бүрээс элемент нь төрлийг тодорхойлоход нэмэлт 4 байт зарцуулдаг (TL-д CRC32). Массивыг мөн хялбархан дүрсэлж болно тогтмол хэмжээ, гэхдээ урьдчилан тодорхойгүй урттай массивын хувьд бид тасардаг.

Тиймээс TL нь вектор гаргахыг зөвшөөрдөггүй тул хажуу талд нь нэмэх шаардлагатай болсон. Эцсийн эцэст баримт бичигт дараахь зүйлийг тусгасан болно.

Цуваалалт нь t төрлийн хувьсагчийн тодорхой утгаас хамаарахгүй ижил "вектор" (const 0x1cb5c415 = crc32("вектор t:Type # [ t ] = Vector t") -ийг үргэлж ашигладаг.

Нэмэлт параметрийн t утга нь үр дүнгийн төрлөөс (цуваа салгахаас өмнө үргэлж мэдэгддэг) үүсэлтэй тул цуваачлалд оролцдоггүй.

Ойролцоогоор харна уу: vector {t:Type} # [ t ] = Vector t - гэхдээ хаана ч байхгүй Энэ тодорхойлолт нь өөрөө эхний тоо нь векторын урттай тэнцүү байх ёстой гэсэн үг биш юм! Мөн хаанаас ч ирдэггүй. Энэ бол таны гараар санаж, хэрэгжүүлэх ёстой өгөгдсөн зүйл юм. Бусад газар, баримт бичигт энэ төрөл нь бодит биш гэдгийг үнэнээр дурдсан байдаг:

Вектор t полиморф псевдотип нь "төрөл" бөгөөд утга нь хайрцагласан эсвэл нүцгэн аль ч төрлийн t-ийн утгуудын дараалал юм.

... гэхдээ үүнд анхаарлаа хандуулдаггүй. Математикийн хичээлийг давахаас залхсан (магадгүй их сургуулиас таньдаг байж магадгүй) бууж өгөхөөр шийдэж, практик дээр түүнтэй хэрхэн ажиллах талаар бодоход таны толгойд энэ нь ноцтой юм гэсэн сэтгэгдэл төрдөг. Математикийн үндсэн санааг хэн ч биш харин Cool People (хоёр математикч - МУЗ-ийн ялагч) зохион бүтээсэн нь тодорхой. Зорилго - шоудах - биеллээ.

Дашрамд хэлэхэд, тооны тухай. Үүнийг сануулъя # энэ нь синоним юм nat, натурал тоо:

Төрөл илэрхийллүүд байдаг (type-expr) болон тоон илэрхийллүүд (nat-expr). Гэсэн хэдий ч тэдгээрийг ижил аргаар тодорхойлдог.

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

гэхдээ дүрмийн хувьд тэдгээрийг ижил байдлаар дүрсэлсэн байдаг, i.e. Энэ ялгааг дахин санаж, гараар хэрэгжүүлэх ёстой.

За, тийм ээ, загварын төрлүүд (vector<int>, vector<User>) нийтлэг танигчтай (#1cb5c415), i.e. гэж зарлаж байгааг мэдэж байгаа бол

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

Энэ бол hasshmap загварын төрлийг int - Төрөл хосын вектор гэж тодорхойлсон тодорхойлолт юм. C++ хэл дээр энэ нь иймэрхүү харагдах болно:

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

тиймээс, alpha - түлхүүр үг! Гэхдээ зөвхөн C++ хэл дээр та T гэж бичиж болно, гэхдээ та альфа, бета гэж бичих хэрэгтэй ... Гэхдээ 8 параметрээс илүүгүй, уран зөгнөл энд дуусдаг. Нэгэн цагт Санкт-Петербургт ийм яриа өрнөсөн бололтой:

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

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

Гэхдээ энэ нь TL-ийн "ерөнхийдөө" анхны хэвлэгдсэн хэрэгжилтийн тухай байв. Telegram үйлчлүүлэгчид өөрсдийнхөө хэрэгжилтийг авч үзье.

Василийд хэлсэн үг:

Василий, [09.10.18 17:07] Хамгийн гол нь тэд бөөн хийсвэрлэл бүтээж, дараа нь боолтоор цохиж, код үүсгэгчийг таяг таягаар бүрхсэн тул илжиг халуухан байдаг.
Үүний үр дүнд эхлээд dock pilot.jpg-ээс
Дараа нь dzhekichan.webp кодоос

Мэдээжийн хэрэг, алгоритм, математикийн мэдлэгтэй хүмүүсээс бид тэд Aho, Ullmann-ыг уншсан, DSL хөрвүүлэгчээ бичихэд хэдэн арван жилийн турш энэ салбарт стандарт болсон хэрэглүүрүүдийг мэддэг байх гэж найдаж болно, тийм үү? ..

Зохиогчоор telegram-cli Энэ бол Виталий Валтман бөгөөд түүний (cli) хил хязгаараас гадуур TLO формат үүссэнээс ойлгож болно, багийн гишүүн - одоо TL задлан шинжлэх номын сан хуваарилагдсан байна. тус тусад нь, түүнд ямар сэтгэгдэл төрж байна TL задлан шинжлэгч? ...

16.12 04:18 Василий: Би хэн нэгэн lex+yacc-ийг эзэмшээгүй гэж бодож байна
16.12 04:18 Василий: Би үүнийг өөрөөр тайлбарлаж чадахгүй
16.12 04:18 Василий: За, эсвэл тэд ВК дахь мөрийн тоог төлсөн.
16.12 04:19 Василий: 3к+ мөр гэх мэт.<censored> задлан шинжлэлийн оронд

Магадгүй үл хамаарах зүйл үү? Хэрхэн гэдгийг харцгаая хийдэг Энэ бол АЛБАН ЁСНЫ үйлчлүүлэгч - Telegram Desktop:

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

Python-д 1100+ мөр, хэд хэдэн тогтмол илэрхийлэл + вектор гэх мэт тусгай тохиолдлууд, мэдээжийн хэрэг, энэ нь TL-ийн синтаксийн дагуу байх ёстой гэж схемд тунхаглагдсан байдаг, гэхдээ тэд үүнийг задлан шинжлэхдээ энэ синтакс дээр тулгуурласан... Энэ бүхэн яагаад гайхамшиг байсан юм бэ гэсэн асуулт гарч ирнэ.иХэрэв хэн ч үүнийг баримт бичгийн дагуу задлан шинжлэхгүй бол илүү давхраатай юм уу?!

Дашрамд хэлэхэд... Бид CRC32 шалгалтын тухай ярьж байсныг санаж байна уу? Тиймээс Telegram Desktop код үүсгэгч дээр CRC32 тооцоолсон төрлүүдэд хамаарах үл хамаарах зүйлүүдийн жагсаалт байдаг. таарахгүй байна диаграммд заасантай хамт!

Василий, [18.12/22 49:XNUMX] энд би ийм TL хэрэгтэй эсэх талаар бодох болно.
Хэрэв би өөр хэрэглүүртэй хутгалдахыг хүсвэл мөрийн завсар оруулж эхлэх байсан бол задлан шинжлэлийн тал хувь нь олон мөрийн тодорхойлолт дээр тасрах болно.
tdesktop ч гэсэн

Нэг давхаргын тухай санааг санаарай, бид үүнийг хэсэг хугацааны дараа эргэн харах болно.

За, telegram-cli нь албан бус, Telegram Desktop нь албан ёсны, харин бусад нь яах вэ? Хэн мэдэх вэ? .. Андройдын клиент кодонд схем задлагч огт байхгүй байсан (энэ нь нээлттэй эхийн талаар асуулт үүсгэдэг, гэхдээ энэ нь хоёрдугаар хэсэгт зориулагдсан), гэхдээ өөр хэд хэдэн инээдтэй код байсан, гэхдээ тэдгээрийн талаар илүү ихийг эндээс харж болно. доорх дэд хэсэг.

Практикт цувралжуулалт өөр ямар асуултуудыг тавьдаг вэ? Жишээлбэл, тэд мэдээж бит талбарууд болон нөхцөлт талбаруудаар маш их зүйлийг хийсэн:

Василий: flags.0? true
талбар байгаа гэсэн үг бөгөөд хэрэв туг тавьсан бол үнэнтэй тэнцүү байна

Василий: flags.1? int
талбар байгаа бөгөөд цувралаас хасах шаардлагатай гэсэн үг

Василий: Хөшөө, юу хийж байгаадаа санаа зовох хэрэггүй!
Василий: Үнэнийг нүцгэн тэг урттай төрөл гэж баримт бичигт хаа нэгтээ дурдсан байдаг, гэхдээ тэдний баримтаас юу ч цуглуулах боломжгүй.
Василий: Нээлттэй эхийн хэрэгжүүлэлтүүдэд энэ нь бас тийм биш боловч олон тооны таяг, тулгуурууд байдаг.

Телетоны талаар юу хэлэх вэ? MTProto-ийн сэдвийг харвал, жишээ нь - баримт бичигт ийм хэсгүүд байдаг, гэхдээ тэмдэг % энэ нь зөвхөн "өгөгдсөн нүцгэн төрөлд тохирсон" гэж тодорхойлсон, өөрөөр хэлбэл. Доорх жишээнүүдэд алдаа эсвэл ямар нэгэн бичиг баримтгүй зүйл байна:

Василий, [22.06.18 18:38] Нэг газар:

msg_container#73f1f8dc messages:vector message = MessageContainer;

Өөр хэлбэрээр:

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

Эдгээр нь хоёр том ялгаа бөгөөд бодит амьдрал дээр ямар нэгэн нүцгэн вектор гарч ирдэг

Би нүцгэн векторын тодорхойлолтыг хараагүй бөгөөд нэгийг ч тааралдсангүй

Анализыг гараараа телетоноор бичдэг

Түүний диаграммд тодорхойлолтыг тайлбарласан болно msg_container

Дахин хэлэхэд% -ийн тухай асуулт хэвээр байна. Үүнийг тайлбарлаагүй байна.

Вадим Гончаров, [22.06.18 19:22] болон tdesktop дээр?

Василий, [22.06.18 19:23] Гэхдээ тэдний ердийн хөдөлгүүр дээрх TL задлагч үүнийг бас идэхгүй байх магадлалтай.

// parsed manually

TL бол сайхан хийсвэрлэл, хэн ч үүнийг бүрэн хэрэгжүүлдэггүй

Мөн % нь тэдний схемийн хувилбарт байхгүй байна

Гэхдээ энд баримт бичиг өөртэйгөө зөрчилдөж байгаа тул idk

Энэ нь дүрмээс олдсон тул тэд семантикийг тайлбарлахаа мартсан байж магадгүй юм

Та баримт бичгийг TL дээр харсан, хагас литргүйгээр үүнийг олж чадахгүй

"За яахав" гэж өөр нэг уншигч "Та ямар нэг зүйлийг шүүмжилдэг, тиймээс үүнийг хэрхэн хийх ёстойг надад харуулаач" гэж хэлэх болно.

Василий хариулав: "Шинжлэгчийн хувьд би ийм зүйлд дуртай

    args: /* empty */ { $$ = NULL; }
        | args arg { $$ = g_list_append( $1, $2 ); }
        ;

    arg: LC_ID ':' type-term { $$ = tl_arg_new( $1, $3 ); }
            | LC_ID ':' condition '?' type-term { $$ = tl_arg_new_cond( $1, $5, $3 ); free($3); }
            | UC_ID ':' type-term { $$ = tl_arg_new( $1, $3 ); }
            | type-term { $$ = tl_arg_new( "", $1 ); }
            | '[' LC_ID ']' { $$ = tl_arg_new_mult( "", tl_type_new( $2, TYPE_MOD_NONE ) ); }
            ;

-аас илүү таалагдаж байна

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

буюу

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

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

Энэ бол БҮХЭН lexer юм:

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

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

тэдгээр. Зөөлөн хэлэх нь илүү хялбар юм."

Ерөнхийдөө үр дүнд нь TL-ийн бодит ашигласан дэд багцын задлан шинжлэгч болон код үүсгэгч нь дүрмийн 100 орчим мөр, генераторын ~300 мөр (бүгдийг тоолоход) багтдаг. print-ийн үүсгэсэн код), анги тус бүрийг судлахад зориулсан төрлийн мэдээллийн багц. Полиморф төрөл бүр нь хоосон хийсвэр суурь анги болж хувирдаг бөгөөд бүтээгчид түүнээс өвлөн авч, цуваа болгох, цуваа арилгах аргуудтай байдаг.

Төрөл бүрийн хэлэнд төрөл байхгүй

Хүчтэй бичих нь сайн хэрэг, тийм ээ? Үгүй ээ, энэ бол холивар биш (хэдийгээр би динамик хэлийг илүүд үздэг), харин TL-ийн хүрээнд тавигдсан постулат юм. Үүний үндсэн дээр хэл нь бидэнд бүх төрлийн шалгалтыг өгөх ёстой. За яахав, магадгүй тэр өөрөө биш, харин хэрэгжилт, гэхдээ тэр ядаж тэдгээрийг дүрслэх ёстой. Мөн бид ямар боломжуудыг хүсч байна вэ?

Юуны өмнө хязгаарлалт. Файл байршуулах баримт бичгийг бид эндээс харж болно:

Дараа нь файлын хоёртын контентыг хэсэг болгон хуваана. Бүх хэсгүүд ижил хэмжээтэй байх ёстой ( хэсэг_хэмжээ ) ба дараах нөхцөлийг хангасан байх ёстой.

  • part_size % 1024 = 0 (1KB-д хуваагддаг)
  • 524288 % part_size = 0 (512KB нь хэсэг_хэмжээнд жигд хуваагдах ёстой)

Хэмжээ нь part_size-ээс бага байвал сүүлчийн хэсэг нь эдгээр нөхцлийг хангах шаардлагагүй.

Хэсэг бүр дарааллын дугаартай байх ёстой, файлын хэсэг, 0-ээс 2,999 хүртэлх утгатай.

Файлыг хуваасны дараа сервер дээр хадгалах аргыг сонгох хэрэгтэй. Ашиглах upload.saveBigFilePart файлын бүрэн хэмжээ 10 МБ-аас их бол upload.saveFilePart жижиг файлуудын хувьд.
[...] дараах өгөгдөл оруулах алдаануудын аль нэгийг нь буцааж болно:

  • FILE_PARTS_INVALID — Хэсгийн тоо буруу байна. Үнэ нь хооронд биш юм 1..3000

Диаграммд эдгээрийн аль нэг нь байна уу? Үүнийг TL ашиглан ямар нэгэн байдлаар илэрхийлэх боломжтой юу? Үгүй Гэхдээ уучлаарай, өвөөгийн Турбо Паскаль хүртэл тодорхойлсон төрлүүдийг дүрсэлж чаддаг байсан мужууд. Мөн тэрээр одоо илүү сайн мэддэг өөр нэг зүйлийг мэдэж байсан enum - тогтмол (бага) тооны утгуудын тоололоос бүрдэх төрөл. C - тоон гэх мэт хэл дээр бид зөвхөн төрлүүдийн талаар л ярьсан гэдгийг анхаарна уу тоо. Гэхдээ бас массивууд, мөрүүд байдаг ... жишээлбэл, энэ мөрөнд зөвхөн утасны дугаар багтах боломжтой гэж тайлбарлавал сайхан байх болно, тийм үү?

Эдгээрийн аль нь ч TL-д байхгүй. Гэхдээ жишээ нь JSON Schema-д байдаг. Хэрэв өөр хэн нэгэн 512 KB-ийн хуваагдлын талаар үүнийг кодоор шалгах шаардлагатай гэж маргаж магадгүй бол үйлчлүүлэгч зүгээр л чадахгүй байсан хүрээнээс гадуур дугаар илгээх 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] энэ нь үндсэндээ хуралдааны эхэнд давхаргын талаар тохиролцсон гэсэн үг юм.

Дашрамд хэлэхэд, үйлчлүүлэгчийн зэрэглэлийг бууруулаагүй болно

Шинэчлэлтүүд, i.e. төрөл Updates Энэ схемд сервер нь API хүсэлтийн хариуд биш, харин үйл явдал тохиолдоход бие даан үйлчлүүлэгч рүү илгээдэг зүйл юм. Энэ бол өөр нийтлэлд хэлэлцэх нарийн төвөгтэй сэдэв боловч одоогоор сервер нь үйлчлүүлэгч офлайн байсан ч шинэчлэлтүүдийг хадгалдаг гэдгийг мэдэх нь чухал юм.

Тиймээс, хэрэв та боохоос татгалзвал тус бүрээс багцын хувилбарыг зааж өгөхөд энэ нь логикийн хувьд дараах боломжит асуудлуудад хүргэдэг:

  • Үйлчлүүлэгч аль хувилбарыг дэмжиж байгаагаа мэдэгдэхээс өмнө сервер нь үйлчлүүлэгч рүү шинэчлэлт илгээдэг
  • Үйлчлүүлэгчийг сайжруулсны дараа би юу хийх ёстой вэ?
  • хэн байна баталгааПроцессын явцад давхаргын дугаарын талаарх серверийн үзэл бодол өөрчлөгдөхгүй гэж үү?

Энэ нь зөвхөн онолын таамаглал гэж та бодож байна уу, практик дээр сервер зөв бичигдсэн (ядаж л сайн шалгагдсан) учраас ийм зүйл болохгүй гэж үү? Ха! Яаж ч байсан хамаагүй!

Бид наймдугаар сард яг ийм зүйлтэй тулгарсан. 14-р сарын XNUMX-нд 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-д ямар нэг зүйл танигдаагүй бол энэ нь гарын үсгээр хоёртын систем юм БҮГД явбал код тайлах боломжгүй болно. Ийм нөхцөлд та юу хийх ёстой вэ?

За, хэний ч толгойд орж ирдэг хамгийн эхний зүйл бол салгаж, дахин оролдох явдал юм. Тусалсангүй. Бид google-ээр CRC32-ыг хайдаг - эдгээр нь бид 73 дээр ажилласан хэдий ч 82-р схемийн объектууд болж хувирсан. Бид логуудыг анхааралтай хардаг - хоёр өөр схемийн таних тэмдэгтүүд байна!

Асуудал нь зөвхөн манай албан бус үйлчлүүлэгчид байгаа юм болов уу? Үгүй ээ, бид Telegram Desktop 1.2.17-г (олон тооны Линукс түгээлтэд нийлүүлсэн хувилбар) эхлүүлж, энэ нь Онцгой байдлын бүртгэлд бичнэ: MTP Unexpected type id #b5223b0f MTPMessageMedia дээр уншина...

Telegram-ийн протокол, зохион байгуулалтын арга барилыг шүүмжлэх. 1-р хэсэг, техникийн: үйлчлүүлэгчийг эхнээс нь бичих туршлага - TL, MT

Албан бус үйлчлүүлэгчдийн нэгэнд үүнтэй төстэй асуудал аль хэдийн тохиолдсон гэдгийг Google харуулсан боловч дараа нь хувилбарын дугаар, үүний дагуу таамаглал өөр байсан ...

Тэгэхээр бид яах ёстой вэ? Василий бид хоёр салсан: тэр хэлхээг 91 болгон шинэчлэх гэж оролдсон, би хэд хоног хүлээгээд 73-ыг оролдохоор шийдсэн. Хоёр арга хоёулаа үр дүнтэй байсан боловч эдгээр нь эмпирик учраас танд хэдэн хувилбар хэрэгтэй вэ гэдгийг ойлгохгүй байна. үсрэх, эсвэл хэр удаан хүлээх хэрэгтэй.

Хожим нь би нөхцөл байдлыг дахин гаргаж чадсан: бид үйлчлүүлэгчийг ажиллуулж, унтрааж, хэлхээг өөр давхаргад дахин эмхэтгэж, дахин эхлүүлж, асуудлыг дахин барьж, өмнөх рүүгээ буцна - уу, ямар ч хэлхээг солих, үйлчлүүлэгч дахин эхлүүлэх хэдэн минут туслах болно. Та өөр өөр давхаргуудаас өгөгдлийн бүтэцтэй холимог хүлээн авах болно.

Тайлбар? Төрөл бүрийн шууд бус шинж тэмдгүүдээс та таамаглаж байгаачлан сервер нь өөр өөр машинууд дээр өөр өөр төрлийн олон процессуудаас бүрддэг. Магадгүй "буфер" хийх үүрэгтэй сервер нь дээд албан тушаалтнуудын өгсөн зүйлийг дараалалд оруулсан бөгөөд тэд үүнийг үүсгэх үед байсан схемийн дагуу өгсөн байх магадлалтай. Энэ дараалал "ялзарсан" хүртэл энэ талаар юу ч хийж чадахгүй.

Магадгүй... гэхдээ энэ бол аймшигтай таяг байна уу?!.. Үгүй ээ, галзуу санаануудыг бодохын өмнө албан ёсны үйлчлүүлэгчдийн кодыг харцгаая. Android хувилбарт бид ямар ч TL задлан шинжлэгч олдоггүй ч (GitHub үүнийг хөндөхөөс татгалздаг) сериалчлалтай (de) файлыг олдог. Энд кодын хэсгүүд байна:

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;

Энэ "гараар үүсгэсэн" гэсэн тайлбар нь энэ файлын зөвхөн нэг хэсгийг гараар бичсэн (засвар үйлчилгээний хар дарсан зүүдийг бүхэлд нь төсөөлж байна уу?), үлдсэн хэсэг нь машин үүсгэсэн болохыг харуулж байна. Гэсэн хэдий ч, дараа нь өөр нэг асуулт гарч ирдэг - эх сурвалжууд байгаа эсэх бүрэн биш (а la GPL нь Линуксийн цөмд байдаг), гэхдээ энэ нь хоёр дахь хэсгийн сэдэв юм.

Гэхдээ хангалттай. Энэ бүх цуваачлалыг ажиллуулдаг протокол руу шилжье.

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 биш, дараа нь бие
  • Завсрын түвшинд энэ нь 4 байт урт ба талбар бөгөөд үйлчлүүлэгч анх удаа илгээх ёстой 0xeeeeeeee Завсрын түвшинд байгааг илтгэнэ
  • Сүлжээний ажилтны үүднээс авч үзвэл хамгийн их донтуулдаг нь: урт, дарааллын дугаар, мөн MTProto, бие, CRC32 голчлон БИШ. Тийм ээ, энэ бүхэн TCP дээр байна. Энэ нь биднийг дараалсан байт урсгал хэлбэрээр найдвартай тээвэрлэлтээр хангадаг; ямар ч дараалал, ялангуяа шалгах нийлбэр шаардлагагүй. За, одоо хэн нэгэн TCP нь 16 битийн хяналтын нийлбэртэй тул өгөгдлийн эвдрэл гарсан гэж намайг эсэргүүцэх болно. Гайхалтай, гэхдээ бид үнэндээ 16 байтаас урт хэш бүхий криптограф протоколтой бөгөөд эдгээр бүх алдаанууд, тэр ч байтугай түүнээс ч илүү нь SHA-ийн тохиромжгүй байдлаас илүү өндөр түвшинд баригдах болно. Үүн дээр CRC32-д ямар ч цэг байхгүй.

Нэг байт урттай товчилсон хувилбарыг "4 байт өгөгдөлтэй уялдуулах шаардлагатай" гэсэн үндэслэл бүхий Дунд зэрэгтэй харьцуулж үзье, энэ нь үнэхээр утгагүй юм. Юу вэ, Telegram программистууд маш чадваргүй тул залгуураас өгөгдлийг зэрэгцүүлсэн буфер руу уншиж чадахгүй гэж үздэг үү? Та үүнийг хийх хэрэгтэй, учир нь унших нь танд хэдэн байтыг буцааж өгөх боломжтой (мөн прокси серверүүд бас байдаг, жишээ нь...). Эсвэл нөгөө талаас, хэрэв бид 16 байт дээр их хэмжээний дэвсгэртэй хэвээр байвал яагаад товчилсон хэсгийг блоклох хэрэгтэй вэ - 3 байт хэмнээрэй заримдаа ?

Николай Дуров дугуйг, тэр дундаа сүлжээний протоколуудыг ямар ч бодит хэрэгцээгүйгээр дахин зохион бүтээх дуртай гэсэн сэтгэгдэл төрдөг.

Бусад тээврийн сонголтууд, үүнд. Вэб болон MTProxy, хэрэв хүсэлт байгаа бол бид одоо, магадгүй өөр нийтлэлд авч үзэхгүй. Үүнтэй ижил MTProxy-ийн тухайд, 2018 онд худалдаанд гарсны дараахан үйлчилгээ үзүүлэгчид үүнийг хурдан хааж сурсан гэдгийг одоо л санацгаая. тойрч гарах блок, дээр багцын хэмжээ! Мөн Си хэл дээр бичсэн (дахин Уолтман) MTProxy сервер нь Линуксийн онцлогтой хэт холбоотой байсан ч энэ нь огт шаардлагагүй байсан (Фил Кулин батлах болно), Go эсвэл Node.js дээр ижил төстэй сервер ашиглах болно. зуу хүрэхгүй мөрөнд багтах.

Гэхдээ бид бусад асуудлуудыг авч үзсэний дараа бүлгийн төгсгөлд эдгээр хүмүүсийн техникийн мэдлэгийн талаар дүгнэлт хийх болно. Одоохондоо MTProto сессийг байрлуулсан OSI давхарга 5 руу шилжье.

Түлхүүр, мессеж, сесс, Диффи-Хеллман

Тэд үүнийг бүхэлд нь зөв байршуулаагүй байна... Сесс нь Идэвхтэй сешнүүдийн доор байгаа интерфейс дээр харагдах сесс биш юм. Гэхдээ дарааллаар нь.

Telegram-ийн протокол, зохион байгуулалтын арга барилыг шүүмжлэх. 1-р хэсэг, техникийн: үйлчлүүлэгчийг эхнээс нь бичих туршлага - TL, MT

Тиймээс бид тээврийн давхаргаас мэдэгдэж буй урттай байт мөрийг хүлээн авсан. Энэ нь шифрлэгдсэн мессеж эсвэл энгийн текст юм - хэрэв бид тохиролцооны үндсэн шатанд байгаа бөгөөд үүнийг хийж байгаа бол. Бид "түлхүүр" гэж нэрлэгддэг олон ойлголтын алийг нь ярьж байна вэ? Энэ асуудлыг Telegram багийнханд тодорхой болгоё (Би өглөөний 4 цагт ядарсан тархитай өөрийн бичиг баримтаа англи хэлнээс орчуулсандаа хүлцэл өчье, зарим хэллэгийг байгаагаар нь үлдээхэд илүү хялбар байсан):

гэж нэрлэгддэг хоёр байгууллага байдаг сесс - "Одоогийн сесс" дор албан ёсны үйлчлүүлэгчдийн UI дахь нэг нь сесс бүр нь бүхэл бүтэн төхөөрөмж / үйлдлийн системтэй тохирч байна.
Хоёрдугаарт - MTProto сесс, аль нь мессежийн дарааллын дугаартай (доод түвшний утгаараа), аль нь өөр TCP холболтуудын хооронд үргэлжилж болно. Файл татаж авах ажиллагааг хурдасгахын тулд хэд хэдэн MTProto сессийг нэгэн зэрэг суулгаж болно.

Энэ хоёрын хооронд уулзалт гэсэн ойлголт байдаг зөвшөөрөл. Муухай тохиолдолд бид үүнийг хэлж чадна UI сесс адил байна зөвшөөрөл, гэхдээ харамсалтай нь бүх зүйл төвөгтэй байдаг. Харцгаая:

  • Шинэ төхөөрөмж дээрх хэрэглэгч эхлээд үүсгэдэг баталгаажуулах_түлхүүр мөн үүнийг дансанд, жишээлбэл SMS-ээр холбодог - ийм учраас зөвшөөрөл
  • Энэ нь эхний дотор болсон MTProto сесс, байгаа session_id өөрийнхөө дотор.
  • Энэ үе шатанд хослол зөвшөөрөл и session_id дуудаж болно Жишээ нь - энэ үг нь зарим үйлчлүүлэгчийн баримт бичиг, кодонд гарч ирдэг
  • Дараа нь үйлчлүүлэгч нээх боломжтой хэд хэдэн MTProto сессүүд ижил дор баталгаажуулах_түлхүүр - ижил DC руу.
  • Дараа нь нэг өдөр үйлчлүүлэгч файлаа авах хүсэлт гаргах шаардлагатай болно өөр DC - мөн энэ DC-д зориулж шинээр үүсгэгдэх болно баталгаажуулах_түлхүүр !
  • Бүртгүүлж буй шинэ хэрэглэгч биш гэдгийг системд мэдэгдэх зөвшөөрөл (UI сесс), үйлчлүүлэгч API дуудлагыг ашигладаг auth.exportAuthorization гэртээ DC auth.importAuthorization шинэ DC-д.
  • Бүх зүйл адилхан, хэд хэдэн нээлттэй байж болно MTProto сессүүд (тус бүр өөрийн гэсэн session_id) энэ шинэ DC-д, доор өөрийн баталгаажуулах_түлхүүр.
  • Эцэст нь, үйлчлүүлэгч Төгс Forward нууцлалыг хүсч болно. Бүр баталгаажуулах_түлхүүр байсан Байнгын Түлхүүр - нэг DC - ба үйлчлүүлэгч дуудаж болно auth.bindTempAuthKey ашиглах түр хугацаагаар баталгаажуулах_түлхүүр - дахиад л ганцхан temp_auth_key нэг DC, бүгдэд нийтлэг MTProto сессүүд энэ DC руу.

анзаараарай давс (болон ирээдүйн давс) бас нэг дээр байна баталгаажуулах_түлхүүр тэдгээр. хүн бүрийн дунд хуваалцсан MTProto сессүүд ижил DC руу.

"Өөр өөр TCP холболтуудын хооронд" гэдэг нь юу гэсэн үг вэ? Тэгэхээр энэ нь гэсэн үг гэх мэт зүйл вэб сайт дээрх зөвшөөрлийн күүки - энэ нь тухайн серверт олон TCP холболтууд хэвээр (амьд үлддэг) боловч нэг л өдөр мууддаг. Зөвхөн HTTP-ээс ялгаатай нь MTProto-д сесс доторх мессежүүдийг дараалан дугаарлаж баталгаажуулдаг; хэрэв тэдгээр нь хонгилд орсон бол холболт тасарсан - шинэ холболт үүсгэсний дараа сервер өмнөх үед дамжуулаагүй бүх зүйлийг энэ сессэд илгээх болно. TCP холболт.

Гэсэн хэдий ч дээрх мэдээллийг олон сарын турш шалгасны эцэст нэгтгэн дүгнэж байна. Энэ хооронд бид үйлчлүүлэгчээ эхнээс нь хэрэгжүүлж байна уу? -Эхлэл рүүгээ буцъя.

Тиймээс үүсгэцгээе auth_key дээр Telegram-аас Диффи-Хеллманы хувилбарууд. Баримт бичгийг ойлгохыг хичээцгээе ...

Василий, [19.06.18 20:05] өгөгдөл_хэштэй := SHA1(өгөгдөл) + өгөгдөл + (ямар ч санамсаргүй байт); урт нь 255 байттай тэнцүү байхаар;
шифрлэгдсэн_өгөгдөл := RSA(хэштэй_өгөгдөл, серверийн_нийтийн_түлхүүр); 255 байт урт тоог (том эндиан) шаардлагатай модулиар шаардлагатай хүчин чадалд хүргэж, үр дүнг 256 байт тоо болгон хадгална.

Тэд бага зэрэг мансууруулах бодис хэрэглэдэг

Эрүүл хүний ​​DH шиг харагддаггүй
dx-д хоёр нийтийн түлхүүр байхгүй

Эцэст нь энэ асуудлыг шийдсэн боловч үлдэгдэл үлдсэн - үйлчлүүлэгч тухайн тоог хүчин зүйлээр тооцож чадсан гэдгээ нотлох ажлыг баталгаажуулсан болно. DoS халдлагаас хамгаалах төрөл. Мөн RSA түлхүүрийг зөвхөн нэг чиглэлд, үндсэндээ шифрлэхэд ашигладаг new_nonce. Хэдийгээр энэ энгийн мэт санагдах мэс засал амжилттай болох ч танд юу тохиолдох вэ?

Василий, [20.06.18/00/26 XNUMX:XNUMX] Би аппидын хүсэлтийг хараахан авч амжаагүй байна

Би энэ хүсэлтийг DH-д илгээсэн

Мөн тээврийн док дээр 4 байт алдааны кодоор хариулах боломжтой гэж бичсэн байна. Тэгээд л болоо

За тэр надад -404 гэж хэлсэн, тэгээд яах вэ?

Тиймээс би түүнд: "Ийм хурууны хээтэй серверийн түлхүүрээр шифрлэгдсэн тэнэгээ барьж ав, би DH хүсч байна" гэж хэлэхэд тэр тэнэг 404 гэж хариулав.

Энэ серверийн хариуг та юу гэж бодож байна вэ? Юу хийх вэ? Асуух хүн байхгүй (гэхдээ энэ талаар хоёрдугаар хэсэгт илүү ихийг хэлэх болно).

Энд бүх ашиг сонирхлыг усан онгоцны зогсоол дээр хийдэг

Надад өөр хийх зүйл байхгүй, би зүгээр л тоог нааш цааш хөрвүүлэхийг мөрөөддөг байсан

32 битийн хоёр тоо. Би бусдын адил тэднийг савласан

Гэхдээ үгүй ​​ээ, энэ хоёрыг эхлээд BE гэж мөрөнд нэмэх хэрэгтэй

Вадим Гончаров, [20.06.18 15:49] мөн үүнээс болж 404?

Василий, [20.06.18 15:49] ТИЙМ!

Вадим Гончаров, [20.06.18 15:50] тиймээс тэр юу "олоогүй" болохыг ойлгохгүй байна.

Василий, [20.06.18 15:50] ойролцоогоор

Би анхдагч хүчин зүйлүүдэд ийм задрал олж чадаагүй байна%)

Бид алдааны мэдээг ч удирдаж чадаагүй

Василий, [20.06.18 20:18] Өө, бас MD5 байна. Аль хэдийн гурван өөр хэш

Гол хурууны хээг дараах байдлаар тооцоолно.

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

SHA1 ба sha2

Ингээд оруулъя auth_key Бид Diffie-Hellman ашиглан 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 ашиглан цаашид шифрлэлт хийх хэрэгтэй.

Зурвасын түлхүүр нь мессежийн үндсэн SHA128-ийн 256 дунд бит (сесс, мессежийн ID гэх мэт) бөгөөд зөвшөөрлийн түлхүүрээс авсан 32 байтаар хавсаргах байтуудаар тодорхойлогддог.

Василий, [22.06.18 14:08] Дундаж, гичий, бит

Хүлээн авсан auth_key. Бүгд. Тэднээс цааш... баримт бичигт тодорхойгүй байна. Нээлттэй эх кодыг чөлөөтэй судлаарай.

MTProto 2.0 нь 12-1024 байт дүүргэлт шаарддаг бөгөөд үүнээс үүдэн гарсан мессежийн уртыг 16 байтаар хуваах нөхцөлтэйг анхаарна уу.

Тэгэхээр та хэр их дэвсгэр нэмэх ёстой вэ?

Тийм ээ, алдаа гарсан тохиолдолд 404 бас байдаг

Хэрэв хэн нэгэн баримт бичгийн диаграм, текстийг сайтар судалж үзвэл тэнд MAC байхгүй байгааг анзаарсан. Тэр AES нь өөр хаана ч ашигладаггүй IGE горимд ашиглагддаг. Мэдээжийн хэрэг тэд энэ тухай FAQ-даа бичдэг... Энд, мессежийн түлхүүр нь өөрөө шифрлэгдсэн өгөгдлийн SHA хэш бөгөөд бүрэн бүтэн байдлыг шалгахад ашигладаг бөгөөд хэрэв таарахгүй бол ямар нэг шалтгааны улмаас баримт бичиг. тэднийг чимээгүйхэн үл тоомсорлохыг зөвлөж байна (гэхдээ аюулгүй байдлын талаар яах вэ, хэрэв тэд биднийг эвдсэн бол яах вэ?).

Би криптографч биш, магадгүй онолын үүднээс энэ тохиолдолд энэ горимд буруу зүйл байхгүй. Гэхдээ би Telegram Desktop-г жишээ болгон ашиглан практик асуудлыг тодорхой нэрлэж чадна. Энэ нь орон нутгийн кэшийг (эдгээр бүх D877F783D5D3EF8C) MTProto дахь мессежийн нэгэн адил шифрлэдэг (зөвхөн энэ тохиолдолд 1.0 хувилбар), өөрөөр хэлбэл. эхлээд мессежийн түлхүүр, дараа нь өгөгдөл өөрөө (мөн гол томоос гадна хаа нэгтээ auth_key 256 байт, үүнгүйгээр msg_key ашиггүй). Тиймээс том файлууд дээр асуудал мэдэгдэхүйц болно. Тухайлбал, та өгөгдлийн хоёр хувийг хадгалах хэрэгтэй - шифрлэгдсэн, шифрлэгдсэн. Хэрэв мегабайт, эсвэл урсгалтай видео байгаа бол, жишээ нь?.. Шифрлэгдсэн текстийн дараа MAC-тай сонгодог схемүүд нь шууд дамжуулж дамжуулж унших боломжийг танд олгоно. Гэхдээ MTProto-ийн тусламжтайгаар та хийх хэрэгтэй болно эхэндээ мессежийг бүхэлд нь шифрлэх эсвэл тайлж, зөвхөн дараа нь сүлжээнд эсвэл диск рүү шилжүүлэх. Тиймээс Telegram Desktop-ийн хамгийн сүүлийн хувилбаруудад кэш дотор байна user_data Өөр форматыг бас ашигладаг - CTR горимд AES-тэй.

Василий, [21.06.18 01:27] Өө, би IGE гэж юу болохыг олж мэдсэн: IGE нь Kerberos-д зориулагдсан "баталгаажуулах шифрлэлтийн горим"-ын анхны оролдлого юм. Энэ нь бүтэлгүйтсэн оролдлого байсан (энэ нь бүрэн бүтэн байдлыг хамгаалахгүй), устгах шаардлагатай болсон. Энэ нь OCB болон GCM гэх мэт горимуудаар төгсөж, ажилладаг баталгаажуулах шифрлэлтийн горимын 20 жилийн эрэл хайгуулын эхлэл байсан юм.

Одоо тэрэгний аргументууд:

Николай Дуров тэргүүтэй Telegram-ын ард байгаа баг нь МУЗ-ийн аварга зургаан хүнээс бүрддэг бөгөөд тэдний тал нь математикийн ухааны докторууд юм. Тэд MTProto-ийн одоогийн хувилбарыг гаргахад хоёр жил орчим хугацаа зарцуулсан.

Энэ нь инээдтэй юм. Доод түвшинд хоёр жил

Эсвэл та зүгээр л TL авч болно

За, бид шифрлэлт болон бусад нарийн ширийн зүйлийг хийсэн гэж бодъё. Эцэст нь TL-ээр цувуулсан хүсэлтийг илгээж, хариултуудыг цуваа болгох боломжтой юу? Тэгэхээр та юу, яаж илгээх ёстой вэ? Энд аргыг хэлье initConnection, магадгүй энэ мөн үү?

Василий, [25.06.18 18:46] Холболтыг эхлүүлж, хэрэглэгчийн төхөөрөмж болон програмын мэдээллийг хадгална.

Энэ нь app_id, төхөөрөмжийн_загвар, системийн_хувилбар, програмын_хувилбар болон хэлний кодыг хүлээн зөвшөөрдөг.

Мөн зарим асуулга

Ердийнх шиг баримт бичиг. Нээлттэй эх сурвалжийг чөлөөтэй судлаарай

Хэрэв invokeWithLayer дээр бүх зүйл тодорхой байсан бол энд юу нь болохгүй байна вэ? Бидэнд байгаа гэж бодъё - үйлчлүүлэгч серверээс асуух зүйлтэй байсан - бидний илгээхийг хүссэн хүсэлт байна:

Василий, [25.06.18 19:13] Кодоос харахад эхний дуудлагыг энэ новшийн дотор ороосон, харин новш нь өөрөө invokewithlayer-д ороосон байна.

Яагаад initConnection нь тусдаа дуудлага байж болохгүй, гэхдээ заавал боодол байх ёстой вэ? Тийм ээ, энэ нь үндсэн түлхүүртэй адил нэг удаа биш харин сесс бүрийн эхэнд хийх ёстой. Гэхдээ! Үүнийг зөвшөөрөлгүй хэрэглэгч дуудах боломжгүй! Одоо бид хэрэгжих шатандаа ирлээ Энэ нэг баримт бичгийн хуудас - энэ нь бидэнд хэлдэг ...

API аргуудын зөвхөн багахан хэсгийг зөвшөөрөлгүй хэрэглэгчдэд ашиглах боломжтой:

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

Тэдний хамгийн эхнийх нь, auth.sendCode, мөн бидний api_id болон api_hash илгээдэг анхны хүсэлт байдаг бөгөөд үүний дараа бид код бүхий SMS хүлээн авдаг. Хэрэв бид буруу DC-д байгаа бол (жишээлбэл, энэ улсын утасны дугаарыг өөр хүн ашигладаг) бид хүссэн DC-ийн дугаартай алдаа хүлээн авах болно. DC дугаараар ямар IP хаягтай холбогдохыг мэдэхийн тулд бидэнд тусална уу help.getConfig. Нэгэн цагт ердөө 5 удаагийн бичлэг байсан бол 2018 оны алдартай үйл явдлуудын дараа энэ тоо эрс нэмэгдсэн.

Одоо бид сервер дээр нэрээ нууцлан энэ шатанд ирснээ санацгаая. Зүгээр л IP хаяг авах нь хэтэрхий үнэтэй биш гэж үү? Яагаад үүнийг болон бусад үйлдлүүдийг MTProto-ийн шифрлэгдээгүй хэсэгт хийж болохгүй гэж? "Хуурамч хаягаар хариу өгөх нь RKN биш гэдгийг бид яаж баталгаажуулах вэ?" Гэсэн эсэргүүцлийг би сонсож байна. Үүний тулд бид ерөнхийдөө албан ёсны үйлчлүүлэгчид гэдгийг санаж байна RSA түлхүүрүүдийг суулгасан, өөрөөр хэлбэл чи зүгээр үү тэмдэг энэ мэдээлэл. Үнэн хэрэгтээ энэ нь үйлчлүүлэгчид бусад сувгаар дамжуулан хүлээн авдаг блоклохыг тойрч гарах талаар мэдээлэл авахын тулд аль хэдийн хийгдсэн (логикийн хувьд үүнийг MTProto-д өөрөө хийх боломжгүй; та хаана холбогдохоо мэдэх хэрэгтэй).

БОЛЖ БАЙНА УУ. Үйлчлүүлэгчийн зөвшөөрлийн энэ үе шатанд бид хараахан зөвшөөрөөгүй бөгөөд бидний өргөдлийг бүртгүүлээгүй байна. Бид одоохондоо зөвшөөрөлгүй хэрэглэгчдэд ашиглах боломжтой аргуудад сервер ямар хариу үйлдэл үзүүлэхийг харахыг хүсч байна. Бас энд…

Василий, [10.07.18 14:45] https://core.telegram.org/method/help.getConfig

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

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

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

Уг схемд эхнийх нь хоёрдугаарт ордог

Tdesktop схемд гурав дахь утга нь байна

Тийм ээ, тэр цагаас хойш мэдээж баримт бичиг шинэчлэгдсэн. Хэдийгээр энэ нь удахгүй дахин хамааралгүй болж магадгүй юм. Шинэхэн хөгжүүлэгч яаж мэдэх ёстой вэ? Өргөдлөө бүртгүүлчихвэл мэдэгдэх болов уу? Василий үүнийг хийсэн, гэхдээ харамсалтай нь тэд түүнд юу ч илгээгээгүй (дахин бид энэ талаар хоёрдугаар хэсэгт ярих болно).

...Бид аль хэдийн API руу шилжсэнийг та анзаарсан, i.e. дараагийн түвшинд, MTProto сэдвээр ямар нэг зүйлийг алдсан уу? Гайхах зүйл алга:

Василий, [28.06.18 02:04] Мм, тэд e2e дээрх зарим алгоритмуудыг эргэлзэж байна.

Mtproto нь хоёр домэйны шифрлэлтийн алгоритмууд болон түлхүүрүүд, түүнчлэн бага зэрэг боодлын бүтцийг тодорхойлдог.

Гэхдээ тэд стекийн янз бүрийн түвшнийг байнга хольж байдаг тул mtproto хаана дуусч, дараагийн түвшин эхэлсэн нь үргэлж тодорхой байдаггүй.

Тэд хэрхэн холилддог вэ? Жишээлбэл, PFS-ийн ижил түр зуурын түлхүүр энд байна (дашрамд хэлэхэд Telegram Desktop үүнийг хийх боломжгүй). Үүнийг API хүсэлтээр гүйцэтгэдэг auth.bindTempAuthKey, өөрөөр хэлбэл дээд түвшнээс. Гэхдээ үүнтэй зэрэгцэн энэ нь доод түвшний шифрлэлтэд саад учруулдаг - үүний дараа, жишээлбэл, та үүнийг дахин хийх хэрэгтэй. initConnection гэх мэт, энэ биш зүгээр л ердийн хүсэлт. Бас нэг онцгой зүйл нь талбар хэдий ч DC-д зөвхөн НЭГ түр зуурын түлхүүртэй байж болно auth_key_id Мессеж бүрд дор хаяж мессеж бүрийг түлхүүрийг өөрчлөх боломжийг олгодог бөгөөд сервер нь түр зуурын түлхүүрийг хүссэн үедээ "мартах" эрхтэй - энэ тохиолдолд юу хийх талаар баримт бичигт заагаагүй байна ... яагаад болохгүй гэж. Ирээдүйн давсны багцтай адил танд хэд хэдэн түлхүүр байхгүй байна уу?

MTProto-ийн сэдвийн талаар анхаарах хэд хэдэн зүйл бий.

Мессежийн мессеж, msg_id, msg_seqno, баталгаажуулалт, буруу чиглэлийн ping болон бусад өвөрмөц байдал

Та яагаад тэдний талаар мэдэх хэрэгтэй байна вэ? Учир нь тэд илүү өндөр түвшинд "алддаг" бөгөөд та 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-ийн адил, гэхдээ by 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 алдаа (мэдээжийн хэрэг, хариултуудын семантикийг үл хүндэтгэдэг, зарим газар кодуудын хооронд санамсаргүй байдлаар хуваарилагдсан байдаг), мөр нь иймэрхүү харагдаж байна. ТОМ_ҮСГИЙН_БА_ДУГААР. Жишээлбэл, PHONE_NUMBER_OCCUPIED эсвэл FILE_PART_Х_ДУГААР БАЙНА. За, энэ нь танд энэ шугам хэрэгтэй хэвээр байх болно задлан шинжлэх. Жишээлбэл, FLOOD_WAIT_3600 та нэг цаг хүлээх хэрэгтэй гэсэн үг юм PHONE_MIGRATE_5, энэ угтвартай утасны дугаар нь 5-р DC-д бүртгэгдсэн байх ёстой. Бидэнд нэг төрлийн хэл бий, тийм ээ? Бидэнд мөрийн маргаан хэрэггүй, ердийнх нь аргумент байх болно, за.

Дахин хэлэхэд, энэ нь үйлчилгээний мессежийн хуудсан дээр байдаггүй, гэхдээ энэ төслийн хувьд ердийнх шиг мэдээллийг олж авах боломжтой. өөр баримт бичгийн хуудсан дээрБайна. Эсвэл сэжиг төрүүлэх. Нэгдүгээрт, харна уу, бичих/давхаргын зөрчил - RpcError дотор үүрлэж болно RpcResult. Яагаад гадаа болохгүй гэж? Юуг нь тооцож үзээгүй юм бэ?.. Үүний дагуу тэр баталгаа хаана байна RpcError дотор суулгаж болохгүй RpcResult, гэхдээ шууд эсвэл өөр төрөлд үүрлэсэн байх уу?.. Хэрэв чадахгүй бол яагаад дээд түвшинд байхгүй байна, i.e. энэ нь дутуу байна 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-ийн чих цухуйж байна (бидэнд байт вектор хэрэгтэй байсан ба доод хоёр битэд тоолол, дээд хоёр битэд тугнууд байсан). Гол нь өөр. Энэ бүхэн яагаад амьдрал дээр байдгийг ойлгох хүн байна уу? жинхэнэ үйлчлүүлэгчид шаардлагатай юу?.. Хүнд хэцүү, гэхдээ хэрэв хүн дибаг хийж, интерактив горимд ажиллаж байвал ямар нэг ашиг тустай гэж төсөөлж болно - серверээс юу, яаж гэдгийг асуу. Гэхдээ энд хүсэлтийг тайлбарласан болно хоёр талын аялал.

Үүнээс үзэхэд тал бүр мессежийг шифрлэх, илгээхээс гадна өөрсдийнхөө тухай, тэдэнд өгсөн хариултуудын талаарх мэдээллийг үл мэдэгдэх хугацаанд хадгалах ёстой. Баримт бичигт эдгээр функцүүдийн цаг хугацаа эсвэл практикт хэрэглэгдэх боломжуудыг тайлбарлаагүй болно. ямар ч арга алга. Хамгийн гайхалтай нь тэдгээрийг албан ёсны үйлчлүүлэгчдийн кодонд ашигладаг явдал юм! Тэдэнд нийтийн бичиг баримтад тусгагдаагүй зүйлийг хэлсэн бололтой. Кодоос ойлгоорой яагаад вэ, TL-ийн хувьд энгийн байхаа больсон - энэ нь (харьцангуй) логикийн хувьд тусгаарлагдсан хэсэг биш, харин програмын архитектурт холбогдсон хэсэг, i.e. програмын кодыг ойлгоход илүү их цаг хугацаа шаардагдана.

Пинг болон цаг. Дараалал.

Бүх зүйлээс, хэрэв бид серверийн архитектурын талаархи таамаглалыг санаж байвал (хүсэлтийг арын хэсэгт хуваарилах) TCP-д хүргэх бүх баталгааг үл харгалзан (мэдээлэл хүргэгдсэн, эсвэл танд дутагдлын талаар мэдэгдэх болно, гэхдээ харамсалтай нь) Мэдээллийг асуудал гарахаас өмнө хүргэх болно), үүнийг MTProto-д баталгаажуулна - баталгаа байхгүй. Сервер таны мессежийг амархан алдаж эсвэл хаях боломжтой бөгөөд энэ талаар юу ч хийж чадахгүй, янз бүрийн төрлийн таяг ашиглахад л хангалттай.

Юуны өмнө - мессежийн дараалал. За, нэг зүйл бол бүх зүйл эхнээсээ тодорхой байсан - батлагдаагүй мессежийг хадгалж, дахин илгээх ёстой. Тэгээд хэдэн цагийн дараа? Тэгээд шоглогчид түүнийг мэддэг. Магадгүй эдгээр донтсон үйлчилгээний мессежүүд нь таягтай энэ асуудлыг ямар нэгэн байдлаар шийдэж байгаа байх, жишээ нь Telegram Desktop дээр тэдгээрт тохирох 4 орчим дараалал байдаг (магадгүй илүү дээр дурдсанчлан, үүний тулд та түүний код, архитектурыг илүү нухацтай судлах хэрэгтэй. Цаг хугацаа, бид үүнийг дээж болгон авах боломжгүй гэдгийг бид мэднэ; MTProto схемийн тодорхой тооны төрлийг үүнд ашигладаггүй).

Яагаад ийм зүйл болж байна вэ? Магадгүй серверийн программистууд кластер доторх найдвартай байдлыг хангах, тэр ч байтугай урд тэнцвэржүүлэгч дээр буфер хийх боломжгүй байсан тул энэ асуудлыг үйлчлүүлэгч рүү шилжүүлсэн байх. Василий цөхрөнгөө барсандаа TCP-ийн алгоритмуудыг ашиглан зөвхөн хоёр дараалал бүхий өөр хувилбарыг хэрэгжүүлэхийг оролдов - сервер рүү RTT-ийг хэмжиж, батлагдаагүй хүсэлтийн тооноос хамааран "цонхны" хэмжээг (мэдээлэл дотор) тохируулах. Өөрөөр хэлбэл, серверийн ачааллыг үнэлэх ийм бүдүүлэг эвристик нь бидний хэдэн хүсэлтийг нэгэн зэрэг зажилж, алдахгүй байх явдал юм.

За, чи ойлгож байна, тийм үү? Хэрэв та TCP дээр ажиллаж байгаа протокол дээр TCP-ийг дахин хэрэгжүүлэх шаардлагатай бол энэ нь маш муу боловсруулсан протоколыг илтгэнэ.

Өө, яагаад танд нэгээс олон дараалал хэрэгтэй байна вэ, энэ нь өндөр түвшний API-тай ажилладаг хүний ​​хувьд юу гэсэн үг вэ? Хараач, та хүсэлт гаргаж, дарааллаар нь бичдэг, гэхдээ ихэнхдээ шууд илгээж чадахгүй. Яагаад? Учир нь хариулт нь байх болно msg_id, энэ нь түр зуурынхаБи бол шошго бөгөөд түүний даалгаварыг аль болох оройтож хойшлуулах нь дээр - хэрэв сервер бид хоёрын хоорондох цаг таарахгүйн улмаас татгалзсан тохиолдолд (мэдээжийн хэрэг, бид цагийг одоогийнхоос өөрчилдөг таяг хийж болно. серверийн хариултаас тооцоолсон дельта нэмэх замаар сервер рүү - албан ёсны үйлчлүүлэгчид үүнийг хийдэг боловч буферийн улмаас бүдүүлэг бөгөөд буруу байна). Тиймээс, та номын сангаас орон нутгийн функцийн дуудлагаар хүсэлт гаргахад мессеж дараах үе шатуудыг дамждаг.

  1. Энэ нь нэг дараалалд байрладаг бөгөөд шифрлэлтийг хүлээж байна.
  2. Томилогдсон msg_id мөн мессеж өөр дараалалд очсон - дамжуулах боломжтой; залгуур руу илгээнэ үү.
  3. a) Сервер MsgsAck-д хариу өгсөн - мессежийг хүргэсэн тул бид үүнийг "бусад дараалал" -аас устгана.
    б) Эсвэл эсрэгээр, тэр ямар нэг зүйлд дургүй байсан, тэр badmsg гэж хариулсан - "өөр дараалал" -аас дахин илгээх
    в) Юу ч мэдэгдэхгүй, мессежийг өөр дарааллаас дахин илгээх шаардлагатай - гэхдээ яг хэзээ болох нь тодорхойгүй байна.
  4. Сервер эцэст нь хариулав RpcResult - бодит хариулт (эсвэл алдаа) - зөвхөн хүргэгдээд зогсохгүй боловсруулсан.

Магадгүй, сав ашиглах нь асуудлыг хэсэгчлэн шийдэж чадна. Энэ нь хэд хэдэн мессежийг нэг дор багцалсан бөгөөд сервер нь бүгдэд нь нэг дор, нэг дор баталгаажуулсан хариу илгээдэг. msg_id. Гэхдээ хэрэв ямар нэг зүйл буруу болвол тэр энэ багцаас бүхэлд нь татгалзах болно.

Мөн энэ үед техникийн бус хүчин зүйлүүд гарч ирдэг. Туршлагаас харахад бид олон суга таяг харсан бөгөөд үүнээс гадна бид муу зөвлөгөө, архитектурын олон жишээг харах болно - ийм нөхцөлд итгэж, ийм шийдвэр гаргах нь зүйтэй болов уу? Асуулт нь риторик (мэдээжийн хэрэг биш).

Бид юу яриад байгаа юм бэ? Хэрэв та "мэдээжийн талаархи хар тамхины мессеж" сэдвээр "Чи тэнэг юм аа, чи бидний гайхалтай төлөвлөгөөг ойлгосонгүй!" гэх мэтээр таамаглаж болно. (Тиймээс эхлээд баримт бичгийг энгийн хүмүүс бичих ёстой, багц солилцооны үндэслэл, жишээнүүдийн хамт бич, дараа нь бид ярилцах болно), тэгвэл цаг хугацаа/хугацаа нь зөвхөн практик бөгөөд тодорхой асуулт юм, энд бүх зүйл эртнээс мэдэгдэж байсан. Баримт бичиг бидэнд хугацаа хэтэрсэн талаар юу хэлж байна вэ?

Сервер нь RPC хариултыг ашиглан үйлчлүүлэгчээс (ихэвчлэн RPC асуулга) мессеж хүлээн авсныг ихэвчлэн хүлээн зөвшөөрдөг. Хэрэв хариу удаан ирвэл сервер эхлээд төлбөрийн баримтыг илгээж, дараа нь RPC хариу илгээж болно.

Үйлчлүүлэгч серверээс мессеж хүлээн авснаа (ихэвчлэн RPC хариулт) хүлээн зөвшөөрч, хэрэв хэтэрхий оройтоогүй бол (хэрэв үүнийг хүлээн авснаас хойш 60-120 секундын дараа үүсгэсэн бол) дараагийн RPC асуулгад мэдэгдлийг нэмж оруулдаг. серверээс ирсэн мессеж). Гэсэн хэдий ч, удаан хугацааны туршид сервер рүү мессеж илгээх шалтгаан байхгүй эсвэл серверээс хүлээн зөвшөөрөгдөөгүй олон тооны мессеж (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 секунд тутамд нэг удаа илгээдэг бол жишээлбэл, disconnect_delay-г 75 секундтэй тэнцүүлж болно.

Чи галзуурчихсан юм уу?! 60 секундын дараа галт тэрэг буудал руу орж, зорчигчдыг буулгаж, авах ба хонгилд дахин холбоо тасарна. 120 секундын дараа, та үүнийг сонсох үед энэ нь өөр нэгэнд хүрч, холболт тасрах магадлалтай. Хөл нь хаанаас ирж байгаа нь тодорхой байна - "Би дуугарах чимээ сонссон, гэхдээ хаана байгааг мэдэхгүй байна", Nagl-ийн алгоритм болон интерактив ажилд зориулагдсан TCP_NODELAY сонголт байдаг. Гэхдээ уучлаарай, 200 гэсэн үндсэн утгыг нь барь Миллисекунд Хэрэв та үнэхээр ижил төстэй зүйлийг дүрсэлж, хэд хэдэн багцыг хэмнэхийг хүсч байвал үүнийг 5 секунд эсвэл "Хэрэглэгч бичиж байна ..." гэсэн мессежийн хугацаа дууссанаас үл хамааран хойшлуул. Гэхдээ дахиад байхгүй.

Эцэст нь пинг. Энэ нь TCP холболтын найдвартай байдлыг шалгах явдал юм. Энэ нь инээдтэй юм, гэхдээ 10 орчим жилийн өмнө би манай факультетийн дотуур байрны элчийн талаар шүүмжлэлтэй текст бичсэн - тэндхийн зохиогчид мөн үйлчлүүлэгчээс сервер рүү хандсан, харин эсрэгээр нь биш. Харин 3-р курсын оюутнууд нэг хэрэг, олон улсын оффис бол өөр хэрэг, тийм үү?..

Нэгдүгээрт, бага зэрэг боловсролын хөтөлбөр. TCP холболт нь пакет солилцоогүй тохиолдолд хэдэн долоо хоног амьдрах боломжтой. Энэ нь зорилгоосоо шалтгаалж сайн муу аль аль нь юм. Хэрэв танд SSH холболт серверт нээлттэй байсан бол та компьютерээсээ босоод чиглүүлэгчээ дахин ачаалж, байрандаа буцаж ирсэн бол сайн байна - энэ серверээр дамжуулан сесс тасраагүй (та юу ч бичээгүй, пакет байхгүй) , энэ нь тохиромжтой. Сервер дээр олон мянган үйлчлүүлэгч байгаа бол (сайн уу, Postgres!) болон үйлчлүүлэгчийн хост удаан хугацааны өмнө дахин ачаалагдсан байж магадгүй - гэхдээ бид энэ талаар мэдэхгүй байх болно.

Чат/IM системүүд нь нэг нэмэлт шалтгааны улмаас хоёр дахь тохиолдол байдаг - онлайн статус. Хэрэв хэрэглэгч "унасан" бол та энэ талаар түүний ярилцагчдад мэдэгдэх хэрэгтэй. Үгүй бол та Жабберыг бүтээгчдийн хийсэн алдаа (мөн 20 жилийн турш зассан) байх болно - хэрэглэгч салсан боловч тэд түүнийг онлайн байна гэж үзэн түүн рүү мессеж бичсээр байна (энэ нь мөн эдгээрт бүрмөсөн алдагдсан) салгахаас хэдхэн минутын өмнө). Үгүй ээ, TCP таймер хэрхэн ажилладагийг ойлгодоггүй олон хүмүүс санамсаргүй байдлаар оруулдаг TCP_KEEPALIVE сонголт (хэдэн арван секунд гэх мэт зэрлэг утгыг тохируулах замаар) энд тус болохгүй - та зөвхөн үйлдлийн системийн цөм биш гэдгийг анхаарах хэрэгтэй. Хэрэглэгчийн машин нь амьд, гэхдээ бас хэвийн ажиллаж, хариу үйлдэл үзүүлэх чадвартай, мөн програм нь өөрөө (та үүнийг царцааж чадахгүй гэж бодож байна уу? Ubuntu 18.04 дээрх Telegram Desktop миний хувьд нэгээс олон удаа хөлдсөн).

Тийм учраас та ping хийх хэрэгтэй сервер үйлчлүүлэгч, харин эсрэгээр биш - хэрэв үйлчлүүлэгч үүнийг хийвэл, холболт тасарсан бол ping-г хүргэхгүй, зорилгодоо хүрэхгүй.

Telegram дээр бид юу харж байна вэ? Энэ нь яг эсрэгээрээ! За тэгэхээр. Албан ёсоор бол мэдээжийн хэрэг хоёр тал бие биенээ ping хийх боломжтой. Практикт үйлчлүүлэгчид таяг хэрэглэдэг ping_delay_disconnect, энэ нь сервер дээрх таймерыг тохируулдаг. За, уучлаарай, үйлчлүүлэгч хэр удаан тэнд пинггүйгээр амьдрахыг шийдэх нь хамаагүй. Ачаалал дээрээ тулгуурлан сервер илүү сайн мэддэг. Гэхдээ мэдээжийн хэрэг, хэрэв та нөөцийг үл тоомсорлох юм бол та өөрийн муу Пиноккио болж, таяг таяг хийх болно ...

Үүнийг хэрхэн зохион бүтээх ёстой байсан бэ?

Дээрх баримтууд нь Telegram/VKontakte-ийн баг нь компьютерийн сүлжээг тээвэрлэх (ба түүнээс доош) түвшинд тийм ч чадваргүй, холбогдох асуудлаар тэдний ур чадвар доогуур байгааг тодорхой харуулж байна гэж би үзэж байна.

Энэ нь яагаад ийм төвөгтэй болж хувирав, Telegram-ын архитекторууд хэрхэн эсэргүүцэхийг оролдох вэ? Тэд TCP холболтын эвдрэлийг даван туулах сесс хийхийг оролдсон нь, өөрөөр хэлбэл, одоо хүргэгдээгүй зүйлийг бид дараа нь хүргэх болно. Тэд мөн UDP тээвэрлэлт хийхийг оролдсон байх, гэхдээ тэд бэрхшээлтэй тулгараад орхисон (тиймээс баримт бичиг хоосон байна - сайрхах зүйл байхгүй). Гэхдээ ерөнхийдөө сүлжээ, ялангуяа TCP хэрхэн ажилладаг, хаана найдах, хаана үүнийг өөрөө хийх хэрэгтэй (мөн хэрхэн) талаар ойлголт дутмаг байгаагаас үүдэн үүнийг криптографтай хослуулах оролдлого "хоёр шувуутай. нэг чулуу” гэдэг нь үр дүн юм.

Энэ нь яаж хэрэгтэй байсан бэ? Үүний үндсэн дээр msg_id Энэ нь дахин тоглуулах халдлагаас урьдчилан сэргийлэхийн тулд криптографийн үүднээс шаардлагатай цагийн тэмдэг бөгөөд түүнд өвөрмөц танигч функц хавсаргасан нь алдаа юм. Тиймээс, одоогийн архитектурыг үндсээр нь өөрчлөхгүйгээр (Шинэчлэлтүүдийн урсгалыг үүсгэх үед энэ нь энэ цуврал нийтлэлийн өөр нэг хэсэгт зориулсан өндөр түвшний API сэдэв юм) дараахь зүйлийг хийх шаардлагатай болно.

  1. Үйлчлүүлэгчтэй TCP холболтыг барьж байгаа сервер хариуцлагыг хүлээнэ - хэрэв энэ нь залгуураас уншсан бол алдааг хүлээн зөвшөөрч, боловсруулах эсвэл буцаана уу, ямар ч алдагдалгүй. Дараа нь баталгаажуулалт нь id-н вектор биш, зүгээр л "хамгийн сүүлд хүлээн авсан seq_no" - зүгээр л TCP-тэй адил тоо (хоёр тоо - таны дараалал ба батлагдсан тоо). Бид үргэлж хуралдаанд байдаг, тийм үү?
  2. Дахин тоглуулах халдлагаас урьдчилан сэргийлэх цагийн тэмдэг нь тусдаа талбар болж хувирдаг. Энэ нь шалгагдсан боловч бусад зүйлд нөлөөлөхгүй. Хангалттай ба uint32 - хэрэв бидний давс дор хаяж хагас өдөр тутамд өөрчлөгдвөл бид 16 битийг тухайн үеийн бүхэл тоон хэсгийн доод эрэмбийн битүүдэд, үлдсэнийг нь секундын бутархай хэсэгт (одоогийнх шиг) хуваарилж чадна.
  3. Устгасан msg_id огт - арын хэсэгт хүсэлтийг ялгах үүднээс авч үзвэл, нэгдүгээрт, үйлчлүүлэгчийн id, хоёрдугаарт, сесс id нь тэдгээрийг нэгтгэдэг. Үүний дагуу хүсэлтийн танигчийн хувьд зөвхөн нэг зүйл хангалттай seq_no.

Энэ нь бас хамгийн амжилттай сонголт биш, бүрэн санамсаргүй нь танигч болж чадна - дашрамд хэлэхэд, энэ нь мессеж илгээх үед өндөр түвшний API дээр хийгдсэн байдаг. Архитектурыг харьцангуй абсолютаас бүрэн өөрчлөх нь дээр байх болно, гэхдээ энэ бол энэ нийтлэл биш харин өөр хэсэгт зориулагдсан сэдэв юм.

API?

Та-дам! Тиймээс, өвдөлт, таягтай замаар дүүрэн замыг туулж, бид эцэст нь сервер рүү ямар ч хүсэлт илгээж, тэдэнд ямар ч хариулт авахаас гадна серверээс шинэчлэлтүүдийг хүлээн авах боломжтой болсон (хүсэлтийн хариуд биш, харин өөрөө) PUSH шиг бидэнд илгээдэг, хэрэв хэн нэгэн ийм байдлаар илүү ойлгомжтой байвал).

Анхаар, одоо нийтлэлд Перл дэх цорын ганц жишээ байх болно! (синтаксийг сайн мэдэхгүй хүмүүсийн хувьд адислахын эхний аргумент нь объектын өгөгдлийн бүтэц, хоёр дахь нь түүний ангилал юм):

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

Тийм ээ, зориуд спойлер биш - хэрэв та уншиж амжаагүй бол үргэлжлүүлээд үзээрэй!

Өө, wai~~... энэ ямар харагдаж байна вэ? Маш танил зүйл ... магадгүй энэ нь 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-д PFS ашиглах үед TLS сессийн тасалбар (RFC 5077) түлхүүрүүдийг дахин тохиролцохгүйгээр, сервер дээр түлхүүр мэдээллийг хадгалахгүйгээр шифрлэгдсэн сессийг үргэлжлүүлэх. Эхний холболтыг нээх, түлхүүр үүсгэх үед сервер холболтын төлөвийг шифрлэж, үйлчлүүлэгч рүү дамжуулдаг (сессийн тасалбар хэлбэрээр). Үүний дагуу холболтыг сэргээх үед үйлчлүүлэгч сессийн тасалбар, түүний дотор сессийн түлхүүрийг сервер рүү буцааж илгээдэг. Тасалбар нь өөрөө түр зуурын түлхүүрээр (сессийн тасалбарын түлхүүр) шифрлэгдсэн бөгөөд энэ нь сервер дээр хадгалагддаг бөгөөд кластерт шийдэлд SSL боловсруулдаг бүх урд талын серверүүдэд хуваарилагдах ёстой.[10]. Тиймээс, түр зуурын серверийн түлхүүрүүд эвдэрсэн тохиолдолд, жишээлбэл, удаан хугацаагаар хадгалагдах үед сессийн тасалбарыг нэвтрүүлэх нь PFS-ийг зөрчиж болзошгүй (OpenSSL, nginx, Apache тэдгээрийг програмын бүх хугацаанд анхдагчаар хадгалдаг; алдартай сайтууд ашигладаг. түлхүүрийг хэдэн цаг, хэдэн өдөр хүртэл).

Энд RTT нь тэг биш, та дор хаяж ClientHello болон ServerHello-г солилцох хэрэгтэй бөгөөд үүний дараа үйлчлүүлэгч Дууссантай хамт өгөгдөл илгээх боломжтой. Гэхдээ бид энд шинээр нээгдсэн олон тооны холболттой вэб байхгүй, харин холболт нь ихэвчлэн нэг ба түүнээс дээш, бага зэрэг удаан үргэлжилдэг, вэб хуудсууд руу харьцангуй богино хүсэлт гаргадаг мессенжертэй гэдгийг санах нь зүйтэй. дотооддоо. Өөрөөр хэлбэл, хэрэв бид үнэхээр муу метроны хэсэгтэй тааралдаагүй бол үүнийг хүлээн зөвшөөрөх боломжтой.

Өөр зүйл мартсан уу? Сэтгэгдэл дээр бичээрэй.

Үргэлжлэл бий!

Энэхүү цуврал нийтлэлийн хоёр дахь хэсэгт бид техникийн бус харин зохион байгуулалтын асуудлыг авч үзэх болно - хандлага, үзэл баримтлал, интерфейс, хэрэглэгчдэд хандах хандлага гэх мэт. Гэхдээ энд танилцуулсан техникийн мэдээлэлд үндэслэн.

Гурав дахь хэсэг нь техникийн бүрэлдэхүүн хэсэг / хөгжүүлэлтийн туршлагыг үргэлжлүүлэн шинжлэх болно. Та сурах болно, ялангуяа:

  • олон төрлийн TL төрлийн тахлын үргэлжлэл
  • суваг болон супер бүлгүүдийн талаарх үл мэдэгдэх зүйлс
  • Яагаад харилцах цонхнууд жагсаалтаас муу байдаг
  • үнэмлэхүй ба харьцангуй мессеж хаяглалтын тухай
  • зураг, зураг хоёрын ялгаа юу вэ
  • эможи налуу бичвэрт хэрхэн саад болдог

болон бусад таяг! Хамтдаа байгаарай!

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх