टेलीग्राम के प्रोटोकॉल और संगठनात्मक दृष्टिकोण की आलोचना। भाग 1, तकनीकी: क्लाइंट को शुरुआत से लिखने का अनुभव - टीएल, एमटी

हाल ही में, टेलीग्राम कितना अच्छा है, ड्यूरोव भाई नेटवर्क सिस्टम बनाने में कितने प्रतिभाशाली और अनुभवी हैं, आदि के बारे में पोस्ट हैबे पर अधिक बार दिखाई देने लगी हैं। साथ ही, बहुत कम लोगों ने वास्तव में खुद को तकनीकी उपकरण में डुबोया है - अधिक से अधिक, वे JSON पर आधारित काफी सरल (और MTProto से काफी अलग) बॉट एपीआई का उपयोग करते हैं, और आमतौर पर बस स्वीकार करते हैं विश्वास पर सभी प्रशंसाएं और पीआर जो संदेशवाहक के चारों ओर घूमती हैं। लगभग डेढ़ साल पहले, एशेलॉन एनजीओ में मेरे सहयोगी वसीली (दुर्भाग्य से, हैबे पर उनका खाता ड्राफ्ट के साथ मिटा दिया गया था) ने पर्ल में स्क्रैच से अपना खुद का टेलीग्राम क्लाइंट लिखना शुरू किया, और बाद में इन पंक्तियों के लेखक शामिल हो गए। पर्ल क्यों, कुछ लोग तुरंत पूछेंगे? क्योंकि ऐसी परियोजनाएं पहले से ही अन्य भाषाओं में मौजूद हैं। वास्तव में, यह बात नहीं है, कोई अन्य भाषा भी हो सकती है जहां कोई नहीं है तैयार पुस्तकालय, और तदनुसार लेखक को सभी तरह से जाना होगा खरोंच से. इसके अलावा, क्रिप्टोग्राफी भरोसे का विषय है, लेकिन सत्यापित करें। सुरक्षा के उद्देश्य से बनाए गए उत्पाद के साथ, आप केवल निर्माता की तैयार लाइब्रेरी पर भरोसा नहीं कर सकते हैं और उस पर आँख बंद करके भरोसा नहीं कर सकते हैं (हालाँकि, यह दूसरे भाग का विषय है)। फिलहाल, लाइब्रेरी "औसत" स्तर पर काफी अच्छी तरह से काम करती है (आपको कोई भी एपीआई अनुरोध करने की अनुमति देती है)।

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

सामग्री की तालिका:

दस्तावेज़ीकरण... यह मौजूद है, है ना? क्या यह सच है?..

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

एक युवा लेखक के रूप में कहाँ से शुरुआत करें?

इससे कोई फर्क नहीं पड़ता कि आप शुरुआत से लिखते हैं या उदाहरण के लिए, तैयार पुस्तकालयों का उपयोग करते हैं पायथन के लिए टेलीथॉन या PHP के लिए मेडलिन, किसी भी स्थिति में, आपको पहले इसकी आवश्यकता होगी अपना आवेदन पंजीकृत करें - पैरामीटर प्राप्त करें api_id и api_hash (जिन लोगों ने VKontakte API के साथ काम किया है वे तुरंत समझ जाते हैं) जिससे सर्वर एप्लिकेशन की पहचान करेगा। यह करना है इसे कानूनी कारणों से करें, लेकिन हम दूसरे भाग में इस बारे में अधिक बात करेंगे कि पुस्तकालय लेखक इसे प्रकाशित क्यों नहीं कर सकते। आप परीक्षण मूल्यों से संतुष्ट हो सकते हैं, हालांकि वे बहुत सीमित हैं - तथ्य यह है कि अब आप पंजीकरण कर सकते हैं केवल एक ऐप, इसलिए इसमें जल्दबाजी न करें।

अब, तकनीकी दृष्टिकोण से, हमें इस तथ्य में रुचि होनी चाहिए कि पंजीकरण के बाद हमें दस्तावेज़ीकरण, प्रोटोकॉल आदि के अपडेट के बारे में टेलीग्राम से सूचनाएं प्राप्त होनी चाहिए। अर्थात्, कोई यह मान सकता है कि डॉक वाली साइट को बस छोड़ दिया गया था और विशेष रूप से उन लोगों के साथ काम करना जारी रखा, जिन्होंने ग्राहक बनाना शुरू किया था, क्योंकि ये तो और आसान है। लेकिन नहीं, ऐसा कुछ देखने को नहीं मिला, कोई सूचना नहीं आयी.

और यदि आप शुरुआत से लिखते हैं, तो प्राप्त मापदंडों का उपयोग करना वास्तव में अभी भी बहुत दूर है। हालांकि https://core.telegram.org/ और गेटिंग स्टार्टेड में सबसे पहले इनके बारे में बात करते हैं, असल में आपको सबसे पहले इन पर अमल करना होगा एमटीप्रोटो प्रोटोकॉल - लेकिन यदि आप विश्वास करते हैं OSI मॉडल के अनुसार लेआउट प्रोटोकॉल के सामान्य विवरण के लिए पृष्ठ के अंत में, तो यह पूरी तरह से व्यर्थ है।

वास्तव में, MTProto से पहले और बाद में, एक साथ कई स्तरों पर (जैसा कि OS कर्नेल में काम करने वाले विदेशी नेटवर्कर्स कहते हैं, परत उल्लंघन), एक बड़ा, दर्दनाक और भयानक विषय रास्ते में आ जाएगा...

बाइनरी क्रमांकन: टीएल (टाइप लैंग्वेज) और इसकी योजना, और परतें, और कई अन्य डरावने शब्द

यह विषय, वास्तव में, टेलीग्राम की समस्याओं की कुंजी है। और अगर आप इसमें गहराई से जाने की कोशिश करेंगे तो बहुत सारे भयानक शब्द मिलेंगे।

तो, यहाँ आरेख है। यदि यह शब्द आपके मन में आये तो कहिये, JSON स्कीमा, आपने सही सोचा। लक्ष्य एक ही है: प्रेषित डेटा के संभावित सेट का वर्णन करने के लिए कुछ भाषा। यहीं पर समानताएं समाप्त हो जाती हैं। यदि पेज से एमटीप्रोटो प्रोटोकॉल, या आधिकारिक क्लाइंट के स्रोत ट्री से, हम कुछ स्कीमा खोलने का प्रयास करेंगे, हम कुछ इस तरह देखेंगे:

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 संस्करण में स्कीमा के लिंक हैं - लेकिन इससे यह स्पष्ट नहीं होता है).

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

कार्यात्मक भाषाओं और स्वचालित प्रकार के अनुमान से परिचित पाठक, निश्चित रूप से, इस भाषा में विवरण भाषा को उदाहरण से भी अधिक परिचित देखेंगे, और कह सकते हैं कि यह वास्तव में सिद्धांत रूप में बुरा नहीं है। इस पर आपत्तियाँ हैं:

  • हाँ, लक्ष्य अच्छा लगता है, लेकिन अफ़सोस, वह हासिल नहीं हुआ
  • रूसी विश्वविद्यालयों में शिक्षा आईटी विशिष्टताओं के बीच भी भिन्न होती है - हर किसी ने संबंधित पाठ्यक्रम नहीं लिया है
  • अंततः, जैसा कि हम देखेंगे, व्यवहार में यह है आवश्यक नहीं, चूंकि वर्णित टीएल का केवल एक सीमित उपसमूह ही उपयोग किया जाता है

РР ° Рљ СЃРєР ° Р · Р ° Р » लियोनर्ड चैनल पर #perl फ़्रीनोड आईआरसी नेटवर्क में, जिसने टेलीग्राम से मैट्रिक्स तक एक गेट लागू करने का प्रयास किया (उद्धरण का अनुवाद स्मृति से गलत है):

ऐसा महसूस होता है जैसे किसी को पहली बार टाइप थ्योरी से परिचित कराया गया था, वह उत्साहित हो गया, और इसके साथ खेलने की कोशिश करने लगा, वास्तव में इसकी परवाह नहीं की कि व्यवहार में इसकी आवश्यकता है या नहीं।

अपने लिए देखें, यदि किसी प्राथमिक चीज़ के रूप में बेयर-टाइप (int, long, आदि) की आवश्यकता प्रश्न नहीं उठाती है - अंततः उन्हें मैन्युअल रूप से कार्यान्वित किया जाना चाहिए - उदाहरण के लिए, आइए उनसे प्राप्त करने का प्रयास करें वेक्टर. अर्थात्, वास्तव में, सरणी, यदि आप परिणामी चीज़ों को उनके उचित नामों से बुलाते हैं।

लेकिन इससे पहले

उन लोगों के लिए टीएल सिंटैक्स के सबसेट का संक्षिप्त विवरण जो आधिकारिक दस्तावेज़ नहीं पढ़ते हैं

constructor = Type;
myVec ids:Vector<long> = Type;

fixed#abcdef34 id:int = Type2;

fixedVec set:Vector<Type2> = FixedVec;

constructorOne#crc32 field1:int = PolymorType;
constructorTwo#2crc32 field_a:long field_b:Type3 field_c:int = PolymorType;
constructorThree#deadcrc bit_flags_of_what_really_present:# optional_field4:bit_flags_of_what_really_present.1?Type = PolymorType;

an_id#12abcd34 id:int = Type3;
a_null#6789cdef = Type3;

परिभाषा हमेशा शुरू होती है डिजाइनर, जिसके बाद वैकल्पिक रूप से (व्यवहार में - हमेशा) प्रतीक के माध्यम से # होना चाहिए CRC32 इस प्रकार की सामान्यीकृत विवरण स्ट्रिंग से. इसके बाद फ़ील्ड का विवरण आता है; यदि वे मौजूद हैं, तो प्रकार खाली हो सकता है। यह सब एक समान चिह्न के साथ समाप्त होता है, उस प्रकार का नाम जिससे यह कंस्ट्रक्टर - यानी, वास्तव में, उपप्रकार - संबंधित है। बराबर चिह्न के दाईं ओर वाला व्यक्ति है बहुरूपी - यानी, कई विशिष्ट प्रकार इसके अनुरूप हो सकते हैं।

यदि परिभाषा पंक्ति के बाद आती है ---functions---, तो सिंटैक्स वही रहेगा, लेकिन अर्थ अलग होगा: कंस्ट्रक्टर आरपीसी फ़ंक्शन का नाम बन जाएगा, फ़ील्ड पैरामीटर बन जाएंगे (ठीक है, यानी, यह बिल्कुल वही दी गई संरचना रहेगी, जैसा कि नीचे वर्णित है) , यह बस निर्दिष्ट अर्थ होगा), और "बहुरूपी प्रकार " - लौटाए गए परिणाम का प्रकार। सच है, यह अभी भी बहुरूपी ही रहेगा - बस अनुभाग में परिभाषित किया गया है ---types---, लेकिन इस कंस्ट्रक्टर पर "विचार नहीं किया जाएगा"। बुलाए गए फ़ंक्शंस के प्रकारों को उनके तर्कों द्वारा ओवरलोड करना, यानी। किसी कारण से, एक ही नाम लेकिन अलग-अलग हस्ताक्षर वाले कई फ़ंक्शन, जैसे कि C++ में, TL में उपलब्ध नहीं कराए गए हैं।

यदि यह OOP नहीं है तो "कंस्ट्रक्टर" और "पॉलीमॉर्फिक" क्यों? खैर, वास्तव में, किसी के लिए ओओपी शब्दों में इसके बारे में सोचना आसान होगा - एक अमूर्त वर्ग के रूप में एक बहुरूपी प्रकार, और निर्माता इसके प्रत्यक्ष वंशज वर्ग हैं, और final अनेक भाषाओं की शब्दावली में। वास्तव में, निःसंदेह, केवल यहीं समानता OO प्रोग्रामिंग भाषाओं में वास्तविक अतिभारित कंस्ट्रक्टर विधियों के साथ। चूँकि यहाँ केवल डेटा संरचनाएँ हैं, कोई विधियाँ नहीं हैं (हालाँकि फ़ंक्शंस और विधियों का आगे का विवरण सिर में भ्रम पैदा करने में काफी सक्षम है कि वे मौजूद हैं, लेकिन यह एक अलग मामला है) - आप एक कंस्ट्रक्टर को एक मान के रूप में सोच सकते हैं कौन निर्माण किया जा रहा है बाइट स्ट्रीम पढ़ते समय टाइप करें।

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

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

आप सोच सकते हैं कि टेम्प्लेट और जेनेरिक दोनों ही पेशेवरों या जावा में हैं। लेकिन कोई नहीं। लगभग। यह केवल वास्तविक सर्किट में कोण ब्रैकेट का उपयोग करने का मामला, और इसका उपयोग केवल वेक्टर के लिए किया जाता है। एक बाइट स्ट्रीम में, ये वेक्टर प्रकार के लिए 4 CRC32 बाइट्स होंगे, हमेशा समान, फिर 4 बाइट्स - सरणी तत्वों की संख्या, और फिर ये तत्व स्वयं।

इसमें यह तथ्य जोड़ें कि क्रमबद्धता हमेशा 4 बाइट्स के शब्दों में होती है, सभी प्रकार इसके गुणक होते हैं - अंतर्निहित प्रकारों का भी वर्णन किया गया है bytes и string लंबाई के मैन्युअल क्रमांकन और 4 द्वारा इस संरेखण के साथ - अच्छा, यह सामान्य और अपेक्षाकृत प्रभावी भी लगता है? यद्यपि टीएल को एक प्रभावी बाइनरी क्रमांकन होने का दावा किया जाता है, लेकिन उनके साथ, किसी भी चीज़ के विस्तार के साथ, यहां तक ​​​​कि बूलियन मान और एकल-वर्ण स्ट्रिंग को 4 बाइट्स तक, क्या JSON अभी भी अधिक मोटा होगा? देखिए, अनावश्यक फ़ील्ड को भी बिट फ़्लैग के साथ छोड़ा जा सकता है, सब कुछ काफी अच्छा है, और भविष्य के लिए एक्स्टेंसिबल भी है, तो बाद में कंस्ट्रक्टर में नए वैकल्पिक फ़ील्ड क्यों नहीं जोड़े जाते?..

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

