टेलीग्रामच्या प्रोटोकॉल आणि संस्थात्मक दृष्टिकोनांवर टीका. भाग १, तांत्रिक: क्लायंटला सुरवातीपासून लिहिण्याचा अनुभव - TL, MT

अलीकडे, टेलीग्राम किती चांगला आहे, दुरोव बंधू नेटवर्क सिस्टीम तयार करण्यात किती हुशार आणि अनुभवी आहेत, इत्यादी पोस्ट्स हॅब्रेवर अधिक वेळा दिसू लागल्या आहेत. त्याच वेळी, फार कमी लोकांनी स्वतःला तांत्रिक उपकरणात बुडवून घेतले आहे - जास्तीत जास्त, ते JSON वर आधारित एक बर्‍यापैकी साधे (आणि MTProto पेक्षा बरेच वेगळे) Bot API वापरतात आणि सहसा ते स्वीकारतात विश्वास वर मेसेंजरभोवती फिरणारी सर्व प्रशंसा आणि पीआर. जवळपास दीड वर्षापूर्वी, एशेलॉन एनजीओ व्हॅसिली मधील माझ्या सहकारी (दुर्दैवाने, त्याचे हॅब्रेवरील खाते मसुद्यासह मिटवले गेले होते) पर्लमध्ये सुरवातीपासून स्वतःचे टेलिग्राम क्लायंट लिहायला सुरुवात केली आणि नंतर या ओळींचे लेखक सामील झाले. का पर्ल, काही लगेच विचारतील? कारण असे प्रकल्प इतर भाषांमध्ये आधीपासूनच अस्तित्वात आहेत. खरं तर, हा मुद्दा नाही, जिथे नाही तिथे दुसरी कोणतीही भाषा असू शकते. तयार लायब्ररी, आणि त्यानुसार लेखकाने सर्व मार्गाने जाणे आवश्यक आहे सुरवातीपासून. शिवाय, क्रिप्टोग्राफी ही विश्वासार्ह बाब आहे, परंतु सत्यापित करा. सुरक्षिततेच्या उद्देशाने उत्पादनासह, आपण निर्मात्याकडून तयार केलेल्या लायब्ररीवर अवलंबून राहू शकत नाही आणि त्यावर आंधळेपणाने विश्वास ठेवू शकत नाही (तथापि, हा दुसऱ्या भागाचा विषय आहे). या क्षणी, लायब्ररी "सरासरी" स्तरावर चांगली कार्य करते (तुम्हाला कोणत्याही API विनंत्या करण्याची परवानगी देते).

तथापि, पोस्टच्या या मालिकेत जास्त क्रिप्टोग्राफी किंवा गणित असणार नाही. परंतु इतर अनेक तांत्रिक तपशील आणि आर्किटेक्चरल क्रॅच असतील (जे स्क्रॅचमधून लिहिणार नाहीत त्यांच्यासाठी देखील उपयुक्त आहेत, परंतु कोणत्याही भाषेत लायब्ररी वापरतील). तर, क्लायंटला सुरवातीपासून लागू करण्याचा प्रयत्न करणे हे मुख्य ध्येय होते अधिकृत कागदपत्रांनुसार. म्हणजेच, अधिकृत क्लायंटचा सोर्स कोड बंद आहे असे गृहीत धरू (पुन्हा, दुसऱ्या भागात हे खरे आहे या विषयावर अधिक तपशीलवार चर्चा करू. घडते म्हणून), परंतु, जुन्या दिवसांप्रमाणे, उदाहरणार्थ, आरएफसी सारखे एक मानक आहे - स्त्रोत कोड “न पाहता”, तो अधिकृत असो (टेलीग्राम डेस्कटॉप, मोबाईल), किंवा अनधिकृत टेलिथॉन?

लेख:

दस्तऐवजीकरण... ते अस्तित्वात आहे, बरोबर? खरं आहे का?..

या लेखाच्या नोट्सचे तुकडे गेल्या उन्हाळ्यात गोळा केले जाऊ लागले. हे सर्व वेळ अधिकृत वेबसाइटवर https://core.telegram.org दस्तऐवजीकरण लेयर 23 प्रमाणे होते, म्हणजे 2014 मध्ये कुठेतरी अडकलो (लक्षात ठेवा, तेव्हा चॅनेलही नव्हते?). अर्थात, सैद्धांतिकदृष्ट्या, यामुळे आम्हाला 2014 मध्ये त्या वेळी कार्यक्षमतेसह क्लायंट लागू करण्याची परवानगी मिळाली असावी. परंतु या स्थितीतही, दस्तऐवजीकरण प्रथमतः अपूर्ण होते आणि दुसरे म्हणजे, काही ठिकाणी ते स्वतःचे विरोधाभास होते. अगदी एक महिन्यापूर्वी, सप्टेंबर 2019 मध्ये, ते होते दैवयोगाने असे आढळून आले की साइटवर दस्तऐवजाचे एक मोठे अद्यतन आहे, पूर्णपणे अलीकडील लेयर 105 साठी, आता सर्वकाही पुन्हा वाचणे आवश्यक आहे. खरंच, बरेच लेख सुधारित केले गेले, परंतु बरेच अपरिवर्तित राहिले. म्हणून, दस्तऐवजीकरणाबद्दल खालील टीका वाचताना, आपण हे लक्षात ठेवले पाहिजे की यापैकी काही गोष्टी यापुढे संबंधित नाहीत, परंतु काही अजूनही आहेत. अखेरीस, आधुनिक जगात 5 वर्षे फक्त एक लांब वेळ नाही, पण खूप भरपूर. त्या काळापासून (विशेषतः जर तुम्ही टाकून दिलेल्या आणि पुनर्जीवित केलेल्या जिओचॅट साइट्सचा विचार केला नाही तर) या योजनेतील API पद्धतींची संख्या शंभरवरून अडीचशेहून अधिक झाली आहे!

तरुण लेखक म्हणून कुठून सुरुवात करावी?

तुम्ही सुरवातीपासून लिहा किंवा वापरा याने काही फरक पडत नाही, उदाहरणार्थ, रेडीमेड लायब्ररी पायथनसाठी टेलिथॉन किंवा PHP साठी मेडलाइन, कोणत्याही परिस्थितीत, आपल्याला प्रथम आवश्यक असेल तुमचा अर्ज नोंदवा - पॅरामीटर्स मिळवा api_id и api_hash (ज्यांनी VKontakte API सह कार्य केले आहे ते त्वरित समजतात) ज्याद्वारे सर्व्हर अनुप्रयोग ओळखेल. या आहे कायदेशीर कारणास्तव ते करा, परंतु लायब्ररी लेखक ते का प्रकाशित करू शकत नाहीत याबद्दल आम्ही दुसऱ्या भागात अधिक बोलू. आपण चाचणी मूल्यांसह समाधानी असू शकता, जरी ते खूप मर्यादित आहेत - वस्तुस्थिती अशी आहे की आता आपण नोंदणी करू शकता फक्त एक अॅप, त्यामुळे घाई करू नका.

आता, तांत्रिक दृष्टिकोनातून, आम्हाला या वस्तुस्थितीमध्ये स्वारस्य असले पाहिजे की नोंदणीनंतर आम्हाला टेलीग्रामकडून दस्तऐवज, प्रोटोकॉल इत्यादीच्या अद्यतनांबद्दल सूचना प्राप्त झाल्या पाहिजेत. म्हणजेच, एखादी व्यक्ती असे गृहीत धरू शकते की डॉक्स असलेली साइट फक्त सोडली गेली होती आणि ज्यांनी ग्राहक बनवण्यास सुरुवात केली त्यांच्याबरोबर विशेषत: कार्य करणे सुरू ठेवले, कारण ते सोपे आहे. पण नाही, तसं काही दिसलं नाही, माहिती आली नाही.

आणि जर आपण सुरवातीपासून लिहित असाल, तर प्राप्त केलेले पॅरामीटर्स वापरणे खरोखरच खूप लांब आहे. तरी https://core.telegram.org/ आणि गेटिंग स्टार्टमध्‍ये त्‍यांच्‍याबद्दल बोलते सर्व प्रथम, खरं तर, तुम्‍हाला प्रथम अंमलात आणावे लागेल MTProto प्रोटोकॉल - पण जर तुमचा विश्वास असेल OSI मॉडेलनुसार लेआउट प्रोटोकॉलच्या सामान्य वर्णनासाठी पृष्ठाच्या शेवटी, नंतर ते पूर्णपणे व्यर्थ आहे.

खरं तर, MTProto आधी आणि नंतर दोन्ही, एकाच वेळी अनेक स्तरांवर (OS कर्नलमध्ये काम करणारे परदेशी नेटवर्कर्स म्हणतात, लेयर उल्लंघन), एक मोठा, वेदनादायक आणि भयंकर विषय मार्गी लागेल...

बायनरी सिरियलायझेशन: TL (प्रकार भाषा) आणि त्याची योजना, आणि स्तर आणि इतर अनेक भयानक शब्द

हा विषय, खरं तर, टेलिग्रामच्या समस्यांची गुरुकिल्ली आहे. आणि जर तुम्ही त्यात खोलवर जाण्याचा प्रयत्न केला तर बरेच भयानक शब्द असतील.

तर, आकृती येथे आहे. हा शब्द तुमच्या मनात आला तर म्हणा, 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 आवृत्तीमध्ये स्कीमाचे दुवे आहेत - परंतु त्यामुळे ते अधिक स्पष्ट होत नाही).

पृष्ठ उघडा बायनरी डेटा सीरियलायझेशन आणि मशरूम आणि वेगळ्या गणिताच्या जादुई दुनियेत डुबकी मारा, चौथ्या वर्षी मॅटनसारखेच काहीतरी. वर्णमाला, प्रकार, मूल्य, संयोजक, फंक्शनल कॉम्बिनेटर, सामान्य रूप, संमिश्र प्रकार, बहुरूपी प्रकार... आणि एवढेच फक्त पहिले पान! पुढे तुमची वाट पाहत आहे TL भाषा, ज्यामध्ये आधीच क्षुल्लक विनंती आणि प्रतिसादाचे उदाहरण असले तरीही, अधिक सामान्य प्रकरणांना उत्तर देत नाही, याचा अर्थ असा आहे की आपल्याला आणखी आठ एम्बेड केलेल्या रशियनमधून इंग्रजीमध्ये अनुवादित गणिताच्या रीटेलिंगमधून जावे लागेल. पृष्ठे

फंक्शनल भाषा आणि स्वयंचलित प्रकार अनुमानांशी परिचित वाचक, अर्थातच, या भाषेतील वर्णन भाषा, अगदी उदाहरणावरूनही, अधिक परिचित असतील आणि म्हणू शकतील की हे तत्त्वतः वाईट नाही. यावरील आक्षेप पुढीलप्रमाणे आहेत.

  • होय, ध्येय छान वाटतंय, पण अरेरे, ती साध्य झाले नाही
  • रशियन विद्यापीठांमधील शिक्षण आयटी वैशिष्ट्यांमध्ये देखील बदलते - प्रत्येकाने संबंधित अभ्यासक्रम घेतलेला नाही
  • शेवटी, जसे आपण पाहू, सराव मध्ये ते आहे आवश्यक नाही, कारण वर्णन केलेल्या TL चा फक्त मर्यादित उपसंच वापरला जातो

म्हटल्याप्रमाणे लिओनेर्ड चॅनेलवर #perl फ्रीनोड आयआरसी नेटवर्कमध्ये, ज्याने टेलीग्राम ते मॅट्रिक्सपर्यंत गेट लागू करण्याचा प्रयत्न केला (कोटचे भाषांतर मेमरीमधून चुकीचे आहे):

असे वाटते की एखाद्याला प्रथमच थिअरी टाईप करण्याची ओळख करून देण्यात आली आहे, तो उत्साही झाला आहे आणि त्याच्याशी खेळण्याचा प्रयत्न करू लागला आहे, प्रत्यक्ष व्यवहारात त्याची गरज आहे की नाही याची काळजी घेतली नाही.

स्वतःच पहा, जर काही प्राथमिक म्हणून बेअर-प्रकार (इंट, लाँग, इ.) ची गरज प्रश्न निर्माण करत नसेल तर - शेवटी ते व्यक्तिचलितपणे लागू केले जाणे आवश्यक आहे - उदाहरणार्थ, त्यांच्यापासून मिळवण्याचा प्रयत्न करूया. वेक्टर. म्हणजे खरं तर रचना, आपण परिणामी वस्तूंना त्यांच्या योग्य नावाने कॉल केल्यास.

पण आधी

जे अधिकृत दस्तऐवज वाचत नाहीत त्यांच्यासाठी 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;

व्याख्या नेहमी सुरू होते बांधकाम करणारा, त्यानंतर पर्यायाने (सरावात - नेहमी) चिन्हाद्वारे # असणे आवश्यक आहे सीआरसी 32 या प्रकारच्या सामान्यीकृत वर्णन स्ट्रिंगमधून. पुढे फील्डचे वर्णन येते; ते अस्तित्वात असल्यास, प्रकार रिक्त असू शकतो. हे सर्व समान चिन्हासह समाप्त होते, ज्या प्रकारचे हे कन्स्ट्रक्टरचे नाव आहे - म्हणजे, खरं तर, उपप्रकार - संबंधित आहे. समान चिन्हाच्या उजवीकडे असलेला माणूस आहे बहुरूपी - म्हणजे, अनेक विशिष्ट प्रकार त्याच्याशी संबंधित असू शकतात.

जर व्याख्या रेषेनंतर आली ---functions---, नंतर वाक्यरचना समान राहील, परंतु अर्थ भिन्न असेल: कन्स्ट्रक्टर हे RPC फंक्शनचे नाव बनेल, फील्ड पॅरामीटर्स बनतील (चांगले, म्हणजे, खाली वर्णन केल्याप्रमाणे, ती दिलेली रचना अगदी तशीच राहील. , हा फक्त नियुक्त केलेला अर्थ असेल), आणि "पॉलीमॉर्फिक प्रकार " - परत केलेल्या निकालाचा प्रकार. खरे आहे, ते अद्याप बहुरूपी राहील - फक्त विभागात परिभाषित केले आहे ---types---, परंतु या कन्स्ट्रक्टरचा “विचार केला जाणार नाही”. कॉल फंक्शन्सचे प्रकार त्यांच्या युक्तिवादांद्वारे ओव्हरलोड करणे, म्हणजे. काही कारणास्तव, C++ प्रमाणे एकाच नावाची परंतु भिन्न स्वाक्षरी असलेली अनेक कार्ये, TL मध्ये प्रदान केलेली नाहीत.

ओओपी नसल्यास "कन्स्ट्रक्टर" आणि "पॉलीमॉर्फिक" का? बरं, खरं तर, एखाद्याला ओओपीच्या दृष्टीने याबद्दल विचार करणे सोपे होईल - एक अमूर्त वर्ग म्हणून बहुरूपी प्रकार, आणि कन्स्ट्रक्टर हे त्याचे थेट वंशज वर्ग आहेत, आणि final अनेक भाषांच्या परिभाषेत. खरं तर, अर्थातच, फक्त येथे समानता ओओ प्रोग्रामिंग भाषांमध्ये वास्तविक ओव्हरलोड कन्स्ट्रक्टर पद्धतींसह. येथे फक्त डेटा स्ट्रक्चर्स असल्याने, तेथे कोणत्याही पद्धती नाहीत (जरी फंक्शन्स आणि पद्धतींचे वर्णन ते अस्तित्वात असल्याबद्दल डोक्यात गोंधळ निर्माण करण्यास सक्षम आहे, परंतु ही एक वेगळी बाब आहे) - तुम्ही कन्स्ट्रक्टरचा एक मूल्य म्हणून विचार करू शकता. जे बांधले जात आहे बाइट प्रवाह वाचताना टाइप करा.

हे कसे घडते? deserializer, जे नेहमी 4 बाइट्स वाचते, मूल्य पाहते 0xcrc32 - आणि पुढे काय होईल हे समजते field1 प्रकारासह int, म्हणजे तंतोतंत 4 बाइट्स वाचते, यावर प्रकारासह ओव्हरलाईंग फील्ड PolymorType वाचा. पाहतो 0x2crc32 आणि समजते की पुढे दोन फील्ड आहेत, प्रथम long, याचा अर्थ आपण 8 बाइट्स वाचतो. आणि मग पुन्हा एक जटिल प्रकार, ज्याला त्याच प्रकारे डीसीरियलाइज केले जाते. उदाहरणार्थ, Type3 सर्किटमध्ये अनुक्रमे दोन कन्स्ट्रक्टर म्हणून घोषित केले जाऊ शकते, नंतर त्यांना एकतर भेटणे आवश्यक आहे 0x12abcd34, ज्यानंतर तुम्हाला आणखी 4 बाइट्स वाचण्याची आवश्यकता आहे int, किंवा 0x6789cdef, ज्यानंतर काहीही होणार नाही. इतर काहीही - आपल्याला अपवाद फेकणे आवश्यक आहे. असं असलं तरी, यानंतर आपण 4 बाइट्स वाचण्यासाठी परत जाऊ int फील्ड field_c в constructorTwo आणि त्याबरोबर आम्ही आमचे वाचन पूर्ण करतो PolymorType.

शेवटी, पकडले तर 0xdeadcrc ते constructorThree, मग सर्वकाही अधिक क्लिष्ट होते. आमचे पहिले क्षेत्र आहे bit_flags_of_what_really_present प्रकारासह # - खरं तर, हे फक्त प्रकारासाठी एक उपनाव आहे nat, म्हणजे "नैसर्गिक संख्या". अर्थात, खरेतर, अस्वाक्षरित इंट, तसे, वास्तविक सर्किट्समध्ये अस्वाक्षरित संख्या आढळतात तेव्हा एकमेव केस असते. तर, पुढे प्रश्नचिन्ह असलेले बांधकाम आहे, याचा अर्थ असा की हे फील्ड - संबंधित बिट संदर्भित फील्डमध्ये सेट केले असल्यासच ते वायरवर उपस्थित असेल (अंदाजे टर्नरी ऑपरेटरसारखे). तर, हे बिट सेट केले आहे असे गृहीत धरू, याचा अर्थ असा की पुढे आपल्याला असे फील्ड वाचण्याची आवश्यकता आहे Type, ज्यात आमच्या उदाहरणात 2 कन्स्ट्रक्टर आहेत. एक रिक्त आहे (केवळ अभिज्ञापकाचा समावेश आहे), दुसर्‍यामध्ये फील्ड आहे ids प्रकारासह ids:Vector<long>.