दूसरी बात, आइए याद रखें CRC32, जिसका प्रयोग यहाँ मूलतः इस प्रकार किया जाता है हैश फ़ंक्शन विशिष्ट रूप से यह निर्धारित करने के लिए कि किस प्रकार को (डी)क्रमबद्ध किया जा रहा है। यहां हमें टकराव की समस्या का सामना करना पड़ रहा है - और नहीं, संभावना 232 में से एक नहीं है, बल्कि बहुत अधिक है। किसने याद किया कि CRC32 को संचार चैनल में त्रुटियों का पता लगाने (और सही करने) के लिए डिज़ाइन किया गया है, और तदनुसार दूसरों की हानि के लिए इन गुणों में सुधार करता है? उदाहरण के लिए, यह बाइट्स को पुनर्व्यवस्थित करने की परवाह नहीं करता है: यदि आप दो पंक्तियों से CRC32 की गणना करते हैं, तो दूसरे में आप पहले 4 बाइट्स को अगले 4 बाइट्स के साथ स्वैप करते हैं - यह वही होगा। जब हमारा इनपुट लैटिन वर्णमाला (और थोड़ा विराम चिह्न) से पाठ स्ट्रिंग है, और ये नाम विशेष रूप से यादृच्छिक नहीं हैं, तो ऐसी पुनर्व्यवस्था की संभावना बहुत बढ़ जाती है।

वैसे, वहां क्या था इसकी जांच किसने की? वास्तव में सीआरसी32? प्रारंभिक स्रोत कोडों में से एक (वॉल्टमैन से पहले भी) में एक हैश फ़ंक्शन था जो प्रत्येक वर्ण को संख्या 239 से गुणा करता था, जो इन लोगों को बहुत प्रिय था, हा हा!

अंत में, ठीक है, हमें एहसास हुआ कि फ़ील्ड प्रकार वाले कंस्ट्रक्टर Vector<int> и Vector<PolymorType> अलग CRC32 होगा. ऑनलाइन प्रदर्शन के बारे में क्या? और सैद्धांतिक दृष्टिकोण से, क्या यह प्रकार का हिस्सा बन जाता है?? मान लीजिए कि हम दस हजार संख्याओं की एक सरणी पास करते हैं, ठीक है Vector<int> सब कुछ स्पष्ट है, लंबाई और अन्य 40000 बाइट्स। क्या हुआ अगर ये Vector<Type2>, जिसमें केवल एक फ़ील्ड शामिल है int और यह इस प्रकार में अकेला है - क्या हमें 10000xabcdef0 को 34 बार और फिर 4 बाइट्स दोहराने की आवश्यकता है int, या भाषा इसे कंस्ट्रक्टर से हमारे लिए स्वतंत्र करने में सक्षम है fixedVec और 80000 बाइट्स के बजाय, फिर से केवल 40000 स्थानांतरित करें?

यह बिल्कुल भी बेकार सैद्धांतिक प्रश्न नहीं है - कल्पना कीजिए कि आपको समूह उपयोगकर्ताओं की एक सूची प्राप्त होती है, जिनमें से प्रत्येक के पास एक आईडी, पहला नाम, अंतिम नाम है - मोबाइल कनेक्शन पर स्थानांतरित डेटा की मात्रा में अंतर महत्वपूर्ण हो सकता है। यह वास्तव में टेलीग्राम क्रमबद्धता की प्रभावशीलता है जो हमें विज्ञापित की जाती है।

तो ...

वेक्टर, जो कभी रिलीज़ नहीं हुआ

यदि आप कॉम्बिनेटर आदि के विवरण के पन्नों को खंगालने की कोशिश करते हैं, तो आप देखेंगे कि एक वेक्टर (और यहां तक ​​कि एक मैट्रिक्स) औपचारिक रूप से कई शीटों के टुपल्स के माध्यम से आउटपुट होने की कोशिश कर रहा है। लेकिन अंत में वे भूल जाते हैं, अंतिम चरण छोड़ दिया जाता है, और बस एक वेक्टर की परिभाषा दे दी जाती है, जो अभी तक किसी प्रकार से बंधी नहीं है। क्या बात क्या बात? भाषाओं में प्रोग्रामिंग, विशेष रूप से कार्यात्मक वाले, संरचना का पुनरावर्ती रूप से वर्णन करना काफी विशिष्ट है - अपने आलसी मूल्यांकन के साथ संकलक स्वयं ही सब कुछ समझेगा और करेगा। भाषा में डेटा क्रमबद्धता जिस चीज़ की आवश्यकता है वह है दक्षता: इसका केवल वर्णन करना ही पर्याप्त है सूची, अर्थात। दो तत्वों की संरचना - पहला एक डेटा तत्व है, दूसरा एक ही संरचना है या पूंछ के लिए एक खाली स्थान है (पैक) (cons) लिस्प में)। लेकिन जाहिर तौर पर इसकी आवश्यकता होगी प्रत्येक के तत्व अपने प्रकार का वर्णन करने के लिए अतिरिक्त 4 बाइट्स (टीएल के मामले में सीआरसी32) खर्च करता है। किसी सारणी का वर्णन भी आसानी से किया जा सकता है निर्धारित माप, लेकिन पहले से अज्ञात लंबाई की एक सरणी के मामले में, हम अलग हो जाते हैं।

इसलिए, चूंकि टीएल वेक्टर को आउटपुट करने की अनुमति नहीं देता है, इसलिए इसे किनारे पर जोड़ना होगा। अंततः दस्तावेज़ कहता है:

क्रमांकन हमेशा एक ही कंस्ट्रक्टर "वेक्टर" (const 0x1cb5c415 = crc32("वेक्टर t:Type # [ t ] = वेक्टर t") का उपयोग करता है जो कि प्रकार t के वेरिएबल के विशिष्ट मान पर निर्भर नहीं है।

वैकल्पिक पैरामीटर t का मान क्रमबद्धता में शामिल नहीं है क्योंकि यह परिणाम प्रकार से प्राप्त होता है (हमेशा डीसेरिएलाइज़ेशन से पहले ज्ञात होता है)।

ज़रा बारीकी से देखें: vector {t:Type} # [ t ] = Vector t - लेकिन कहीं नहीं यह परिभाषा स्वयं यह नहीं कहती कि पहली संख्या वेक्टर की लंबाई के बराबर होनी चाहिए! और यह कहीं से नहीं आता है. यह एक ऐसी बात है जिसे ध्यान में रखना होगा और अपने हाथों से कार्यान्वित करना होगा। अन्यत्र, दस्तावेज़ में ईमानदारी से यह भी उल्लेख किया गया है कि प्रकार वास्तविक नहीं है:

वेक्टर टी बहुरूपी छद्मरूप एक "प्रकार" है जिसका मान किसी भी प्रकार टी के मानों का अनुक्रम है, या तो बॉक्स्ड या नंगे।

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

वैसे, संख्या के बारे में। आइए हम आपको वो याद दिला दें # यह एक पर्यायवाची है nat, प्राकृतिक संख्या:

प्रकार के भाव हैं (प्रकार-expr) और संख्यात्मक अभिव्यक्तियाँ (नेट-expr). हालाँकि, उन्हें उसी तरह परिभाषित किया गया है।

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

लेकिन व्याकरण में उनका वर्णन उसी प्रकार किया गया है, अर्थात्। इस अंतर को फिर से याद रखना चाहिए और हाथों-हाथ कार्यान्वयन में लाना चाहिए।

ठीक है, हाँ, टेम्पलेट प्रकार (vector<int>, vector<User>) एक सामान्य पहचानकर्ता है (#1cb5c415), अर्थात। यदि आप जानते हैं कि कॉल की घोषणा की गई है

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

तब आप केवल एक वेक्टर की प्रतीक्षा नहीं कर रहे हैं, बल्कि उपयोगकर्ताओं के एक वेक्टर की प्रतीक्षा कर रहे हैं। ज्यादा ठीक, चाहिए प्रतीक्षा करें - वास्तविक कोड में, प्रत्येक तत्व, यदि नंगे प्रकार का नहीं है, तो एक कंस्ट्रक्टर होगा, और कार्यान्वयन में एक अच्छे तरीके से यह जांचना आवश्यक होगा - लेकिन हमें इस वेक्टर के प्रत्येक तत्व में बिल्कुल भेजा गया था वह प्रकार? क्या होगा यदि यह किसी प्रकार का PHP होता, जिसमें एक सरणी में विभिन्न तत्वों में विभिन्न प्रकार हो सकते हैं?

इस बिंदु पर आप सोचने लगते हैं - क्या ऐसा टीएल आवश्यक है? हो सकता है कि कार्ट के लिए मानव सीरिएलाइज़र का उपयोग करना संभव हो, वही प्रोटोबफ़ जो उस समय पहले से मौजूद था? यह तो सिद्धांत था, आइए व्यवहार पर नजर डालें।

कोड में मौजूदा टीएल कार्यान्वयन

ड्यूरोव के शेयर की बिक्री के साथ प्रसिद्ध घटनाओं से पहले ही टीएल का जन्म VKontakte की गहराई में हुआ था और (निश्चित रूप से), टेलीग्राम का विकास शुरू होने से पहले ही। और खुले स्रोत में पहले कार्यान्वयन का स्रोत कोड आप बहुत सारी मज़ेदार बैसाखियाँ पा सकते हैं। और भाषा को अब टेलीग्राम की तुलना में अधिक पूर्ण रूप से वहां लागू किया गया था। उदाहरण के लिए, योजना में हैश का बिल्कुल भी उपयोग नहीं किया जाता है (जिसका अर्थ है विचलित व्यवहार वाला एक अंतर्निहित छद्म प्रकार (वेक्टर की तरह)। या

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

यह इंट-टाइप जोड़े के वेक्टर के रूप में हैशमैप टेम्पलेट प्रकार की परिभाषा है। C++ में यह कुछ इस तरह दिखेगा:

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

इसलिए, alpha - कीवर्ड! लेकिन केवल C++ में आप T लिख सकते हैं, लेकिन आपको अल्फा, बीटा लिखना चाहिए... लेकिन 8 पैरामीटर से अधिक नहीं, यहीं कल्पना समाप्त होती है। ऐसा लगता है कि एक बार सेंट पीटर्सबर्ग में कुछ इस तरह के संवाद हुए थे:

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

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

लेकिन यह "सामान्य तौर पर" टीएल के पहले प्रकाशित कार्यान्वयन के बारे में था। आइए टेलीग्राम ग्राहकों में स्वयं कार्यान्वयन पर विचार करें।

वसीली को शब्द:

वसीली, [09.10.18 17:07] सबसे बढ़कर, गधा गर्म है क्योंकि उन्होंने अमूर्तताओं का एक समूह बनाया, और फिर उन पर एक बोल्ट लगाया, और कोड जनरेटर को बैसाखी से ढक दिया
परिणामस्वरूप, सबसे पहले डॉक पायलट.जेपीजी से
फिर कोड dzhekichan.webp से

बेशक, एल्गोरिदम और गणित से परिचित लोगों से, हम उम्मीद कर सकते हैं कि उन्होंने अहो, उलेमन को पढ़ा है, और उन उपकरणों से परिचित हैं जो दशकों से उद्योग में अपने डीएसएल कंपाइलर लिखने के लिए वास्तविक मानक बन गए हैं, है ना?..

लेखक द्वारा टेलीग्राम-CLI विटाली वाल्टमैन, जैसा कि इसकी (सीएलआई) सीमाओं के बाहर टीएलओ प्रारूप की घटना से समझा जा सकता है, टीम का एक सदस्य है - अब टीएल पार्सिंग के लिए एक पुस्तकालय आवंटित किया गया है अलग, उसकी धारणा क्या है टीएल पार्सर? ..

16.12 04:18 वसीली: मुझे लगता है कि किसी ने लेक्स+यासीसी में महारत हासिल नहीं की है
16.12 04:18 वसीली: मैं इसे अन्यथा नहीं समझा सकता
16.12 04:18 वसीली: ठीक है, या उन्हें वीके में लाइनों की संख्या के लिए भुगतान किया गया था
16.12 04:19 वसीली: 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);

पायथन में 1100+ लाइनें, कुछ नियमित अभिव्यक्ति + वेक्टर जैसे विशेष मामले, जो निश्चित रूप से योजना में घोषित किए गए हैं क्योंकि यह टीएल सिंटैक्स के अनुसार होना चाहिए, लेकिन वे इसे पार्स करने के लिए इस सिंटैक्स पर निर्भर थे... सवाल उठता है कि यह सब चमत्कार क्यों था?иयदि दस्तावेज़ के अनुसार कोई भी इसे पार्स नहीं कर रहा है तो यह अधिक स्तरित है?!

वैसे... याद रखें हमने CRC32 जाँच के बारे में बात की थी? तो, टेलीग्राम डेस्कटॉप कोड जनरेटर में उन प्रकारों के लिए अपवादों की एक सूची होती है जिनमें CRC32 की गणना की जाती है मिलता जुलता नहीं है चित्र में दर्शाए गए के साथ!

वसीली, [18.12/22 49:XNUMX] और यहां मैं इस बारे में सोचूंगा कि क्या ऐसे टीएल की आवश्यकता है
यदि मैं वैकल्पिक कार्यान्वयन के साथ खिलवाड़ करना चाहता हूं, तो मैं लाइन ब्रेक डालना शुरू कर दूंगा, आधे पार्सर मल्टी-लाइन परिभाषाओं पर टूट जाएंगे
हालाँकि, tdesktop भी

वन-लाइनर की बात याद रखें, हम इस पर थोड़ी देर बाद लौटेंगे।

ठीक है, टेलीग्राम-सीएलआई अनौपचारिक है, टेलीग्राम डेस्कटॉप आधिकारिक है, लेकिन दूसरों के बारे में क्या? कौन जानता है?.. एंड्रॉइड क्लाइंट कोड में कोई स्कीमा पार्सर नहीं था (जो ओपन सोर्स के बारे में सवाल उठाता है, लेकिन यह दूसरे भाग के लिए है), लेकिन कोड के कई अन्य मज़ेदार टुकड़े थे, लेकिन उनके बारे में और भी बहुत कुछ नीचे उपधारा.

क्रमबद्धता व्यवहार में और कौन से प्रश्न उठाती है? उदाहरण के लिए, उन्होंने निश्चित रूप से बिट फ़ील्ड और सशर्त फ़ील्ड के साथ बहुत सी चीज़ें कीं:

वसीली: flags.0? true
इसका मतलब है कि फ़ील्ड मौजूद है और यदि ध्वज सेट है तो यह सत्य के बराबर है

वसीली: flags.1? int
इसका मतलब है कि फ़ील्ड मौजूद है और उसे डी-क्रमांकित करने की आवश्यकता है

वसीली: गधे, तुम क्या कर रहे हो इसके बारे में चिंता मत करो!
वसीली: दस्तावेज़ में कहीं उल्लेख है कि ट्रू एक शून्य-लंबाई प्रकार है, लेकिन उनके दस्तावेज़ से कुछ भी इकट्ठा करना असंभव है
वसीली: ओपन सोर्स कार्यान्वयन में भी यह मामला नहीं है, लेकिन बैसाखियों और समर्थनों का एक समूह है

टेलीथॉन के बारे में क्या? MTProto के विषय को आगे देखते हुए, एक उदाहरण - दस्तावेज़ में ऐसे टुकड़े हैं, लेकिन संकेत % इसे केवल "किसी दिए गए नंगे प्रकार के अनुरूप" के रूप में वर्णित किया गया है, अर्थात। नीचे दिए गए उदाहरणों में या तो कोई त्रुटि है या कुछ अप्रलेखित है:

वसीली, [22.06.18 18:38] एक स्थान पर:

msg_container#73f1f8dc messages:vector message = MessageContainer;

एक अलग में:

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

और ये दो बड़े अंतर हैं, वास्तविक जीवन में किसी प्रकार का नग्न वेक्टर आता है

मैंने न तो कोई खाली वेक्टर परिभाषा देखी है और न ही कोई ऐसी परिभाषा देखी है

टेलीथॉन में विश्लेषण हाथ से लिखा जाता है

उनके चित्र में परिभाषा पर टिप्पणी की गई है msg_container

फिर, सवाल % के बारे में है। इसका वर्णन नहीं किया गया है.

वादिम गोंचारोव, [22.06.18 19:22] और टीडेस्कटॉप में?

वसीली, [22.06.18 19:23] लेकिन नियमित इंजनों पर उनका टीएल पार्सर संभवतः इसे नहीं खाएगा

// parsed manually

टीएल एक सुंदर अमूर्तता है, कोई भी इसे पूरी तरह से लागू नहीं करता है

और % योजना के उनके संस्करण में नहीं है

लेकिन यहां दस्तावेज़ स्वयं विरोधाभासी है, इसलिए idk

यह व्याकरण में पाया गया था, वे शब्दार्थ का वर्णन करना भूल सकते थे

आपने टीएल पर दस्तावेज़ देखा, आप आधा लीटर के बिना इसका पता नहीं लगा सकते

"ठीक है, मान लीजिए," एक अन्य पाठक कहेगा, "आप किसी चीज़ की आलोचना करते हैं, तो मुझे दिखाएँ कि यह कैसे किया जाना चाहिए।"

वसीली उत्तर देते हैं: “जहां तक ​​पार्सर की बात है, मुझे ऐसी चीज़ें पसंद हैं

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

वे। इसे हल्के ढंग से रखना आसान है।"

सामान्य तौर पर, परिणामस्वरूप, टीएल के वास्तव में उपयोग किए गए सबसेट के लिए पार्सर और कोड जनरेटर व्याकरण की लगभग 100 पंक्तियों और जनरेटर की ~ 300 लाइनों (सभी की गिनती) में फिट होते हैं printका उत्पन्न कोड), प्रत्येक कक्षा में आत्मनिरीक्षण के लिए प्रकार की जानकारी बन्स सहित। प्रत्येक बहुरूपी प्रकार एक खाली अमूर्त आधार वर्ग में बदल जाता है, और कंस्ट्रक्टर इससे विरासत में मिलते हैं और उनके पास क्रमबद्धता और डिसेरिएलाइज़ेशन के तरीके होते हैं।

प्रकार की भाषा में प्रकारों का अभाव

सशक्त टाइपिंग अच्छी बात है, है ना? नहीं, यह कोई होलीवर नहीं है (हालाँकि मैं गतिशील भाषाएँ पसंद करता हूँ), बल्कि टीएल के ढांचे के भीतर एक अभिधारणा है। इसके आधार पर, भाषा को हमारे लिए सभी प्रकार की जाँचें प्रदान करनी चाहिए। ठीक है, ठीक है, शायद वह स्वयं नहीं, बल्कि कार्यान्वयन, लेकिन उसे कम से कम उनका वर्णन करना चाहिए। और हम किस प्रकार के अवसर चाहते हैं?

सबसे पहले, बाधाएँ। यहां हम फ़ाइलें अपलोड करने के दस्तावेज़ में देखते हैं:

फिर फ़ाइल की बाइनरी सामग्री को भागों में विभाजित किया जाता है। सभी भागों का आकार समान होना चाहिए ( भाग_आकार ) और निम्नलिखित शर्तें पूरी होनी चाहिए:

  • part_size % 1024 = 0 (1KB से विभाज्य)
  • 524288 % part_size = 0 (512KB भाग_आकार से समान रूप से विभाज्य होना चाहिए)

अंतिम भाग को इन शर्तों को पूरा करने की आवश्यकता नहीं है, बशर्ते इसका आकार भाग_आकार से कम हो।

प्रत्येक भाग में एक क्रम संख्या होनी चाहिए, फ़ाइल_भाग, जिसका मान 0 से 2,999 तक है।

फ़ाइल के विभाजन के बाद आपको इसे सर्वर पर सहेजने के लिए एक विधि चुननी होगी। उपयोग upload.saveBigFilePart यदि फ़ाइल का पूर्ण आकार 10 एमबी से अधिक है upload.saveFilePart छोटी फ़ाइलों के लिए.
[…] निम्नलिखित डेटा इनपुट त्रुटियों में से एक लौटाया जा सकता है:

  • FILE_PARTS_INVALID - भागों की अमान्य संख्या। मान बीच में नहीं है 1..3000

क्या इनमें से कोई चित्र में है? क्या यह किसी तरह टीएल का उपयोग करके अभिव्यक्त किया जा सकता है? नहीं। लेकिन क्षमा करें, यहां तक ​​कि दादाजी का टर्बो पास्कल भी निर्दिष्ट प्रकारों का वर्णन करने में सक्षम था पर्वतमाला. और वह एक और बात जानता था, जिसे अब बेहतर रूप में जाना जाता है enum - एक प्रकार जिसमें मूल्यों की एक निश्चित (छोटी) संख्या की गणना होती है। C - न्यूमेरिक जैसी भाषाओं में, ध्यान दें कि अभी तक हमने केवल प्रकारों के बारे में बात की है नंबर. लेकिन सरणियाँ, स्ट्रिंग्स भी हैं... उदाहरण के लिए, यह वर्णन करना अच्छा होगा कि इस स्ट्रिंग में केवल एक फ़ोन नंबर हो सकता है, है ना?

इनमें से कुछ भी टीएल में नहीं है। लेकिन, उदाहरण के लिए, JSON स्कीमा में है। और यदि कोई अन्य व्यक्ति 512 केबी की विभाज्यता के बारे में यह तर्क दे सकता है कि इसे अभी भी कोड में जांचने की आवश्यकता है, तो सुनिश्चित करें कि ग्राहक बस नहीं कर सका सीमा से बाहर कोई नंबर भेजें 1..3000 (और संबंधित त्रुटि उत्पन्न नहीं हो सकती थी) यह संभव होता, है ना?..

वैसे, त्रुटियों और वापसी मूल्यों के बारे में। यहां तक ​​कि जिन लोगों ने टीएल के साथ काम किया है, उनकी आंखें धुंधली हो गई हैं - इसका हमें तुरंत एहसास नहीं हुआ प्रत्येक टीएल में एक फ़ंक्शन वास्तव में न केवल वर्णित रिटर्न प्रकार लौटा सकता है, बल्कि एक त्रुटि भी दे सकता है। लेकिन टीएल का उपयोग करके किसी भी तरह से इसका अनुमान नहीं लगाया जा सकता है। बेशक, यह पहले से ही स्पष्ट है और व्यवहार में इसकी कोई आवश्यकता नहीं है (हालांकि वास्तव में, आरपीसी विभिन्न तरीकों से किया जा सकता है, हम इस पर बाद में वापस आएंगे) - लेकिन सार प्रकार के गणित की अवधारणाओं की शुद्धता के बारे में क्या? स्वर्गीय दुनिया से?.. मैंने टग उठाया - तो इसका मिलान करो।

और अंततः, पठनीयता के बारे में क्या? खैर, वहाँ, सामान्य तौर पर, मैं चाहूंगा विवरण स्कीमा में यह सही है (JSON स्कीमा में, फिर से, यह है), लेकिन यदि आप पहले से ही इसके साथ तनावग्रस्त हैं, तो व्यावहारिक पक्ष के बारे में क्या - कम से कम तुच्छ रूप से अपडेट के दौरान अंतर को देखना? अपने लिए देखें वास्तविक उदाहरण:

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

या

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

यह हर किसी पर निर्भर करता है, लेकिन उदाहरण के लिए, GitHub ऐसी लंबी लाइनों के अंदर परिवर्तनों को उजागर करने से इनकार करता है। खेल "10 अंतर ढूंढें", और मस्तिष्क तुरंत जो देखता है वह यह है कि दोनों उदाहरणों में शुरुआत और अंत समान हैं, आपको बीच में कहीं पढ़ने की ज़रूरत है... मेरी राय में, यह सिर्फ सिद्धांत में नहीं है, लेकिन विशुद्ध रूप से दृश्यात्मक रूप से गंदा और मैला.

वैसे, सिद्धांत की शुद्धता के बारे में। हमें बिट फ़ील्ड की आवश्यकता क्यों है? क्या ऐसा नहीं लगता कि वे गंध प्रकार सिद्धांत के दृष्टिकोण से बुरा? स्पष्टीकरण को आरेख के पुराने संस्करणों में देखा जा सकता है। सबसे पहले, हाँ, यह ऐसा ही था, प्रत्येक छींक के लिए एक नया प्रकार बनाया गया था। ये मूल बातें अभी भी इस रूप में मौजूद हैं, उदाहरण के लिए:

storage.fileUnknown#aa963b05 = storage.FileType;
storage.filePartial#40bc6f52 = storage.FileType;
storage.fileJpeg#7efe0e = storage.FileType;
storage.fileGif#cae1aadf = storage.FileType;
storage.filePng#a4f63c0 = storage.FileType;
storage.filePdf#ae1e508d = storage.FileType;
storage.fileMp3#528a0677 = storage.FileType;
storage.fileMov#4b09ebbc = storage.FileType;
storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;

लेकिन अब कल्पना करें, यदि आपकी संरचना में 5 वैकल्पिक फ़ील्ड हैं, तो आपको सभी संभावित विकल्पों के लिए 32 प्रकारों की आवश्यकता होगी। संयुक्त विस्फोट. इस प्रकार, टीएल सिद्धांत की क्रिस्टल शुद्धता एक बार फिर क्रमबद्धता की कठोर वास्तविकता के कच्चे लोहे के गधे के खिलाफ बिखर गई।

इसके अलावा, कुछ स्थानों पर ये लोग स्वयं अपनी टाइपोलॉजी का उल्लंघन करते हैं। उदाहरण के लिए, 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 से शुरू हुईं। दस्तावेज़ हमें एक विशेष टीएल सुविधा के बारे में बताता है:

यदि कोई क्लाइंट लेयर 2 का समर्थन करता है, तो निम्नलिखित कंस्ट्रक्टर का उपयोग किया जाना चाहिए:

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

व्यवहार में, इसका मतलब है कि प्रत्येक एपीआई कॉल से पहले, मान के साथ एक int 0x289dd1f6 विधि संख्या से पहले जोड़ा जाना चाहिए।

सामान्य लगता है. लेकिन आगे क्या हुआ? फिर प्रकट हुए

invokeWithLayer3#b7475268 query:!X = X;

तो आगे क्या है? जैसा कि आप अनुमान लगा सकते हैं,

invokeWithLayer4#dea0d430 query:!X = X;

मज़ेदार? नहीं, अभी हंसना जल्दबाजी होगी, इस तथ्य के बारे में सोचें प्रत्येक किसी अन्य परत के अनुरोध को ऐसे विशेष प्रकार में लपेटने की आवश्यकता है - यदि वे सभी आपके लिए अलग हैं, तो आप उन्हें और कैसे अलग कर सकते हैं? और सामने केवल 4 बाइट्स जोड़ना एक बहुत ही कुशल तरीका है। इसलिए,

invokeWithLayer5#417a57ae query:!X = X;

लेकिन यह स्पष्ट है कि कुछ समय बाद यह एक प्रकार का बैचेनलिया बन जाएगा। और समाधान आया:

अद्यतन: परत 9 से प्रारंभ करते हुए, सहायक विधियाँ invokeWithLayerN के साथ ही प्रयोग किया जा सकता है initConnection

हुर्रे! 9 संस्करणों के बाद, हम आख़िरकार उस पर पहुँचे जो 80 के दशक में इंटरनेट प्रोटोकॉल में किया जाता था - कनेक्शन की शुरुआत में एक बार संस्करण पर सहमति!

तो आगे क्या है?..

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

लेकिन अब भी आप हंस सकते हैं. अन्य 9 परतों के बाद ही, संस्करण संख्या के साथ एक सार्वभौमिक कंस्ट्रक्टर अंततः जोड़ा गया, जिसे कनेक्शन की शुरुआत में केवल एक बार कॉल करने की आवश्यकता थी, और परतों का अर्थ गायब हो गया था, अब यह सिर्फ एक सशर्त संस्करण है, जैसे हर जगह। समस्या हल हो गई।

बिल्कुल?..

वसीली, [16.07.18 14:01] शुक्रवार को भी मैंने सोचा:
टेलीसर्वर बिना अनुरोध के ईवेंट भेजता है। अनुरोधों को InvokeWithLayer में लपेटा जाना चाहिए। सर्वर अद्यतनों को लपेटता नहीं है; प्रतिक्रियाओं और अद्यतनों को लपेटने के लिए कोई संरचना नहीं है।

वे। क्लाइंट उस लेयर को निर्दिष्ट नहीं कर सकता जिसमें वह अपडेट चाहता है

वादिम गोंचारोव, [16.07.18 14:02] क्या InvokeWithLayer सैद्धांतिक रूप से एक बैसाखी नहीं है?

वसीली, [16.07.18 14:02] यही एकमात्र तरीका है

वादिम गोंचारोव, [16.07.18 14:02] जिसका मूलतः मतलब सत्र की शुरुआत में परत पर सहमति होना चाहिए

वैसे, इसका तात्पर्य यह है कि क्लाइंट डाउनग्रेड प्रदान नहीं किया जाता है

अद्यतन, यानी प्रकार Updates योजना में, यह वही है जो सर्वर क्लाइंट को एपीआई अनुरोध के जवाब में नहीं भेजता है, बल्कि स्वतंत्र रूप से तब भेजता है जब कोई घटना घटती है। यह एक जटिल विषय है जिस पर किसी अन्य पोस्ट में चर्चा की जाएगी, लेकिन अभी यह जानना महत्वपूर्ण है कि क्लाइंट ऑफ़लाइन होने पर भी सर्वर अपडेट सहेजता है।

इस प्रकार, यदि आप लपेटने से इनकार करते हैं प्रत्येक के पैकेज के संस्करण को इंगित करने के लिए, यह तार्किक रूप से निम्नलिखित संभावित समस्याओं की ओर ले जाता है:

  • सर्वर क्लाइंट को यह बताने से पहले ही अपडेट भेज देता है कि वह किस संस्करण का समर्थन करता है
  • क्लाइंट को अपग्रेड करने के बाद मुझे क्या करना चाहिए?
  • कौन गारंटी देता हैप्रक्रिया के दौरान परत संख्या के बारे में सर्वर की राय नहीं बदलेगी?

क्या आपको लगता है कि यह पूरी तरह से सैद्धांतिक अटकलें हैं, और व्यवहार में ऐसा नहीं हो सकता, क्योंकि सर्वर सही ढंग से लिखा गया है (कम से कम, इसका अच्छी तरह से परीक्षण किया गया है)? हा! चाहे वह कैसा भी हो!

यह बिल्कुल वैसा ही है जैसा हमने अगस्त में देखा था। 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.

और फिर कई मेगाबाइट स्टैक ट्रेस (ठीक है, उसी समय लॉगिंग ठीक हो गई थी)। आख़िरकार, यदि आपके टीएल में कुछ पहचाना नहीं गया है, तो यह हस्ताक्षर द्वारा बाइनरी है, रेखा के नीचे सभी जाता है, डिकोड करना असंभव हो जाएगा। ऐसी स्थिति में आपको क्या करना चाहिए?

खैर, पहली बात जो किसी के भी मन में आती है वह है डिस्कनेक्ट करना और फिर से प्रयास करना। कोई सहायता नहीं की। हमने सीआरसी32 को गूगल किया - ये स्कीम 73 की वस्तुएं निकलीं, हालांकि हमने 82 पर काम किया। हम लॉग को ध्यान से देखते हैं - वहां दो अलग-अलग स्कीमों के पहचानकर्ता हैं!

शायद समस्या पूरी तरह से हमारे अनौपचारिक ग्राहक में है? नहीं, हमने टेलीग्राम डेस्कटॉप 1.2.17 (कई लिनक्स वितरणों में आपूर्ति किया गया संस्करण) लॉन्च किया है, यह अपवाद लॉग पर लिखता है: एमटीपी अनपेक्षित प्रकार आईडी # बी5223बी0एफ एमटीपीमैसेजमीडिया में पढ़ा गया...

टेलीग्राम के प्रोटोकॉल और संगठनात्मक दृष्टिकोण की आलोचना। भाग 1, तकनीकी: क्लाइंट को शुरुआत से लिखने का अनुभव - टीएल, एमटी

Google ने दिखाया कि इसी तरह की समस्या पहले भी एक अनौपचारिक ग्राहक के साथ हो चुकी थी, लेकिन तब संस्करण संख्याएँ और, तदनुसार, धारणाएँ भिन्न थीं...

तो हमें क्या करना चाहिए? वसीली और मैं अलग हो गए: उन्होंने सर्किट को 91 पर अपडेट करने की कोशिश की, मैंने कुछ दिन इंतजार करने और 73 पर प्रयास करने का फैसला किया। दोनों तरीकों ने काम किया, लेकिन चूंकि वे अनुभवजन्य हैं, इसलिए इस बात की कोई समझ नहीं है कि आपको कितने संस्करणों को ऊपर या नीचे की आवश्यकता है कूदना है, या आपको कितनी देर तक इंतजार करना होगा।

बाद में मैं स्थिति को पुन: उत्पन्न करने में सक्षम था: हम क्लाइंट को लॉन्च करते हैं, इसे बंद करते हैं, सर्किट को दूसरी परत पर पुन: संकलित करते हैं, पुनरारंभ करते हैं, समस्या को फिर से पकड़ते हैं, पिछले एक पर लौटते हैं - उफ़, सर्किट स्विचिंग की कोई मात्रा नहीं और क्लाइंट एक के लिए पुनरारंभ होता है कुछ मिनट मदद करेंगे. आपको विभिन्न परतों से डेटा संरचनाओं का मिश्रण प्राप्त होगा।

स्पष्टीकरण? जैसा कि आप विभिन्न अप्रत्यक्ष लक्षणों से अनुमान लगा सकते हैं, सर्वर में विभिन्न मशीनों पर विभिन्न प्रकार की कई प्रक्रियाएँ होती हैं। सबसे अधिक संभावना है, सर्वर जो "बफ़रिंग" के लिए ज़िम्मेदार है, उसने कतार में वही डाल दिया जो उसके वरिष्ठों ने उसे दिया था, और उन्होंने इसे उस योजना में दिया था जो पीढ़ी के समय मौजूद थी। और जब तक यह कतार "सड़ी" न हो जाए, तब तक इसके बारे में कुछ नहीं किया जा सकता था।

शायद... लेकिन यह एक भयानक बैसाखी है?!.. नहीं, पागल विचारों के बारे में सोचने से पहले, आइए आधिकारिक ग्राहकों के कोड को देखें। एंड्रॉइड संस्करण में हमें कोई टीएल पार्सर नहीं मिलता है, लेकिन हमें (डी) क्रमांकन के साथ एक भारी फ़ाइल (गिटहब इसे छूने से इंकार कर देता है) मिलती है। यहां कोड स्निपेट हैं:

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;

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

लेकिन बहुत हो गया. आइए उस प्रोटोकॉल पर चलते हैं जिसके ऊपर यह सारा क्रमांकन चलता है।

एमटीप्रोटो

तो चलिए खोलते हैं सामान्य विवरण и प्रोटोकॉल का विस्तृत विवरण और पहली चीज़ जिस पर हमारा ध्यान जाता है वह है शब्दावली। और हर चीज़ की प्रचुरता के साथ। सामान्य तौर पर, यह टेलीग्राम की एक मालिकाना विशेषता प्रतीत होती है - अलग-अलग जगहों पर चीजों को अलग-अलग कॉल करना, या एक शब्द के साथ अलग-अलग चीजों को कॉल करना, या इसके विपरीत (उदाहरण के लिए, उच्च-स्तरीय एपीआई में, यदि आप स्टिकर पैक देखते हैं, तो यह नहीं है) तुमने क्या सोचा)।