तुम्‍हाला वाटेल की टेम्‍पलेट आणि जेनेरिक्स दोन्ही प्रो किंवा जावामध्‍ये आहेत. पण नाही. जवळजवळ. या फक्त वास्तविक सर्किट्समध्ये कोन कंस वापरण्याचे प्रकरण, आणि ते केवळ वेक्टरसाठी वापरले जाते. बाइट स्ट्रीममध्ये, हे स्वतः व्हेक्टर प्रकारासाठी 4 CRC32 बाइट्स असतील, नेहमी समान, नंतर 4 बाइट्स - अॅरे घटकांची संख्या आणि नंतर हे घटक स्वतःच.

यामध्ये हे तथ्य जोडू की अनुक्रमिकीकरण नेहमी 4 बाइट्सच्या शब्दांमध्ये होते, सर्व प्रकार त्याचे गुणाकार असतात - अंगभूत प्रकार देखील वर्णन केले जातात. bytes и string लांबीचे मॅन्युअल सीरियलायझेशन आणि 4 ने हे संरेखन - बरं, ते सामान्य आणि अगदी तुलनेने प्रभावी वाटते? जरी TL एक प्रभावी बायनरी सीरियलायझेशन असल्याचा दावा केला जात असला तरी, त्यांच्याबरोबर नरकात, जवळजवळ कोणत्याही गोष्टीच्या विस्तारासह, अगदी बुलियन मूल्ये आणि एकल-कॅरेक्टर स्ट्रिंग 4 बाइट्सपर्यंत, तरीही JSON जास्त जाड होईल का? पहा, बिट फ्लॅगसह अनावश्यक फील्ड देखील वगळले जाऊ शकतात, सर्व काही अगदी चांगले आहे आणि भविष्यासाठी विस्तारण्यायोग्य देखील आहे, मग नंतर कन्स्ट्रक्टरमध्ये नवीन पर्यायी फील्ड का जोडू नये?..

पण नाही, जर तुम्ही माझे संक्षिप्त वर्णन वाचले नाही तर संपूर्ण कागदपत्रे वाचली आणि अंमलबजावणीबद्दल विचार करा. सर्वप्रथम, कन्स्ट्रक्टरच्या CRC32 ची गणना योजनेच्या मजकूर वर्णनाच्या सामान्यीकृत ओळीनुसार केली जाते (अतिरिक्त व्हाईटस्पेस काढा, इ.) - म्हणून नवीन फील्ड जोडल्यास, प्रकार वर्णन ओळ बदलेल, आणि म्हणून त्याची CRC32 आणि , परिणामी, क्रमिकीकरण. आणि जुन्या क्लायंटला नवीन ध्वजसंच असलेले फील्ड मिळाल्यास आणि त्यांचे पुढे काय करायचे हे त्याला माहित नसेल तर तो काय करेल?..

दुसरे म्हणजे, लक्षात ठेवूया सीआरसी 32, जे येथे मूलत: म्हणून वापरले जाते हॅश फंक्शन्स कोणता प्रकार (डी) क्रमबद्ध केला जात आहे हे अद्वितीयपणे निर्धारित करण्यासाठी. येथे आपल्याला टक्करांच्या समस्येचा सामना करावा लागतो - आणि नाही, संभाव्यता 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 हस्तांतरित करा?

हा अजिबात निष्क्रिय सैद्धांतिक प्रश्न नाही - कल्पना करा की तुम्हाला गट वापरकर्त्यांची यादी मिळाली आहे, ज्यापैकी प्रत्येकाचे आयडी, नाव, आडनाव आहे - मोबाइल कनेक्शनवर हस्तांतरित केलेल्या डेटाच्या प्रमाणात फरक लक्षणीय असू शकतो. हे तंतोतंत टेलीग्राम सीरियलायझेशनची प्रभावीता आहे जी आम्हाला जाहिरात केली जाते.

त्यामुळे…

वेक्टर, जो कधीही सोडला गेला नाही

जर तुम्ही कॉम्बिनेटर्सच्या वर्णनाची पाने पाहण्याचा प्रयत्न केला तर तुम्हाला दिसेल की एक वेक्टर (आणि मॅट्रिक्स देखील) औपचारिकपणे अनेक शीट्सच्या ट्युपल्समधून आउटपुट करण्याचा प्रयत्न करत आहे. परंतु शेवटी ते विसरतात, अंतिम चरण वगळले जाते आणि वेक्टरची व्याख्या फक्त दिली जाते, जी अद्याप प्रकाराशी जोडलेली नाही. काय झला? भाषांमध्ये प्रोग्रामिंग, विशेषत: फंक्शनल, संरचनाचे वारंवार वर्णन करणे अगदी वैशिष्ट्यपूर्ण आहे - त्याच्या आळशी मूल्यांकनासह कंपाइलर सर्वकाही समजून घेईल आणि स्वतः करेल. भाषेत डेटा अनुक्रमणिका काय आवश्यक आहे कार्यक्षमतेची: फक्त वर्णन करणे पुरेसे आहे यादी, म्हणजे दोन घटकांची रचना - पहिला डेटा घटक आहे, दुसरा समान रचना आहे किंवा शेपटीसाठी रिक्त जागा आहे (पॅक (cons) लिस्प मध्ये). पण हे स्पष्टपणे आवश्यक असेल प्रत्येक घटक त्याच्या प्रकाराचे वर्णन करण्यासाठी अतिरिक्त 4 बाइट्स (TL मध्ये CRC32) खर्च करतो. अॅरेचे वर्णनही सहज करता येते निश्चित आकार, परंतु आगाऊ अज्ञात लांबीच्या अॅरेच्या बाबतीत, आम्ही खंडित करतो.

म्हणून, TL सदिश आउटपुट करण्यास परवानगी देत ​​​​नाही, ते बाजूला जोडणे आवश्यक होते. शेवटी दस्तऐवज म्हणते:

सीरियलायझेशन नेहमी समान कन्स्ट्रक्टर “वेक्टर” (const 0x1cb5c415 = crc32(“vector t:Type # [ t ] = Vector t”) वापरते जे t प्रकाराच्या व्हेरिएबलच्या विशिष्ट मूल्यावर अवलंबून नसते.

पर्यायी पॅरामीटर t चे मूल्य अनुक्रमिकरणामध्ये गुंतलेले नाही कारण ते परिणाम प्रकारातून घेतले जाते (नेहमी डीसीरियलायझेशनपूर्वी ओळखले जाते).

जवळून पहा: vector {t:Type} # [ t ] = Vector t - परंतु कोठेही नाही ही व्याख्या स्वतःच असे म्हणत नाही की पहिली संख्या सदिशाच्या लांबीइतकी असली पाहिजे! आणि ते कुठूनही येत नाही. हे एक दिले आहे जे लक्षात ठेवले पाहिजे आणि आपल्या हातांनी अंमलात आणले पाहिजे. इतरत्र, दस्तऐवजीकरण अगदी प्रामाणिकपणे नमूद करते की प्रकार वास्तविक नाही:

व्हेक्टर टी पॉलीमॉर्फिक स्यूडोटाइप हा एक "प्रकार" आहे ज्याचे मूल्य हे बॉक्स्ड किंवा बेअर, कोणत्याही प्रकारच्या t च्या मूल्यांचा क्रम आहे.

... पण त्यावर लक्ष केंद्रित करत नाही. जेव्हा तुम्ही, गणिताच्या (कदाचित युनिव्हर्सिटीच्या अभ्यासक्रमातून तुम्हाला माहीतही असेल) वेडिंग करून कंटाळले असता, हार मानण्याचा निर्णय घेतो आणि प्रत्यक्ष व्यवहारात त्यासोबत कसे काम करायचे ते पाहतो, तेव्हा तुमच्या डोक्यात ठसा उमटतो की हे गंभीर आहे. मुळात गणित, हे स्पष्टपणे छान लोक (दोन गणितज्ञ - ACM विजेते) यांनी शोधले होते, आणि फक्त कोणीही नाही. ध्येय - दाखवणे - साध्य झाले आहे.

तसे, संख्या बद्दल. त्याची आठवण करून द्या # तो एक समानार्थी शब्द आहे nat, नैसर्गिक संख्या:

प्रकार अभिव्यक्ती आहेत (टाइप-एक्सप्र) आणि अंकीय अभिव्यक्ती (nat-expr). तथापि, ते त्याच प्रकारे परिभाषित केले आहेत.

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

परंतु व्याकरणात त्यांचे वर्णन त्याच प्रकारे केले आहे, म्हणजे. हा फरक पुन्हा लक्षात ठेवला पाहिजे आणि हाताने अंमलात आणला पाहिजे.

ठीक आहे, होय, टेम्पलेट प्रकार (vector<int>, vector<User>) एक सामान्य अभिज्ञापक आहे (#1cb5c415), म्हणजे जर तुम्हाला माहित असेल की कॉल म्हणून घोषित केले आहे

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

मग तुम्ही यापुढे फक्त वेक्टरची वाट पाहत नाही, तर वापरकर्त्यांच्या वेक्टरची वाट पाहत आहात. अधिक तंतोतंत, पाहिजे प्रतीक्षा करा - वास्तविक कोडमध्ये, प्रत्येक घटक, बेअर प्रकार नसल्यास, एक कन्स्ट्रक्टर असेल आणि अंमलबजावणीच्या चांगल्या मार्गाने ते तपासणे आवश्यक आहे - परंतु आम्हाला या वेक्टरच्या प्रत्येक घटकामध्ये अचूकपणे पाठवले गेले आहे. तो प्रकार? जर ते काही प्रकारचे PHP असेल तर, ज्यामध्ये अॅरेमध्ये वेगवेगळ्या घटकांमध्ये भिन्न प्रकार असू शकतात?

या टप्प्यावर आपण विचार करू लागतो - असा TL आवश्यक आहे का? कदाचित कार्टसाठी मानवी सीरियलायझर वापरणे शक्य होईल, तोच प्रोटोबफ जो आधीपासून अस्तित्वात होता? तो सिद्धांत होता, चला सराव पाहू.

कोडमध्ये विद्यमान TL अंमलबजावणी

टीएलचा जन्म व्हीकॉन्टाक्टेच्या खोलीत दुरोवच्या शेअरच्या विक्रीसह प्रसिद्ध कार्यक्रमांपूर्वीच झाला होता आणि (खात्रीने), टेलीग्रामचा विकास सुरू होण्यापूर्वीच. आणि ओपन सोर्स मध्ये पहिल्या अंमलबजावणीचा स्त्रोत कोड तुम्हाला खूप मजेदार क्रॅच सापडतील. आणि ती भाषा आता टेलीग्राममध्ये आहे त्यापेक्षा अधिक पूर्णपणे तेथे लागू केली गेली. उदाहरणार्थ, स्कीममध्ये हॅश अजिबात वापरले जात नाहीत (म्हणजे विचलित वर्तनासह अंगभूत स्यूडोटाइप (वेक्टरसारखे)). किंवा

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

परंतु आपण विचार करूया, पूर्णतेच्या फायद्यासाठी, ट्रेस करण्यासाठी, म्हणून बोलायचे तर, विचारांच्या राक्षसाच्या उत्क्रांतीबद्दल.

#define ZHUKOV_BYTES_HACK

#ifdef ZHUKOV_BYTES_HACK

/* dirty hack for Zhukov request */

किंवा हे सुंदर:

    static const char *reserved_words_polymorhic[] = {

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

      };

हा तुकडा टेम्प्लेट्सबद्दल आहे जसे:

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

ही हॅशमॅप टेम्प्लेट प्रकाराची int - प्रकार जोड्यांचा वेक्टर म्हणून व्याख्या आहे. C++ मध्ये हे असे काहीतरी दिसेल:

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

तर, alpha - कीवर्ड! पण फक्त C++ मध्ये तुम्ही T लिहू शकता, पण तुम्ही अल्फा, बीटा लिहावे... पण 8 पॅरामीटर्सपेक्षा जास्त नाही, तिथेच कल्पनारम्य संपते. असे दिसते की सेंट पीटर्सबर्गमध्ये एकेकाळी असे काही संवाद घडले होते:

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

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

परंतु हे "सर्वसाधारणपणे" TL च्या प्रथम प्रकाशित अंमलबजावणीबद्दल होते. टेलीग्राम क्लायंटमधील स्वतःच्या अंमलबजावणीचा विचार करूया.

वसिलीला शब्द:

Vasily, [09.10.18 17:07] सर्वात जास्त, गाढव गरम आहे कारण त्यांनी अ‍ॅब्स्ट्रॅक्शन्सचा एक समूह तयार केला, आणि नंतर त्यांच्यावर एक बोल्ट मारला आणि कोड जनरेटरला क्रॅचने झाकले.
परिणामी, प्रथम डॉक pilot.jpg वरून
नंतर कोड dzhekichan.webp वरून

अर्थात, अल्गोरिदम आणि गणिताशी परिचित असलेल्या लोकांकडून, आम्ही अशी अपेक्षा करू शकतो की त्यांनी अहो, उल्लमन वाचले आहे आणि त्यांचे DSL कंपायलर लिहिण्यासाठी अनेक दशकांपासून उद्योगात वास्तविक मानक बनलेल्या साधनांशी परिचित आहेत, बरोबर?..

द्वारे telegram-cli Vitaly Valtman आहे, TLO फॉर्मेट त्याच्या (cli) सीमेबाहेरच्या घटनेवरून समजू शकतो, संघाचा एक सदस्य - आता TL पार्सिंगसाठी एक लायब्ररी वाटप करण्यात आली आहे स्वतंत्रपणे, तिची छाप काय आहे TL पार्सर? ..

16.12 04:18 वॅसिली: मला वाटते की कोणीतरी lex+yacc मध्ये प्रभुत्व मिळवले नाही
16.12 04:18 वॅसिली: मी अन्यथा स्पष्ट करू शकत नाही
16.12 04:18 वॅसिली: ठीक आहे, किंवा त्यांना व्हीके मधील ओळींच्या संख्येसाठी पैसे दिले गेले.
16.12 04:19 Vasily: 3k+ लाईन्स इ.<censored> पार्सर ऐवजी

कदाचित अपवाद? कसे ते पाहू करते हा अधिकृत क्लायंट आहे - टेलीग्राम डेस्कटॉप:

    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 तपासण्याबद्दल बोललो होतो? तर, टेलीग्राम डेस्कटॉप कोड जनरेटरमध्ये त्या प्रकारांसाठी अपवादांची सूची आहे ज्यामध्ये CRC32 ची गणना केली जाते. जुळत नाही आकृतीमध्ये दर्शविलेल्या एकासह!

Vasily, [18.12/22 49:XNUMX] आणि इथे मी अशा TL ची गरज आहे का याचा विचार करेन
जर मला पर्यायी अंमलबजावणीमध्ये गोंधळ घालायचा असेल, तर मी लाइन ब्रेक्स घालण्यास सुरुवात करेन, अर्धे पार्सर्स मल्टी-लाइन परिभाषांवर खंडित होतील
tdesktop, तथापि, खूप

वन-लाइनरबद्दलचा मुद्दा लक्षात ठेवा, आम्ही थोड्या वेळाने त्यावर परत येऊ.

ठीक आहे, टेलिग्राम-क्ली अनधिकृत आहे, टेलिग्राम डेस्कटॉप अधिकृत आहे, परंतु इतरांचे काय? कोणास ठाऊक?.. Android क्लायंट कोडमध्ये स्कीमा पार्सर अजिबात नव्हता (जे ओपन सोर्सबद्दल प्रश्न उपस्थित करते, परंतु हे दुसर्‍या भागासाठी आहे), परंतु कोडचे इतर अनेक मजेदार तुकडे होते, परंतु त्यामध्ये अधिक खाली उपविभाग.

सराव मध्ये क्रमिकीकरण इतर कोणते प्रश्न उपस्थित करते? उदाहरणार्थ, त्यांनी बर्‍याच गोष्टी केल्या, अर्थातच, बिट फील्ड आणि सशर्त फील्डसह:

वसिली: flags.0? true
याचा अर्थ फील्ड उपस्थित आहे आणि ध्वज सेट केल्यास ते सत्य आहे

वसिली: flags.1? int
याचा अर्थ असा आहे की फील्ड सध्या आहे आणि डीसीरियल करणे आवश्यक आहे

वॅसिली: गांड, तू काय करत आहेस याची काळजी करू नकोस!
व्हॅसिली: डॉकमध्ये कुठेतरी असा उल्लेख आहे की खरा शून्य-लांबीचा प्रकार आहे, परंतु त्यांच्या डॉकमधून काहीही एकत्र करणे अशक्य आहे
व्हॅसिली: ओपन सोर्स अंमलबजावणीमध्येही असे नाही, परंतु क्रॅचेस आणि सपोर्ट्सचा एक समूह आहे

टेलिथॉनचे काय? एमटीप्रोटोच्या विषयाकडे पहात आहोत, एक उदाहरण - दस्तऐवजीकरणात असे तुकडे आहेत, परंतु चिन्ह % त्याचे वर्णन केवळ "दिलेल्या बेअर-प्रकाराशी संबंधित" असे केले आहे, म्हणजे. खालील उदाहरणांमध्ये एकतर त्रुटी आहे किंवा काहीतरी कागदोपत्री नाही:

वॅसिली, [२२.०६.१८ १८:३८] एकाच ठिकाणी:

msg_container#73f1f8dc messages:vector message = MessageContainer;

वेगळ्या मध्ये:

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

आणि हे दोन मोठे फरक आहेत, वास्तविक जीवनात काही प्रकारचे नग्न वेक्टर येतात

मी बेअर वेक्टर व्याख्या पाहिली नाही आणि एकही आढळली नाही

टेलिथॉनमध्ये विश्लेषण हाताने लिहिले जाते

त्याच्या आकृतीमध्ये व्याख्येवर भाष्य केले आहे msg_container

पुन्हा, प्रश्न % बद्दल उरतो. त्याचे वर्णन नाही.

वदिम गोंचारोव, [२२.०६.१८ १९:२२] आणि tdesktop मध्ये?

वॅसिली, [२२.०६.१८ १९:२३] पण त्यांचे नियमित इंजिनवरील टीएल पार्सर बहुधा हे खाणार नाहीत

// parsed manually

TL एक सुंदर अ‍ॅबस्ट्रॅक्शन आहे, कोणीही ते पूर्णपणे लागू करत नाही

आणि % त्यांच्या योजनेच्या आवृत्तीमध्ये नाही

पण इथे दस्तऐवज स्वतःच विरोधाभास करतात, म्हणून idk

व्याकरणात आढळले, ते शब्दार्थाचे वर्णन करण्यास विसरले असतील

तुम्ही TL वर दस्तऐवज पाहिले, तुम्ही अर्ध्या लिटरशिवाय ते शोधू शकत नाही

“ठीक आहे, चला म्हणू,” दुसरा वाचक म्हणेल, “तुम्ही एखाद्या गोष्टीवर टीका करता, म्हणून ती कशी करावी ते मला दाखवा.”

वॅसिली उत्तर देते: “पार्सरसाठी, मला अशा गोष्टी आवडतात

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

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

पेक्षा कसे तरी चांगले

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

किंवा

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

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

हा संपूर्ण लेक्सर आहे:

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

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

त्या सोपे म्हणजे ते सौम्यपणे मांडणे.

सर्वसाधारणपणे, परिणामी, TL च्या प्रत्यक्षात वापरलेल्या उपसंचासाठी पार्सर आणि कोड जनरेटर व्याकरणाच्या अंदाजे 100 ओळींमध्ये आणि जनरेटरच्या ~ 300 ओळींमध्ये बसतात (सर्व मोजून printचे व्युत्पन्न केलेले कोड), प्रत्येक वर्गातील आत्मनिरीक्षणासाठी माहिती बन्स टाईप करा. प्रत्येक पॉलिमॉर्फिक प्रकार रिकाम्या अमूर्त बेस क्लासमध्ये बदलतो आणि कन्स्ट्रक्टर्सना त्यातून वारसा मिळतो आणि त्यांच्याकडे अनुक्रमिकरण आणि डीसीरियलायझेशनच्या पद्धती असतात.

प्रकार भाषेत प्रकारांचा अभाव

सशक्त टायपिंग ही चांगली गोष्ट आहे, बरोबर? नाही, हे होलिव्हर नाही (जरी मी डायनॅमिक भाषांना प्राधान्य देतो), परंतु TL च्या चौकटीत एक पोस्ट्युलेट आहे. त्यावर आधारित, भाषेने आमच्यासाठी सर्व प्रकारचे धनादेश दिले पाहिजेत. बरं, ठीक आहे, कदाचित तो स्वत: नाही, परंतु अंमलबजावणी, परंतु त्याने कमीतकमी त्यांचे वर्णन केले पाहिजे. आणि आम्हाला कोणत्या प्रकारच्या संधी हव्या आहेत?

सर्व प्रथम, मर्यादा. फाइल्स अपलोड करण्यासाठीच्या दस्तऐवजात आपण पाहतो:

फाइलची बायनरी सामग्री नंतर भागांमध्ये विभागली जाते. सर्व भाग समान आकाराचे असणे आवश्यक आहे ( part_size ) आणि खालील अटी पूर्ण केल्या पाहिजेत:

  • part_size % 1024 = 0 (1KB ने विभाज्य)
  • 524288 % part_size = 0 (512KB भाग_आकाराने समान रीतीने विभाज्य असणे आवश्यक आहे)

शेवटच्या भागाला या अटी पूर्ण करणे आवश्यक नाही, जर त्याचा आकार part_size पेक्षा कमी असेल.

प्रत्येक भागाला अनुक्रमांक असावा, फाइल_भाग, 0 ते 2,999 च्या मूल्यासह.

फाईलचे विभाजन झाल्यानंतर तुम्हाला ती सर्व्हरवर सेव्ह करण्यासाठी पद्धत निवडणे आवश्यक आहे. वापरा upload.saveBigFilePart फाइलचा पूर्ण आकार 10 MB पेक्षा जास्त असल्यास आणि upload.saveFilePart लहान फायलींसाठी.
[...] खालीलपैकी एक डेटा इनपुट त्रुटी परत केली जाऊ शकते:

  • FILE_PARTS_INVALID — भागांची अवैध संख्या. मूल्य दरम्यान नाही 1..3000

यापैकी काही आकृतीत आहे का? TL वापरून हे कसेतरी व्यक्त करण्यायोग्य आहे का? नाही. पण मला माफ करा, अगदी आजोबांचे टर्बो पास्कल निर्दिष्ट प्रकारांचे वर्णन करण्यास सक्षम होते श्रेणी. आणि त्याला आणखी एक गोष्ट माहीत होती, जी आता अधिक ओळखली जाते enum - निश्चित (लहान) मूल्यांच्या गणनेचा समावेश असलेला प्रकार. सी - संख्यात्मक सारख्या भाषांमध्ये, लक्षात घ्या की आतापर्यंत आपण फक्त प्रकारांबद्दल बोललो आहोत संख्या. पण अ‍ॅरे, स्ट्रिंग्स देखील आहेत... उदाहरणार्थ, या स्ट्रिंगमध्ये फक्त फोन नंबर असू शकतो हे वर्णन करणे चांगले होईल, बरोबर?

यापैकी काहीही TL मध्ये नाही. परंतु, उदाहरणार्थ, JSON स्कीमामध्ये आहे. आणि जर कोणीतरी 512 KB च्या विभाज्यतेबद्दल वाद घालू शकतो, की हे अद्याप कोडमध्ये तपासले जाणे आवश्यक आहे, तर क्लायंट फक्त याची खात्री करा मी करू शकलो नाही श्रेणीबाहेरचा नंबर पाठवा 1..3000 (आणि संबंधित त्रुटी उद्भवू शकली नसती) हे शक्य झाले असते, बरोबर?..

तसे, त्रुटी आणि परतावा मूल्यांबद्दल. ज्यांनी TL सोबत काम केले आहे ते देखील त्यांचे डोळे धूसर करतात - हे आमच्यावर लगेच दिसून आले नाही प्रत्येक TL मधील फंक्शन केवळ वर्णन केलेला रिटर्न प्रकारच नाही तर त्रुटी देखील देऊ शकते. परंतु हे TL वापरून कोणत्याही प्रकारे काढले जाऊ शकत नाही. अर्थात, हे आधीच स्पष्ट आहे आणि व्यवहारात कशाचीही गरज नाही (जरी खरं तर, RPC वेगवेगळ्या प्रकारे करता येते, आम्ही यावर नंतर परत येऊ) - परंतु अमूर्त प्रकारांच्या गणिताच्या संकल्पनांच्या शुद्धतेचे काय? स्वर्गीय जगातून?.. मी टग उचलला - म्हणून जुळवा.

आणि शेवटी, वाचनीयतेचे काय? बरं, तिथे, सर्वसाधारणपणे, मला आवडेल वर्णन स्कीमामध्ये ते योग्य आहे (जेएसओएन स्कीमामध्ये, पुन्हा, ते आहे), परंतु जर तुम्ही आधीच यासह ताणलेले असाल, तर व्यावहारिक बाजूचे काय - अद्यतनांदरम्यान फरक पाहण्यासाठी किमान क्षुल्लक? येथे स्वत: साठी पहा वास्तविक उदाहरणे:

-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, उदाहरणार्थ, अशा लांब ओळींमधील बदल हायलाइट करण्यास नकार देतो. "१० फरक शोधा" हा खेळ, आणि मेंदूला जे लगेच दिसते ते म्हणजे दोन्ही उदाहरणांची सुरुवात आणि शेवट सारखाच आहे, तुम्हाला मध्यभागी कुठेतरी कंटाळवाणेपणे वाचण्याची आवश्यकता आहे... माझ्या मते, हे केवळ सिद्धांतात नाही, पण निव्वळ दृष्यदृष्ट्या गलिच्छ आणि आळशी.

तसे, सिद्धांताच्या शुद्धतेबद्दल. आम्हाला बिट फील्डची आवश्यकता का आहे? असे वाटत नाही का ते वास प्रकार सिद्धांताच्या दृष्टिकोनातून वाईट? आकृतीच्या पूर्वीच्या आवृत्त्यांमध्ये स्पष्टीकरण पाहिले जाऊ शकते. सुरुवातीला, होय, ते असेच होते, प्रत्येक शिंकासाठी एक नवीन प्रकार तयार केला गेला. हे मूलतत्त्व अजूनही या स्वरूपात अस्तित्वात आहे, उदाहरणार्थ:

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 प्रकारांची आवश्यकता असेल. संयुक्त स्फोट. अशाप्रकारे, टीएल सिद्धांताची क्रिस्टल शुद्धता पुन्हा एकदा सीरियलायझेशनच्या कठोर वास्तविकतेच्या कास्ट-आयर्न गाढवांच्या विरूद्ध विस्कळीत झाली.

याव्यतिरिक्त, काही ठिकाणी हे लोक स्वतःच त्यांच्या स्वतःच्या टायपोलॉजीचे उल्लंघन करतात. उदाहरणार्थ, MTProto (पुढील प्रकरण) मध्ये Gzip द्वारे प्रतिसाद संकुचित केला जाऊ शकतो, सर्व काही ठीक आहे - लेयर्स आणि सर्किटचे उल्लंघन केल्याशिवाय. पुन्हा एकदा, RpcResult स्वतःच कापणी झाली नाही तर त्यातील सामग्री होती. बरं, हे का करायचं?.. मला एक क्रॅच कापून टाकावी लागली जेणेकरून कॉम्प्रेशन कुठेही काम करेल.

किंवा दुसरे उदाहरण, आम्हाला एकदा त्रुटी आढळली - ती पाठवली गेली InputPeerUser त्याऐवजी InputUser. किंवा या उलट. पण ते काम केले! म्हणजेच, सर्व्हरने प्रकाराकडे लक्ष दिले नाही. हे कसे असू शकते? टेलीग्राम-क्ली मधील कोडच्या तुकड्यांद्वारे आम्हाला उत्तर दिले जाऊ शकते:

  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 स्तरांनंतर, आवृत्ती क्रमांकासह एक सार्वत्रिक कन्स्ट्रक्टर शेवटी जोडला गेला, ज्याला कनेक्शनच्या सुरूवातीस फक्त एकदाच कॉल करणे आवश्यक आहे, आणि स्तरांचा अर्थ नाहीसा झाला आहे असे दिसते, आता ती फक्त एक सशर्त आवृत्ती आहे, जसे की इतर सर्वत्र. समस्या सुटली.

नक्की?..

Vasily, [16.07.18 14:01] अगदी शुक्रवारी मी विचार केला:
टेलिसर्व्हर विनंतीशिवाय कार्यक्रम पाठवतो. विनंत्या InvokeWithLayer मध्ये गुंडाळल्या गेल्या पाहिजेत. सर्व्हर अद्यतने गुंडाळत नाही; प्रतिसाद आणि अद्यतने गुंडाळण्यासाठी कोणतीही रचना नाही.

त्या. क्लायंटला ज्या लेयरमध्ये अपडेट्स हवे आहेत ते निर्दिष्ट करू शकत नाही

Vadim Goncharov, [16.07.18 14:02] InvokeWithLayer तत्वतः एक कुबडी नाही का?

Vasily, [16.07.18 14:02] हा एकमेव मार्ग आहे

Vadim Goncharov, [16.07.18 14:02] ज्याचा अर्थ सत्राच्या सुरूवातीला स्तरावर सहमती असणे आवश्यक आहे.

तसे, हे असे आहे की क्लायंट डाउनग्रेड प्रदान केलेले नाही

अद्यतने, i.e. प्रकार Updates स्कीममध्ये, सर्व्हर क्लायंटला एपीआय विनंतीला प्रतिसाद देत नाही, परंतु जेव्हा एखादी घटना घडते तेव्हा स्वतंत्रपणे पाठवते. हा एक जटिल विषय आहे ज्याची चर्चा दुसर्‍या पोस्टमध्ये केली जाईल, परंतु सध्या हे जाणून घेणे महत्त्वाचे आहे की क्लायंट ऑफलाइन असताना देखील सर्व्हर अद्यतने जतन करतो.

अशा प्रकारे, आपण लपेटणे नाकारल्यास प्रत्येक त्याची आवृत्ती सूचित करण्यासाठी पॅकेज, हे तार्किकदृष्ट्या खालील संभाव्य समस्यांना कारणीभूत ठरते:

  • क्लायंटने कोणत्या आवृत्तीचे समर्थन करते हे कळवण्यापूर्वीच सर्व्हर क्लायंटला अद्यतने पाठवतो
  • क्लायंट अपग्रेड केल्यानंतर मी काय करावे?
  • कोण हमीप्रक्रियेदरम्यान लेयर नंबरबद्दल सर्व्हरचे मत बदलणार नाही?

तुम्हाला असे वाटते का की हे पूर्णपणे सैद्धांतिक अनुमान आहे, आणि व्यवहारात असे होऊ शकत नाही, कारण सर्व्हर योग्यरित्या लिहिलेले आहे (किमान, ते चांगले तपासले आहे)? हा! ते कसेही असो!

ऑगस्टमध्ये नेमके हेच घडले. 14 ऑगस्ट रोजी, टेलीग्राम सर्व्हरवर काहीतरी अपडेट केले जात असल्याचे संदेश आले होते... आणि नंतर लॉगमध्ये:

2019-08-15 09:28:35.880640 MSK warn  main: ANON:87: unknown object type: 0x80d182d1 at TL/Object.pm line 213.
2019-08-15 09:28:35.751899 MSK warn  main: ANON:87: unknown object type: 0xb5223b0f at TL/Object.pm line 213.

आणि नंतर अनेक मेगाबाइट्स स्टॅक ट्रेस (चांगले, त्याच वेळी लॉगिंग निश्चित केले होते). शेवटी, जर तुमच्या TL मध्ये काहीतरी ओळखले गेले नाही, तर ते स्वाक्षरीद्वारे बायनरी आहे, पुढे ओळीच्या खाली सर्व जाते, डीकोडिंग अशक्य होईल. अशा परिस्थितीत आपण काय करावे?

बरं, कोणाच्याही मनात येणारी पहिली गोष्ट म्हणजे डिस्कनेक्ट करणे आणि पुन्हा प्रयत्न करणे. मदत केली नाही. आम्ही CRC32 गुगल करतो - हे स्कीम 73 मधील ऑब्जेक्ट्स असल्याचे दिसून आले, जरी आम्ही 82 वर काम केले. आम्ही लॉग काळजीपूर्वक पाहतो - दोन वेगवेगळ्या स्कीम्सचे आयडेंटिफायर आहेत!

कदाचित समस्या पूर्णपणे आमच्या अनधिकृत क्लायंटमध्ये आहे? नाही, आम्ही टेलीग्राम डेस्कटॉप 1.2.17 लाँच करतो (अनेक लिनक्स वितरणांमध्ये पुरवलेली आवृत्ती), ते अपवाद लॉगवर लिहिते: MTP अनपेक्षित प्रकार id #b5223b0f MTPMessageMedia मध्ये वाचला…

टेलीग्रामच्या प्रोटोकॉल आणि संस्थात्मक दृष्टिकोनांवर टीका. भाग १, तांत्रिक: क्लायंटला सुरवातीपासून लिहिण्याचा अनुभव - TL, MT

Google ने दर्शविले की अशाच प्रकारची समस्या एका अनधिकृत क्लायंटला आधीच आली होती, परंतु नंतर आवृत्ती क्रमांक आणि त्यानुसार, गृहितक भिन्न होते ...

मग आपण काय करावे? व्हॅसिली आणि मी वेगळे झालो: त्याने सर्किट 91 वर अपडेट करण्याचा प्रयत्न केला, मी काही दिवस थांबून 73 वर प्रयत्न करण्याचा निर्णय घेतला. दोन्ही पद्धतींनी काम केले, परंतु त्या अनुभवजन्य असल्याने, आपल्याला किती आवृत्त्या वर किंवा खाली आवश्यक आहेत हे समजत नाही. उडी मारण्यासाठी, किंवा तुम्हाला किती वेळ प्रतीक्षा करावी लागेल.

नंतर मी परिस्थितीचे पुनरुत्पादन करू शकलो: आम्ही क्लायंट लाँच करतो, ते बंद करतो, सर्किटला दुसर्या लेयरमध्ये पुन्हा संकलित करतो, रीस्टार्ट करतो, समस्या पुन्हा पकडतो, मागील एकावर परत येतो - अरेरे, सर्किट स्विचिंगची कोणतीही रक्कम नाही आणि क्लायंट रीस्टार्ट होत नाही. काही मिनिटे मदत करतील. तुम्हाला वेगवेगळ्या लेयर्समधून डेटा स्ट्रक्चर्सचे मिश्रण मिळेल.

स्पष्टीकरण? विविध अप्रत्यक्ष लक्षणांवरून तुम्ही अंदाज लावू शकता, सर्व्हरमध्ये वेगवेगळ्या मशीन्सवर वेगवेगळ्या प्रकारच्या अनेक प्रक्रिया असतात. बहुधा, "बफरिंग" साठी जबाबदार असलेल्या सर्व्हरने त्याच्या वरिष्ठांनी जे दिले ते रांगेत ठेवले आणि त्यांनी ते पिढीच्या वेळी असलेल्या योजनेत दिले. आणि ही रांग "सडलेली" होईपर्यंत, याबद्दल काहीही केले जाऊ शकत नाही.

कदाचित... पण ही एक भयानक कुबडी आहे?!.. नाही, विलक्षण कल्पनांबद्दल विचार करण्यापूर्वी, अधिकृत क्लायंटचा कोड पाहू. अँड्रॉइड आवृत्तीमध्ये आम्हाला कोणताही TL पार्सर सापडत नाही, परंतु आम्हाला (डी) सीरियलायझेशनसह एक मोठी फाइल (गिटहबने त्यास स्पर्श करण्यास नकार दिला) सापडतो. येथे कोड स्निपेट्स आहेत:

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

मित्रांनो, एका थरात काय आहे हे तुम्ही ठरवू शकत नाही का?! बरं, ठीक आहे, समजा “दोन” एका त्रुटीने सोडले गेले, बरं, असं होतं, पण तीन?.. लगेच, पुन्हा तोच रेक? ही कसली पोर्नोग्राफी आहे, माफ करा..?

टेलिग्राम डेस्कटॉपच्या सोर्स कोडमध्ये, तसे, एक समान गोष्ट घडते - जर असे असेल तर, योजनेसाठी लागोपाठ अनेक कमिट केल्याने त्याचा स्तर क्रमांक बदलू नका, परंतु काहीतरी निश्चित करा. योजनेसाठी डेटाचा कोणताही अधिकृत स्रोत नसलेल्या परिस्थितीत, अधिकृत क्लायंटचा स्त्रोत कोड वगळता तो कोठून मिळवता येईल? आणि तुम्ही ते तिथून घेतल्यास, तुम्ही सर्व पद्धतींची चाचणी करेपर्यंत योजना पूर्णपणे बरोबर असल्याची खात्री बाळगता येणार नाही.

हे देखील कसे तपासले जाऊ शकते? मला आशा आहे की युनिट, फंक्शनल आणि इतर चाचण्यांचे चाहते टिप्पण्यांमध्ये सामायिक करतील.

ठीक आहे, चला कोडचा दुसरा भाग पाहू:

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;

ही टिप्पणी "स्वतः तयार केलेली" सूचित करते की या फाईलचा फक्त एक भाग व्यक्तिचलितपणे लिहिला गेला होता (तुम्ही संपूर्ण देखभाल दुःस्वप्न कल्पना करू शकता का?), आणि उर्वरित मशीन-व्युत्पन्न होते. तथापि, नंतर दुसरा प्रश्न उद्भवतो - स्त्रोत उपलब्ध आहेत पूर्णपणे नाही (लिनक्स कर्नलमध्ये एक ला जीपीएल ब्लॉब), परंतु हा दुसऱ्या भागासाठी आधीच एक विषय आहे.

पण पुरे. चला त्या प्रोटोकॉलवर जाऊ या ज्याच्या वर हे सर्व क्रमवारी चालते.

एमटीपीप्रोटो

तर, उघडूया सामान्य वर्णन и प्रोटोकॉलचे तपशीलवार वर्णन आणि पहिली गोष्ट जी आपण अडखळतो ती म्हणजे शब्दावली. आणि प्रत्येक गोष्टीच्या विपुलतेसह. सर्वसाधारणपणे, हे टेलीग्रामचे एक मालकीचे वैशिष्ट्य आहे असे दिसते - वेगवेगळ्या ठिकाणी गोष्टी वेगळ्या पद्धतीने कॉल करणे, किंवा एका शब्दाने वेगळ्या गोष्टी करणे, किंवा त्याउलट (उदाहरणार्थ, उच्च-स्तरीय API मध्ये, जर तुम्हाला स्टिकर पॅक दिसला तर ते नाही. तुम्हाला काय वाटले).

उदाहरणार्थ, नेहमीच्या टेलीग्राम क्लायंट इंटरफेसपेक्षा येथे “संदेश” आणि “सत्र” चा अर्थ काहीतरी वेगळा आहे. बरं, संदेशासह सर्व काही स्पष्ट आहे, त्याचा अर्थ OOP शब्दात केला जाऊ शकतो किंवा फक्त "पॅकेट" शब्द म्हटले जाऊ शकते - ही एक निम्न, वाहतूक पातळी आहे, इंटरफेस प्रमाणेच संदेश नाहीत, बरेच सेवा संदेश आहेत . पण सत्र... पण प्रथम गोष्टी प्रथम.

वाहतूक स्तर

पहिली गोष्ट म्हणजे वाहतूक. ते आम्हाला 5 पर्यायांबद्दल सांगतील:

  • टीसीपी
  • वेबसाइट्स
  • HTTPS वर वेबसॉकेट
  • HTTP
  • HTTPS

Vasily, [15.06.18 15:04] येथे UDP वाहतूक देखील आहे, परंतु ते दस्तऐवजीकरण केलेले नाही

आणि TCP तीन प्रकारांमध्ये

पहिला TCP वर UDP सारखा आहे, प्रत्येक पॅकेटमध्ये अनुक्रम क्रमांक आणि crc समाविष्ट आहे
कार्टवरील कागदपत्रे वाचणे इतके वेदनादायक का आहे?

बरं, ते आता आहे TCP आधीच 4 प्रकारांमध्ये आहे:

  • संक्षिप्त
  • इंटरमिजिएट
  • पॅड केलेले मध्यवर्ती
  • पूर्ण

ठीक आहे, MTProxy साठी पॅडेड इंटरमीडिएट, हे नंतर सुप्रसिद्ध कार्यक्रमांमुळे जोडले गेले. परंतु जेव्हा तुम्ही एकासह मिळवू शकता तेव्हा आणखी दोन आवृत्त्या (एकूण तीन) का? मुख्य MTProto ची लांबी आणि पेलोड कसे सेट करायचे या चारही मूलत: भिन्न आहेत, ज्याची पुढे चर्चा केली जाईल:

  • संक्षिप्त मध्ये ते 1 किंवा 4 बाइट्स आहे, परंतु 0xef नाही, नंतर शरीर
  • इंटरमीडिएटमध्ये हे 4 बाइट्स लांबीचे आणि एक फील्ड आहे आणि क्लायंटने प्रथमच पाठवणे आवश्यक आहे 0xeeeeeeee ते इंटरमीडिएट असल्याचे दर्शविण्यासाठी
  • नेटवर्करच्या दृष्टिकोनातून, संपूर्णपणे सर्वात व्यसनाधीन: लांबी, अनुक्रम क्रमांक, आणि मुख्यतः MTProto, शरीर, CRC32 नाही. होय, हे सर्व TCP वर आहे. जे आम्हाला अनुक्रमिक बाइट प्रवाहाच्या रूपात विश्वसनीय वाहतूक प्रदान करते; कोणत्याही अनुक्रमांची आवश्यकता नाही, विशेषतः चेकसम. ठीक आहे, आता कोणीतरी माझ्यावर आक्षेप घेईल की TCP मध्ये 16-बिट चेकसम आहे, त्यामुळे डेटा करप्ट होतो. छान, परंतु आमच्याकडे 16 बाइट्सपेक्षा लांब हॅशसह एक क्रिप्टोग्राफिक प्रोटोकॉल आहे, या सर्व त्रुटी - आणि त्याहूनही अधिक - उच्च स्तरावर SHA जुळत नसल्यामुळे पकडल्या जातील. या वर CRC32 मध्ये कोणताही मुद्दा नाही.

चला संक्षिप्त तुलना करूया, ज्यामध्ये लांबीचा एक बाइट शक्य आहे, इंटरमीडिएटशी, जो “जर 4-बाइट डेटा संरेखन आवश्यक असेल” असे समर्थन देतो, जे अगदी मूर्खपणाचे आहे. काय, असे मानले जाते की टेलीग्राम प्रोग्रामर इतके अक्षम आहेत की ते सॉकेटमधून संरेखित बफरमध्ये डेटा वाचू शकत नाहीत? तुम्हाला अजूनही हे करावे लागेल, कारण वाचन तुम्हाला कितीही बाइट्स परत करू शकते (आणि प्रॉक्सी सर्व्हर देखील आहेत, उदाहरणार्थ...). किंवा दुसरीकडे, जर आपल्याकडे 16 बाइट्सच्या वर भारी पॅडिंग असेल तर संक्षेप का ब्लॉक करा - 3 बाइट्स वाचवा कधी कधी ?

निकोलाई डुरोव्हला कोणत्याही वास्तविक व्यावहारिक गरजेशिवाय, नेटवर्क प्रोटोकॉलसह चाके पुन्हा शोधणे खरोखरच आवडते असा समज होतो.

इतर वाहतूक पर्याय, समावेश. वेब आणि MTProxy, आम्ही आता विचार करणार नाही, कदाचित दुसर्‍या पोस्टमध्ये, विनंती असल्यास. याच MTProxy बद्दल, आपण आता फक्त हे लक्षात ठेवूया की 2018 मध्ये रिलीझ झाल्यानंतर लगेचच, प्रदाते त्वरीत ते ब्लॉक करण्यास शिकले, ज्याचा हेतू आहे बायपास ब्लॉकिंगद्वारा पॅकेज आकार! आणि C मध्ये (पुन्हा वॉल्टमॅनने) लिहिलेले MTProxy सर्व्हर हे लिनक्सच्या वैशिष्ट्यांशी अत्याधिकपणे जोडलेले होते, जरी याची अजिबात आवश्यकता नव्हती (फिल कुलिन पुष्टी करेल), आणि Go किंवा Node.js मधील समान सर्व्हर शंभरपेक्षा कमी ओळींमध्ये फिट.

परंतु या लोकांच्या तांत्रिक साक्षरतेबद्दल आम्ही विभागाच्या शेवटी, इतर मुद्द्यांचा विचार करून निष्कर्ष काढू. आत्तासाठी, OSI लेयर 5, सत्राकडे जाऊया - ज्यावर त्यांनी MTProto सत्र ठेवले.

की, संदेश, सत्रे, डिफी-हेलमन

त्यांनी ते तिथे पूर्णपणे बरोबर ठेवले नाही... सत्र हे तेच सत्र नसते जे सक्रिय सत्रांतर्गत इंटरफेसमध्ये दृश्यमान असते. पण क्रमाने.

टेलीग्रामच्या प्रोटोकॉल आणि संस्थात्मक दृष्टिकोनांवर टीका. भाग १, तांत्रिक: क्लायंटला सुरवातीपासून लिहिण्याचा अनुभव - TL, MT

म्हणून आम्हाला ट्रान्सपोर्ट लेयरमधून ज्ञात लांबीची बाइट स्ट्रिंग मिळाली. हा एकतर एन्क्रिप्ट केलेला संदेश किंवा साधा मजकूर आहे - जर आम्ही अजूनही मुख्य कराराच्या टप्प्यावर आहोत आणि प्रत्यक्षात ते करत आहोत. आपण "की" नावाच्या संकल्पनांच्या कोणत्या समूहाबद्दल बोलत आहोत? टेलीग्राम टीमसाठीच हा मुद्दा स्पष्ट करूया (सकाळी ४ वाजता थकलेल्या मेंदूने माझ्या स्वत:च्या कागदपत्रांचे इंग्रजीतून भाषांतर केल्याबद्दल माफी मागतो, काही वाक्ये जसे आहेत तशी सोडणे सोपे होते):

दोन संस्था म्हणतात सत्र - “चालू सत्रे” अंतर्गत अधिकृत क्लायंटच्या UI मधील एक, जेथे प्रत्येक सत्र संपूर्ण डिव्हाइस / OS शी संबंधित आहे.
दुसरा - MTProto सत्र, ज्यामध्ये संदेशाचा क्रम क्रमांक (निम्न-स्तरीय अर्थाने) आहे आणि कोणता भिन्न TCP कनेक्शन दरम्यान टिकू शकते. एकाच वेळी अनेक MTProto सत्रे स्थापित केली जाऊ शकतात, उदाहरणार्थ, फाइल डाउनलोडिंगला गती देण्यासाठी.

या दोघांमध्ये सत्र एक संकल्पना आहे अधिकृतता. अधोगतीच्या बाबतीत आपण असे म्हणू शकतो UI सत्र सारखेच आहे अधिकृतता, पण अरेरे, सर्व काही क्लिष्ट आहे. चला पाहूया:

  • नवीन उपकरणावरील वापरकर्ता प्रथम जनरेट करतो auth_key आणि ते खात्यावर बंधनकारक करते, उदाहरणार्थ SMS द्वारे - म्हणूनच अधिकृतता
  • हे पहिल्या आत घडले MTProto सत्र, ज्यात आहे session_id स्वतःच्या आत.
  • या टप्प्यावर, संयोजन अधिकृतता и session_id म्हटले जाऊ शकते उदाहरण - हा शब्द काही क्लायंटच्या कागदपत्रांमध्ये आणि कोडमध्ये दिसतो
  • त्यानंतर, क्लायंट उघडू शकतो अनेक MTProto सत्रे त्याच अंतर्गत auth_key - त्याच DC ला.
  • मग, एक दिवस क्लायंटला फाईलची विनंती करावी लागेल दुसरा डीसी - आणि यासाठी एक नवीन डीसी तयार केला जाईल auth_key !
  • प्रणालीला सूचित करण्यासाठी की नोंदणी करणारा नवीन वापरकर्ता नाही तर तोच आहे अधिकृतता (UI सत्र), क्लायंट API कॉल वापरतो auth.exportAuthorization घरी डीसी मध्ये auth.importAuthorization नवीन DC मध्ये.
  • सर्व काही समान आहे, अनेक खुले असू शकतात MTProto सत्रे (प्रत्येक स्वतःचा session_id) या नवीन DC ला, अंतर्गत त्याच्या auth_key.
  • शेवटी, क्लायंटला परफेक्ट फॉरवर्ड गुप्तता हवी असेल. प्रत्येक auth_key होते स्थायी की - प्रति DC - आणि क्लायंट कॉल करू शकतो auth.bindTempAuthKey वापरासाठी तात्पुरती auth_key - आणि पुन्हा, फक्त एक temp_auth_key प्रति DC, सर्वांसाठी सामान्य MTProto सत्रे या DC ला.

त्याची नोंद घ्या मीठ (आणि भविष्यातील लवण) देखील एक आहे auth_key त्या सर्वांमध्ये सामायिक केले MTProto सत्रे त्याच DC ला.

"वेगवेगळ्या TCP कनेक्शन दरम्यान" म्हणजे काय? तर याचा अर्थ असे काहीतरी वेबसाइटवर अधिकृतता कुकी - ती दिलेल्या सर्व्हरवर अनेक TCP कनेक्शन टिकून राहते (जगते), परंतु एक दिवस ती खराब होते. एचटीटीपीच्या विपरीत, एमटीप्रोटोमध्ये सत्रातील संदेश अनुक्रमे क्रमांकित आणि पुष्टी केले जातात; जर त्यांनी बोगद्यात प्रवेश केला, तर कनेक्शन तुटले होते - नवीन कनेक्शन स्थापित केल्यानंतर, सर्व्हर दयाळूपणे या सत्रातील सर्व काही पाठवेल जे त्याने मागीलमध्ये वितरित केले नाही. TCP कनेक्शन.

तथापि, वरील माहिती अनेक महिन्यांच्या तपासानंतर सारांशित केली आहे. दरम्यान, आम्ही आमच्या क्लायंटची सुरवातीपासून अंमलबजावणी करत आहोत का? - चला सुरुवातीकडे परत जाऊया.

चला तर जनरेट करूया auth_key वर टेलिग्राममधील डिफी-हेलमन आवृत्त्या. चला कागदपत्रे समजून घेण्याचा प्रयत्न करूया...

Vasily, [19.06.18 20:05] data_with_hash := SHA1(डेटा) + डेटा + (कोणत्याही यादृच्छिक बाइट्स); लांबी 255 बाइट्स इतकी आहे;
encrypted_data := RSA(data_with_hash, server_public_key); 255-बाइट लांब संख्या (मोठा एंडियन) आवश्यक मॉड्यूलसवर आवश्यक पॉवरवर वाढविला जातो आणि परिणाम 256-बाइट क्रमांक म्हणून संग्रहित केला जातो.

त्यांना काही डोप DH आहे

निरोगी व्यक्तीच्या डीएचसारखे दिसत नाही
dx मध्ये दोन सार्वजनिक की नाहीत

बरं, शेवटी हे सोडवण्यात आलं, पण एक अवशेष उरला - कामाचा पुरावा क्लायंटने केला आहे की तो नंबर फॅक्टर करण्यात सक्षम होता. DoS हल्ल्यांपासून संरक्षणाचा प्रकार. आणि RSA की फक्त एकदाच एका दिशेने वापरली जाते, मूलत: एनक्रिप्शनसाठी new_nonce. परंतु हे वरवर साधे ऑपरेशन यशस्वी होत असताना, तुम्हाला कशाचा सामना करावा लागेल?

व्हॅसिली, [20.06.18/00/26 XNUMX:XNUMX] मी अद्याप ऍपिड विनंतीवर पोहोचलो नाही

ही विनंती मी डीएचला पाठवली

आणि, ट्रान्सपोर्ट डॉकमध्ये असे म्हटले आहे की ते एरर कोडच्या 4 बाइट्ससह प्रतिसाद देऊ शकते. इतकंच

बरं, त्याने मला सांगितले -404, मग काय?

म्हणून मी त्याला म्हणालो: “तुझ्या फिंगरप्रिंटसह सर्व्हर कीसह एन्क्रिप्ट केलेले तुझे बल्शिट पकड, मला DH पाहिजे आहे,” आणि त्याने मूर्ख 404 सह प्रतिसाद दिला.

या सर्व्हर प्रतिसादाबद्दल तुम्हाला काय वाटेल? काय करायचं? कोणी विचारणार नाही (परंतु दुसर्‍या भागात त्याबद्दल अधिक).

येथे सर्व व्याज गोदीवर केले जाते

मला दुसरे काही करायचे नाही, मी फक्त आकडे परत परत बदलण्याचे स्वप्न पाहिले

दोन 32 बिट संख्या. मी त्यांना इतरांप्रमाणे पॅक केले

पण नाही, या दोघांना प्रथम BE म्हणून ओळीत जोडणे आवश्यक आहे

Vadim Goncharov, [20.06.18 15:49] आणि यामुळे 404?

वॅसिली, [२०.०६.१८ १५:४९] होय!

Vadim Goncharov, [20.06.18 15:50] त्यामुळे त्याला काय सापडले नाही ते मला समजत नाही

वॅसिली, [२०.०६.१८ १५:५०] अंदाजे

मला असे विघटन अविभाज्य घटकांमध्ये आढळले नाही%)

आम्ही त्रुटी अहवाल व्यवस्थापित देखील केले नाही

वॅसिली, [२०.०६.१८ २०:१८] अरे, एमडी ५ देखील आहे. आधीच तीन भिन्न हॅश

की फिंगरप्रिंटची गणना खालीलप्रमाणे केली जाते:

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

SHA1 आणि sha2

तर टाकूया auth_key डिफी-हेलमन वापरून आम्हाला 2048 बिट आकारात मिळाले. पुढे काय? पुढे आम्हाला कळले की या कीचे खालचे 1024 बिट्स कोणत्याही प्रकारे वापरले जात नाहीत... पण आता याचा विचार करूया. या चरणावर, आमच्याकडे सर्व्हरसह सामायिक केलेले रहस्य आहे. TLS सत्राचा एक अॅनालॉग स्थापित केला गेला आहे, जी एक अतिशय महाग प्रक्रिया आहे. परंतु सर्व्हरला अद्याप आपण कोण आहोत याबद्दल काहीही माहिती नाही! अद्याप नाही, प्रत्यक्षात. अधिकृतता. त्या. जर तुम्ही "लॉगिन-पासवर्ड" च्या संदर्भात विचार केला असेल, जसे की तुम्ही एकदा ICQ मध्ये केले होते, किंवा किमान "लॉगिन-की", SSH प्रमाणे (उदाहरणार्थ, काही gitlab/github वर). आम्हाला एक अनामिक मिळाले. सर्व्हरने आम्हाला "हे फोन नंबर दुसर्‍या DC द्वारे सर्व्हिस केलेले आहेत" असे सांगितले तर काय? किंवा अगदी “तुमचा फोन नंबर बंदी आहे”? चावी उपयोगी पडेल आणि तोपर्यंत कुजणार नाही या आशेने आपण सर्वात चांगले करू शकतो.

तसे, आम्हाला ते आरक्षणासह "प्राप्त" झाले. उदाहरणार्थ, आम्ही सर्व्हरवर विश्वास ठेवतो का? ते बनावट असेल तर? क्रिप्टोग्राफिक तपासणी आवश्यक असेल:

Vasily, [21.06.18 17:53] ते मोबाईल क्लायंटना 2kbit क्रमांक तपासण्यासाठी देतात.

पण हे अजिबात स्पष्ट नाही, nafeijoa

Vasily, [21.06.18 18:02] कागदपत्र सोपे नसल्यास काय करावे हे सांगितलेले नाही

सांगितले नाही. या प्रकरणात अधिकृत Android क्लायंट काय करतो ते पाहूया? ए तेच आहे (आणि हो, संपूर्ण फाईल मनोरंजक आहे) - जसे ते म्हणतात, मी ते येथे सोडेन:

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

नाही, नक्कीच ते अजूनही आहे काही संख्येच्या प्राथमिकतेसाठी चाचण्या आहेत, परंतु वैयक्तिकरित्या मला यापुढे गणिताचे पुरेसे ज्ञान नाही.

ठीक आहे, आम्हाला मास्टर की मिळाली. लॉग इन करण्यासाठी, i.e. विनंत्या पाठवा, तुम्हाला AES वापरून पुढील एनक्रिप्शन करणे आवश्यक आहे.

मेसेज की ही मेसेज बॉडीच्या SHA128 चे 256 मधले बिट्स (सत्र, मेसेज आयडी इ.सह), पॅडिंग बाइट्ससह, ऑथोरायझेशन की मधून घेतलेल्या 32 बाइट्सने प्रीपेंड केलेली आहे.

वॅसिली, [२२.०६.१८ १४:०८] सरासरी, कुत्री, बिट्स

समजले auth_key. सर्व. त्यांच्या पलीकडे... हे दस्तऐवजातून स्पष्ट नाही. मुक्त स्त्रोत कोडचा अभ्यास करण्यास मोकळ्या मनाने.

लक्षात ठेवा की MTProto 2.0 ला 12 ते 1024 बाइट्स पॅडिंग आवश्यक आहे, तरीही परिणामी संदेशाची लांबी 16 बाइट्सने विभाज्य असावी या अटीच्या अधीन आहे.

तर तुम्ही किती पॅडिंग घालावे?

आणि हो, त्रुटी आढळल्यास 404 देखील आहे

जर कोणी दस्तऐवजीकरणाच्या आकृतीचा आणि मजकूराचा काळजीपूर्वक अभ्यास केला, तर त्यांच्या लक्षात आले की तेथे MAC नाही. आणि ते AES एका विशिष्ट IGE मोडमध्ये वापरले जाते जे इतर कोठेही वापरले जात नाही. ते, अर्थातच, त्यांच्या FAQ मध्ये याबद्दल लिहितात... येथे, जसे की, मेसेज की स्वतः डिक्रिप्ट केलेल्या डेटाची SHA हॅश देखील आहे, अखंडता तपासण्यासाठी वापरली जाते - आणि काही जुळत नसल्यास, काही कारणास्तव दस्तऐवजीकरण शांतपणे त्यांच्याकडे दुर्लक्ष करण्याची शिफारस करते (परंतु सुरक्षेचे काय, जर त्यांनी आम्हाला तोडले तर काय?).

मी क्रिप्टोग्राफर नाही, कदाचित सैद्धांतिक दृष्टिकोनातून या प्रकरणात या मोडमध्ये काहीही चुकीचे नाही. पण उदाहरण म्हणून टेलीग्राम डेस्कटॉप वापरून मी एका व्यावहारिक समस्येचे स्पष्टपणे नाव देऊ शकतो. हे स्थानिक कॅशे (हे सर्व D877F783D5D3EF8C) MTProto मधील संदेशांप्रमाणेच कूटबद्ध करते (केवळ या प्रकरणात आवृत्ती 1.0), उदा. प्रथम संदेश की, नंतर डेटा स्वतः (आणि कुठेतरी मुख्य मोठा बाजूला auth_key 256 बाइट्स, त्याशिवाय msg_key निरुपयोगी). त्यामुळे, मोठ्या फायलींवर समस्या लक्षात येते. बहुदा, आपल्याला डेटाच्या दोन प्रती ठेवण्याची आवश्यकता आहे - एनक्रिप्टेड आणि डिक्रिप्टेड. आणि उदाहरणार्थ, मेगाबाइट्स किंवा स्ट्रीमिंग व्हिडिओ असल्यास?... सायफरटेक्स्ट नंतर MAC सह क्लासिक स्कीम तुम्हाला ते प्रवाह वाचण्याची परवानगी देतात, ते त्वरित प्रसारित करतात. परंतु MTProto सह तुम्हाला हे करावे लागेल प्रथम संपूर्ण संदेश एन्क्रिप्ट किंवा डिक्रिप्ट करा, त्यानंतरच तो नेटवर्क किंवा डिस्कवर हस्तांतरित करा. म्हणून, कॅशेमध्ये टेलिग्राम डेस्कटॉपच्या नवीनतम आवृत्त्यांमध्ये user_data आणखी एक स्वरूप देखील वापरले जाते - CTR मोडमध्ये AES सह.

Vasily, [21.06.18 01:27] अरे, मला IGE म्हणजे काय हे कळले: IGE हा मूळतः Kerberos साठी "ऑथेंटिकेटिंग एन्क्रिप्शन मोड" चा पहिला प्रयत्न होता. हा एक अयशस्वी प्रयत्न होता (तो अखंडतेचे संरक्षण प्रदान करत नाही), आणि तो काढावा लागला. कार्य करणार्‍या प्रमाणीकृत एन्क्रिप्शन मोडसाठी 20 वर्षांच्या शोधाची ही सुरुवात होती, जी अलीकडेच OCB आणि GCM सारख्या मोडमध्ये संपली.

आणि आता कार्टच्या बाजूने युक्तिवाद:

निकोलाई दुरोव यांच्या नेतृत्वाखालील टेलीग्रामच्या मागे असलेल्या संघात सहा ACM चॅम्पियन आहेत, त्यापैकी निम्मे गणितात Ph.D आहेत. MTProto ची वर्तमान आवृत्ती आणण्यासाठी त्यांना सुमारे दोन वर्षे लागली.

ते मजेशीर आहे. खालच्या स्तरावर दोन वर्षे

किंवा तुम्ही फक्त tls घेऊ शकता

ठीक आहे, समजा आम्ही एन्क्रिप्शन आणि इतर बारकावे पूर्ण केले आहेत. शेवटी TL मध्ये अनुक्रमित विनंत्या पाठवणे आणि प्रतिसाद डीसीरियल करणे शक्य आहे का? मग आपण काय आणि कसे पाठवावे? येथे, पद्धत म्हणूया initConnection, कदाचित हे आहे?

Vasily, [25.06.18 18:46] कनेक्शन सुरू करते आणि वापरकर्त्याच्या डिव्हाइसवर आणि अनुप्रयोगावरील माहिती जतन करते.

ते app_id, device_model, system_version, app_version आणि lang_code स्वीकारते.

आणि काही प्रश्न

नेहमीप्रमाणे दस्तऐवजीकरण. मुक्त स्त्रोताचा अभ्यास करण्यास मोकळ्या मनाने

invokeWithLayer सह सर्वकाही अंदाजे स्पष्ट असल्यास, येथे काय चूक आहे? असे दिसून आले की, आमच्याकडे आहे असे म्हणूया - क्लायंटकडे आधीपासूनच सर्व्हरला विचारण्यासाठी काहीतरी होते - आम्हाला एक विनंती पाठवायची आहे:

Vasily, [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 पाठवतो आणि त्यानंतर आम्हाला कोडसह एसएमएस प्राप्त होतो. आणि जर आपण चुकीच्या DC मध्ये आहोत (उदाहरणार्थ, या देशातील टेलिफोन नंबर दुसर्‍याद्वारे दिले जातात), तर आम्हाला इच्छित DC क्रमांकासह त्रुटी प्राप्त होईल. DC नंबरद्वारे तुम्हाला कोणता IP पत्ता जोडायचा आहे हे शोधण्यासाठी, आम्हाला मदत करा help.getConfig. एका वेळी फक्त 5 नोंदी होत्या, परंतु 2018 च्या प्रसिद्ध कार्यक्रमांनंतर, संख्या लक्षणीय वाढली आहे.

आता आपण हे लक्षात ठेवूया की आपण अज्ञातपणे सर्व्हरवर या टप्प्यावर पोहोचलो आहोत. फक्त IP पत्ता मिळवणे खूप महाग नाही का? हे आणि इतर ऑपरेशन्स MTProto च्या अनएनक्रिप्टेड भागात का करू नये? मी आक्षेप ऐकतो: "खोट्या पत्त्यांसह प्रतिसाद देणारा RKN नाही याची आम्ही खात्री कशी करू शकतो?" यासाठी आम्ही लक्षात ठेवतो की, सर्वसाधारणपणे, अधिकृत ग्राहक RSA की एम्बेड केलेल्या आहेत, म्हणजे तुम्ही फक्त करू शकता चिन्ह ही माहिती. वास्तविक, क्लायंटला इतर चॅनेलद्वारे प्राप्त होणार्‍या बायपास ब्लॉकिंगच्या माहितीसाठी हे आधीच केले जात आहे (तार्किकदृष्ट्या, हे MTProto मध्येच केले जाऊ शकत नाही; तुम्हाला कुठे कनेक्ट करायचे हे देखील माहित असणे आवश्यक आहे).

ठीक आहे. क्लायंट अधिकृततेच्या या टप्प्यावर, आम्ही अद्याप अधिकृत नाही आणि आमचा अर्ज नोंदणीकृत केलेला नाही. अनधिकृत वापरकर्त्यासाठी उपलब्ध असलेल्या पद्धतींना सर्व्हर काय प्रतिसाद देतो हे आम्हाला आत्ताच पहायचे आहे. आणि इथे…

वॅसिली, [२०.०६.१८ १५:५०] 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 स्कीमामध्ये तिसरे मूल्य आहे

होय, तेव्हापासून, अर्थातच, दस्तऐवजीकरण अद्यतनित केले गेले आहे. जरी ते लवकरच पुन्हा असंबद्ध होऊ शकते. नवशिक्या विकसकाला कसे कळले पाहिजे? कदाचित तुम्ही तुमचा अर्ज नोंदवला तर ते तुम्हाला कळवतील? वॅसिलीने हे केले, परंतु अरेरे, त्यांनी त्याला काहीही पाठवले नाही (पुन्हा, आम्ही याबद्दल दुसऱ्या भागात बोलू).

...आपल्या लक्षात आले आहे की आम्ही आधीच एपीआय वर हलविले आहे, म्हणजे. पुढील स्तरावर, आणि MTProto विषयात काहीतरी चुकले? आश्चर्य नाही:

व्हॅसिली, [२८.०६.१८ ०२:०४] मिमी, ते e28.06.18e वरील काही अल्गोरिदम्सचा अभ्यास करत आहेत

Mtproto दोन्ही डोमेनसाठी एन्क्रिप्शन अल्गोरिदम आणि की परिभाषित करते, तसेच थोडी रॅपर संरचना

परंतु ते सतत स्टॅकच्या विविध स्तरांचे मिश्रण करतात, त्यामुळे mtproto कुठे संपला आणि पुढील स्तर सुरू झाला हे नेहमी स्पष्ट होत नाही.

ते कसे मिसळतात? बरं, PFS साठी हीच तात्पुरती की आहे, उदाहरणार्थ (तसे, टेलीग्राम डेस्कटॉप हे करू शकत नाही). हे API विनंतीद्वारे कार्यान्वित केले जाते auth.bindTempAuthKey, म्हणजे वरच्या स्तरावरून. परंतु त्याच वेळी ते खालच्या स्तरावर एनक्रिप्शनमध्ये हस्तक्षेप करते - त्यानंतर, उदाहरणार्थ, आपल्याला ते पुन्हा करण्याची आवश्यकता आहे initConnection इत्यादी, हे नाही फक्त सामान्य विनंती. विशेष म्हणजे तुमच्याकडे प्रति DC फक्त एक तात्पुरती की असू शकते, जरी फील्ड auth_key_id प्रत्येक संदेशामध्ये आपल्याला किमान प्रत्येक संदेशाची की बदलण्याची परवानगी देते आणि सर्व्हरला तात्पुरती की कधीही "विसरण्याचा" अधिकार आहे - या प्रकरणात काय करावे हे दस्तऐवजीकरण सांगत नाही... बरं, का होऊ शकत नाही भविष्यातील क्षारांच्या संचाप्रमाणे तुमच्याकडे अनेक चाव्या नाहीत आणि?..

MTProto थीमबद्दल लक्षात घेण्यासारख्या इतर काही गोष्टी आहेत.

संदेश संदेश, msg_id, msg_seqno, पुष्टीकरणे, चुकीच्या दिशेने पिंग आणि इतर वैशिष्ट्यपूर्ण

तुम्हाला त्यांच्याबद्दल जाणून घेण्याची गरज का आहे? कारण ते उच्च स्तरावर "गळती" करतात आणि API सह कार्य करताना तुम्हाला त्यांची जाणीव असणे आवश्यक आहे. समजू की आम्हाला msg_key मध्ये स्वारस्य नाही; खालच्या स्तराने आमच्यासाठी सर्वकाही डिक्रिप्ट केले आहे. परंतु डिक्रिप्ट केलेल्या डेटामध्ये आमच्याकडे खालील फील्ड आहेत (डेटाची लांबी देखील आहे, त्यामुळे पॅडिंग कुठे आहे हे आम्हाला माहित आहे, परंतु ते महत्त्वाचे नाही):

  • मीठ - int64
  • सत्र_आयडी - int64
  • message_id — int64
  • seq_no - int32

आम्ही तुम्हाला आठवण करून देतो की संपूर्ण डीसीसाठी फक्त एक मीठ आहे. तिच्याबद्दल का माहित? फक्त विनंती आहे म्हणून नाही get_future_salts, जे तुम्हाला सांगते की कोणते अंतराल वैध असतील, परंतु तुमचे मीठ "सडलेले" असल्यास, संदेश (विनंती) गमावला जाईल. सर्व्हर, अर्थातच, जारी करून नवीन मीठ अहवाल देईल new_session_created - परंतु जुन्यासह तुम्हाला ते कसे तरी पुन्हा पाठवावे लागेल, उदाहरणार्थ. आणि ही समस्या ऍप्लिकेशन आर्किटेक्चरला प्रभावित करते.

सर्व्हरला अनेक कारणांमुळे सत्रे पूर्णपणे सोडण्याची आणि अशा प्रकारे प्रतिसाद देण्याची परवानगी आहे. वास्तविक, क्लायंटच्या बाजूने MTProto सत्र म्हणजे काय? या दोन संख्या आहेत session_id и seq_no या सत्रातील संदेश. बरं, आणि अंतर्निहित टीसीपी कनेक्शन, अर्थातच. समजा आमच्या क्लायंटला अजूनही बर्‍याच गोष्टी कशा करायच्या हे माहित नाही, तो डिस्कनेक्ट झाला आणि पुन्हा कनेक्ट झाला. हे त्वरीत घडल्यास - नवीन टीसीपी कनेक्शनमध्ये जुने सत्र चालू राहिले, वाढवा seq_no पुढील. यास बराच वेळ लागल्यास, सर्व्हर ते हटवू शकतो, कारण त्याच्या बाजूला ती देखील एक रांग आहे, जसे आम्हाला आढळले.

ते काय असावे seq_no? अरे, एक अवघड प्रश्न आहे. याचा अर्थ काय आहे हे प्रामाणिकपणे समजून घेण्याचा प्रयत्न करा:

सामग्री-संबंधित संदेश

स्पष्ट पोचपावती आवश्यक असलेला संदेश. यामध्ये सर्व वापरकर्ता आणि अनेक सेवा संदेश समाविष्ट आहेत, अक्षरशः कंटेनर आणि पावती वगळता.

संदेश क्रम क्रमांक (msg_seqno)

या संदेशापूर्वी प्रेषकाने तयार केलेल्या “सामग्री-संबंधित” संदेशांच्या दुप्पट संख्येइतका 32-बिट क्रमांक (ज्यांना पोचपावती आवश्यक आहे, आणि विशेषतः जे कंटेनर नाहीत) आणि नंतर वर्तमान संदेश असल्यास एकाने वाढवलेला. सामग्री-संबंधित संदेश. कंटेनर नेहमी त्याच्या संपूर्ण सामग्रीनंतर तयार केला जातो; म्हणून, त्याची अनुक्रम संख्या त्यात समाविष्ट असलेल्या संदेशांच्या अनुक्रम संख्यांपेक्षा मोठी किंवा समान आहे.

1 ने वाढलेली आणि नंतर दुसरी 2 ने वाढवलेली ही सर्कस कोणत्या प्रकारची आहे?.. मला शंका आहे की सुरुवातीला त्यांचा अर्थ "ACK साठी सर्वात कमी महत्त्वाचा भाग आहे, बाकीची संख्या आहे", परंतु परिणाम एकसारखा नाही - विशेषतः, ते बाहेर येते, पाठविले जाऊ शकते अनेक पुष्टीकरणे समान आहेत seq_no! कसे? बरं, उदाहरणार्थ, सर्व्हर आम्हाला काहीतरी पाठवतो, पाठवतो आणि आम्ही स्वतःच शांत राहतो, फक्त त्याच्या संदेशांच्या पावतीची पुष्टी करणार्‍या सेवा संदेशांसह प्रतिसाद देतो. या प्रकरणात, आमच्या आउटगोइंग पुष्टीकरणांमध्ये समान आउटगोइंग क्रमांक असेल. जर तुम्ही TCP शी परिचित असाल आणि तुम्हाला वाटले असेल की हे कसेतरी जंगली वाटते, परंतु ते फारसे जंगली वाटत नाही, कारण TCP मध्ये seq_no बदलत नाही, परंतु पुष्टीकरण जाते seq_no दुसऱ्या बाजूला, मी तुम्हाला अस्वस्थ करण्यासाठी घाई करीन. MTProto मध्ये पुष्टीकरण प्रदान केले आहे नाही वर seq_no, TCP प्रमाणे, परंतु द्वारे msg_id !

हे काय आहे msg_id, या फील्ड सर्वात महत्वाचे? नावाप्रमाणेच एक अद्वितीय संदेश अभिज्ञापक. हे 64-बिट क्रमांक म्हणून परिभाषित केले आहे, त्यातील सर्वात कमी बिट्समध्ये पुन्हा "सर्व्हर-नॉट-सर्व्हर" जादू आहे आणि उर्वरित युनिक्स टाइमस्टॅम्प आहे, ज्यामध्ये फ्रॅक्शनल भाग समाविष्ट आहे, 32 बिट्स डावीकडे हलवले आहेत. त्या. टाईमस्टॅम्प प्रति se (आणि वेळ खूप भिन्न असलेले संदेश सर्व्हरद्वारे नाकारले जातील). यावरून असे दिसून येते की सर्वसाधारणपणे हा एक अभिज्ञापक आहे जो क्लायंटसाठी जागतिक आहे. ते दिले - चला लक्षात ठेवा session_id - आम्हाला हमी आहे: कोणत्याही परिस्थितीत एका सत्रासाठी असलेला संदेश वेगळ्या सत्रात पाठवला जाऊ शकत नाही. म्हणजेच, हे आधीच आहे की बाहेर वळते तीन स्तर - सत्र, सत्र क्रमांक, संदेश आयडी. अशी गुंतागुंत का, हे गूढ फार मोठे आहे.

त्यामुळे msg_id साठी आवश्यक...

RPC: विनंत्या, प्रतिसाद, त्रुटी. पुष्टी.

तुमच्या लक्षात आले असेल की, आकृतीमध्ये कुठेही विशेष "आरपीसी विनंती करा" प्रकार किंवा कार्य नाही, तरीही उत्तरे आहेत. शेवटी, आमच्याकडे सामग्री-संबंधित संदेश आहेत! ते आहे, कोणत्याही संदेश एक विनंती असू शकते! किंवा नसावे. शेवटी, प्रत्येक आहे msg_id. पण उत्तरे आहेत:

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

हा कोणत्या संदेशाला प्रतिसाद आहे हे येथेच सूचित केले जाते. म्हणून, API च्या वरच्या स्तरावर, तुम्हाला तुमच्या विनंतीची संख्या किती होती हे लक्षात ठेवावे लागेल - मला वाटते की कार्य असिंक्रोनस आहे हे स्पष्ट करण्याची गरज नाही आणि एकाच वेळी अनेक विनंत्या प्रगतीपथावर असू शकतात, कोणती उत्तरे कोणत्याही क्रमाने दिली जाऊ शकतात? तत्त्वतः, यावरून आणि कामगार नसलेल्या त्रुटी संदेशांवरून, यामागील आर्किटेक्चर शोधले जाऊ शकते: सर्व्हर जो तुमच्याशी TCP कनेक्शन राखतो तो फ्रंट-एंड बॅलन्सर असतो, तो बॅकएंडला विनंत्या अग्रेषित करतो आणि त्याद्वारे परत गोळा करतो message_id. असे दिसते की येथे सर्वकाही स्पष्ट, तार्किक आणि चांगले आहे.

होय?.. आणि जर आपण याबद्दल विचार केला तर? शेवटी, RPC प्रतिसादाला स्वतःचे फील्ड देखील आहे msg_id! आम्हाला सर्व्हरवर ओरडण्याची गरज आहे का “तुम्ही माझ्या उत्तराचे उत्तर देत नाही!”? आणि हो, पुष्टीकरणांबद्दल काय होते? पृष्ठाबद्दल संदेशांबद्दल संदेश काय आहे ते सांगते

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

आणि ते प्रत्येक बाजूने केले पाहिजे. पण नेहमीच नाही! तुम्हाला RpcResult मिळाल्यास, ते स्वतःच पुष्टीकरण म्हणून काम करते. म्हणजेच, सर्व्हर तुमच्या विनंतीला MsgsAck सह प्रतिसाद देऊ शकतो - जसे की, “मला ते मिळाले आहे.” RpcResult लगेच प्रतिसाद देऊ शकतो. हे दोन्ही असू शकते.

आणि हो, तुम्हाला अजून उत्तर द्यावे लागेल! पुष्टी. अन्यथा, सर्व्हर ते अपरिहार्य मानेल आणि ते तुम्हाला परत पाठवेल. पुन्हा जोडणी झाल्यावरही. पण इथे अर्थातच कालबाह्यतेचा प्रश्न निर्माण होतो. त्यांना थोड्या वेळाने पाहू.

यादरम्यान, संभाव्य क्वेरी अंमलबजावणी त्रुटी पाहू.

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

अरे, कोणीतरी उद्गार काढेल, येथे अधिक मानवी स्वरूप आहे - एक ओळ आहे! तुमचा वेळ घ्या. येथे त्रुटींची यादी, पण अर्थातच पूर्ण नाही. त्यावरून आपल्याला कळते की कोड आहे असे काहीतरी HTTP त्रुटी (उत्तम, अर्थातच, प्रतिसादांच्या शब्दार्थांचा आदर केला जात नाही, काही ठिकाणी ते कोडमध्ये यादृच्छिकपणे वितरीत केले जातात), आणि ओळ अशी दिसते CAPITAL_LETTERS_AND_NUMBERS. उदाहरणार्थ, PHONE_NUMBER_OCCUPIED किंवा FILE_PART_Х_MISSING. बरं, म्हणजे, तुम्हाला अजूनही या ओळीची आवश्यकता असेल पार्स. उदाहरणार्थ FLOOD_WAIT_3600 याचा अर्थ असा की तुम्हाला एक तास थांबावे लागेल, आणि PHONE_MIGRATE_5, या उपसर्गासह टेलिफोन नंबर 5 व्या DC मध्ये नोंदणीकृत असणे आवश्यक आहे. आमच्याकडे एक प्रकारची भाषा आहे, बरोबर? आम्हाला स्ट्रिंगवरून युक्तिवादाची आवश्यकता नाही, नियमित लोक करतील, ठीक आहे.

पुन्हा, हे सेवा संदेश पृष्ठावर नाही, परंतु, या प्रकल्पात नेहमीप्रमाणे, माहिती आढळू शकते दुसर्‍या दस्तऐवजीकरण पृष्ठावर. किंवा संशय व्यक्त करणे. प्रथम, पहा, टायपिंग/लेयर उल्लंघन - RpcError मध्ये नेस्टेड केले जाऊ शकते RpcResult. बाहेर का नाही? आम्ही काय विचारात घेतले नाही?.. त्यानुसार, याची हमी कुठे आहे RpcError मध्ये एम्बेड केले जाऊ शकत नाही RpcResult, परंतु थेट किंवा दुसर्‍या प्रकारात नेस्टेड व्हा?.. आणि जर ते शक्य नसेल, तर ते वरच्या स्तरावर का नाही, म्हणजे ते गहाळ आहे req_msg_id ? ..

परंतु सेवा संदेशांबद्दल पुढे जाऊया. क्लायंट विचार करू शकतो की सर्व्हर बर्याच काळापासून विचार करत आहे आणि ही अद्भुत विनंती करेल:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

या प्रश्नाची तीन संभाव्य उत्तरे आहेत, पुन्हा पुष्टीकरण यंत्रणेला छेद देणारी; ते काय असावे हे समजून घेण्याचा प्रयत्न करणे (आणि कोणत्या प्रकारांची सामान्य यादी ज्यांना पुष्टीकरणाची आवश्यकता नाही) वाचकांसाठी गृहपाठ म्हणून सोडले जाते (टीप: मधील माहिती टेलीग्राम डेस्कटॉप स्त्रोत कोड पूर्ण नाही).

अंमली पदार्थांचे व्यसन: संदेश स्थिती

सर्वसाधारणपणे, TL, MTProto आणि Telegram मधील बर्‍याच ठिकाणी हट्टीपणाची भावना असते, परंतु सभ्यता, चातुर्य आणि इतर गोष्टींमुळे मऊ कौशल आम्ही नम्रपणे याबद्दल मौन पाळले आणि संवादांमधील अश्लीलता सेन्सॉर केली. तथापि, या ठिकाणीОबहुतेक पृष्ठ सुमारे आहे संदेशांबद्दल संदेश माझ्यासाठी देखील हे धक्कादायक आहे, जो बर्याच काळापासून नेटवर्क प्रोटोकॉलसह काम करत आहे आणि वेगवेगळ्या प्रमाणात कुटिलपणाच्या सायकली पाहिल्या आहेत.

हे निरुपद्रवीपणे, पुष्टीकरणांसह सुरू होते. पुढे ते आम्हाला सांगतात

bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;

बरं, MTProto सोबत काम सुरू करणार्‍या प्रत्येकाला त्यांच्याशी सामना करावा लागेल; "सुधारित - पुन्हा संकलित - लॉन्च" सायकलमध्ये, संख्या त्रुटी किंवा मीठ मिळणे जे संपादनादरम्यान खराब होऊ शकते ही एक सामान्य गोष्ट आहे. तथापि, येथे दोन मुद्दे आहेत:

  1. याचा अर्थ मूळ संदेश हरवला आहे. आम्हाला काही रांगा तयार कराव्या लागतील, आम्ही ते नंतर पाहू.
  2. या विचित्र त्रुटी क्रमांक काय आहेत? 16, 17, 18, 19, 20, 32, 33, 34, 35, 48, 64... इतर नंबर कुठे आहेत, टॉमी?

दस्तऐवजीकरण सांगते:

हेतू असा आहे की error_code मूल्ये गटबद्ध केली आहेत (error_code >> 4): उदाहरणार्थ, कोड 0x40 — 0x4f कंटेनर विघटनातील त्रुटींशी संबंधित आहेत.

परंतु, प्रथम, दुसर्या दिशेने एक शिफ्ट, आणि दुसरे म्हणजे, काही फरक पडत नाही, इतर कोड कुठे आहेत? लेखकाच्या डोक्यात?.. तथापि, या क्षुल्लक गोष्टी आहेत.

संदेश स्थिती आणि संदेशाच्या प्रतींबद्दल संदेशांमध्ये व्यसन सुरू होते:

  • संदेश स्थिती माहितीसाठी विनंती
    जर दोन्ही पक्षाला त्याच्या आउटगोइंग संदेशांच्या स्थितीबद्दल काही काळ माहिती प्राप्त झाली नसेल, तर तो इतर पक्षाकडून स्पष्टपणे विनंती करू शकतो:
    msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;
  • संदेशांच्या स्थितीबाबत माहितीपूर्ण संदेश
    msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
    येथे, info एक स्ट्रिंग आहे ज्यामध्ये येणार्‍या msg_ids सूचीमधील प्रत्येक संदेशासाठी संदेश स्थितीचा एक बाइट असतो:

    • 1 = संदेशाबद्दल काहीही माहिती नाही (msg_id खूप कमी आहे, दुसरा पक्ष कदाचित विसरला असेल)
    • 2 = संदेश प्राप्त झाला नाही (msg_id संचयित अभिज्ञापकांच्या श्रेणीमध्ये येतो; तथापि, दुसर्‍या पक्षाला नक्कीच तसा संदेश प्राप्त झालेला नाही)
    • 3 = संदेश प्राप्त झाला नाही (msg_id खूप जास्त आहे; तथापि, दुसर्‍या पक्षाला तो अद्याप प्राप्त झालेला नाही)
    • 4 = संदेश प्राप्त झाला (लक्षात घ्या की हा प्रतिसाद त्याच वेळी पावतीची पावती देखील आहे)
    • +8 = संदेश आधीच मान्य आहे
    • +16 = संदेशास पोचपावती आवश्यक नाही
    • +32 = संदेशामध्ये समाविष्ट असलेली RPC क्वेरी प्रक्रिया केली जात आहे किंवा प्रक्रिया आधीच पूर्ण झाली आहे
    • +64 = आधीच तयार केलेल्या संदेशाला सामग्री-संबंधित प्रतिसाद
    • +128 = इतर पक्षाला माहिती आहे की संदेश आधीच प्राप्त झाला आहे
      या प्रतिसादाला पोचपावती आवश्यक नाही. ही संबंधित msgs_state_req ची आणि स्वतःची पावती आहे.
      लक्षात ठेवा की जर अचानक असे दिसून आले की दुसर्‍या पक्षाकडे असा संदेश नाही जो त्याला पाठविला गेला आहे असे दिसते, तर तो संदेश पुन्हा पाठविला जाऊ शकतो. जरी दुसर्‍या पक्षाला संदेशाच्या दोन प्रती एकाच वेळी मिळाल्या असल्या तरी, डुप्लिकेटकडे दुर्लक्ष केले जाईल. (जर बराच वेळ निघून गेला असेल आणि मूळ msg_id यापुढे वैध नसेल, तर संदेश msg_copy मध्ये गुंडाळला जावा).
  • संदेशांच्या स्थितीचे स्वैच्छिक संप्रेषण
    कोणताही पक्ष स्वेच्छेने दुसर्‍या पक्षाला दुसर्‍या पक्षाद्वारे प्रसारित केलेल्या संदेशांच्या स्थितीबद्दल सूचित करू शकतो.
    msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo
  • एका संदेशाच्या स्थितीचा विस्तारित स्वैच्छिक संप्रेषण
    ...
    msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
    msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
  • संदेश पुन्हा पाठवण्याची स्पष्ट विनंती
    msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;
    रिमोट पार्टी विनंती केलेले संदेश पुन्हा पाठवून त्वरित प्रतिसाद देते […]
  • उत्तरे पुन्हा पाठवण्याची स्पष्ट विनंती
    msg_resend_ans_req#8610baeb msg_ids:Vector long = MsgResendReq;
    रिमोट पार्टी पुन्हा पाठवून लगेच प्रतिसाद देते उत्तरे विनंती केलेल्या संदेशांना […]
  • संदेशाच्या प्रती
    काही परिस्थितींमध्ये, यापुढे वैध नसलेला msg_id असलेला जुना संदेश पुन्हा पाठवण्याची गरज आहे. नंतर, ते कॉपी कंटेनरमध्ये गुंडाळले जाते:
    msg_copy#e06046b2 orig_message:Message = MessageCopy;
    एकदा प्राप्त झाल्यानंतर, रॅपर नसल्याप्रमाणे संदेशावर प्रक्रिया केली जाते. तथापि, जर orig_message.msg_id हा संदेश प्राप्त झाल्याचे निश्चितपणे ज्ञात असेल, तर नवीन संदेशावर प्रक्रिया केली जात नाही (त्याचवेळी, तो आणि orig_message.msg_id ची कबुली दिली जाते). orig_message.msg_id चे मूल्य कंटेनरच्या msg_id पेक्षा कमी असणे आवश्यक आहे.

चला तर काय गप्प बसू msgs_state_info पुन्हा अपूर्ण टीएलचे कान चिकटत आहेत (आम्हाला बाइट्सचा वेक्टर हवा होता आणि खालच्या दोन बिट्समध्ये एनम होता आणि वरच्या दोन बिट्समध्ये ध्वज होते). मुद्दा वेगळा आहे. हे सर्व व्यवहारात का आहे हे कोणाला समजते का? वास्तविक क्लायंटमध्ये आवश्यक?.. अडचणीसह, परंतु एखादी व्यक्ती डीबगिंगमध्ये आणि परस्परसंवादी मोडमध्ये गुंतलेली असल्यास काही फायद्याची कल्पना करू शकते - सर्व्हरला काय आणि कसे विचारा. परंतु येथे विनंत्या वर्णन केल्या आहेत फेरी प्रवास.

हे खालीलप्रमाणे आहे की प्रत्येक पक्षाने केवळ एन्क्रिप्ट आणि संदेश पाठवू नये, तर अज्ञात कालावधीसाठी स्वतःबद्दलचा, त्यांच्या प्रतिसादांबद्दलचा डेटा देखील संग्रहित केला पाहिजे. दस्तऐवजीकरण या वैशिष्ट्यांच्या वेळेचे किंवा व्यावहारिक लागूतेचे वर्णन करत नाही. नाही मार्ग. सर्वात आश्चर्यकारक गोष्ट म्हणजे ते अधिकृत क्लायंटच्या कोडमध्ये वापरले जातात! वरवर पाहता त्यांना असे काहीतरी सांगण्यात आले होते जे सार्वजनिक दस्तऐवजात समाविष्ट नव्हते. कोडवरून समजून घ्या का, आता TL च्या बाबतीत तितके सोपे नाही - तो (तुलनेने) तार्किकदृष्ट्या वेगळा भाग नाही, परंतु अनुप्रयोग आर्किटेक्चरशी जोडलेला भाग आहे, म्हणजे. अनुप्रयोग कोड समजून घेण्यासाठी अधिक वेळ लागेल.

पिंग्ज आणि वेळा. रांगा.

सर्व गोष्टींवरून, जर आम्हाला सर्व्हर आर्किटेक्चर (बॅकएंडवर विनंत्यांचे वितरण) बद्दलचे अंदाज आठवत असतील तर, एक ऐवजी दुःखद गोष्ट आहे - TCP मधील सर्व वितरण हमी असूनही (एकतर डेटा वितरित केला जाईल किंवा तुम्हाला ब्रेकबद्दल सूचित केले जाईल, परंतु समस्या येईपर्यंत डेटा वितरित केला जाईल), ते पुष्टीकरण एमटीप्रोटोमध्येच - कोणतीही हमी नाही. सर्व्हर तुमचा संदेश सहजपणे गमावू किंवा फेकून देऊ शकतो आणि त्याबद्दल काहीही केले जाऊ शकत नाही, फक्त विविध प्रकारचे क्रॅच वापरा.

आणि सर्व प्रथम - संदेश रांगा. बरं, एका गोष्टीसह सर्वकाही अगदी सुरुवातीपासूनच स्पष्ट होते - एक अपुष्ट संदेश संग्रहित केला पाहिजे आणि राग आला पाहिजे. आणि किती वेळानंतर? आणि विदूषक त्याला ओळखतो. कदाचित त्या व्यसनाधीन सेवा संदेशांमुळे ही समस्या क्रॅचने सोडवली जाईल, म्हणा, टेलिग्राम डेस्कटॉपमध्ये त्यांच्याशी संबंधित सुमारे 4 रांगा आहेत (कदाचित अधिक, आधीच नमूद केल्याप्रमाणे, यासाठी तुम्हाला त्याच्या कोड आणि आर्किटेक्चरचा अधिक गंभीरपणे शोध घेणे आवश्यक आहे; त्याच वेळी वेळ, आम्हाला माहित आहे की ते नमुना म्हणून घेतले जाऊ शकत नाही; एमटीप्रोटो योजनेतील काही विशिष्ट प्रकार त्यात वापरले जात नाहीत).

असे का होत आहे? कदाचित, सर्व्हर प्रोग्रामर क्लस्टरमध्ये विश्वासार्हता सुनिश्चित करू शकले नाहीत किंवा समोरच्या बॅलन्सरवर बफरिंग देखील करू शकले नाहीत आणि ही समस्या क्लायंटकडे हस्तांतरित केली. निराशेतून, Vasily ने TCP कडील अल्गोरिदम वापरून फक्त दोन रांगांसह पर्यायी पर्याय अंमलात आणण्याचा प्रयत्न केला - सर्व्हरवर RTT मोजणे आणि पुष्टी न झालेल्या विनंत्यांच्या संख्येनुसार "विंडो" (संदेशांमध्ये) आकार समायोजित करणे. म्हणजेच, सर्व्हरच्या लोडचे मूल्यांकन करण्यासाठी इतके उग्र ह्युरिस्टिक म्हणजे आमच्या किती विनंत्या तो एकाच वेळी चघळू शकतो आणि गमावू शकत नाही.

बरं, म्हणजे, तुला समजलं, बरोबर? तुम्हाला TCP वर चालणाऱ्या प्रोटोकॉलच्या वर पुन्हा TCP लागू करायचे असल्यास, हे अतिशय खराब डिझाइन केलेले प्रोटोकॉल सूचित करते.

अरे हो, तुम्हाला एकापेक्षा जास्त रांगेची गरज का आहे आणि तरीही उच्च-स्तरीय API सह काम करणार्‍या व्यक्तीसाठी याचा अर्थ काय आहे? बघा, तुम्ही विनंती करता, ती क्रमवारी लावता, पण अनेकदा तुम्ही ती लगेच पाठवू शकत नाही. का? कारण उत्तर असेल msg_id, जे तात्पुरते आहेаमी एक लेबल आहे, ज्याची असाइनमेंट शक्य तितक्या उशीरापर्यंत पुढे ढकलणे चांगले आहे - जर सर्व्हरने आमच्या आणि त्याच्या दरम्यानच्या वेळेच्या जुळणीमुळे ते नाकारले तर (अर्थातच, आम्ही सध्याच्या काळापासून आमचा वेळ बदलू शकतो. सर्व्हरच्या प्रतिसादांमधून गणना केलेला डेल्टा जोडून सर्व्हरवर - अधिकृत क्लायंट हे करतात, परंतु बफरिंगमुळे ते कच्चे आणि चुकीचे आहे). म्हणून, जेव्हा तुम्ही लायब्ररीतून स्थानिक फंक्शन कॉलसह विनंती करता, तेव्हा संदेश खालील टप्प्यांमधून जातो:

  1. हे एका रांगेत आहे आणि एन्क्रिप्शनची वाट पाहत आहे.
  2. नियुक्त केले msg_id आणि संदेश दुसर्‍या रांगेत गेला - शक्य अग्रेषित; सॉकेटवर पाठवा.
  3. अ) सर्व्हरने MsgsAck ला प्रतिसाद दिला - संदेश वितरित झाला, आम्ही तो “इतर रांगेतून” हटवतो.
    ब) किंवा त्याउलट, त्याला काहीतरी आवडले नाही, त्याने उत्तर दिले badmsg - “दुसऱ्या रांगेतून” पुन्हा पाठवा
    c) काहीही माहित नाही, संदेश दुसर्‍या रांगेतून पाठविला जाणे आवश्यक आहे - परंतु नक्की केव्हा ते माहित नाही.
  4. शेवटी सर्व्हरने प्रतिसाद दिला RpcResult - वास्तविक प्रतिसाद (किंवा त्रुटी) - केवळ वितरितच नाही तर प्रक्रिया देखील केली जाते.

कदाचित, कंटेनरचा वापर अंशतः समस्या सोडवू शकतो. हे असे होते जेव्हा संदेशांचा एक समूह एकामध्ये पॅक केला जातो आणि सर्व्हरने त्या सर्वांना एकाच वेळी पुष्टीकरणासह प्रतिसाद दिला. msg_id. पण तो या पॅकला देखील नाकारेल, जर काही चूक झाली असेल तर, संपूर्णपणे.

आणि या टप्प्यावर गैर-तांत्रिक विचार लागू होतात. अनुभवावरून, आम्ही अनेक क्रॅच पाहिल्या आहेत आणि त्याव्यतिरिक्त, आम्ही आता वाईट सल्ले आणि आर्किटेक्चरची आणखी उदाहरणे पाहू - अशा परिस्थितीत, विश्वास ठेवणे आणि असे निर्णय घेणे योग्य आहे का? प्रश्न वक्तृत्वाचा आहे (अर्थात नाही).

आम्ही कशाबद्दल बोलत आहोत? जर "संदेशांबद्दल औषध संदेश" या विषयावर तुम्ही तरीही "तुम्ही मूर्ख आहात, तुम्हाला आमची चमकदार योजना समजली नाही!" सारख्या आक्षेपांसह अंदाज लावू शकता. (म्हणून प्रथम दस्तऐवज लिहा, सामान्य लोकांप्रमाणे, तर्क आणि पॅकेट एक्सचेंजच्या उदाहरणांसह, नंतर आपण बोलू), नंतर वेळ/टाइमआउट्स हा पूर्णपणे व्यावहारिक आणि विशिष्ट प्रश्न आहे, येथे सर्व काही बर्याच काळापासून ज्ञात आहे. दस्तऐवजीकरण आम्हाला कालबाह्यतेबद्दल काय सांगते?