उदाहरण के लिए, "संदेश" और "सत्र" का मतलब सामान्य टेलीग्राम क्लाइंट इंटरफ़ेस की तुलना में यहां कुछ अलग है। खैर, संदेश के साथ सब कुछ स्पष्ट है, इसकी व्याख्या ओओपी शब्दों में की जा सकती है, या बस "पैकेट" शब्द कहा जा सकता है - यह एक निम्न, परिवहन स्तर है, इंटरफ़ेस में समान संदेश नहीं हैं, कई सेवा संदेश हैं . लेकिन सत्र... लेकिन सबसे पहले चीज़ें।

ट्रांसपोर्ट परत

पहली चीज़ है परिवहन. वे हमें 5 विकल्पों के बारे में बताएंगे:

  • टीसीपी
  • वैबसाइट
  • HTTPS पर वेबसोकेट
  • HTTP
  • HTTPS

वसीली, [15.06.18 15:04] यूडीपी परिवहन भी है, लेकिन यह प्रलेखित नहीं है

और टीसीपी तीन वेरिएंट में

पहला टीसीपी पर यूडीपी के समान है, प्रत्येक पैकेट में एक अनुक्रम संख्या और सीआरसी शामिल है
कार्ट पर दस्तावेज़ पढ़ना इतना दर्दनाक क्यों है?

ख़ैर, अब यह वहीं है टीसीपी पहले से ही 4 वेरिएंट में है:

  • संकुचित
  • मध्यवर्ती
  • गद्देदार मध्यवर्ती
  • पूर्ण

खैर, ठीक है, MTProxy के लिए पैडेड इंटरमीडिएट, इसे बाद में प्रसिद्ध घटनाओं के कारण जोड़ा गया था। लेकिन जब आप एक से काम चला सकते हैं तो दो और संस्करण (कुल तीन) क्यों? ये चारों अनिवार्य रूप से केवल मुख्य MTProto की लंबाई और पेलोड सेट करने के तरीके में भिन्न हैं, जिस पर आगे चर्चा की जाएगी:

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

आइए एब्रिज्ड की तुलना करें, जिसमें एक बाइट की लंबाई संभव है, इंटरमीडिएट के साथ, जो "किसी मामले में 4-बाइट डेटा संरेखण की आवश्यकता है" को उचित ठहराता है, जो काफी बकवास है। क्या, ऐसा माना जाता है कि टेलीग्राम प्रोग्रामर इतने अक्षम हैं कि वे सॉकेट से डेटा को संरेखित बफर में नहीं पढ़ सकते हैं? आपको अभी भी ऐसा करना होगा, क्योंकि पढ़ने से आपको कितनी भी संख्या में बाइट्स मिल सकती हैं (और उदाहरण के लिए, प्रॉक्सी सर्वर भी हैं...)। या दूसरी ओर, यदि हमारे पास अभी भी 16 बाइट्स के शीर्ष पर भारी पैडिंग है तो संक्षिप्त को ब्लॉक क्यों करें - 3 बाइट्स बचाएं कभी कभी ?

किसी को यह आभास होता है कि निकोलाई ड्यूरोव वास्तव में बिना किसी वास्तविक व्यावहारिक आवश्यकता के, नेटवर्क प्रोटोकॉल सहित पहियों को फिर से आविष्कार करना पसंद करते हैं।

अन्य परिवहन विकल्प, सहित। वेब और MTProxy, हम अभी विचार नहीं करेंगे, शायद किसी अन्य पोस्ट में, यदि कोई अनुरोध हो। इसी MTProxy के बारे में, आइए अब केवल यह याद रखें कि 2018 में रिलीज़ होने के तुरंत बाद, प्रदाताओं ने इसे तुरंत ब्लॉक करना सीख लिया, जिसका उद्देश्य था बायपास अवरोधनद्वारा पैकेज का आकार! और यह तथ्य भी कि सी में लिखा गया (वाल्टमैन द्वारा फिर से) एमटीप्रॉक्सी सर्वर लिनक्स विशिष्टताओं से अत्यधिक जुड़ा हुआ था, हालांकि इसकी बिल्कुल भी आवश्यकता नहीं थी (फिल कुलिन पुष्टि करेगा), और गो या नोड.जेएस में एक समान सर्वर होगा सौ से भी कम पंक्तियों में फिट।

लेकिन हम अनुभाग के अंत में अन्य मुद्दों पर विचार करने के बाद इन लोगों की तकनीकी साक्षरता के बारे में निष्कर्ष निकालेंगे। अभी के लिए, आइए OSI परत 5, सत्र पर चलते हैं - जिस पर उन्होंने MTProto सत्र रखा था।

कुंजियाँ, संदेश, सत्र, डिफी-हेलमैन

उन्होंने इसे वहां पूरी तरह से सही तरीके से नहीं रखा... एक सत्र वही सत्र नहीं है जो सक्रिय सत्र के तहत इंटरफ़ेस में दिखाई देता है। लेकिन क्रम में.

टेलीग्राम के प्रोटोकॉल और संगठनात्मक दृष्टिकोण की आलोचना। भाग 1, तकनीकी: क्लाइंट को शुरुआत से लिखने का अनुभव - टीएल, एमटी

इसलिए हमें ट्रांसपोर्ट लेयर से ज्ञात लंबाई की एक बाइट स्ट्रिंग प्राप्त हुई। यह या तो एक एन्क्रिप्टेड संदेश है या सादा पाठ है - यदि हम अभी भी महत्वपूर्ण समझौते के चरण में हैं और वास्तव में ऐसा कर रहे हैं। हम "कुंजी" नामक अवधारणाओं के किस समूह के बारे में बात कर रहे हैं? आइए इस मुद्दे को टेलीग्राम टीम के लिए ही स्पष्ट करें (मैं सुबह 4 बजे थके हुए दिमाग के साथ अपने स्वयं के दस्तावेज़ का अंग्रेजी से अनुवाद करने के लिए क्षमा चाहता हूं, कुछ वाक्यांशों को वैसे ही छोड़ना आसान था):

वहाँ दो संस्थाएँ कहलाती हैं सत्र - "वर्तमान सत्र" के अंतर्गत आधिकारिक ग्राहकों के यूआई में से एक, जहां प्रत्येक सत्र एक संपूर्ण डिवाइस/ओएस से मेल खाता है।
दूसरा - एमटीप्रोटो सत्र, जिसमें संदेश की अनुक्रम संख्या (निम्न-स्तरीय अर्थ में) है, और कौन सी विभिन्न टीसीपी कनेक्शनों के बीच रह सकता है। उदाहरण के लिए, फ़ाइल डाउनलोडिंग को तेज़ करने के लिए, एक ही समय में कई MTProto सत्र स्थापित किए जा सकते हैं।

इन दोनों के बीच सत्र एक अवधारणा है प्राधिकरण. पतित मामले में, हम ऐसा कह सकते हैं यूआई सत्र वैसा ही है जैसा कि प्राधिकरण, लेकिन अफ़सोस, सब कुछ जटिल है। आओ देखे:

  • नए डिवाइस पर उपयोगकर्ता पहले जेनरेट करता है प्रमाणन कुंजी और इसे खाते तक सीमित कर देता है, उदाहरण के लिए एसएमएस के माध्यम से - इसीलिए प्राधिकरण
  • यह पहले के अंदर हुआ एमटीप्रोटो सत्र, जो है session_id अपने अंदर.
  • इस चरण में, संयोजन प्राधिकरण и session_id बुलाया जा सकता है उदाहरण - यह शब्द कुछ ग्राहकों के दस्तावेज़ और कोड में दिखाई देता है
  • फिर, क्लाइंट खुल सकता है कई एमटीप्रोटो सत्र उसी के तहत प्रमाणन कुंजी - उसी डीसी को।
  • फिर, एक दिन ग्राहक को फ़ाइल का अनुरोध करना होगा एक और डी.सी - और इस डीसी के लिए एक नया डीसी तैयार किया जाएगा प्रमाणन कुंजी !
  • सिस्टम को यह सूचित करने के लिए कि यह कोई नया उपयोगकर्ता नहीं है जो पंजीकरण कर रहा है, बल्कि वही पंजीकरण कर रहा है प्राधिकरण (यूआई सत्र), क्लाइंट एपीआई कॉल का उपयोग करता है auth.exportAuthorization घर में डी.सी auth.importAuthorization नए डीसी में.
  • सब कुछ वैसा ही है, कई खुले हो सकते हैं एमटीप्रोटो सत्र (प्रत्येक का अपना है session_id) इस नए डीसी के तहत उसके प्रमाणन कुंजी.
  • अंततः, ग्राहक शायद परफेक्ट फॉरवर्ड सेक्रेसी चाहता हो। प्रत्येक प्रमाणन कुंजी यह था स्थायी कुंजी - प्रति डीसी - और ग्राहक कॉल कर सकता है auth.bindTempAuthKey इस्तेमाल के लिए अस्थायी प्रमाणन कुंजी - और फिर, केवल एक temp_auth_key प्रति डीसी, सभी के लिए सामान्य एमटीप्रोटो सत्र इस डीसी को.