सर्व्हर सहसा RPC प्रतिसाद वापरून क्लायंटकडून संदेशाची पावती (सामान्यत: RPC क्वेरी) स्वीकारतो. प्रतिसाद येण्यास बराच वेळ असल्यास, सर्व्हर प्रथम पावती पोचपावती पाठवू शकतो, आणि काहीसे नंतर, RPC प्रतिसाद स्वतः पाठवू शकतो.

क्लायंट साधारणपणे सर्व्हरवरून संदेश मिळाल्याची पावती (सामान्यतः, RPC प्रतिसाद) पुढील RPC क्वेरीमध्ये पोचपावती जोडून कबूल करतो जर तो खूप उशीरा प्रसारित केला गेला नाही (जर तो व्युत्पन्न झाला असेल तर, पावतीनंतर 60-120 सेकंदांनंतर म्हणा. सर्व्हरवरील संदेशाचे). तथापि, जर दीर्घ कालावधीसाठी सर्व्हरवर संदेश पाठविण्याचे कोणतेही कारण नसल्यास किंवा सर्व्हरकडून मोठ्या संख्येने अपात्र संदेश असल्यास (म्हणा, 16 पेक्षा जास्त), क्लायंट स्वतंत्रपणे पोचपावती पाठवतो.

... मी भाषांतर करतो: आपल्याला त्याची किती आणि कशी गरज आहे हे आपल्याला माहित नाही, म्हणून आपण असे मानूया की ते असे होऊ द्या.

आणि पिंग्स बद्दल:

पिंग संदेश (पिंग/पोंग)

ping#7abe77ec ping_id:long = Pong;

प्रतिसाद सामान्यतः त्याच कनेक्शनवर परत केला जातो:

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

या संदेशांना पोचपावती आवश्यक नाहीत. एक पिंग फक्त पिंगला प्रतिसाद म्हणून प्रसारित केला जातो तर पिंग दोन्ही बाजूंनी सुरू केला जाऊ शकतो.

डिफर्ड कनेक्शन क्लोजर + PING

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

पिंग सारखे कार्य करते. याव्यतिरिक्त, हे प्राप्त झाल्यानंतर, सर्व्हर एक टायमर सुरू करतो जो वर्तमान कनेक्शन disconnect_delay सेकंदांनंतर बंद करेल जोपर्यंत त्याला समान प्रकारचा नवीन संदेश मिळत नाही जो आपोआप मागील सर्व टायमर रीसेट करतो. क्लायंटने दर 60 सेकंदात एकदा हे पिंग पाठवल्यास, उदाहरणार्थ, तो 75 सेकंदांच्या बरोबरीने disconnect_delay सेट करू शकतो.

तू वेडा आहेस का?! 60 सेकंदात, ट्रेन स्टेशनमध्ये प्रवेश करेल, उतरेल आणि प्रवाशांना उचलेल आणि पुन्हा बोगद्यातील संपर्क तुटेल. 120 सेकंदांमध्‍ये, तुम्‍ही ते ऐकत असताना, ते दुसर्‍यावर येईल आणि बहुधा कनेक्‍शन खंडित होईल. बरं, पाय कुठून येत आहेत हे स्पष्ट आहे - “मी एक रिंगिंग ऐकली आहे, पण ते कुठे आहे ते माहित नाही”, तेथे Nagl चे अल्गोरिदम आणि TCP_NODELAY पर्याय आहे, जो परस्परसंवादी कार्यासाठी आहे. परंतु, माफ करा, त्याचे डीफॉल्ट मूल्य - 200 धरून ठेवा मिलीसेकंद जर तुम्हाला खरोखरच असे काहीतरी चित्रण करायचे असेल आणि संभाव्य दोन पॅकेट्सवर सेव्ह करायचे असेल, तर ते 5 सेकंदांसाठी बंद करा, किंवा "वापरकर्ता टाइप करत आहे..." मेसेज टाईमआउट आता आहे. पण आणखी नाही.