ध्यान दें नमक (और भविष्य के लवण) भी एक पर है प्रमाणन कुंजी वे। सभी के बीच साझा किया गया एमटीप्रोटो सत्र उसी डीसी को.

"विभिन्न टीसीपी कनेक्शनों के बीच" का क्या मतलब है? तो इसका मतलब है कुछ इस तरह किसी वेबसाइट पर प्राधिकरण कुकी - यह किसी दिए गए सर्वर पर कई टीसीपी कनेक्शन बनाए रखती है (जीवित रहती है), लेकिन एक दिन यह खराब हो जाती है। केवल HTTP के विपरीत, MTProto में एक सत्र के भीतर संदेशों को क्रमिक रूप से क्रमांकित और पुष्टि की जाती है; यदि वे सुरंग में प्रवेश करते हैं, तो कनेक्शन टूट गया है - एक नया कनेक्शन स्थापित करने के बाद, सर्वर कृपया इस सत्र में वह सब कुछ भेजेगा जो उसने पिछले में वितरित नहीं किया था टीसीपी कनेक्शन.

हालाँकि, उपरोक्त जानकारी कई महीनों की जाँच के बाद संक्षेप में प्रस्तुत की गई है। इस बीच, क्या हम अपने क्लाइंट को शुरू से लागू कर रहे हैं? - चलिए शुरुआत पर वापस चलते हैं।

तो चलिए उत्पन्न करते हैं auth_key पर टेलीग्राम से डिफी-हेलमैन संस्करण. आइए दस्तावेज़ीकरण को समझने का प्रयास करें...

वसीली, [19.06.18 20:05] data_with_hash := SHA1(डेटा) + डेटा + (कोई यादृच्छिक बाइट्स); इस प्रकार कि लंबाई 255 बाइट्स के बराबर हो;
एन्क्रिप्टेड_डेटा := आरएसए(डेटा_विथ_हैश, सर्वर_पब्लिक_की); एक 255-बाइट लंबी संख्या (बड़ी एंडियन) को अपेक्षित मापांक पर अपेक्षित शक्ति तक बढ़ाया जाता है, और परिणाम 256-बाइट संख्या के रूप में संग्रहीत किया जाता है।

उनके पास कुछ डोप डीएच है

किसी स्वस्थ व्यक्ति का डीएच ऐसा नहीं लगता
dx में कोई दो सार्वजनिक कुंजियाँ नहीं हैं

खैर, अंत में इसे सुलझा लिया गया, लेकिन एक अवशेष रह गया - ग्राहक द्वारा किए गए काम का सबूत है कि वह संख्या को कारक करने में सक्षम था। DoS हमलों के विरुद्ध सुरक्षा का प्रकार. और आरएसए कुंजी का उपयोग केवल एक बार एक दिशा में किया जाता है, अनिवार्य रूप से एन्क्रिप्शन के लिए new_nonce. लेकिन जब यह साधारण सा दिखने वाला ऑपरेशन सफल हो जाएगा, तो आपको क्या सामना करना पड़ेगा?

वसीली, [20.06.18/00/26 XNUMX:XNUMX] मुझे अभी तक ऐपिड अनुरोध नहीं मिला है

मैंने यह अनुरोध डीएच को भेजा

और, ट्रांसपोर्ट डॉक में यह कहा गया है कि यह त्रुटि कोड के 4 बाइट्स के साथ प्रतिक्रिया दे सकता है। बस इतना ही

अच्छा, उसने मुझसे कहा -404, तो क्या हुआ?

तो मैंने उससे कहा: "इस तरह के फिंगरप्रिंट वाली सर्वर कुंजी के साथ एन्क्रिप्टेड अपनी बकवास को पकड़ो, मुझे डीएच चाहिए," और उसने बेवकूफी भरे 404 के साथ जवाब दिया।

आप इस सर्वर प्रतिक्रिया के बारे में क्या सोचेंगे? क्या करें? पूछने वाला कोई नहीं है (लेकिन उस पर दूसरे भाग में और अधिक)।

यहां सारा हित कटघरे में होता है

मेरे पास करने के लिए और कुछ नहीं है, मैंने बस संख्याओं को आगे-पीछे बदलने का सपना देखा है

दो 32 बिट संख्याएँ. मैंने उन्हें हर किसी की तरह पैक किया

लेकिन नहीं, इन दोनों को पहले बीई के रूप में पंक्ति में जोड़ने की जरूरत है

वादिम गोंचारोव, [20.06.18 15:49] और इस 404 के कारण?

वसीली, [20.06.18 15:49] हाँ!

वादिम गोंचारोव, [20.06.18 15:50] इसलिए मुझे समझ नहीं आता कि वह क्या "नहीं ढूंढ सका"

वसीली, [20.06.18 15:50] के बारे में

मुझे अभाज्य गुणनखंडों में ऐसा कोई अपघटन नहीं मिला)

हमने त्रुटि रिपोर्टिंग का प्रबंधन भी नहीं किया

वसीली, [20.06.18 20:18] ओह, एमडी5 भी है। पहले से ही तीन अलग-अलग हैश

कुंजी फ़िंगरप्रिंट की गणना इस प्रकार की जाती है:

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

SHA1 और sha2

तो चलिए डालते हैं auth_key डिफी-हेलमैन का उपयोग करके हमें आकार में 2048 बिट्स प्राप्त हुए। आगे क्या होगा? आगे हमें पता चला कि इस कुंजी के निचले 1024 बिट्स का किसी भी तरह से उपयोग नहीं किया गया है... लेकिन आइए अभी इस बारे में सोचें। इस चरण में, हमारे पास सर्वर के साथ एक साझा रहस्य है। टीएलएस सत्र का एक एनालॉग स्थापित किया गया है, जो एक बहुत महंगी प्रक्रिया है। लेकिन सर्वर को अभी भी कुछ नहीं पता कि हम कौन हैं! वास्तव में अभी तक नहीं। प्राधिकार. वे। यदि आपने "लॉगिन-पासवर्ड" के संदर्भ में सोचा था, जैसा कि आपने एक बार ICQ में किया था, या कम से कम "लॉगिन-कुंजी" के रूप में, जैसा कि SSH में (उदाहरण के लिए, कुछ gitlab/github पर)। हमें एक गुमनाम प्राप्त हुआ। यदि सर्वर हमें बताता है कि "ये फ़ोन नंबर किसी अन्य डीसी द्वारा सेवित हैं" तो क्या होगा? या फिर "आपका फ़ोन नंबर प्रतिबंधित है"? सबसे अच्छा तो हम यह कर सकते हैं कि चाबी को इस उम्मीद में अपने पास रखें कि यह उपयोगी होगी और तब तक खराब नहीं होगी।

वैसे, हमने इसे आरक्षण के साथ "प्राप्त" किया। उदाहरण के लिए, क्या हमें सर्वर पर भरोसा है? अगर यह नकली है तो क्या होगा? क्रिप्टोग्राफ़िक जाँच की आवश्यकता होगी:

वसीली, [21.06.18 17:53] वे मोबाइल ग्राहकों को प्रारंभिकता के लिए 2 केबिट नंबर की जांच करने की पेशकश करते हैं)

लेकिन यह बिल्कुल भी स्पष्ट नहीं है, नफीजोआ

वसीली, [21.06.18 18:02] दस्तावेज़ यह नहीं बताता कि यदि यह सरल नहीं हो तो क्या करना चाहिए

नहीं कहा. आइए देखें कि इस मामले में आधिकारिक एंड्रॉइड क्लाइंट क्या करता है? ए वही है (और हाँ, पूरी फ़ाइल दिलचस्प है) - जैसा कि वे कहते हैं, मैं इसे यहीं छोड़ दूँगा:

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

नहीं, निःसंदेह यह अभी भी वहाँ है कुछ किसी संख्या की आदिमता के लिए परीक्षण होते हैं, लेकिन व्यक्तिगत रूप से मुझे अब गणित का पर्याप्त ज्ञान नहीं है।

ठीक है, हमें मास्टर कुंजी मिल गई। लॉग इन करने के लिए, यानी अनुरोध भेजें, आपको एईएस का उपयोग करके आगे एन्क्रिप्शन करने की आवश्यकता है।

संदेश कुंजी को संदेश निकाय (सत्र, संदेश आईडी इत्यादि सहित) के SHA128 के 256 मध्य बिट्स के रूप में परिभाषित किया गया है, जिसमें पैडिंग बाइट्स भी शामिल हैं, जो प्राधिकरण कुंजी से लिए गए 32 बाइट्स द्वारा पूर्वबद्ध हैं।

वसीली, [22.06.18 14:08] औसत, कुतिया, बिट्स

एक मिल गया auth_key. सभी। उनसे परे... यह दस्तावेज़ से स्पष्ट नहीं है। ओपन सोर्स कोड का बेझिझक अध्ययन करें।

ध्यान दें कि MTProto 2.0 को 12 से 1024 बाइट्स पैडिंग की आवश्यकता होती है, फिर भी यह शर्त के अधीन है कि परिणामी संदेश की लंबाई 16 बाइट्स से विभाज्य होगी।

तो आपको कितनी पैडिंग जोड़नी चाहिए?

और हाँ, त्रुटि होने पर 404 भी है

यदि किसी ने दस्तावेज़ीकरण के आरेख और पाठ का ध्यानपूर्वक अध्ययन किया, तो उन्होंने देखा कि वहां कोई MAC नहीं है। और वह AES एक निश्चित IGE मोड में उपयोग किया जाता है जिसका उपयोग कहीं और नहीं किया जाता है। बेशक, वे इसके बारे में अपने FAQ में लिखते हैं... यहां, जैसे, संदेश कुंजी स्वयं भी डिक्रिप्टेड डेटा का SHA हैश है, जिसका उपयोग अखंडता की जांच करने के लिए किया जाता है - और बेमेल के मामले में, किसी कारण से दस्तावेज़ीकरण उन्हें चुपचाप अनदेखा करने की अनुशंसा की जाती है (लेकिन सुरक्षा के बारे में क्या, अगर वे हमें तोड़ दें तो क्या होगा?)।

मैं एक क्रिप्टोग्राफर नहीं हूं, शायद सैद्धांतिक दृष्टिकोण से इस मामले में इस मोड में कुछ भी गलत नहीं है। लेकिन उदाहरण के तौर पर टेलीग्राम डेस्कटॉप का उपयोग करके मैं स्पष्ट रूप से एक व्यावहारिक समस्या का नाम बता सकता हूं। यह स्थानीय कैश (इन सभी D877F783D5D3EF8C) को उसी तरह एन्क्रिप्ट करता है जैसे MTProto में संदेश (केवल इस मामले में संस्करण 1.0), यानी। पहले संदेश कुंजी, फिर डेटा स्वयं (और कहीं मुख्य बड़े से अलग)। auth_key 256 बाइट्स, जिसके बिना msg_key बेकार)। इसलिए, बड़ी फ़ाइलों पर समस्या ध्यान देने योग्य हो जाती है। अर्थात्, आपको डेटा की दो प्रतियां रखनी होंगी - एन्क्रिप्टेड और डिक्रिप्टेड। और अगर उदाहरण के लिए, मेगाबाइट्स, या स्ट्रीमिंग वीडियो हैं?.. सिफरटेक्स्ट के बाद मैक के साथ क्लासिक योजनाएं आपको इसे तुरंत प्रसारित करते हुए स्ट्रीम पढ़ने की अनुमति देती हैं। लेकिन MTProto के साथ आपको यह करना होगा पहले संपूर्ण संदेश को एन्क्रिप्ट या डिक्रिप्ट करें, उसके बाद ही उसे नेटवर्क या डिस्क पर स्थानांतरित करें। इसलिए, टेलीग्राम डेस्कटॉप के नवीनतम संस्करणों में कैश इन user_data एक अन्य प्रारूप का भी उपयोग किया जाता है - सीटीआर मोड में एईएस के साथ।

वसीली, [21.06.18 01:27] ओह, मुझे पता चला कि आईजीई क्या है: आईजीई मूल रूप से केर्बरोस के लिए "प्रमाणीकरण एन्क्रिप्शन मोड" का पहला प्रयास था। यह एक असफल प्रयास था (यह अखंडता सुरक्षा प्रदान नहीं करता है), और इसे हटाना पड़ा। यह काम करने वाले प्रमाणित एन्क्रिप्शन मोड के लिए 20 साल की खोज की शुरुआत थी, जो हाल ही में ओसीबी और जीसीएम जैसे मोड में समाप्त हुई।

और अब कार्ट पक्ष की ओर से तर्क:

निकोलाई डुरोव के नेतृत्व वाली टेलीग्राम के पीछे की टीम में छह एसीएम चैंपियन शामिल हैं, जिनमें से आधे गणित में पीएचडी हैं। MTProto के वर्तमान संस्करण को लॉन्च करने में उन्हें लगभग दो साल लग गए।

अजीब बात है. निचले स्तर पर दो साल

या आप बस टीएलएस ले सकते हैं

ठीक है, मान लीजिए कि हमने एन्क्रिप्शन और अन्य बारीकियां पूरी कर ली हैं। क्या अंततः टीएल में क्रमबद्ध अनुरोध भेजना और प्रतिक्रियाओं को डिसेरिएलाइज़ करना संभव है? तो आपको क्या और कैसे भेजना चाहिए? यहाँ, मान लीजिए, विधि initConnection, शायद यही है?

वसीली, [25.06.18 18:46] कनेक्शन आरंभ करता है और उपयोगकर्ता के डिवाइस और एप्लिकेशन पर जानकारी सहेजता है।

यह ऐप_आईडी, डिवाइस_मॉडल, सिस्टम_वर्जन, ऐप_वर्जन और लैंग_कोड स्वीकार करता है।

और कुछ प्रश्न

हमेशा की तरह दस्तावेज़ीकरण. खुले स्रोत का अध्ययन करने के लिए स्वतंत्र महसूस करें

यदि invokeWithLayer के साथ सब कुछ लगभग स्पष्ट था, तो यहाँ क्या गलत है? यह पता चला है, मान लीजिए कि हमारे पास - क्लाइंट के पास सर्वर से पूछने के लिए पहले से ही कुछ था - एक अनुरोध है जिसे हम भेजना चाहते थे:

वसीली, [25.06.18 19:13] कोड को देखते हुए, पहली कॉल इस बकवास में लपेटी गई है, और बकवास स्वयं invokewithlayer में लपेटी गई है

initConnection एक अलग कॉल क्यों नहीं हो सकता, लेकिन एक रैपर होना चाहिए? हां, जैसा कि यह निकला, इसे प्रत्येक सत्र की शुरुआत में हर बार किया जाना चाहिए, न कि एक बार, जैसा कि मुख्य कुंजी के साथ होता है। लेकिन! इसे किसी अनधिकृत उपयोगकर्ता द्वारा कॉल नहीं किया जा सकता है! अब हम उस स्थिति में पहुंच गए हैं जहां यह लागू है।' यह वाला दस्तावेज़ीकरण पृष्ठ - और यह हमें बताता है कि...

अनधिकृत उपयोगकर्ताओं के लिए एपीआई विधियों का केवल एक छोटा सा हिस्सा उपलब्ध है:

  • auth.sendCode
  • auth.resendCode
  • खाता.पासवर्ड प्राप्त करें
  • auth.checkPassword
  • auth.checkफ़ोन
  • auth.signUp
  • auth.signIn
  • auth.importAuthorization
  • मदद.getConfig
  • help.getNearestDc
  • help.getAppUpdate
  • help.getCdnConfig
  • langpack.getLangPack
  • langpack.getStrings
  • langpack.getDifference
  • langpack.getभाषाएँ
  • langpack.getभाषा

उनमें से सबसे पहले, auth.sendCode, और वह पोषित पहला अनुरोध है जिसमें हम api_id और api_hash भेजते हैं, और जिसके बाद हमें एक कोड के साथ एक एसएमएस प्राप्त होता है। और यदि हम गलत डीसी में हैं (उदाहरण के लिए, इस देश में टेलीफोन नंबर दूसरे द्वारा दिए जाते हैं), तो हमें वांछित डीसी की संख्या के साथ एक त्रुटि प्राप्त होगी। यह जानने के लिए कि आपको डीसी नंबर द्वारा किस आईपी पते से कनेक्ट करने की आवश्यकता है, हमारी सहायता करें help.getConfig. एक समय में केवल 5 प्रविष्टियाँ थीं, लेकिन 2018 की प्रसिद्ध घटनाओं के बाद यह संख्या काफी बढ़ गई है।

अब आइए याद रखें कि हम गुमनाम रूप से सर्वर पर इस चरण तक पहुंचे थे। क्या आईपी एड्रेस प्राप्त करना बहुत महंगा नहीं है? MTProto के अनएन्क्रिप्टेड हिस्से में ऐसा और अन्य ऑपरेशन क्यों नहीं किए जाते? मैंने आपत्ति सुनी: "हम यह कैसे सुनिश्चित कर सकते हैं कि यह आरकेएन नहीं है जो गलत पते के साथ जवाब देगा?" इसके लिए हमें याद है कि, सामान्य तौर पर, आधिकारिक ग्राहक RSA कुंजियाँ एम्बेडेड हैं, अर्थात। क्या आप बस कर सकते हैं? подписать यह जानकारी। दरअसल, यह पहले से ही ग्राहकों को अन्य चैनलों के माध्यम से प्राप्त होने वाली ब्लॉकिंग को बायपास करने की जानकारी के लिए किया जा रहा है (तार्किक रूप से, यह MTProto में ही नहीं किया जा सकता है; आपको यह भी जानना होगा कि कहां कनेक्ट करना है)।

ठीक है। ग्राहक प्राधिकरण के इस चरण में, हम अभी तक अधिकृत नहीं हैं और हमने अपना आवेदन पंजीकृत नहीं किया है। हम अभी केवल यह देखना चाहते हैं कि सर्वर किसी अनधिकृत उपयोगकर्ता के लिए उपलब्ध तरीकों पर क्या प्रतिक्रिया देता है। और यहां…

वसीली, [10.07.18 14:45] https://core.telegram.org/method/help.getConfig

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

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

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

योजना में प्रथम, द्वितीय आता है

Tdesktop स्कीमा में तीसरा मान है

हां, तब से, निश्चित रूप से, दस्तावेज़ीकरण अद्यतन किया गया है। हालाँकि यह जल्द ही फिर से अप्रासंगिक हो सकता है। एक नौसिखिया डेवलपर को कैसे पता होना चाहिए? शायद यदि आप अपना आवेदन पंजीकृत करते हैं, तो वे आपको सूचित करेंगे? वसीली ने ऐसा किया, लेकिन अफसोस, उन्होंने उसे कुछ भी नहीं भेजा (फिर से, हम इस बारे में दूसरे भाग में बात करेंगे)।

...आपने देखा कि हम पहले ही किसी तरह एपीआई में चले गए हैं, यानी। अगले स्तर तक, और MTProto विषय में कुछ छूट गया? कोई आश्चर्य नहीं:

वसीली, [28.06.18 02:04] मम, वे ई2ई पर कुछ एल्गोरिदम के माध्यम से खोजबीन कर रहे हैं

माउंटप्रोटो दोनों डोमेन के लिए एन्क्रिप्शन एल्गोरिदम और कुंजियों को परिभाषित करता है, साथ ही एक रैपर संरचना को भी परिभाषित करता है

लेकिन वे लगातार स्टैक के विभिन्न स्तरों को मिलाते रहते हैं, इसलिए यह हमेशा स्पष्ट नहीं होता है कि एमटीप्रोटो कहाँ समाप्त हुआ और अगला स्तर शुरू हुआ

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

MTProto थीम के बारे में ध्यान देने योग्य कुछ अन्य बातें हैं।

संदेश संदेश, msg_id, msg_seqno, पुष्टिकरण, गलत दिशा में पिंग और अन्य विशिष्टताएँ

आपको उनके बारे में जानने की आवश्यकता क्यों है? क्योंकि वे उच्च स्तर पर "रिसाव" करते हैं, और एपीआई के साथ काम करते समय आपको उनके बारे में पता होना चाहिए। आइए मान लें कि हमें 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 की वृद्धि के साथ यह किस तरह का सर्कस है?.. मुझे संदेह है कि शुरू में उनका मतलब था "एसीके के लिए सबसे कम महत्वपूर्ण बिट, बाकी एक संख्या है", लेकिन परिणाम बिल्कुल वैसा नहीं है - विशेष रूप से, यह बाहर आता है, भेजा जा सकता है कई पुष्टियाँ समान हैं seq_no! कैसे? ठीक है, उदाहरण के लिए, सर्वर हमें कुछ भेजता है, भेजता है, और हम स्वयं चुप रहते हैं, केवल उसके संदेशों की प्राप्ति की पुष्टि करने वाले सेवा संदेशों के साथ प्रतिक्रिया करते हैं। इस मामले में, हमारी आउटगोइंग पुष्टिकरणों में वही आउटगोइंग नंबर होगा। यदि आप टीसीपी से परिचित हैं और सोचते हैं कि यह किसी तरह से जंगली लगता है, लेकिन यह बहुत जंगली नहीं लगता है, क्योंकि टीसीपी में seq_no बदलता नहीं है, लेकिन पुष्टि हो जाती है seq_no दूसरी ओर, मैं तुम्हें परेशान करने में जल्दबाजी करूंगा। MTProto में पुष्टिकरण प्रदान किए गए हैं नहीं पर seq_no, जैसा कि टीसीपी में है, लेकिन द्वारा msg_id !

यह क्या है msg_id, इन क्षेत्रों में सबसे महत्वपूर्ण? जैसा कि नाम से पता चलता है, एक अद्वितीय संदेश पहचानकर्ता। इसे 64-बिट संख्या के रूप में परिभाषित किया गया है, जिनमें से सबसे कम बिट्स में फिर से "सर्वर-नहीं-सर्वर" जादू है, और बाकी एक यूनिक्स टाइमस्टैम्प है, जिसमें आंशिक भाग भी शामिल है, 32 बिट्स को बाईं ओर स्थानांतरित कर दिया गया है। वे। टाइमस्टैम्प प्रति से (और बहुत अधिक भिन्न समय वाले संदेशों को सर्वर द्वारा अस्वीकार कर दिया जाएगा)। इससे यह पता चलता है कि सामान्य तौर पर यह एक पहचानकर्ता है जो क्लाइंट के लिए वैश्विक है। यह देखते हुए - आइए याद रखें session_id - हमें गारंटी है: किसी भी परिस्थिति में एक सत्र के लिए भेजा गया संदेश दूसरे सत्र में नहीं भेजा जा सकता. यही है, यह पता चला है कि वहाँ पहले से ही है तीन स्तर - सत्र, सत्र संख्या, संदेश आईडी। इतनी अधिक जटिलता क्यों, यह रहस्य बहुत बड़ा है।

इस प्रकार, msg_id के लिए चाहिए...

आरपीसी: अनुरोध, प्रतिक्रियाएँ, त्रुटियाँ। पुष्टिकरण।

जैसा कि आपने देखा होगा, आरेख में कहीं भी कोई विशेष "आरपीसी अनुरोध करें" प्रकार या फ़ंक्शन नहीं है, हालांकि उत्तर मौजूद हैं। आख़िरकार, हमारे पास सामग्री-संबंधित संदेश हैं! वह है, कोई संदेश एक अनुरोध हो सकता है! या न होना। आख़िरकार, प्रत्येक के वहाँ है msg_id. लेकिन उत्तर हैं:

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

यहीं पर यह दर्शाया जाता है कि यह किस संदेश का उत्तर है। इसलिए, एपीआई के शीर्ष स्तर पर, आपको यह याद रखना होगा कि आपके अनुरोध की संख्या क्या थी - मुझे लगता है कि यह समझाने की कोई आवश्यकता नहीं है कि कार्य अतुल्यकालिक है, और एक ही समय में कई अनुरोध प्रगति पर हो सकते हैं, जिनके उत्तर किसी भी क्रम में लौटाए जा सकते हैं? सिद्धांत रूप में, इससे और कोई कर्मचारी न होने जैसे त्रुटि संदेशों से, इसके पीछे की वास्तुकला का पता लगाया जा सकता है: सर्वर जो आपके साथ टीसीपी कनेक्शन बनाए रखता है वह एक फ्रंट-एंड बैलेंसर है, यह बैकएंड के लिए अनुरोधों को अग्रेषित करता है और उन्हें वापस एकत्र करता है message_id. ऐसा लगता है कि यहां सब कुछ स्पष्ट, तार्किक और अच्छा है।

हाँ?.. और यदि आप इसके बारे में सोचते हैं? आख़िरकार, RPC प्रतिक्रिया का भी एक क्षेत्र होता है msg_id! क्या हमें सर्वर पर चिल्लाने की ज़रूरत है "आप मेरे उत्तर का उत्तर नहीं दे रहे हैं!"? और हाँ, पुष्टिकरण के बारे में क्या था? पेज के बारे में संदेशों के बारे में संदेश हमें बताता है कि क्या है

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

और यह प्रत्येक पक्ष द्वारा किया जाना चाहिए। लेकिन हमेशा नहीं! यदि आपको RpcResult प्राप्त हुआ है, तो यह स्वयं पुष्टि के रूप में कार्य करता है। यानी, सर्वर आपके अनुरोध का जवाब MsgsAck के साथ दे सकता है - जैसे, "मुझे यह प्राप्त हुआ।" RpcResult तुरंत प्रतिक्रिया दे सकता है। यह दोनों हो सकता है.

और हाँ, आपको अभी भी उत्तर देना होगा! पुष्टि. अन्यथा, सर्वर इसे डिलीवर करने योग्य नहीं मानेगा और आपको दोबारा वापस भेज देगा। दोबारा जुड़ने के बाद भी. लेकिन यहाँ, निश्चित रूप से, टाइमआउट का मुद्दा उठता है। आइए उन पर थोड़ी देर बाद नजर डालें।

इस बीच, आइए संभावित क्वेरी निष्पादन त्रुटियों पर नजर डालें।

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

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

फिर, यह सेवा संदेश पृष्ठ पर नहीं है, लेकिन, जैसा कि इस परियोजना में पहले से ही सामान्य है, जानकारी पाई जा सकती है किसी अन्य दस्तावेज़ पृष्ठ पर। या संदेह पैदा करो. सबसे पहले, देखें, टाइपिंग/लेयर उल्लंघन - RpcError में घोंसला बनाया जा सकता है RpcResult. बाहर क्यों नहीं? हमने क्या ध्यान में नहीं रखा?.. तदनुसार, इसकी गारंटी कहां है RpcError में अंतर्निहित नहीं किया जा सकता RpcResult, लेकिन सीधे तौर पर या किसी अन्य प्रकार में नेस्टेड हो?.. और यदि यह नहीं हो सकता है, तो यह शीर्ष स्तर पर क्यों नहीं है, यानी यह गायब है req_msg_id ? ..

लेकिन आइए सेवा संदेशों के बारे में जारी रखें। क्लाइंट सोच सकता है कि सर्वर लंबे समय से सोच रहा है और यह अद्भुत अनुरोध कर सकता है:

rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;

इस प्रश्न के तीन संभावित उत्तर हैं, जो फिर से पुष्टिकरण तंत्र के साथ प्रतिच्छेद करते हैं; यह समझने की कोशिश करना कि उन्हें क्या होना चाहिए (और उन प्रकारों की सामान्य सूची जिन्हें पुष्टि की आवश्यकता नहीं है) को होमवर्क के रूप में पाठक पर छोड़ दिया गया है (नोट: इसमें दी गई जानकारी टेलीग्राम डेस्कटॉप स्रोत कोड पूरा नहीं है)।

नशीली दवाओं की लत: संदेश स्थितियाँ

सामान्य तौर पर, टीएल, एमटीप्रोटो और टेलीग्राम में कई स्थान सामान्य तौर पर हठ की भावना छोड़ते हैं, लेकिन विनम्रता, चातुर्य और अन्य के कारण सॉफ्ट स्किल्स हमने विनम्रतापूर्वक इस पर चुप्पी साध ली और संवादों में मौजूद अश्लीलता को सेंसर कर दिया। हालाँकि, यह जगहОअधिकांश पृष्ठ इसके बारे में है संदेशों के बारे में संदेश यह मेरे लिए भी चौंकाने वाला है, जो लंबे समय से नेटवर्क प्रोटोकॉल के साथ काम कर रहा है और अलग-अलग डिग्री की टेढ़ी-मेढ़ी साइकिलें देखी हैं।