आणि शेवटी, पिंग्स. म्हणजेच, TCP कनेक्शनची सजीवता तपासत आहे. हे मजेदार आहे, परंतु सुमारे 10 वर्षांपूर्वी मी आमच्या फॅकल्टीच्या डॉर्मच्या मेसेंजरबद्दल एक गंभीर मजकूर लिहिला होता - तेथील लेखकांनी क्लायंटकडून सर्व्हरला पिंग केले, उलट नाही. पण तृतीय वर्षाचे विद्यार्थी एक गोष्ट आहेत आणि आंतरराष्ट्रीय कार्यालय दुसरी गोष्ट आहे, बरोबर?..

प्रथम, एक लहान शैक्षणिक कार्यक्रम. टीसीपी कनेक्शन, पॅकेट एक्सचेंजच्या अनुपस्थितीत, आठवडे जगू शकते. हेतूनुसार हे चांगले आणि वाईट दोन्ही आहे. जर तुमच्याकडे सर्व्हरवर एसएसएच कनेक्शन उघडले असेल तर ते चांगले आहे, तुम्ही संगणकावरून उठले, राउटर रीबूट केले, तुमच्या जागी परत आला - या सर्व्हरद्वारे सत्र फाटलेले नाही (तुम्ही काहीही टाइप केले नाही, कोणतेही पॅकेट नव्हते) , ते सोयीचे आहे. सर्व्हरवर हजारो क्लायंट असल्यास ते वाईट आहे, प्रत्येक संसाधने घेत आहेत (हॅलो, पोस्टग्रेस!), आणि क्लायंटच्या होस्टने खूप पूर्वी रीबूट केले असेल - परंतु आम्हाला त्याबद्दल माहिती नाही.

चॅट/आयएम प्रणाली एका अतिरिक्त कारणासाठी दुसऱ्या प्रकरणात येतात - ऑनलाइन स्थिती. जर वापरकर्ता "पडला", तर तुम्हाला त्याच्या संवादकांना याबद्दल माहिती देणे आवश्यक आहे. अन्यथा, जब्बरच्या निर्मात्यांनी केलेली चूक (आणि 20 वर्षांपासून दुरुस्त केली) तुमची समाप्ती होईल - वापरकर्त्याने डिस्कनेक्ट केले आहे, परंतु तो ऑनलाइन आहे असा विश्वास ठेवून त्यांनी त्याला संदेश लिहिणे सुरू ठेवले आहे (जे यांमध्ये पूर्णपणे गमावले होते. डिस्कनेक्ट शोधण्यापूर्वी काही मिनिटे). नाही, TCP_KEEPALIVE पर्याय, ज्यांना TCP टाइमर कसे काम करतात हे यादृच्छिकपणे (दहापट सेकंदांसारखे वाइल्ड व्हॅल्यू सेट करून) कसे कार्य करतात हे समजत नसलेले अनेक लोक येथे मदत करणार नाहीत - तुम्हाला याची खात्री करणे आवश्यक आहे की केवळ OS कर्नलच नाही. वापरकर्त्याचे मशीन जिवंत आहे, परंतु सामान्यपणे कार्य करत आहे, प्रतिसाद देण्यास सक्षम आहे, आणि अनुप्रयोग स्वतःच (तुम्हाला वाटते की ते गोठवू शकत नाही? उबंटू 18.04 वरील टेलिग्राम डेस्कटॉप माझ्यासाठी एकापेक्षा जास्त वेळा गोठले).

म्हणूनच तुम्हाला पिंग करावे लागेल सर्व्हर क्लायंट, आणि उलट नाही - जर क्लायंटने असे केले तर, कनेक्शन तुटल्यास, पिंग वितरित केले जाणार नाही, ध्येय साध्य होणार नाही.

आम्ही टेलीग्रामवर काय पाहतो? अगदी उलट आहे! बरं, ते आहे. औपचारिकपणे, अर्थातच, दोन्ही बाजू एकमेकांना पिंग करू शकतात. सराव मध्ये, क्लायंट क्रॅच वापरतात ping_delay_disconnect, जे सर्व्हरवर टाइमर सेट करते. बरं, माफ करा, पिंग न करता तिथे किती काळ राहायचं हे क्लायंटवर अवलंबून नाही. सर्व्हर, त्याच्या लोडवर आधारित, चांगले माहीत आहे. पण, अर्थातच, जर तुमची संसाधनांवर हरकत नसेल, तर तुम्ही तुमचा स्वतःचा दुष्ट पिनोचिओ व्हाल आणि एक क्रॅच करेल ...

त्याची रचना कशी असावी?

माझा विश्वास आहे की वरील तथ्ये स्पष्टपणे सूचित करतात की टेलीग्राम/व्हीकॉन्टाक्टे कार्यसंघ संगणक नेटवर्कच्या वाहतूक (आणि खालच्या) स्तरावर आणि संबंधित बाबींमध्ये त्यांची कमी पात्रता या क्षेत्रात फार सक्षम नाही.

ते इतके क्लिष्ट का झाले आणि टेलीग्राम आर्किटेक्ट कसे आक्षेप घेण्याचा प्रयत्न करू शकतात? त्यांनी टीसीपी कनेक्शन खंडित होणारे सत्र बनवण्याचा प्रयत्न केला, म्हणजे जे आता वितरित केले गेले नाही ते आम्ही नंतर वितरित करू. त्यांनी कदाचित यूडीपी वाहतूक करण्याचा प्रयत्न देखील केला असेल, परंतु त्यांना अडचणी आल्या आणि ते सोडून दिले (म्हणूनच दस्तऐवजीकरण रिक्त आहे - यात बढाई मारण्यासारखे काहीही नव्हते). परंतु सामान्यत: नेटवर्क आणि विशिष्ट TCP कसे कार्य करतात, आपण त्यावर कोठे विसंबून राहू शकता, आणि आपण ते स्वतः कुठे करावे (आणि कसे) हे समजून न घेतल्याने आणि क्रिप्टोग्राफीसह "दोन पक्षी" सह एकत्रित करण्याचा प्रयत्न. एक दगड", हा परिणाम आहे.

ते कसे आवश्यक होते? त्या वस्तुस्थितीवर आधारित msg_id रीप्ले अटॅक टाळण्यासाठी क्रिप्टोग्राफिक दृष्टिकोनातून आवश्यक असलेला टाइमस्टॅम्प आहे, त्याला एक अद्वितीय ओळखकर्ता फंक्शन जोडणे चूक आहे. म्हणून, वर्तमान आर्किटेक्चरमध्ये मूलभूतपणे बदल न करता (जेव्हा अपडेट्स प्रवाह व्युत्पन्न केला जातो, पोस्टच्या या मालिकेच्या दुसर्‍या भागासाठी हा एक उच्च-स्तरीय API विषय आहे), एखाद्याला हे करणे आवश्यक आहे:

  1. क्लायंटला TCP कनेक्शन धारण करणारा सर्व्हर जबाबदारी घेतो - जर त्याने सॉकेटमधून वाचले असेल, तर कृपया कबूल करा, प्रक्रिया करा किंवा त्रुटी परत करा, कोणतेही नुकसान होणार नाही. मग पुष्टीकरण हा आयडीचा वेक्टर नसून फक्त “शेवटचा प्राप्त केलेला seq_no” आहे - फक्त एक संख्या, TCP प्रमाणे (दोन संख्या - तुमचा seq आणि पुष्टी केलेला एक). आम्ही नेहमी सत्रात असतो, नाही का?
  2. रीप्ले हल्ल्यांना प्रतिबंध करण्यासाठी टाइमस्टॅम्प एक स्वतंत्र फील्ड बनते, एक ला नॉन्स. हे तपासले जाते, परंतु इतर कशावरही परिणाम होत नाही. पुरेसे आणि uint32 - जर आमचे मीठ दर अर्ध्या दिवसात कमीत कमी बदलत असेल तर, आम्ही वर्तमान वेळेच्या पूर्णांक भागाच्या कमी-ऑर्डर बिट्ससाठी 16 बिट वाटप करू शकतो, उर्वरित - सेकंदाच्या अंशात्मक भागासाठी (आताप्रमाणे).
  3. काढले msg_id अजिबात - बॅकएंडवरील विनंत्या वेगळे करण्याच्या दृष्टिकोनातून, प्रथम, क्लायंट आयडी आहे आणि दुसरे म्हणजे, सत्र आयडी, त्यांना एकत्र करा. त्यानुसार, विनंती ओळखकर्ता म्हणून फक्त एक गोष्ट पुरेशी आहे seq_no.