इसकी शुरुआत सहजता से, पुष्टि के साथ होती है। आगे वे हमें इसके बारे में बताते हैं

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 = संदेश में निहित आरपीसी क्वेरी संसाधित हो रही है या प्रसंस्करण पहले ही पूरा हो चुका है
    • +64 = संदेश पर सामग्री-संबंधी प्रतिक्रिया पहले ही उत्पन्न हो चुकी है
    • +128 = दूसरे पक्ष को इस तथ्य के बारे में पता है कि संदेश पहले ही प्राप्त हो चुका है
      इस प्रतिक्रिया के लिए पावती की आवश्यकता नहीं है. यह अपने आप में प्रासंगिक msgs_state_req की स्वीकृति है।
      ध्यान दें कि यदि अचानक पता चलता है कि दूसरे पक्ष के पास ऐसा कोई संदेश नहीं है जिससे लगे कि उसे भेजा गया है, तो संदेश को आसानी से दोबारा भेजा जा सकता है। भले ही दूसरे पक्ष को एक ही समय में संदेश की दो प्रतियां प्राप्त हों, डुप्लिकेट को नजरअंदाज कर दिया जाएगा। (यदि बहुत अधिक समय बीत चुका है, और मूल संदेश_आईडी अब मान्य नहीं है, तो संदेश को संदेश_कॉपी में लपेटा जाना चाहिए)।
  • संदेशों की स्थिति का स्वैच्छिक संचार
    कोई भी पक्ष स्वेच्छा से दूसरे पक्ष को दूसरे पक्ष द्वारा प्रेषित संदेशों की स्थिति के बारे में सूचित कर सकता है।
    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;
    एक बार प्राप्त होने के बाद, संदेश को ऐसे संसाधित किया जाता है जैसे कि रैपर वहां था ही नहीं। हालाँकि, यदि यह निश्चित रूप से ज्ञात है कि संदेश उत्पत्ति_message.msg_id प्राप्त हुआ था, तो नया संदेश संसाधित नहीं होता है (जबकि उसी समय, यह और उत्पत्ति_message.msg_id को स्वीकार किया जाता है)। Orig_message.msg_id का मान कंटेनर के msg_id से कम होना चाहिए।

चलो चुप भी रहें किस बात पर msgs_state_info फिर से अधूरे टीएल के कान बाहर चिपके हुए हैं (हमें बाइट्स के एक वेक्टर की आवश्यकता थी, और निचले दो बिट्स में एक एनम था, और ऊपरी दो बिट्स में झंडे थे)। बात अलग है. क्या कोई समझता है कि यह सब चलन में क्यों है? एक वास्तविक ग्राहक में आवश्यक?.. कठिनाई के साथ, लेकिन कोई कुछ लाभ की कल्पना कर सकता है यदि कोई व्यक्ति डिबगिंग में लगा हुआ है, और एक इंटरैक्टिव मोड में - सर्वर से पूछें कि क्या और कैसे। लेकिन यहां अनुरोधों का वर्णन किया गया है राउंड ट्रिप.

इसका तात्पर्य यह है कि प्रत्येक पक्ष को न केवल एन्क्रिप्ट करना होगा और संदेश भेजना होगा, बल्कि अज्ञात समय के लिए अपने बारे में, उन पर प्रतिक्रियाओं के बारे में डेटा भी संग्रहीत करना होगा। दस्तावेज़ीकरण में इन सुविधाओं के समय या व्यावहारिक प्रयोज्यता का वर्णन नहीं किया गया है। किसी तरह भी नहीं. सबसे आश्चर्यजनक बात यह है कि इनका उपयोग वास्तव में आधिकारिक ग्राहकों के कोड में किया जाता है! जाहिर तौर पर उन्हें कुछ ऐसा बताया गया जो सार्वजनिक दस्तावेज़ में शामिल नहीं था। कोड से समझें क्यों, अब टीएल के मामले में उतना सरल नहीं है - यह (अपेक्षाकृत) तार्किक रूप से अलग-थलग हिस्सा नहीं है, बल्कि एप्लिकेशन आर्किटेक्चर से जुड़ा एक टुकड़ा है, यानी। एप्लिकेशन कोड को समझने के लिए काफी अधिक समय की आवश्यकता होगी।

पिंग और समय. कतारें.

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

और सबसे पहले - संदेश कतारें। खैर, एक बात तो शुरू से ही स्पष्ट थी - एक अपुष्ट संदेश को संग्रहित किया जाना चाहिए और पुनः भेजा जाना चाहिए। इसमें कितना समय लगेगा? और विदूषक उसे जानता है. शायद वे आदी सेवा संदेश किसी तरह इस समस्या को बैसाखी के साथ हल करते हैं, कहते हैं, टेलीग्राम डेस्कटॉप में उनके अनुरूप लगभग 4 कतारें हैं (शायद अधिक, जैसा कि पहले ही उल्लेख किया गया है, इसके लिए आपको इसके कोड और वास्तुकला में अधिक गंभीरता से विचार करने की आवश्यकता है; साथ ही) समय, हम जानते हैं कि इसे एक नमूने के रूप में नहीं लिया जा सकता है; इसमें MTProto योजना से कुछ निश्चित प्रकार का उपयोग नहीं किया जाता है)।

ऐसा क्यों हो रहा है? संभवतः, सर्वर प्रोग्रामर क्लस्टर के भीतर विश्वसनीयता सुनिश्चित करने, या यहां तक ​​कि फ्रंट बैलेंसर पर बफरिंग करने में असमर्थ थे, और इस समस्या को क्लाइंट को स्थानांतरित कर दिया। निराशा से बाहर, वसीली ने टीसीपी से एल्गोरिदम का उपयोग करके, केवल दो कतारों के साथ एक वैकल्पिक विकल्प लागू करने की कोशिश की - सर्वर पर आरटीटी को मापना और अपुष्ट अनुरोधों की संख्या के आधार पर "विंडो" (संदेशों में) के आकार को समायोजित करना। अर्थात्, सर्वर के लोड का आकलन करने के लिए इतना मोटा अनुमान यह है कि यह हमारे कितने अनुरोधों को एक ही समय में चबा सकता है और खो नहीं सकता है।

ठीक है, यानी, आप समझते हैं, है ना? यदि आपको टीसीपी पर चल रहे प्रोटोकॉल के शीर्ष पर टीसीपी को फिर से लागू करना है, तो यह बहुत खराब तरीके से डिज़ाइन किए गए प्रोटोकॉल को इंगित करता है।

अरे हाँ, आपको एक से अधिक कतार की आवश्यकता क्यों है, और उच्च-स्तरीय एपीआई के साथ काम करने वाले व्यक्ति के लिए इसका क्या मतलब है? देखिए, आप कोई अनुरोध करते हैं, उसे क्रमबद्ध करते हैं, लेकिन अक्सर आप उसे तुरंत नहीं भेज पाते। क्यों? क्योंकि उत्तर होगा msg_id, जो अस्थायी हैаमैं एक लेबल हूं, जिसके असाइनमेंट को यथासंभव देर तक स्थगित करना सबसे अच्छा है - यदि सर्वर हमारे और उसके बीच समय के बेमेल के कारण इसे अस्वीकार कर देता है (बेशक, हम एक ऐसी बैसाखी बना सकते हैं जो हमारे समय को वर्तमान से बदल देती है) सर्वर की प्रतिक्रियाओं से गणना किए गए डेल्टा को जोड़कर सर्वर पर - आधिकारिक ग्राहक ऐसा करते हैं, लेकिन बफरिंग के कारण यह कच्चा और गलत है)। इसलिए, जब आप लाइब्रेरी से स्थानीय फ़ंक्शन कॉल के साथ अनुरोध करते हैं, तो संदेश निम्नलिखित चरणों से गुजरता है:

  1. यह एक कतार में है और एन्क्रिप्शन की प्रतीक्षा कर रहा है।
  2. नियुक्त msg_id और संदेश दूसरी कतार में चला गया - अग्रेषण संभव; सॉकेट को भेजें.
  3. ए) सर्वर ने MsgsAck को जवाब दिया - संदेश वितरित किया गया था, हम इसे "अन्य कतार" से हटा देते हैं।
    बी) या इसके विपरीत, उसे कुछ पसंद नहीं आया, उसने बैडएमएसजी का उत्तर दिया - "दूसरी कतार" से पुनः भेजें
    ग) कुछ भी ज्ञात नहीं है, संदेश को दूसरी कतार से पुनः भेजने की आवश्यकता है - लेकिन यह ठीक से ज्ञात नहीं है कि कब।
  4. अंततः सर्वर ने जवाब दे दिया RpcResult - वास्तविक प्रतिक्रिया (या त्रुटि) - न केवल वितरित की गई, बल्कि संसाधित भी की गई।

शायदकंटेनरों के उपयोग से समस्या आंशिक रूप से हल हो सकती है। ऐसा तब होता है जब संदेशों का एक समूह एक में पैक किया जाता है, और सर्वर उन सभी को एक ही बार में पुष्टिकरण के साथ जवाब देता है msg_id. लेकिन अगर कुछ गलत हुआ तो वह इस पैक को भी पूरी तरह से अस्वीकार कर देगा।

और इस बिंदु पर गैर-तकनीकी विचार चलन में आते हैं। अनुभव से, हमने कई बैसाखियाँ देखी हैं, और इसके अलावा, अब हम बुरी सलाह और वास्तुकला के और भी उदाहरण देखेंगे - ऐसी स्थितियों में, क्या भरोसा करना और ऐसे निर्णय लेना उचित है? प्रश्न अलंकारिक है (निश्चित रूप से नहीं)।

हम किस बारे में बात कर रहे हैं? यदि "संदेशों के बारे में नशीली दवाओं के संदेश" के विषय पर आप अभी भी "आप मूर्ख हैं, आप हमारी शानदार योजना को समझ नहीं पाए!" जैसी आपत्तियों के साथ अनुमान लगा सकते हैं! (इसलिए पहले दस्तावेज लिखें, जैसा कि सामान्य लोगों को करना चाहिए, तर्क और पैकेट एक्सचेंज के उदाहरणों के साथ, फिर हम बात करेंगे), फिर समय/समयबाह्य एक पूरी तरह से व्यावहारिक और विशिष्ट प्रश्न है, यहां सब कुछ लंबे समय से ज्ञात है। दस्तावेज़ हमें टाइमआउट के बारे में क्या बताता है?

एक सर्वर आमतौर पर RPC प्रतिक्रिया का उपयोग करके क्लाइंट (सामान्य रूप से, एक RPC क्वेरी) से एक संदेश की प्राप्ति स्वीकार करता है। यदि कोई प्रतिक्रिया आने में काफी समय लगता है, तो सर्वर पहले एक रसीद पावती भेज सकता है, और कुछ देर बाद, आरपीसी प्रतिक्रिया स्वयं भेज सकता है।

एक क्लाइंट आम तौर पर सर्वर से एक संदेश की प्राप्ति (आमतौर पर, एक आरपीसी प्रतिक्रिया) को अगली आरपीसी क्वेरी में एक पावती जोड़कर स्वीकार करता है यदि यह बहुत देर से प्रसारित नहीं होता है (यदि यह उत्पन्न होता है, तो रसीद के 60-120 सेकंड बाद उत्पन्न होता है) सर्वर से एक संदेश का)। हालाँकि, यदि लंबे समय तक सर्वर पर संदेश भेजने का कोई कारण नहीं है या यदि सर्वर से बड़ी संख्या में अस्वीकृत संदेश हैं (जैसे, 16 से अधिक), तो क्लाइंट एक स्टैंड-अलोन पावती प्रसारित करता है।

... मैं अनुवाद करता हूं: हम खुद नहीं जानते कि हमें इसकी कितनी और कैसे जरूरत है, तो मान लीजिए कि इसे ऐसे ही रहने दें।

और पिंग्स के बारे में:

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

ping#7abe77ec ping_id:long = Pong;

प्रतिक्रिया आमतौर पर उसी कनेक्शन पर लौटाई जाती है:

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

इन संदेशों को पावती की आवश्यकता नहीं है. पोंग केवल पिंग के जवाब में प्रसारित होता है जबकि पिंग दोनों ओर से शुरू किया जा सकता है।

आस्थगित कनेक्शन समापन + पिंग

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

पिंग की तरह काम करता है. इसके अलावा, इसके प्राप्त होने के बाद, सर्वर एक टाइमर शुरू करता है जो वर्तमान कनेक्शन को डिस्कनेक्ट_डेले सेकंड बाद बंद कर देगा जब तक कि उसे उसी प्रकार का एक नया संदेश प्राप्त न हो जाए जो स्वचालित रूप से सभी पिछले टाइमर को रीसेट कर देता है। उदाहरण के लिए, यदि क्लाइंट हर 60 सेकंड में एक बार ये पिंग भेजता है, तो यह 75 सेकंड के बराबर डिस्कनेक्ट_डेले सेट कर सकता है।

क्या तुम पागल हो?! 60 सेकंड में, ट्रेन स्टेशन में प्रवेश करेगी, यात्रियों को उतारेगी और उठाएगी, और सुरंग में फिर से संपर्क खो देगी। 120 सेकंड में, जब आप इसे सुनेंगे, यह दूसरे तक पहुंच जाएगा, और कनेक्शन संभवतः टूट जाएगा। खैर, यह स्पष्ट है कि पैर कहाँ से आ रहे हैं - "मैंने एक बजने की आवाज़ सुनी, लेकिन मुझे नहीं पता कि यह कहाँ है", नागल का एल्गोरिदम और टीसीपी_NODELAY विकल्प है, जो इंटरैक्टिव काम के लिए है। लेकिन, क्षमा करें, इसके डिफ़ॉल्ट मान - 200 को बनाए रखें मिलीसेकंड यदि आप वास्तव में कुछ इसी तरह का चित्रण करना चाहते हैं और कुछ संभावित पैकेटों को सहेजना चाहते हैं, तो इसे 5 सेकंड के लिए बंद कर दें, या जो भी "उपयोगकर्ता टाइप कर रहा है..." संदेश का समय समाप्त हो गया है। लेकिन और नहीं।

और अंत में, पिंग्स। यानी टीसीपी कनेक्शन की जीवंतता की जांच करना। यह हास्यास्पद है, लेकिन लगभग 10 साल पहले मैंने हमारे संकाय के छात्रावास के दूत के बारे में एक आलोचनात्मक पाठ लिखा था - वहां के लेखकों ने क्लाइंट से सर्वर को पिंग भी किया था, न कि इसके विपरीत। लेकिन तीसरे वर्ष के छात्र एक चीज़ हैं, और एक अंतरराष्ट्रीय कार्यालय दूसरी चीज़ है, है ना?..

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

चैट/आईएम सिस्टम एक अतिरिक्त कारण से दूसरे मामले में आते हैं - ऑनलाइन स्टेटस। यदि उपयोगकर्ता "गिर गया", तो आपको उसके वार्ताकारों को इस बारे में सूचित करना होगा। अन्यथा, आप उस गलती के साथ समाप्त हो जाएंगे जो जैबर के रचनाकारों ने की थी (और 20 वर्षों के लिए सही किया गया था) - उपयोगकर्ता ने डिस्कनेक्ट कर दिया है, लेकिन वे उसे संदेश लिखना जारी रखते हैं, यह मानते हुए कि वह ऑनलाइन है (जो इनमें पूरी तरह से खो गए थे) डिस्कनेक्ट का पता चलने से कुछ मिनट पहले)। नहीं, TCP_KEEPALIVE विकल्प, जो बहुत से लोग यह नहीं समझते कि टीसीपी टाइमर कैसे काम करते हैं, उन्हें बेतरतीब ढंग से (दसियों सेकंड जैसे जंगली मान सेट करके) फेंक देते हैं, यहां मदद नहीं करेगा - आपको यह सुनिश्चित करने की ज़रूरत है कि न केवल ओएस कर्नेल उपयोगकर्ता की मशीन जीवित है, लेकिन सामान्य रूप से काम कर रही है, प्रतिक्रिया देने में सक्षम है, और एप्लिकेशन स्वयं (क्या आपको लगता है कि यह फ्रीज नहीं हो सकता? उबंटू 18.04 पर टेलीग्राम डेस्कटॉप मेरे लिए एक से अधिक बार फ्रीज हुआ)।

इसलिए आपको पिंग करना होगा सेवक क्लाइंट, और इसके विपरीत नहीं - यदि क्लाइंट ऐसा करता है, यदि कनेक्शन टूट जाता है, तो पिंग वितरित नहीं किया जाएगा, लक्ष्य प्राप्त नहीं किया जाएगा।

हम टेलीग्राम पर क्या देखते हैं? यह बिल्कुल विपरीत है! ख़ैर, वह तो है। बेशक, औपचारिक रूप से, दोनों पक्ष एक-दूसरे को पिंग कर सकते हैं। व्यवहार में, ग्राहक बैसाखी का उपयोग करते हैं ping_delay_disconnect, जो सर्वर पर टाइमर सेट करता है। ठीक है, क्षमा करें, यह ग्राहक पर निर्भर नहीं है कि वह यह तय करे कि वह कितने समय तक पिंग के बिना वहां रहना चाहता है। सर्वर, अपने लोड के आधार पर, बेहतर जानता है। लेकिन, निःसंदेह, यदि आपको संसाधनों की परवाह नहीं है, तो आप अपने स्वयं के दुष्ट पिनोचियो होंगे, और एक बैसाखी काम करेगी...

इसे कैसे डिज़ाइन किया जाना चाहिए था?

मेरा मानना ​​है कि उपरोक्त तथ्य स्पष्ट रूप से इंगित करते हैं कि टेलीग्राम/VKontakte टीम कंप्यूटर नेटवर्क के परिवहन (और निचले) स्तर के क्षेत्र में बहुत सक्षम नहीं है और प्रासंगिक मामलों में उनकी कम योग्यता है।

यह इतना जटिल क्यों हो गया, और टेलीग्राम आर्किटेक्ट कैसे आपत्ति करने की कोशिश कर सकते हैं? तथ्य यह है कि उन्होंने एक सत्र बनाने की कोशिश की जो टीसीपी कनेक्शन टूटने से बच गया, यानी, जो अभी वितरित नहीं किया गया था, हम बाद में वितरित करेंगे। उन्होंने शायद यूडीपी परिवहन बनाने की भी कोशिश की, लेकिन उन्हें कठिनाइयों का सामना करना पड़ा और उन्होंने इसे छोड़ दिया (यही कारण है कि दस्तावेज़ खाली है - इसमें डींग मारने की कोई बात नहीं थी)। लेकिन सामान्य रूप से नेटवर्क और विशेष रूप से टीसीपी कैसे काम करते हैं, इसकी समझ की कमी के कारण, आप इस पर कहां भरोसा कर सकते हैं, और आपको इसे स्वयं कहां (और कैसे) करने की आवश्यकता है, और इसे क्रिप्टोग्राफी के साथ संयोजित करने का प्रयास "दो पक्षियों के साथ" एक पत्थर”, यह परिणाम है.

यह कैसे आवश्यक था? इस तथ्य पर आधारित है कि msg_id रीप्ले हमलों को रोकने के लिए क्रिप्टोग्राफ़िक दृष्टिकोण से आवश्यक टाइमस्टैम्प है, इसमें एक विशिष्ट पहचानकर्ता फ़ंक्शन संलग्न करना एक गलती है। इसलिए, वर्तमान आर्किटेक्चर को मौलिक रूप से बदले बिना (जब अपडेट स्ट्रीम उत्पन्न होती है, तो यह पोस्ट की इस श्रृंखला के दूसरे भाग के लिए एक उच्च-स्तरीय एपीआई विषय है), किसी को यह करना होगा:

  1. क्लाइंट से टीसीपी कनेक्शन रखने वाला सर्वर ज़िम्मेदारी लेता है - यदि उसने सॉकेट से पढ़ा है, तो कृपया त्रुटि स्वीकार करें, संसाधित करें या लौटाएँ, कोई नुकसान नहीं। तब पुष्टिकरण आईडी का एक वेक्टर नहीं है, बल्कि बस "अंतिम प्राप्त seq_no" है - बस एक संख्या, जैसे टीसीपी में (दो नंबर - आपका seq और पुष्टि की गई)। हम हमेशा सत्र के भीतर हैं, है ना?
  2. रीप्ले हमलों को रोकने के लिए टाइमस्टैम्प एक अलग फ़ील्ड बन जाता है, एक ला नॉन। इसकी जाँच की जाती है, लेकिन इसका किसी अन्य चीज़ पर कोई प्रभाव नहीं पड़ता है। बहुत हो गया और uint32 - यदि हमारा नमक कम से कम हर आधे दिन में बदलता है, तो हम वर्तमान समय के पूर्णांक भाग के निम्न-क्रम वाले बिट्स को 16 बिट्स आवंटित कर सकते हैं, बाकी - एक सेकंड के आंशिक भाग को (अभी की तरह)।
  3. निकाला गया msg_id बिल्कुल - बैकएंड पर अनुरोधों को अलग करने के दृष्टिकोण से, सबसे पहले, क्लाइंट आईडी, और दूसरी, सत्र आईडी, उन्हें संयोजित करती है। तदनुसार, अनुरोध पहचानकर्ता के रूप में केवल एक ही चीज़ पर्याप्त है seq_no.

यह भी सबसे सफल विकल्प नहीं है; एक पूर्ण रैंडम एक पहचानकर्ता के रूप में काम कर सकता है - वैसे, संदेश भेजते समय यह पहले से ही उच्च-स्तरीय एपीआई में किया जाता है। बेहतर होगा कि आर्किटेक्चर को सापेक्ष से निरपेक्ष में पूरी तरह से रीमेक किया जाए, लेकिन यह इस पोस्ट का नहीं, बल्कि दूसरे भाग का विषय है।

एपीआई?

ता-दाम! इसलिए, दर्द और बैसाखियों से भरे रास्ते से संघर्ष करते हुए, हम अंततः सर्वर को कोई भी अनुरोध भेजने और उनका कोई भी उत्तर प्राप्त करने में सक्षम हुए, साथ ही सर्वर से अपडेट प्राप्त करने में सक्षम हुए (किसी अनुरोध के जवाब में नहीं, बल्कि यह स्वयं ही) हमें भेजता है, PUSH की तरह, यदि कोई है तो यह उस तरह से स्पष्ट है)।

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

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

हाँ, जानबूझकर बिगाड़ने वाली बात नहीं है - यदि आपने इसे अभी तक नहीं पढ़ा है, तो आगे बढ़ें और इसे पढ़ें!

ओह, वाह~~... यह कैसा दिखता है? कुछ बहुत ही परिचित... शायद यह JSON में एक विशिष्ट वेब एपीआई की डेटा संरचना है, सिवाय इसके कि कक्षाएं भी वस्तुओं से जुड़ी होती हैं?..

तो यह इस तरह से होता है... यह सब क्या है, साथियों?.. इतना प्रयास - और हम आराम करने के लिए रुक गए जहां वेब प्रोग्रामर थे अभी शुरू कर रहे हैं?..क्या HTTPS पर सिर्फ JSON सरल नहीं होगा?! बदले में हमें क्या मिला? क्या प्रयास सार्थक था?

आइए मूल्यांकन करें कि TL+MTProto ने हमें क्या दिया और कौन से विकल्प संभव हैं। ठीक है, HTTP, जो अनुरोध-प्रतिक्रिया मॉडल पर ध्यान केंद्रित करता है, एक खराब फिट है, लेकिन कम से कम टीएलएस के शीर्ष पर कुछ है?

सघन क्रमबद्धता. JSON के समान इस डेटा संरचना को देखकर, मुझे याद आया कि इसके बाइनरी संस्करण भी हैं। आइए MsgPack को अपर्याप्त रूप से विस्तार योग्य के रूप में चिह्नित करें, लेकिन, उदाहरण के लिए, CBOR - वैसे, एक मानक में वर्णित है RFC 7049. यह इस तथ्य के लिए उल्लेखनीय है कि यह परिभाषित करता है टैग, एक विस्तार तंत्र के रूप में, और बीच में पहले से ही मानकीकृत वहां:

  • 25 + 256 - बार-बार लाइनों को लाइन नंबर के संदर्भ से बदलना, ऐसी सस्ती संपीड़न विधि
  • 26 - क्लास नाम और कंस्ट्रक्टर तर्कों के साथ क्रमबद्ध पर्ल ऑब्जेक्ट
  • 27 - प्रकार के नाम और कंस्ट्रक्टर तर्कों के साथ क्रमबद्ध भाषा-स्वतंत्र वस्तु

खैर, मैंने स्ट्रिंग और ऑब्जेक्ट पैकिंग सक्षम होने के साथ टीएल और सीबीओआर में समान डेटा को क्रमबद्ध करने का प्रयास किया। मेगाबाइट से कहीं न कहीं परिणाम सीबीओआर के पक्ष में भिन्न होने लगा:

cborlen=1039673 tl_len=1095092

इस प्रकार, निष्कर्ष: काफी सरल प्रारूप हैं जो तुलनीय दक्षता के साथ सिंक्रनाइज़ेशन विफलता या अज्ञात पहचानकर्ता की समस्या के अधीन नहीं हैं।

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

टीएलएस में पीएफएस का उपयोग करते समय, टीएलएस सत्र टिकट (RFC 5077) कुंजियों पर दोबारा बातचीत किए बिना और सर्वर पर मुख्य जानकारी संग्रहीत किए बिना एन्क्रिप्टेड सत्र को फिर से शुरू करना। पहला कनेक्शन खोलते समय और कुंजी बनाते समय, सर्वर कनेक्शन स्थिति को एन्क्रिप्ट करता है और इसे क्लाइंट को (सत्र टिकट के रूप में) भेजता है। तदनुसार, जब कनेक्शन फिर से शुरू होता है, तो क्लाइंट सत्र कुंजी सहित एक सत्र टिकट सर्वर पर वापस भेजता है। टिकट स्वयं एक अस्थायी कुंजी (सत्र टिकट कुंजी) के साथ एन्क्रिप्ट किया गया है, जो सर्वर पर संग्रहीत है और इसे क्लस्टर समाधानों में एसएसएल संसाधित करने वाले सभी फ्रंटएंड सर्वरों के बीच वितरित किया जाना चाहिए। [10]। इस प्रकार, यदि अस्थायी सर्वर कुंजियों से छेड़छाड़ की जाती है, तो सत्र टिकट की शुरूआत पीएफएस का उल्लंघन कर सकती है, उदाहरण के लिए, जब उन्हें लंबे समय तक संग्रहीत किया जाता है (ओपनएसएसएल, nginx, अपाचे उन्हें प्रोग्राम की पूरी अवधि के लिए डिफ़ॉल्ट रूप से संग्रहीत करते हैं; लोकप्रिय साइटें उपयोग करती हैं) कई घंटों, दिनों तक की कुंजी)।

यहां आरटीटी शून्य नहीं है, आपको कम से कम क्लाइंटहेलो और सर्वरहेलो का आदान-प्रदान करना होगा, जिसके बाद क्लाइंट फिनिश्ड के साथ डेटा भेज सकता है। लेकिन यहां हमें याद रखना चाहिए कि हमारे पास नए खुले कनेक्शनों के समूह के साथ वेब नहीं है, बल्कि एक संदेशवाहक है, जिसका कनेक्शन अक्सर एक और अधिक या कम लंबे समय तक चलने वाला, वेब पेजों के लिए अपेक्षाकृत कम अनुरोध वाला होता है - सब कुछ मल्टीप्लेक्स है आंतरिक रूप से. यानी, यह काफी स्वीकार्य है अगर हमें वास्तव में खराब सबवे सेक्शन का सामना न करना पड़े।

कुछ और भूल गये? टिप्पणियों में लिखें.

जारी रहती है!

पोस्ट की इस श्रृंखला के दूसरे भाग में हम तकनीकी नहीं, बल्कि संगठनात्मक मुद्दों - दृष्टिकोण, विचारधारा, इंटरफ़ेस, उपयोगकर्ताओं के प्रति दृष्टिकोण आदि पर विचार करेंगे। हालाँकि, यहां प्रस्तुत की गई तकनीकी जानकारी पर आधारित है।

तीसरा भाग तकनीकी घटक/विकास अनुभव का विश्लेषण जारी रखेगा। आप विशेष रूप से सीखेंगे:

  • टीएल प्रकारों की विविधता के साथ महामारी की निरंतरता
  • चैनलों और सुपरग्रुप के बारे में अज्ञात बातें
  • संवाद रोस्टर से भी बदतर क्यों हैं?
  • निरपेक्ष बनाम सापेक्ष संदेश संबोधन के बारे में
  • फोटो और इमेज में क्या अंतर है
  • इमोजी इटैलिक टेक्स्ट में कैसे हस्तक्षेप करता है

और अन्य बैसाखियाँ! बने रहें!

स्रोत: www.habr.com

एक टिप्पणी जोड़ें