हा देखील सर्वात यशस्वी पर्याय नाही; संपूर्ण यादृच्छिक एक अभिज्ञापक म्हणून काम करू शकते - तसे, संदेश पाठवताना हे उच्च-स्तरीय API मध्ये आधीच केले गेले आहे. आर्किटेक्चरला सापेक्ष ते निरपेक्ष रीमेक करणे चांगले होईल, परंतु हा विषय दुसर्‍या भागासाठी आहे, या पोस्टचा नाही.

API?

ता-दाम! त्यामुळे, वेदना आणि क्रॅचने भरलेल्या मार्गातून संघर्ष केल्यावर, आम्ही शेवटी सर्व्हरला कोणत्याही विनंत्या पाठवू शकलो आणि त्यांना कोणतीही उत्तरे प्राप्त करू शकलो, तसेच सर्व्हरकडून अद्यतने प्राप्त करू शकलो (विनंतीला प्रतिसाद म्हणून नाही, परंतु ते स्वतःच आम्हाला पाठवते, जसे की पुश, जर कोणी तसे स्पष्ट असेल तर).

लक्ष द्या, आता लेखात पर्लचे एकमेव उदाहरण असेल! (ज्यांना वाक्यरचना माहित नाही त्यांच्यासाठी, धन्याचा पहिला युक्तिवाद ऑब्जेक्टची डेटा संरचना आहे, दुसरा त्याचा वर्ग आहे):

2019.10.24 12:00:51 $1 = {
'cb' => 'TeleUpd::__ANON__',
'out' => bless( {
'filter' => bless( {}, 'Telegram::ChannelMessagesFilterEmpty' ),
'channel' => bless( {
'access_hash' => '-6698103710539760874',
'channel_id' => '1380524958'
}, 'Telegram::InputPeerChannel' ),
'pts' => '158503',
'flags' => 0,
'limit' => 0
}, 'Telegram::Updates::GetChannelDifference' ),
'req_id' => '6751291954012037292'
};
2019.10.24 12:00:51 $1 = {
'in' => bless( {
'req_msg_id' => '6751291954012037292',
'result' => bless( {
'pts' => 158508,
'flags' => 3,
'final' => 1,
'new_messages' => [],
'users' => [],
'chats' => [
bless( {
'title' => 'Хулиномика',
'username' => 'hoolinomics',
'flags' => 8288,
'id' => 1380524958,
'access_hash' => '-6698103710539760874',
'broadcast' => 1,
'version' => 0,
'photo' => bless( {
'photo_small' => bless( {
'volume_id' => 246933270,
'file_reference' => '
'secret' => '1854156056801727328',
'local_id' => 228648,
'dc_id' => 2
}, 'Telegram::FileLocation' ),
'photo_big' => bless( {
'dc_id' => 2,
'local_id' => 228650,
'file_reference' => '
'secret' => '1275570353387113110',
'volume_id' => 246933270
}, 'Telegram::FileLocation' )
}, 'Telegram::ChatPhoto' ),
'date' => 1531221081
}, 'Telegram::Channel' )
],
'timeout' => 300,
'other_updates' => [
bless( {
'pts_count' => 0,
'message' => bless( {
'post' => 1,
'id' => 852,
'flags' => 50368,
'views' => 8013,
'entities' => [
bless( {
'length' => 20,
'offset' => 0
}, 'Telegram::MessageEntityBold' ),
bless( {
'length' => 18,
'offset' => 480,
'url' => 'https://alexeymarkov.livejournal.com/[url_вырезан].html'
}, 'Telegram::MessageEntityTextUrl' )
],
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'text' => '???? 165',
'data' => 'send_reaction_0'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'data' => 'send_reaction_1',
'text' => '???? 9'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'message' => 'А вот и новая книга! 
// [текст сообщения вырезан чтоб не нарушать правил Хабра о рекламе]
напечатаю.',
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'date' => 1571724559,
'edit_date' => 1571907562
}, 'Telegram::Message' ),
'pts' => 158508
}, 'Telegram::UpdateEditChannelMessage' ),
bless( {
'pts' => 158508,
'message' => bless( {
'edit_date' => 1571907589,
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'date' => 1571807301,
'message' => 'Почему Вы считаете Facebook плохой компанией? Можете прокомментировать? По-моему, это шикарная компания. Без долгов, с хорошей прибылью, а если решат дивы платить, то и еще могут нехило подорожать.
Для меня ответ совершенно очевиден: потому что Facebook делает ужасный по качеству продукт. Да, у него монопольное положение и да, им пользуется огромное количество людей. Но мир не стоит на месте. Когда-то владельцам Нокии было смешно от первого Айфона. Они думали, что лучше Нокии ничего быть не может и она навсегда останется самым удобным, красивым и твёрдым телефоном - и доля рынка это красноречиво демонстрировала. Теперь им не смешно.
Конечно, рептилоиды сопротивляются напору молодых гениев: так Цукербергом был пожран Whatsapp, потом Instagram. Но всё им не пожрать, Паша Дуров не продаётся!
Так будет и с Фейсбуком. Нельзя всё время делать говно. Кто-то когда-то сделает хороший продукт, куда всё и уйдут.
#соцсети #facebook #акции #рептилоиды',
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'data' => 'send_reaction_0',
'text' => '???? 452'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'text' => '???? 21',
'data' => 'send_reaction_1'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'entities' => [
bless( {
'length' => 199,
'offset' => 0
}, 'Telegram::MessageEntityBold' ),
bless( {
'length' => 8,
'offset' => 919
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'offset' => 928,
'length' => 9
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'length' => 6,
'offset' => 938
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'length' => 11,
'offset' => 945
}, 'Telegram::MessageEntityHashtag' )
],
'views' => 6964,
'flags' => 50368,
'id' => 854,
'post' => 1
}, 'Telegram::Message' ),
'pts_count' => 0
}, 'Telegram::UpdateEditChannelMessage' ),
bless( {
'message' => bless( {
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'data' => 'send_reaction_0',
'text' => '???? 213'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'data' => 'send_reaction_1',
'text' => '???? 8'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'views' => 2940,
'entities' => [
bless( {
'length' => 609,
'offset' => 348
}, 'Telegram::MessageEntityItalic' )
],
'flags' => 50368,
'post' => 1,
'id' => 857,
'edit_date' => 1571907636,
'date' => 1571902479,
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'message' => 'Пост про 1С вызвал бурную полемику. Человек 10 (видимо, 1с-программистов) единодушно написали:
// [текст сообщения вырезан чтоб не нарушать правил Хабра о рекламе]
Я бы добавил, что блестящая у 1С дистрибуция, а маркетинг... ну, такое.'
}, 'Telegram::Message' ),
'pts_count' => 0,
'pts' => 158508
}, 'Telegram::UpdateEditChannelMessage' ),
bless( {
'pts' => 158508,
'pts_count' => 0,
'message' => bless( {
'message' => 'Здравствуйте, расскажите, пожалуйста, чем вредит экономике 1С?
// [текст сообщения вырезан чтоб не нарушать правил Хабра о рекламе]
#софт #it #экономика',
'edit_date' => 1571907650,
'date' => 1571893707,
'to_id' => bless( {
'channel_id' => 1380524958
}, 'Telegram::PeerChannel' ),
'flags' => 50368,
'post' => 1,
'id' => 856,
'reply_markup' => bless( {
'rows' => [
bless( {
'buttons' => [
bless( {
'data' => 'send_reaction_0',
'text' => '???? 360'
}, 'Telegram::KeyboardButtonCallback' ),
bless( {
'data' => 'send_reaction_1',
'text' => '???? 32'
}, 'Telegram::KeyboardButtonCallback' )
]
}, 'Telegram::KeyboardButtonRow' )
]
}, 'Telegram::ReplyInlineMarkup' ),
'views' => 4416,
'entities' => [
bless( {
'offset' => 0,
'length' => 64
}, 'Telegram::MessageEntityBold' ),
bless( {
'offset' => 1551,
'length' => 5
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'length' => 3,
'offset' => 1557
}, 'Telegram::MessageEntityHashtag' ),
bless( {
'offset' => 1561,
'length' => 10
}, 'Telegram::MessageEntityHashtag' )
]
}, 'Telegram::Message' )
}, 'Telegram::UpdateEditChannelMessage' )
]
}, 'Telegram::Updates::ChannelDifference' )
}, 'MTProto::RpcResult' )
};
2019.10.24 12:00:51 $1 = {
'in' => bless( {
'update' => bless( {
'user_id' => 2507460,
'status' => bless( {
'was_online' => 1571907651
}, 'Telegram::UserStatusOffline' )
}, 'Telegram::UpdateUserStatus' ),
'date' => 1571907650
}, 'Telegram::UpdateShort' )
};
2019.10.24 12:05:46 $1 = {
'in' => bless( {
'chats' => [],
'date' => 1571907946,
'seq' => 0,
'updates' => [
bless( {
'max_id' => 141719,
'channel_id' => 1295963795
}, 'Telegram::UpdateReadChannelInbox' )
],
'users' => []
}, 'Telegram::Updates' )
};
2019.10.24 13:01:23 $1 = {
'in' => bless( {
'server_salt' => '4914425622822907323',
'unique_id' => '5297282355827493819',
'first_msg_id' => '6751307555044380692'
}, 'MTProto::NewSessionCreated' )
};
2019.10.24 13:24:21 $1 = {
'in' => bless( {
'chats' => [
bless( {
'username' => 'freebsd_ru',
'version' => 0,
'flags' => 5440,
'title' => 'freebsd_ru',
'min' => 1,
'photo' => bless( {
'photo_small' => bless( {
'local_id' => 328733,
'volume_id' => 235140688,
'dc_id' => 2,
'file_reference' => '
'secret' => '4426006807282303416'
}, 'Telegram::FileLocation' ),
'photo_big' => bless( {
'dc_id' => 2,
'file_reference' => '
'volume_id' => 235140688,
'local_id' => 328735,
'secret' => '71251192991540083'
}, 'Telegram::FileLocation' )
}, 'Telegram::ChatPhoto' ),
'date' => 1461248502,
'id' => 1038300508,
'democracy' => 1,
'megagroup' => 1
}, 'Telegram::Channel' )
],
'users' => [
bless( {
'last_name' => 'Panov',
'flags' => 1048646,
'min' => 1,
'id' => 82234609,
'status' => bless( {}, 'Telegram::UserStatusRecently' ),
'first_name' => 'Dima'
}, 'Telegram::User' )
],
'seq' => 0,
'date' => 1571912647,
'updates' => [
bless( {
'pts' => 137596,
'message' => bless( {
'flags' => 256,
'message' => 'Создать джейл с именем покороче ??',
'to_id' => bless( {
'channel_id' => 1038300508
}, 'Telegram::PeerChannel' ),
'id' => 119634,
'date' => 1571912647,
'from_id' => 82234609
}, 'Telegram::Message' ),
'pts_count' => 1
}, 'Telegram::UpdateNewChannelMessage' )
]
}, 'Telegram::Updates' )
};

होय, हेतुपुरस्सर बिघडणारे नाही - जर तुम्ही ते अजून वाचले नसेल, तर पुढे जा आणि ते करा!

अरे, वाई~~... हे काय दिसते? काहीतरी अतिशय परिचित... कदाचित ही JSON मधील ठराविक वेब API ची डेटा रचना आहे, त्याशिवाय वर्ग देखील ऑब्जेक्टशी संलग्न आहेत?..

तर हे असे घडते... हे सर्व काय आहे, कॉम्रेड्स?.. खूप प्रयत्न - आणि आम्ही वेब प्रोग्रामर जिथे विश्रांती घेतली तिथे थांबलो. फक्त सुरू?..केवळ HTTPS वर JSON सोपे होणार नाही का?! बदल्यात आम्हाला काय मिळाले? प्रयत्नांची किंमत होती का?

TL+MTProto ने आम्हाला काय दिले आणि कोणते पर्याय शक्य आहेत याचे मूल्यमापन करू. बरं, HTTP, जे विनंती-प्रतिसाद मॉडेलवर लक्ष केंद्रित करते, ते एक वाईट फिट आहे, परंतु किमान TLS वर काहीतरी आहे?

कॉम्पॅक्ट सीरियलायझेशन. JSON प्रमाणेच ही डेटा रचना पाहून, मला आठवते की त्याच्या बायनरी आवृत्त्या आहेत. चला MsgPack ला अपुरा एक्स्टेंसिबल म्हणून चिन्हांकित करू, परंतु तेथे आहे, उदाहरणार्थ, CBOR - तसे, मध्ये वर्णन केलेले एक मानक आहे आरएफसी 7049. ते परिभाषित करते या वस्तुस्थितीसाठी लक्षणीय आहे टॅग, एक विस्तार यंत्रणा म्हणून, आणि दरम्यान आधीच प्रमाणित उपलब्ध:

  • 25 + 256 - ओळ क्रमांकाच्या संदर्भासह वारंवार रेषा बदलणे, अशी स्वस्त कॉम्प्रेशन पद्धत
  • 26 - वर्गाचे नाव आणि कन्स्ट्रक्टर आर्ग्युमेंटसह अनुक्रमित पर्ल ऑब्जेक्ट
  • 27 - प्रकार नाव आणि कन्स्ट्रक्टर आर्ग्युमेंटसह अनुक्रमित भाषा-स्वतंत्र ऑब्जेक्ट

बरं, मी समान डेटा TL आणि CBOR मध्ये स्ट्रिंग आणि ऑब्जेक्ट पॅकिंग सक्षम करून अनुक्रमित करण्याचा प्रयत्न केला. परिणाम मेगाबाइटपासून कुठेतरी CBOR च्या बाजूने बदलू लागला:

cborlen=1039673 tl_len=1095092

त्यामुळे निष्कर्ष: तुलनात्मक कार्यक्षमतेसह, सिंक्रोनाइझेशन अयशस्वी किंवा अज्ञात अभिज्ञापकाच्या समस्येच्या अधीन नसलेले बरेच सोपे स्वरूप आहेत.

जलद कनेक्शन स्थापना. याचा अर्थ पुनर्कनेक्शननंतर शून्य RTT (जेव्हा की आधीच एकदा तयार केली गेली आहे) - अगदी पहिल्या MTProto संदेशापासून लागू, परंतु काही आरक्षणांसह - समान मीठ दाबा, सत्र सडलेले नाही, इ. त्याऐवजी TLS आम्हाला काय ऑफर करते? विषयावरील कोट:

TLS मध्ये PFS वापरताना, TLS सत्राची तिकिटे (आरएफसी 5077) की री-निगोशिएट न करता आणि सर्व्हरवर मुख्य माहिती संग्रहित न करता एनक्रिप्टेड सत्र पुन्हा सुरू करण्यासाठी. प्रथम कनेक्शन उघडताना आणि की तयार करताना, सर्व्हर कनेक्शन स्थिती एन्क्रिप्ट करतो आणि क्लायंटला (सत्र तिकिटाच्या स्वरूपात) प्रसारित करतो. त्यानुसार, कनेक्शन पुन्हा सुरू झाल्यावर, क्लायंट सेशन कीसह सेशन तिकीट सर्व्हरला परत पाठवतो. तिकीट स्वतःच तात्पुरती की (सत्र तिकीट की) सह कूटबद्ध केले जाते, जे सर्व्हरवर संग्रहित केले जाते आणि क्लस्टर केलेल्या सोल्यूशन्समध्ये SSL प्रक्रिया करणार्‍या सर्व फ्रंटएंड सर्व्हरमध्ये वितरित केले जाणे आवश्यक आहे.[10] अशा प्रकारे, तात्पुरत्या सर्व्हर की तडजोड केल्यास सत्र तिकिटाचा परिचय पीएफएसचे उल्लंघन करू शकते, उदाहरणार्थ, जेव्हा ते बर्याच काळासाठी संग्रहित केले जातात (ओपनएसएसएल, एनजीनेक्स, अपाचे ते प्रोग्रामच्या संपूर्ण कालावधीसाठी डीफॉल्टनुसार संग्रहित करतात; लोकप्रिय साइट वापरतात अनेक तासांपर्यंत की, दिवसांपर्यंत).

येथे RTT शून्य नाही, तुम्हाला किमान ClientHello आणि ServerHello ची देवाणघेवाण करणे आवश्यक आहे, त्यानंतर क्लायंट समाप्त सोबत डेटा पाठवू शकतो. परंतु येथे आपण हे लक्षात ठेवले पाहिजे की आपल्याकडे नवीन उघडलेल्या कनेक्शनसह वेब नाही, परंतु एक मेसेंजर आहे, ज्याचे कनेक्शन बहुतेक वेळा एक किंवा अधिक किंवा कमी दीर्घकालीन असते, वेब पृष्ठांच्या तुलनेने लहान विनंत्या - सर्व काही मल्टीप्लेक्स आहे. अंतर्गत म्हणजेच, आम्ही खरोखरच खराब भुयारी रेल्वे विभाग गाठत नसल्यास ते स्वीकार्य आहे.

आणखी काही विसरलात? टिप्पण्यांमध्ये लिहा.

पुढे चालू!

पोस्ट्सच्या या मालिकेच्या दुसर्‍या भागात आम्ही तांत्रिक नाही तर संस्थात्मक समस्यांचा विचार करू - दृष्टिकोन, विचारधारा, इंटरफेस, वापरकर्त्यांबद्दलचा दृष्टीकोन इ. तथापि, येथे सादर केलेल्या तांत्रिक माहितीच्या आधारावर.

तिसरा भाग तांत्रिक घटक/विकास अनुभवाचे विश्लेषण करत राहील. आपण विशेषतः शिकाल:

  • TL प्रकारांच्या विविधतेसह पेंडमोनियम सुरू ठेवणे
  • चॅनेल आणि सुपरग्रुप्सबद्दल अज्ञात गोष्टी
  • का संवाद रोस्टरपेक्षा वाईट आहेत
  • निरपेक्ष वि सापेक्ष संदेश पत्त्याबद्दल
  • फोटो आणि इमेज मध्ये काय फरक आहे
  • इमोजी इटॅलिक मजकुरात कसा हस्तक्षेप करतात

आणि इतर क्रचेस! संपर्कात रहा!

स्त्रोत: www.habr.com

एक टिप्पणी जोडा